63e8fafe0016535758fb069ee6bf273883e2ff2a
[oweals/tinc.git] / rt / graph.c
1 /*
2     graph.c -- graph algorithms
3     Copyright (C) 2001-2004 Guus Sliepen <guus@tinc-vpn.org>,
4                   2001-2004 Ivo Timmermans <ivo@tinc-vpn.org>
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 "rt/edge.h"
50 #include "rt/node.h"
51 #include "support/avl.h"
52 #include "support/list.h"
53
54 /* Implementation of Kruskal's algorithm.
55    Running time: O(EN)
56    Please note that sorting on weight is already done by add_edge().
57 */
58
59 void mst_kruskal(void) {
60         avl_node_t *avl, *next;
61         edge_t *edge;
62         node_t *node;
63         int safe_edges = 0;
64         bool skipped;
65
66         /* Do we have something to do at all? */
67
68         if(!edges->head)
69                 return;
70
71         logger(LOG_DEBUG, "Running Kruskal's algorithm:");
72
73         /* Clear MST status on edges */
74
75         avl_foreach(edges, edge, edge->status.mst = false);
76
77         /* Clear visited status on nodes */
78
79         avl_foreach(nodes, node, node->status.visited = false);
80
81         /* Starting point */
82
83         ((edge_t *) edges->head->data)->from->status.visited = true;
84
85         /* Add safe edges */
86
87         for(skipped = false, avl = edges->head; avl; avl = next) {
88                 next = avl->next;
89                 edge = avl->data;
90
91                 if(!edge->reverse || edge->from->status.visited == edge->to->status.visited) {
92                         skipped = true;
93                         continue;
94                 }
95
96                 edge->from->status.visited = true;
97                 edge->to->status.visited = true;
98                 edge->status.mst = true;
99                 edge->reverse->status.mst = true;
100
101                 if(skipped) {
102                         skipped = false;
103                         next = edges->head;
104                         continue;
105                 }
106         }
107 }
108
109 /* Implementation of a simple breadth-first search algorithm.
110    Running time: O(E)
111 */
112
113 void sssp_bfs(void) {
114         list_t *todo;
115         list_node_t *todonode;
116         edge_t *edge;
117         node_t *node;
118         bool indirect;
119         char *name;
120         char *address, *port;
121         int i;
122
123         todo = list_new(NULL);
124
125         /* Clear visited status on nodes */
126
127         avl_foreach(nodes, node, {
128                 node->status.visited = false;
129                 node->status.indirect = true;
130         });
131
132         /* Begin with myself */
133
134         myself->status.visited = true;
135         myself->status.indirect = false;
136         myself->nexthop = myself;
137         myself->via = myself;
138
139         list_add_head(todo, myself);
140
141         /* Loop while todo list is filled */
142
143         while(todo->head) {
144                 list_foreach_node(todo, todonode, {
145                         node = todonode->data;
146
147                         avl_foreach(node->edges, edge, {
148                                 if(!edge->reverse)
149                                         continue;
150
151                                 /* Situation:
152
153                                              /
154                                             /
155                                    ----->(node)---edge-->(edge->to)
156                                             \
157                                              \
158
159                                    node->address is set to the ->address of the edge left of node.
160                                    We are currently examining the edge right of node:
161
162                                    - If edge->reverse->address != node->address, then edge->to is probably
163                                      not reachable for the nodes left of node. We do as if the indirectdata
164                                      flag is set on edge.
165                                    - If edge provides for better reachability of edge->to, update
166                                      edge->to and (re)add it to the todo_tree to (re)examine the reachability
167                                      of nodes behind it.
168                                  */
169
170                                 indirect = node->status.indirect || edge->options & NODE_OPTION_INDIRECT
171                                         || ((node != myself) && sockaddrcmp(&node->address, &edge->reverse->address));
172
173                                 if(edge->to->status.visited && (!edge->to->status.indirect || indirect))
174                                         continue;
175
176                                 edge->to->status.visited = true;
177                                 edge->to->status.indirect = indirect;
178                                 edge->to->nexthop = (node->nexthop == myself) ? edge->to : node->nexthop;
179                                 edge->to->via = indirect ? node->via : edge->to;
180                                 edge->to->options = edge->options;
181
182                                 list_add_head(todo, edge->to);
183                         });
184
185                         list_del_node(todo, todonode);
186                 });
187         }
188
189         list_free(todo);
190
191         /* Check reachability status. */
192
193         avl_foreach(nodes, node, {
194                 if(node->status.visited != node->status.reachable) {
195                         node->status.reachable = !node->status.reachable;
196
197                         if(node->status.reachable)
198                                 logger(LOG_DEBUG, _("Node %s became reachable"), node->name);
199                         else
200                                 logger(LOG_DEBUG, _("Node %s became unreachable"), node->name);
201
202 #if 0
203                         asprintf(&envp[0], "NETNAME=%s", netname ? : "");
204                         asprintf(&envp[1], "DEVICE=%s", device ? : "");
205                         asprintf(&envp[2], "INTERFACE=%s", iface ? : "");
206                         asprintf(&envp[3], "NODE=%s", n->name);
207                         sockaddr2str(&n->address, &address, &port);
208                         asprintf(&envp[4], "REMOTEADDRESS=%s", address);
209                         asprintf(&envp[5], "REMOTEPORT=%s", port);
210                         envp[6] = NULL;
211
212                         asprintf(&name,
213                                          n->status.reachable ? "hosts/%s-up" : "hosts/%s-down",
214                                          n->name);
215                         execute_script(name, envp);
216
217                         free(name);
218                         free(address);
219                         free(port);
220
221                         for(i = 0; i < 7; i++)
222                                 free(envp[i]);
223 #endif
224                 }
225         });
226 }
227
228 void graph(void)
229 {
230         mst_kruskal();
231         sssp_bfs();
232 }