Update copyright information.
[oweals/tinc.git] / src / graph.c
1 /*
2     graph.c -- graph algorithms
3     Copyright (C) 2001-2009 Guus Sliepen <guus@tinc-vpn.org>,
4                   2001-2005 Ivo Timmermans
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20     $Id$
21 */
22
23 /* We need to generate two trees from the graph:
24
25    1. A minimum spanning tree for broadcasts,
26    2. A single-source shortest path tree for unicasts.
27
28    Actually, the first one alone would suffice but would make unicast packets
29    take longer routes than necessary.
30
31    For the MST algorithm we can choose from Prim's or Kruskal's. I personally
32    favour Kruskal's, because we make an extra AVL tree of edges sorted on
33    weights (metric). That tree only has to be updated when an edge is added or
34    removed, and during the MST algorithm we just have go linearly through that
35    tree, adding safe edges until #edges = #nodes - 1. The implementation here
36    however is not so fast, because I tried to avoid having to make a forest and
37    merge trees.
38
39    For the SSSP algorithm Dijkstra's seems to be a nice choice. Currently a
40    simple breadth-first search is presented here.
41
42    The SSSP algorithm will also be used to determine whether nodes are directly,
43    indirectly or not reachable from the source. It will also set the correct
44    destination address and port of a node if possible.
45 */
46
47 #include "system.h"
48
49 #include "avl_tree.h"
50 #include "config.h"
51 #include "connection.h"
52 #include "device.h"
53 #include "edge.h"
54 #include "logger.h"
55 #include "netutl.h"
56 #include "node.h"
57 #include "process.h"
58 #include "subnet.h"
59 #include "utils.h"
60 #include "xalloc.h"
61
62 static bool graph_changed = true;
63
64 /* Implementation of Kruskal's algorithm.
65    Running time: O(EN)
66    Please note that sorting on weight is already done by add_edge().
67 */
68
69 void mst_kruskal(void)
70 {
71         avl_node_t *node, *next;
72         edge_t *e;
73         node_t *n;
74         connection_t *c;
75         int nodes = 0;
76         int safe_edges = 0;
77         bool skipped;
78
79         cp();
80         
81         /* Clear MST status on connections */
82
83         for(node = connection_tree->head; node; node = node->next) {
84                 c = node->data;
85                 c->status.mst = false;
86         }
87
88         /* Do we have something to do at all? */
89
90         if(!edge_weight_tree->head)
91                 return;
92
93         ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Kruskal's algorithm:");
94
95         /* Clear visited status on nodes */
96
97         for(node = node_tree->head; node; node = node->next) {
98                 n = node->data;
99                 n->status.visited = false;
100                 nodes++;
101         }
102
103         /* Starting point */
104
105         for(node = edge_weight_tree->head; node; node = node->next) {
106                 e = node->data;
107                 if(e->from->status.reachable) {
108                         e->from->status.visited = true;
109                         break;
110                 }
111         }
112
113         /* Add safe edges */
114
115         for(skipped = false, node = edge_weight_tree->head; node; node = next) {
116                 next = node->next;
117                 e = node->data;
118
119                 if(!e->reverse || e->from->status.visited == e->to->status.visited) {
120                         skipped = true;
121                         continue;
122                 }
123
124                 e->from->status.visited = true;
125                 e->to->status.visited = true;
126
127                 if(e->connection)
128                         e->connection->status.mst = true;
129
130                 if(e->reverse->connection)
131                         e->reverse->connection->status.mst = true;
132
133                 safe_edges++;
134
135                 ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name,
136                                    e->to->name, e->weight);
137
138                 if(skipped) {
139                         skipped = false;
140                         next = edge_weight_tree->head;
141                         continue;
142                 }
143         }
144
145         ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Done, counted %d nodes and %d safe edges.", nodes,
146                            safe_edges);
147 }
148
149 /* Implementation of a simple breadth-first search algorithm.
150    Running time: O(E)
151 */
152
153 void sssp_bfs(void)
154 {
155         avl_node_t *node, *next, *to;
156         edge_t *e;
157         node_t *n;
158         list_t *todo_list;
159         list_node_t *from, *todonext;
160         bool indirect;
161         char *name;
162         char *address, *port;
163         char *envp[7];
164         int i;
165
166         cp();
167
168         todo_list = list_alloc(NULL);
169
170         /* Clear visited status on nodes */
171
172         for(node = node_tree->head; node; node = node->next) {
173                 n = node->data;
174                 n->status.visited = false;
175                 n->status.indirect = true;
176         }
177
178         /* Begin with myself */
179
180         myself->status.visited = true;
181         myself->status.indirect = false;
182         myself->nexthop = myself;
183         myself->via = myself;
184         list_insert_head(todo_list, myself);
185
186         /* Loop while todo_list is filled */
187
188         for(from = todo_list->head; from; from = todonext) {    /* "from" is the node from which we start */
189                 n = from->data;
190
191                 for(to = n->edge_tree->head; to; to = to->next) {       /* "to" is the edge connected to "from" */
192                         e = to->data;
193
194                         if(!e->reverse)
195                                 continue;
196
197                         /* Situation:
198
199                                    /
200                                   /
201                            ----->(n)---e-->(e->to)
202                                   \
203                                    \
204
205                            Where e is an edge, (n) and (e->to) are nodes.
206                            n->address is set to the e->address of the edge left of n to n.
207                            We are currently examining the edge e right of n from n:
208
209                            - If e->reverse->address != n->address, then e->to is probably
210                              not reachable for the nodes left of n. We do as if the indirectdata
211                              flag is set on edge e.
212                            - If edge e provides for better reachability of e->to, update
213                              e->to and (re)add it to the todo_list to (re)examine the reachability
214                              of nodes behind it.
215                          */
216
217                         indirect = n->status.indirect || e->options & OPTION_INDIRECT
218                                 || ((n != myself) && sockaddrcmp(&n->address, &e->reverse->address));
219
220                         if(e->to->status.visited
221                            && (!e->to->status.indirect || indirect))
222                                 continue;
223
224                         e->to->status.visited = true;
225                         e->to->status.indirect = indirect;
226                         e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop;
227                         e->to->via = indirect ? n->via : e->to;
228                         e->to->options = e->options;
229
230                         if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)
231                                 update_node_udp(e->to, &e->address);
232
233                         list_insert_tail(todo_list, e->to);
234                 }
235
236                 todonext = from->next;
237                 list_delete_node(todo_list, from);
238         }
239
240         list_free(todo_list);
241
242         /* Check reachability status. */
243
244         for(node = node_tree->head; node; node = next) {
245                 next = node->next;
246                 n = node->data;
247
248                 if(n->status.visited != n->status.reachable) {
249                         n->status.reachable = !n->status.reachable;
250
251                         if(n->status.reachable) {
252                                 ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became reachable"),
253                                            n->name, n->hostname);
254                         } else {
255                                 ifdebug(TRAFFIC) logger(LOG_DEBUG, _("Node %s (%s) became unreachable"),
256                                            n->name, n->hostname);
257                         }
258
259                         /* TODO: only clear status.validkey if node is unreachable? */
260
261                         n->status.validkey = false;
262                         n->status.waitingforkey = false;
263
264                         n->maxmtu = MTU;
265                         n->minmtu = 0;
266                         n->mtuprobes = 0;
267
268                         if(n->mtuevent) {
269                                 event_del(n->mtuevent);
270                                 n->mtuevent = NULL;
271                         }
272
273                         xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
274                         xasprintf(&envp[1], "DEVICE=%s", device ? : "");
275                         xasprintf(&envp[2], "INTERFACE=%s", iface ? : "");
276                         xasprintf(&envp[3], "NODE=%s", n->name);
277                         sockaddr2str(&n->address, &address, &port);
278                         xasprintf(&envp[4], "REMOTEADDRESS=%s", address);
279                         xasprintf(&envp[5], "REMOTEPORT=%s", port);
280                         envp[6] = NULL;
281
282                         execute_script(n->status.reachable ? "host-up" : "host-down", envp);
283
284                         xasprintf(&name,
285                                          n->status.reachable ? "hosts/%s-up" : "hosts/%s-down",
286                                          n->name);
287                         execute_script(name, envp);
288
289                         free(name);
290                         free(address);
291                         free(port);
292
293                         for(i = 0; i < 6; i++)
294                                 free(envp[i]);
295
296                         subnet_update(n, NULL, n->status.reachable);
297                 }
298         }
299 }
300
301 void graph(void)
302 {
303         subnet_cache_flush();
304         sssp_bfs();
305         mst_kruskal();
306         graph_changed = true;
307 }
308
309
310
311 /* Dump nodes and edges to a graphviz file.
312            
313    The file can be converted to an image with
314    dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true
315 */
316
317 void dump_graph(void)
318 {
319         avl_node_t *node;
320         node_t *n;
321         edge_t *e;
322         char *filename = NULL, *tmpname = NULL;
323         FILE *file;
324         
325         if(!graph_changed || !get_config_string(lookup_config(config_tree, "GraphDumpFile"), &filename))
326                 return;
327
328         graph_changed = false;
329
330         ifdebug(PROTOCOL) logger(LOG_NOTICE, "Dumping graph");
331         
332         if(filename[0] == '|') {
333                 file = popen(filename + 1, "w");
334         } else {
335                 xasprintf(&tmpname, "%s.new", filename);
336                 file = fopen(tmpname, "w");
337         }
338
339         if(!file) {
340                 logger(LOG_ERR, "Unable to open graph dump file %s: %s", filename, strerror(errno));
341                 free(tmpname);
342                 return;
343         }
344
345         fprintf(file, "digraph {\n");
346         
347         /* dump all nodes first */
348         for(node = node_tree->head; node; node = node->next) {
349                 n = node->data;
350                 fprintf(file, " %s [label = \"%s\"];\n", n->name, n->name);
351         }
352
353         /* now dump all edges */
354         for(node = edge_weight_tree->head; node; node = node->next) {
355                 e = node->data;
356                 fprintf(file, " %s -> %s;\n", e->from->name, e->to->name);
357         }
358
359         fprintf(file, "}\n");   
360         
361         if(filename[0] == '|') {
362                 pclose(file);
363         } else {
364                 fclose(file);
365 #ifdef HAVE_MINGW
366                 unlink(filename);
367 #endif
368                 rename(tmpname, filename);
369                 free(tmpname);
370         }
371 }