xref: /dragonfly/contrib/dhcpcd/src/route.c (revision 6e316fcd)
1 /*
2  * dhcpcd - route management
3  * Copyright (c) 2006-2018 Roy Marples <roy@marples.name>
4  * All rights reserved
5 
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <assert.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <stdbool.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include "config.h"
37 #include "common.h"
38 #include "dhcpcd.h"
39 #include "if.h"
40 #include "ipv4.h"
41 #include "ipv4ll.h"
42 #include "ipv6.h"
43 #include "logerr.h"
44 #include "route.h"
45 #include "sa.h"
46 
47 /*
48  * On some systems, host routes have no need for a netmask.
49  * However DHCP specifies host routes using an all-ones netmask.
50  * This handy function allows easy comparison when the two
51  * differ.
52  */
53 static int
54 rt_cmp_netmask(const struct rt *rt1, const struct rt *rt2)
55 {
56 
57 	if (rt1->rt_flags & RTF_HOST && rt2->rt_flags & RTF_HOST)
58 		return 0;
59 	return sa_cmp(&rt1->rt_netmask, &rt2->rt_netmask);
60 }
61 
62 void
63 rt_init(struct dhcpcd_ctx *ctx)
64 {
65 
66 	TAILQ_INIT(&ctx->routes);
67 	TAILQ_INIT(&ctx->kroutes);
68 	TAILQ_INIT(&ctx->froutes);
69 }
70 
71 static void
72 rt_desc(const char *cmd, const struct rt *rt)
73 {
74 	char dest[INET_MAX_ADDRSTRLEN], gateway[INET_MAX_ADDRSTRLEN];
75 	int prefix;
76 	const char *ifname;
77 	bool gateway_unspec;
78 
79 	assert(cmd != NULL);
80 	assert(rt != NULL);
81 
82 	sa_addrtop(&rt->rt_dest, dest, sizeof(dest));
83 	prefix = sa_toprefix(&rt->rt_netmask);
84 	sa_addrtop(&rt->rt_gateway, gateway, sizeof(gateway));
85 	gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
86 	ifname = rt->rt_ifp == NULL ? "(null)" : rt->rt_ifp->name;
87 
88 	if (rt->rt_flags & RTF_HOST) {
89 		if (gateway_unspec)
90 			loginfox("%s: %s host route to %s",
91 			    ifname, cmd, dest);
92 		else
93 			loginfox("%s: %s host route to %s via %s",
94 			    ifname, cmd, dest, gateway);
95 	} else if (sa_is_unspecified(&rt->rt_dest) &&
96 		   sa_is_unspecified(&rt->rt_netmask))
97 	{
98 		if (gateway_unspec)
99 			loginfox("%s: %s default route",
100 			    ifname, cmd);
101 		else
102 			loginfox("%s: %s default route via %s",
103 			    ifname, cmd, gateway);
104 	} else if (gateway_unspec)
105 		loginfox("%s: %s%s route to %s/%d",
106 		    ifname, cmd,
107 		    rt->rt_flags & RTF_REJECT ? " reject" : "",
108 		    dest, prefix);
109 	else
110 		loginfox("%s: %s%s route to %s/%d via %s",
111 		    ifname, cmd,
112 		    rt->rt_flags & RTF_REJECT ? " reject" : "",
113 		    dest, prefix, gateway);
114 }
115 
116 void
117 rt_headclear0(struct dhcpcd_ctx *ctx, struct rt_head *rts, int af)
118 {
119 	struct rt *rt, *rtn;
120 
121 	if (rts == NULL)
122 		return;
123 	assert(ctx != NULL);
124 	assert(&ctx->froutes != rts);
125 
126 	TAILQ_FOREACH_SAFE(rt, rts, rt_next, rtn) {
127 		if (af != AF_UNSPEC &&
128 		    rt->rt_dest.sa_family != af &&
129 		    rt->rt_gateway.sa_family != af)
130 			continue;
131 		TAILQ_REMOVE(rts, rt, rt_next);
132 		TAILQ_INSERT_TAIL(&ctx->froutes, rt, rt_next);
133 	}
134 }
135 
136 void
137 rt_headclear(struct rt_head *rts, int af)
138 {
139 	struct rt *rt;
140 
141 	if (rts == NULL || (rt = TAILQ_FIRST(rts)) == NULL)
142 		return;
143 	rt_headclear0(rt->rt_ifp->ctx, rts, af);
144 }
145 
146 static void
147 rt_headfree(struct rt_head *rts)
148 {
149 	struct rt *rt;
150 
151 	while ((rt = TAILQ_FIRST(rts))) {
152 		TAILQ_REMOVE(rts, rt, rt_next);
153 		free(rt);
154 	}
155 }
156 
157 void
158 rt_dispose(struct dhcpcd_ctx *ctx)
159 {
160 
161 	assert(ctx != NULL);
162 	rt_headfree(&ctx->routes);
163 	rt_headfree(&ctx->kroutes);
164 	rt_headfree(&ctx->froutes);
165 }
166 
167 struct rt *
168 rt_new0(struct dhcpcd_ctx *ctx)
169 {
170 	struct rt *rt;
171 
172 	assert(ctx != NULL);
173 	if ((rt = TAILQ_FIRST(&ctx->froutes)) != NULL)
174 		TAILQ_REMOVE(&ctx->froutes, rt, rt_next);
175 	else if ((rt = malloc(sizeof(*rt))) == NULL) {
176 		logerr(__func__);
177 		return NULL;
178 	}
179 	memset(rt, 0, sizeof(*rt));
180 	return rt;
181 }
182 
183 void
184 rt_setif(struct rt *rt, struct interface *ifp)
185 {
186 
187 	assert(rt != NULL);
188 	assert(ifp != NULL);
189 	rt->rt_ifp = ifp;
190 #ifdef HAVE_ROUTE_METRIC
191 	rt->rt_metric = ifp->metric;
192 #endif
193 }
194 
195 struct rt *
196 rt_new(struct interface *ifp)
197 {
198 	struct rt *rt;
199 
200 	assert(ifp != NULL);
201 	if ((rt = rt_new0(ifp->ctx)) == NULL)
202 		return NULL;
203 	rt_setif(rt, ifp);
204 	return rt;
205 }
206 
207 void
208 rt_free(struct rt *rt)
209 {
210 
211 	assert(rt != NULL);
212 	assert(rt->rt_ifp->ctx != NULL);
213 	TAILQ_INSERT_TAIL(&rt->rt_ifp->ctx->froutes, rt, rt_next);
214 }
215 
216 void
217 rt_freeif(struct interface *ifp)
218 {
219 	struct dhcpcd_ctx *ctx;
220 	struct rt *rt, *rtn;
221 
222 	if (ifp == NULL)
223 		return;
224 	ctx = ifp->ctx;
225 	TAILQ_FOREACH_SAFE(rt, &ctx->routes, rt_next, rtn) {
226 		if (rt->rt_ifp == ifp) {
227 			TAILQ_REMOVE(&ctx->routes, rt, rt_next);
228 			rt_free(rt);
229 		}
230 	}
231 	TAILQ_FOREACH_SAFE(rt, &ctx->kroutes, rt_next, rtn) {
232 		if (rt->rt_ifp == ifp) {
233 			TAILQ_REMOVE(&ctx->kroutes, rt, rt_next);
234 			rt_free(rt);
235 		}
236 	}
237 }
238 
239 struct rt *
240 rt_find(struct rt_head *rts, const struct rt *f)
241 {
242 	struct rt *rt;
243 
244 	assert(rts != NULL);
245 	assert(f != NULL);
246 	TAILQ_FOREACH(rt, rts, rt_next) {
247 		if (sa_cmp(&rt->rt_dest, &f->rt_dest) == 0 &&
248 #ifdef HAVE_ROUTE_METRIC
249 		    (f->rt_ifp == NULL ||
250 		    rt->rt_ifp->metric == f->rt_ifp->metric) &&
251 #endif
252 		    rt_cmp_netmask(f, rt) == 0)
253 			return rt;
254 	}
255 	return NULL;
256 }
257 
258 static void
259 rt_kfree(struct rt *rt)
260 {
261 	struct dhcpcd_ctx *ctx;
262 	struct rt *f;
263 
264 	assert(rt != NULL);
265 	ctx = rt->rt_ifp->ctx;
266 	if ((f = rt_find(&ctx->kroutes, rt)) != NULL) {
267 		TAILQ_REMOVE(&ctx->kroutes, f, rt_next);
268 		rt_free(f);
269 	}
270 }
271 
272 /* If something other than dhcpcd removes a route,
273  * we need to remove it from our internal table. */
274 void
275 rt_recvrt(int cmd, const struct rt *rt)
276 {
277 	struct dhcpcd_ctx *ctx;
278 	struct rt *f;
279 
280 	assert(rt != NULL);
281 	ctx = rt->rt_ifp->ctx;
282 	f = rt_find(&ctx->kroutes, rt);
283 
284 	switch(cmd) {
285 	case RTM_DELETE:
286 		if (f != NULL) {
287 			TAILQ_REMOVE(&ctx->kroutes, f, rt_next);
288 			rt_free(f);
289 		}
290 		if ((f = rt_find(&ctx->routes, rt)) != NULL) {
291 			TAILQ_REMOVE(&ctx->routes, f, rt_next);
292 			rt_desc("deleted", f);
293 			rt_free(f);
294 		}
295 		break;
296 	case RTM_ADD:
297 		if (f != NULL)
298 			break;
299 		if ((f = rt_new(rt->rt_ifp)) == NULL)
300 			break;
301 		memcpy(f, rt, sizeof(*f));
302 		TAILQ_INSERT_TAIL(&ctx->kroutes, f, rt_next);
303 		break;
304 	}
305 
306 #if defined(INET) && defined(HAVE_ROUTE_METRIC)
307 	if (rt->rt_dest.sa_family == AF_INET)
308 		ipv4ll_recvrt(cmd, rt);
309 #endif
310 }
311 
312 static bool
313 rt_add(struct rt *nrt, struct rt *ort)
314 {
315 	struct dhcpcd_ctx *ctx;
316 	bool change;
317 
318 	assert(nrt != NULL);
319 	ctx = nrt->rt_ifp->ctx;
320 
321 	/*
322 	 * Don't install a gateway if not asked to.
323 	 * This option is mainly for VPN users who want their VPN to be the
324 	 * default route.
325 	 * Because VPN's generally don't care about route management
326 	 * beyond their own, a longer term solution would be to remove this
327 	 * and get the VPN to inject the default route into dhcpcd somehow.
328 	 */
329 	if (((nrt->rt_ifp->active &&
330 	    !(nrt->rt_ifp->options->options & DHCPCD_GATEWAY)) ||
331 	    (!nrt->rt_ifp->active && !(ctx->options & DHCPCD_GATEWAY))) &&
332 	    sa_is_unspecified(&nrt->rt_dest) &&
333 	    sa_is_unspecified(&nrt->rt_netmask))
334 		return false;
335 
336 	rt_desc(ort == NULL ? "adding" : "changing", nrt);
337 
338 	change = false;
339 	if (ort == NULL) {
340 		ort = rt_find(&ctx->kroutes, nrt);
341 		if (ort != NULL &&
342 		    ((ort->rt_flags & RTF_REJECT &&
343 		      nrt->rt_flags & RTF_REJECT) ||
344 		     (ort->rt_ifp == nrt->rt_ifp &&
345 #ifdef HAVE_ROUTE_METRIC
346 		    ort->rt_metric == nrt->rt_metric &&
347 #endif
348 		    sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)))
349 		{
350 			if (ort->rt_mtu == nrt->rt_mtu)
351 				return true;
352 			change = true;
353 		}
354 	} else if (ort->rt_dflags & RTDF_FAKE &&
355 	    !(nrt->rt_dflags & RTDF_FAKE) &&
356 	    ort->rt_ifp == nrt->rt_ifp &&
357 #ifdef HAVE_ROUTE_METRIC
358 	    ort->rt_metric == nrt->rt_metric &&
359 #endif
360 	    sa_cmp(&ort->rt_dest, &nrt->rt_dest) == 0 &&
361 	    rt_cmp_netmask(ort, nrt) == 0 &&
362 	    sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)
363 	{
364 		if (ort->rt_mtu == nrt->rt_mtu)
365 			return true;
366 		change = true;
367 	}
368 
369 #ifdef RTF_CLONING
370 	/* BSD can set routes to be cloning routes.
371 	 * Cloned routes inherit the parent flags.
372 	 * As such, we need to delete and re-add the route to flush children
373 	 * to correct the flags. */
374 	if (change && ort != NULL && ort->rt_flags & RTF_CLONING)
375 		change = false;
376 #endif
377 
378 	if (change) {
379 		if (if_route(RTM_CHANGE, nrt) != -1)
380 			return true;
381 		if (errno != ESRCH)
382 			logerr("if_route (CHG)");
383 	}
384 
385 #ifdef HAVE_ROUTE_METRIC
386 	/* With route metrics, we can safely add the new route before
387 	 * deleting the old route. */
388 	if (if_route(RTM_ADD, nrt) != -1) {
389 		if (ort != NULL) {
390 			if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
391 				logerr("if_route (DEL)");
392 			rt_kfree(ort);
393 		}
394 		return true;
395 	}
396 
397 	/* If the kernel claims the route exists we need to rip out the
398 	 * old one first. */
399 	if (errno != EEXIST || ort == NULL)
400 		goto logerr;
401 #endif
402 
403 	/* No route metrics, we need to delete the old route before
404 	 * adding the new one. */
405 #ifdef ROUTE_PER_GATEWAY
406 	errno = 0;
407 #endif
408 	if (ort != NULL) {
409 		if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
410 			logerr("if_route (DEL)");
411 		else
412 			rt_kfree(ort);
413 	}
414 #ifdef ROUTE_PER_GATEWAY
415 	/* The OS allows many routes to the same dest with different gateways.
416 	 * dhcpcd does not support this yet, so for the time being just keep on
417 	 * deleting the route until there is an error. */
418 	if (ort != NULL && errno == 0) {
419 		for (;;) {
420 			if (if_route(RTM_DELETE, ort) == -1)
421 				break;
422 		}
423 	}
424 #endif
425 	if (if_route(RTM_ADD, nrt) != -1)
426 		return true;
427 #ifdef HAVE_ROUTE_METRIC
428 logerr:
429 #endif
430 	logerr("if_route (ADD)");
431 	return false;
432 }
433 
434 static bool
435 rt_delete(struct rt *rt)
436 {
437 	int retval;
438 
439 	rt_desc("deleting", rt);
440 	retval = if_route(RTM_DELETE, rt) == -1 ? false : true;
441 	if (!retval && errno != ENOENT && errno != ESRCH)
442 		logerr(__func__);
443 	/* Remove the route from our kernel table so we can add a
444 	 * IPv4LL default route if possible. */
445 	else
446 		rt_kfree(rt);
447 	return retval;
448 }
449 
450 static bool
451 rt_cmp(const struct rt *r1, const struct rt *r2)
452 {
453 
454 	return (r1->rt_ifp == r2->rt_ifp &&
455 #ifdef HAVE_ROUTE_METRIC
456 	    r1->rt_metric == r2->rt_metric &&
457 #endif
458 	    sa_cmp(&r1->rt_gateway, &r2->rt_gateway) == 0);
459 }
460 
461 static bool
462 rt_doroute(struct rt *rt)
463 {
464 	struct dhcpcd_ctx *ctx;
465 	struct rt *or;
466 
467 	ctx = rt->rt_ifp->ctx;
468 	/* Do we already manage it? */
469 	if ((or = rt_find(&ctx->routes, rt))) {
470 		if (rt->rt_dflags & RTDF_FAKE)
471 			return true;
472 		if (or->rt_dflags & RTDF_FAKE ||
473 		    !rt_cmp(rt, or) ||
474 		    (rt->rt_ifa.sa_family != AF_UNSPEC &&
475 		    sa_cmp(&or->rt_ifa, &rt->rt_ifa) != 0) ||
476 		    or->rt_mtu != rt->rt_mtu)
477 		{
478 			if (!rt_add(rt, or))
479 				return false;
480 		}
481 		TAILQ_REMOVE(&ctx->routes, or, rt_next);
482 		rt_free(or);
483 	} else {
484 		if (rt->rt_dflags & RTDF_FAKE) {
485 			if ((or = rt_find(&ctx->kroutes, rt)) == NULL)
486 				return false;
487 			if (!rt_cmp(rt, or))
488 				return false;
489 		} else {
490 			if (!rt_add(rt, NULL))
491 				return false;
492 		}
493 	}
494 
495 	return true;
496 }
497 
498 void
499 rt_build(struct dhcpcd_ctx *ctx, int af)
500 {
501 	struct rt_head routes, added;
502 	struct rt *rt, *rtn;
503 	unsigned long long o;
504 
505 	/* We need to have the interfaces in the correct order to ensure
506 	 * our routes are managed correctly. */
507 	if_sortinterfaces(ctx);
508 
509 	TAILQ_INIT(&routes);
510 	TAILQ_INIT(&added);
511 
512 	switch (af) {
513 #ifdef INET
514 	case AF_INET:
515 		if (!inet_getroutes(ctx, &routes))
516 			goto getfail;
517 		break;
518 #endif
519 #ifdef INET6
520 	case AF_INET6:
521 		if (!inet6_getroutes(ctx, &routes))
522 			goto getfail;
523 		break;
524 #endif
525 	}
526 
527 	TAILQ_FOREACH_SAFE(rt, &routes, rt_next, rtn) {
528 		if (rt->rt_dest.sa_family != af &&
529 		    rt->rt_gateway.sa_family != af)
530 			continue;
531 		/* Is this route already in our table? */
532 		if ((rt_find(&added, rt)) != NULL)
533 			continue;
534 		if (rt_doroute(rt)) {
535 			TAILQ_REMOVE(&routes, rt, rt_next);
536 			TAILQ_INSERT_TAIL(&added, rt, rt_next);
537 		}
538 	}
539 
540 	/* Remove old routes we used to manage. */
541 	TAILQ_FOREACH_SAFE(rt, &ctx->routes, rt_next, rtn) {
542 		if (rt->rt_dest.sa_family != af &&
543 		    rt->rt_gateway.sa_family != af)
544 			continue;
545 		TAILQ_REMOVE(&ctx->routes, rt, rt_next);
546 		if (rt_find(&added, rt) == NULL) {
547 			o = rt->rt_ifp->options ?
548 			    rt->rt_ifp->options->options :
549 			    ctx->options;
550 			if ((o &
551 				(DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
552 				(DHCPCD_EXITING | DHCPCD_PERSISTENT))
553 				rt_delete(rt);
554 		}
555 		TAILQ_INSERT_TAIL(&ctx->froutes, rt, rt_next);
556 	}
557 
558 	rt_headclear(&ctx->routes, af);
559 	TAILQ_CONCAT(&ctx->routes, &added, rt_next);
560 
561 getfail:
562 	rt_headclear(&routes, AF_UNSPEC);
563 }
564