Replace the connection_tree with a connection_list.
[tinc] / src / graph.c
1 /*
2     graph.c -- graph algorithms
3     Copyright (C) 2001-2012 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 along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 /* We need to generate two trees from the graph:
22
23    1. A minimum spanning tree for broadcasts,
24    2. A single-source shortest path tree for unicasts.
25
26    Actually, the first one alone would suffice but would make unicast packets
27    take longer routes than necessary.
28
29    For the MST algorithm we can choose from Prim's or Kruskal's. I personally
30    favour Kruskal's, because we make an extra AVL tree of edges sorted on
31    weights (metric). That tree only has to be updated when an edge is added or
32    removed, and during the MST algorithm we just have go linearly through that
33    tree, adding safe edges until #edges = #nodes - 1. The implementation here
34    however is not so fast, because I tried to avoid having to make a forest and
35    merge trees.
36
37    For the SSSP algorithm Dijkstra's seems to be a nice choice. Currently a
38    simple breadth-first search is presented here.
39
40    The SSSP algorithm will also be used to determine whether nodes are directly,
41    indirectly or not reachable from the source. It will also set the correct
42    destination address and port of a node if possible.
43 */
44
45 #include "system.h"
46
47 #include "config.h"
48 #include "connection.h"
49 #include "device.h"
50 #include "edge.h"
51 #include "graph.h"
52 #include "list.h"
53 #include "logger.h"
54 #include "netutl.h"
55 #include "node.h"
56 #include "process.h"
57 #include "protocol.h"
58 #include "subnet.h"
59 #include "utils.h"
60 #include "xalloc.h"
61 #include "graph.h"
62
63 /* Implementation of Kruskal's algorithm.
64    Running time: O(E)
65    Please note that sorting on weight is already done by add_edge().
66 */
67
68 static void mst_kruskal(void) {
69         /* Clear MST status on connections */
70
71         for(list_node_t *node = connection_list->head; node; node = node->next) {
72                 connection_t *c = node->data;
73                 c->status.mst = false;
74         }
75
76         logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Running Kruskal's algorithm:");
77
78         /* Clear visited status on nodes */
79
80         for(splay_node_t *node = node_tree->head; node; node = node->next) {
81                 node_t *n = node->data;
82                 n->status.visited = false;
83         }
84
85         /* Add safe edges */
86
87         for(splay_node_t *node = edge_weight_tree->head, *next; node; node = next) {
88                 next = node->next;
89                 edge_t *e = node->data;
90
91                 if(!e->reverse || (e->from->status.visited && e->to->status.visited))
92                         continue;
93
94                 e->from->status.visited = true;
95                 e->to->status.visited = true;
96
97                 if(e->connection)
98                         e->connection->status.mst = true;
99
100                 if(e->reverse->connection)
101                         e->reverse->connection->status.mst = true;
102
103                 logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name,
104                                    e->to->name, e->weight);
105         }
106 }
107
108 /* Implementation of a simple breadth-first search algorithm.
109    Running time: O(E)
110 */
111
112 static void sssp_bfs(void) {
113         splay_node_t *node, *to;
114         edge_t *e;
115         node_t *n;
116         list_t *todo_list;
117         list_node_t *from, *todonext;
118         bool indirect;
119
120         todo_list = list_alloc(NULL);
121
122         /* Clear visited status on nodes */
123
124         for(node = node_tree->head; node; node = node->next) {
125                 n = node->data;
126                 n->status.visited = false;
127                 n->status.indirect = true;
128                 n->distance = -1;
129         }
130
131         /* Begin with myself */
132
133         myself->status.visited = true;
134         myself->status.indirect = false;
135         myself->nexthop = myself;
136         myself->prevedge = NULL;
137         myself->via = myself;
138         myself->distance = 0;
139         list_insert_head(todo_list, myself);
140
141         /* Loop while todo_list is filled */
142
143         for(from = todo_list->head; from; from = todonext) {    /* "from" is the node from which we start */
144                 n = from->data;
145                 if(n->distance < 0)
146                         abort();
147
148                 for(to = n->edge_tree->head; to; to = to->next) {       /* "to" is the edge connected to "from" */
149                         e = to->data;
150
151                         if(!e->reverse)
152                                 continue;
153
154                         /* Situation:
155
156                                    /
157                                   /
158                            ----->(n)---e-->(e->to)
159                                   \
160                                    \
161
162                            Where e is an edge, (n) and (e->to) are nodes.
163                            n->address is set to the e->address of the edge left of n to n.
164                            We are currently examining the edge e right of n from n:
165
166                            - If edge e provides for better reachability of e->to, update
167                              e->to and (re)add it to the todo_list to (re)examine the reachability
168                              of nodes behind it.
169                          */
170
171                         indirect = n->status.indirect || e->options & OPTION_INDIRECT;
172
173                         if(e->to->status.visited
174                            && (!e->to->status.indirect || indirect)
175                            && (e->to->distance != n->distance + 1 || e->weight >= e->to->prevedge->weight))
176                                 continue;
177
178                         e->to->status.visited = true;
179                         e->to->status.indirect = indirect;
180                         e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop;
181                         e->to->prevedge = e;
182                         e->to->via = indirect ? n->via : e->to;
183                         e->to->options = e->options;
184                         e->to->distance = n->distance + 1;
185
186                         if(!e->to->status.reachable || (e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)
187 )
188                                 update_node_udp(e->to, &e->address);
189
190                         list_insert_tail(todo_list, e->to);
191                 }
192
193                 todonext = from->next;
194                 list_delete_node(todo_list, from);
195         }
196
197         list_free(todo_list);
198 }
199
200 static void check_reachability(void) {
201         splay_node_t *node, *next;
202         node_t *n;
203         char *name;
204         char *address, *port;
205         char *envp[7];
206         int i;
207
208         /* Check reachability status. */
209
210         for(node = node_tree->head; node; node = next) {
211                 next = node->next;
212                 n = node->data;
213
214                 if(n->status.visited != n->status.reachable) {
215                         n->status.reachable = !n->status.reachable;
216                         n->last_state_change = time(NULL);
217
218                         if(n->status.reachable) {
219                                 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became reachable",
220                                            n->name, n->hostname);
221                         } else {
222                                 logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became unreachable",
223                                            n->name, n->hostname);
224                         }
225
226                         if(experimental && OPTION_VERSION(n->options) >= 2)
227                                 n->status.sptps = true;
228
229                         /* TODO: only clear status.validkey if node is unreachable? */
230
231                         n->status.validkey = false;
232                         if(n->status.sptps) {
233                                 sptps_stop(&n->sptps);
234                                 n->status.waitingforkey = false;
235                         }
236                         n->last_req_key = 0;
237
238                         n->maxmtu = MTU;
239                         n->minmtu = 0;
240                         n->mtuprobes = 0;
241
242                         if(timeout_initialized(&n->mtuevent))
243                                 event_del(&n->mtuevent);
244
245                         xasprintf(&envp[0], "NETNAME=%s", netname ? : "");
246                         xasprintf(&envp[1], "DEVICE=%s", device ? : "");
247                         xasprintf(&envp[2], "INTERFACE=%s", iface ? : "");
248                         xasprintf(&envp[3], "NODE=%s", n->name);
249                         sockaddr2str(&n->address, &address, &port);
250                         xasprintf(&envp[4], "REMOTEADDRESS=%s", address);
251                         xasprintf(&envp[5], "REMOTEPORT=%s", port);
252                         envp[6] = NULL;
253
254                         execute_script(n->status.reachable ? "host-up" : "host-down", envp);
255
256                         xasprintf(&name,
257                                          n->status.reachable ? "hosts/%s-up" : "hosts/%s-down",
258                                          n->name);
259                         execute_script(name, envp);
260
261                         free(name);
262                         free(address);
263                         free(port);
264
265                         for(i = 0; i < 6; i++)
266                                 free(envp[i]);
267
268                         subnet_update(n, NULL, n->status.reachable);
269
270                         if(!n->status.reachable) {
271                                 update_node_udp(n, NULL);
272                         } else if(n->connection) {
273                                 if(n->status.sptps) {
274                                         if(n->connection->outgoing)
275                                                 send_req_key(n);
276                                 } else {
277                                         send_ans_key(n);
278                                 }
279                         }
280                 }
281         }
282 }
283
284 void graph(void) {
285         subnet_cache_flush();
286         sssp_bfs();
287         check_reachability();
288         mst_kruskal();
289 }