xref: /netbsd-src/external/bsd/dhcpcd/dist/src/route.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
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 void
48 rt_init(struct dhcpcd_ctx *ctx)
49 {
50 
51 	TAILQ_INIT(&ctx->routes);
52 	TAILQ_INIT(&ctx->kroutes);
53 	TAILQ_INIT(&ctx->froutes);
54 }
55 
56 static void
57 rt_desc(const char *cmd, const struct rt *rt)
58 {
59 	char dest[INET_MAX_ADDRSTRLEN], gateway[INET_MAX_ADDRSTRLEN];
60 	int prefix;
61 	const char *ifname;
62 	bool gateway_unspec;
63 
64 	assert(cmd != NULL);
65 	assert(rt != NULL);
66 	assert(rt->rt_ifp != NULL);
67 
68 	ifname = rt->rt_ifp->name;
69 	sa_addrtop(&rt->rt_dest, dest, sizeof(dest));
70 	prefix = sa_toprefix(&rt->rt_netmask);
71 	sa_addrtop(&rt->rt_gateway, gateway, sizeof(gateway));
72 
73 	gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
74 
75 	if (rt->rt_flags & RTF_HOST) {
76 		if (gateway_unspec)
77 			loginfox("%s: %s host route to %s",
78 			    ifname, cmd, dest);
79 		else
80 			loginfox("%s: %s host route to %s via %s",
81 			    ifname, cmd, dest, gateway);
82 	} else if (sa_is_unspecified(&rt->rt_dest) &&
83 		   sa_is_unspecified(&rt->rt_netmask))
84 	{
85 		if (gateway_unspec)
86 			loginfox("%s: %s default route",
87 			    ifname, cmd);
88 		else
89 			loginfox("%s: %s default route via %s",
90 			    ifname, cmd, gateway);
91 	} else if (gateway_unspec)
92 		loginfox("%s: %s%s route to %s/%d",
93 		    ifname, cmd,
94 		    rt->rt_flags & RTF_REJECT ? " reject" : "",
95 		    dest, prefix);
96 	else
97 		loginfox("%s: %s%s route to %s/%d via %s",
98 		    ifname, cmd,
99 		    rt->rt_flags & RTF_REJECT ? " reject" : "",
100 		    dest, prefix, gateway);
101 }
102 
103 void
104 rt_headclear0(struct dhcpcd_ctx *ctx, struct rt_head *rts, int af)
105 {
106 	struct rt *rt, *rtn;
107 
108 	if (rts == NULL)
109 		return;
110 	assert(ctx != NULL);
111 	assert(&ctx->froutes != rts);
112 
113 	TAILQ_FOREACH_SAFE(rt, rts, rt_next, rtn) {
114 		if (af != AF_UNSPEC &&
115 		    rt->rt_dest.sa_family != af &&
116 		    rt->rt_gateway.sa_family != af)
117 			continue;
118 		TAILQ_REMOVE(rts, rt, rt_next);
119 		TAILQ_INSERT_TAIL(&ctx->froutes, rt, rt_next);
120 	}
121 }
122 
123 void
124 rt_headclear(struct rt_head *rts, int af)
125 {
126 	struct rt *rt;
127 
128 	if (rts == NULL || (rt = TAILQ_FIRST(rts)) == NULL)
129 		return;
130 	rt_headclear0(rt->rt_ifp->ctx, rts, af);
131 }
132 
133 static void
134 rt_headfree(struct rt_head *rts)
135 {
136 	struct rt *rt;
137 
138 	while ((rt = TAILQ_FIRST(rts))) {
139 		TAILQ_REMOVE(rts, rt, rt_next);
140 		free(rt);
141 	}
142 }
143 
144 void
145 rt_dispose(struct dhcpcd_ctx *ctx)
146 {
147 
148 	assert(ctx != NULL);
149 	rt_headfree(&ctx->routes);
150 	rt_headfree(&ctx->kroutes);
151 	rt_headfree(&ctx->froutes);
152 }
153 
154 struct rt *
155 rt_new0(struct dhcpcd_ctx *ctx)
156 {
157 	struct rt *rt;
158 
159 	assert(ctx != NULL);
160 	if ((rt = TAILQ_FIRST(&ctx->froutes)) != NULL)
161 		TAILQ_REMOVE(&ctx->froutes, rt, rt_next);
162 	else if ((rt = malloc(sizeof(*rt))) == NULL) {
163 		logerr(__func__);
164 		return NULL;
165 	}
166 	memset(rt, 0, sizeof(*rt));
167 	return rt;
168 }
169 
170 void
171 rt_setif(struct rt *rt, struct interface *ifp)
172 {
173 
174 	assert(rt != NULL);
175 	assert(ifp != NULL);
176 	rt->rt_ifp = ifp;
177 #ifdef HAVE_ROUTE_METRIC
178 	rt->rt_metric = ifp->metric;
179 #endif
180 }
181 
182 struct rt *
183 rt_new(struct interface *ifp)
184 {
185 	struct rt *rt;
186 
187 	assert(ifp != NULL);
188 	if ((rt = rt_new0(ifp->ctx)) == NULL)
189 		return NULL;
190 	rt_setif(rt, ifp);
191 	return rt;
192 }
193 
194 void
195 rt_free(struct rt *rt)
196 {
197 
198 	assert(rt != NULL);
199 	assert(rt->rt_ifp->ctx != NULL);
200 	TAILQ_INSERT_TAIL(&rt->rt_ifp->ctx->froutes, rt, rt_next);
201 }
202 
203 void
204 rt_freeif(struct interface *ifp)
205 {
206 	struct dhcpcd_ctx *ctx;
207 	struct rt *rt, *rtn;
208 
209 	if (ifp == NULL)
210 		return;
211 	ctx = ifp->ctx;
212 	TAILQ_FOREACH_SAFE(rt, &ctx->routes, rt_next, rtn) {
213 		if (rt->rt_ifp == ifp) {
214 			TAILQ_REMOVE(&ctx->routes, rt, rt_next);
215 			rt_free(rt);
216 		}
217 	}
218 	TAILQ_FOREACH_SAFE(rt, &ctx->kroutes, rt_next, rtn) {
219 		if (rt->rt_ifp == ifp) {
220 			TAILQ_REMOVE(&ctx->kroutes, rt, rt_next);
221 			rt_free(rt);
222 		}
223 	}
224 }
225 
226 struct rt *
227 rt_find(struct rt_head *rts, const struct rt *f)
228 {
229 	struct rt *rt;
230 
231 	assert(rts != NULL);
232 	assert(f != NULL);
233 	TAILQ_FOREACH(rt, rts, rt_next) {
234 		if (sa_cmp(&rt->rt_dest, &f->rt_dest) == 0 &&
235 #ifdef HAVE_ROUTE_METRIC
236 		    (f->rt_ifp == NULL ||
237 		    rt->rt_ifp->metric == f->rt_ifp->metric) &&
238 #endif
239 		    sa_cmp(&rt->rt_netmask, &f->rt_netmask) == 0)
240 			return rt;
241 	}
242 	return NULL;
243 }
244 
245 static void
246 rt_kfree(struct rt *rt)
247 {
248 	struct dhcpcd_ctx *ctx;
249 	struct rt *f;
250 
251 	assert(rt != NULL);
252 	ctx = rt->rt_ifp->ctx;
253 	if ((f = rt_find(&ctx->kroutes, rt)) != NULL) {
254 		TAILQ_REMOVE(&ctx->kroutes, f, rt_next);
255 		rt_free(f);
256 	}
257 }
258 
259 /* If something other than dhcpcd removes a route,
260  * we need to remove it from our internal table. */
261 void
262 rt_recvrt(int cmd, const struct rt *rt)
263 {
264 	struct dhcpcd_ctx *ctx;
265 	struct rt *f;
266 
267 	assert(rt != NULL);
268 	ctx = rt->rt_ifp->ctx;
269 	f = rt_find(&ctx->kroutes, rt);
270 
271 	switch(cmd) {
272 	case RTM_DELETE:
273 		if (f != NULL) {
274 			TAILQ_REMOVE(&ctx->kroutes, f, rt_next);
275 			rt_free(f);
276 		}
277 		if ((f = rt_find(&ctx->routes, rt)) != NULL) {
278 			TAILQ_REMOVE(&ctx->routes, f, rt_next);
279 			rt_desc("deleted", f);
280 			rt_free(f);
281 		}
282 		break;
283 	case RTM_ADD:
284 		if (f != NULL)
285 			break;
286 		if ((f = rt_new(rt->rt_ifp)) == NULL)
287 			break;
288 		memcpy(f, rt, sizeof(*f));
289 		TAILQ_INSERT_TAIL(&ctx->kroutes, f, rt_next);
290 		break;
291 	}
292 
293 #if defined(INET) && defined(HAVE_ROUTE_METRIC)
294 	if (rt->rt_dest.sa_family == AF_INET)
295 		ipv4ll_recvrt(cmd, rt);
296 #endif
297 }
298 
299 static bool
300 rt_add(struct rt *nrt, struct rt *ort)
301 {
302 	struct dhcpcd_ctx *ctx;
303 	bool change;
304 
305 	assert(nrt != NULL);
306 	ctx = nrt->rt_ifp->ctx;
307 
308 	/*
309 	 * Don't install a gateway if not asked to.
310 	 * This option is mainly for VPN users who want their VPN to be the
311 	 * default route.
312 	 * Because VPN's generally don't care about route management
313 	 * beyond their own, a longer term solution would be to remove this
314 	 * and get the VPN to inject the default route into dhcpcd somehow.
315 	 */
316 	if (((nrt->rt_ifp->active &&
317 	    !(nrt->rt_ifp->options->options & DHCPCD_GATEWAY)) ||
318 	    (!nrt->rt_ifp->active && !(ctx->options & DHCPCD_GATEWAY))) &&
319 	    sa_is_unspecified(&nrt->rt_dest) &&
320 	    sa_is_unspecified(&nrt->rt_netmask))
321 		return false;
322 
323 	rt_desc(ort == NULL ? "adding" : "changing", nrt);
324 
325 	change = false;
326 	if (ort == NULL) {
327 		ort = rt_find(&ctx->kroutes, nrt);
328 		if (ort != NULL &&
329 		    ((ort->rt_flags & RTF_REJECT &&
330 		      nrt->rt_flags & RTF_REJECT) ||
331 		     (ort->rt_ifp == nrt->rt_ifp &&
332 #ifdef HAVE_ROUTE_METRIC
333 		    ort->rt_metric == nrt->rt_metric &&
334 #endif
335 		    sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)))
336 		{
337 			if (ort->rt_mtu == nrt->rt_mtu)
338 				return true;
339 			change = true;
340 		}
341 	} else if (ort->rt_dflags & RTDF_FAKE &&
342 	    !(nrt->rt_dflags & RTDF_FAKE) &&
343 	    ort->rt_ifp == nrt->rt_ifp &&
344 #ifdef HAVE_ROUTE_METRIC
345 	    ort->rt_metric == nrt->rt_metric &&
346 #endif
347 	    sa_cmp(&ort->rt_dest, &nrt->rt_dest) == 0 &&
348 	    sa_cmp(&ort->rt_netmask, &nrt->rt_netmask) == 0 &&
349 	    sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)
350 	{
351 		if (ort->rt_mtu == nrt->rt_mtu)
352 			return true;
353 		change = true;
354 	}
355 
356 #ifdef RTF_CLONING
357 	/* BSD can set routes to be cloning routes.
358 	 * Cloned routes inherit the parent flags.
359 	 * As such, we need to delete and re-add the route to flush children
360 	 * to correct the flags. */
361 	if (change && ort != NULL && ort->rt_flags & RTF_CLONING)
362 		change = false;
363 #endif
364 
365 	if (change) {
366 		if (if_route(RTM_CHANGE, nrt) != -1)
367 			return true;
368 		if (errno != ESRCH)
369 			logerr("if_route (CHG)");
370 	}
371 
372 #ifdef HAVE_ROUTE_METRIC
373 	/* With route metrics, we can safely add the new route before
374 	 * deleting the old route. */
375 	if (if_route(RTM_ADD, nrt) != -1) {
376 		if (ort != NULL) {
377 			if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
378 				logerr("if_route (DEL)");
379 			rt_kfree(ort);
380 		}
381 		return true;
382 	}
383 
384 	/* If the kernel claims the route exists we need to rip out the
385 	 * old one first. */
386 	if (errno != EEXIST || ort == NULL)
387 		goto logerr;
388 #endif
389 
390 	/* No route metrics, we need to delete the old route before
391 	 * adding the new one. */
392 #ifdef ROUTE_PER_GATEWAY
393 	errno = 0;
394 #endif
395 	if (ort != NULL) {
396 		if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
397 			logerr("if_route (DEL)");
398 		else
399 			rt_kfree(ort);
400 	}
401 #ifdef ROUTE_PER_GATEWAY
402 	/* The OS allows many routes to the same dest with different gateways.
403 	 * dhcpcd does not support this yet, so for the time being just keep on
404 	 * deleting the route until there is an error. */
405 	if (ort != NULL && errno == 0) {
406 		for (;;) {
407 			if (if_route(RTM_DELETE, ort) == -1)
408 				break;
409 		}
410 	}
411 #endif
412 	if (if_route(RTM_ADD, nrt) != -1)
413 		return true;
414 #ifdef HAVE_ROUTE_METRIC
415 logerr:
416 #endif
417 	logerr("if_route (ADD)");
418 	return false;
419 }
420 
421 static bool
422 rt_delete(struct rt *rt)
423 {
424 	int retval;
425 
426 	rt_desc("deleting", rt);
427 	retval = if_route(RTM_DELETE, rt) == -1 ? false : true;
428 	if (!retval && errno != ENOENT && errno != ESRCH)
429 		logerr(__func__);
430 	/* Remove the route from our kernel table so we can add a
431 	 * IPv4LL default route if possible. */
432 	else
433 		rt_kfree(rt);
434 	return retval;
435 }
436 
437 static bool
438 rt_cmp(const struct rt *r1, const struct rt *r2)
439 {
440 
441 	return (r1->rt_ifp == r2->rt_ifp &&
442 #ifdef HAVE_ROUTE_METRIC
443 	    r1->rt_metric == r2->rt_metric &&
444 #endif
445 	    sa_cmp(&r1->rt_gateway, &r2->rt_gateway) == 0);
446 }
447 
448 static bool
449 rt_doroute(struct rt *rt)
450 {
451 	struct dhcpcd_ctx *ctx;
452 	struct rt *or;
453 
454 	ctx = rt->rt_ifp->ctx;
455 	/* Do we already manage it? */
456 	if ((or = rt_find(&ctx->routes, rt))) {
457 		if (rt->rt_dflags & RTDF_FAKE)
458 			return true;
459 		if (or->rt_dflags & RTDF_FAKE ||
460 		    !rt_cmp(rt, or) ||
461 		    (rt->rt_ifa.sa_family != AF_UNSPEC &&
462 		    sa_cmp(&or->rt_ifa, &rt->rt_ifa) != 0) ||
463 		    or->rt_mtu != rt->rt_mtu)
464 		{
465 			if (!rt_add(rt, or))
466 				return false;
467 		}
468 		TAILQ_REMOVE(&ctx->routes, or, rt_next);
469 		rt_free(or);
470 	} else {
471 		if (rt->rt_dflags & RTDF_FAKE) {
472 			if ((or = rt_find(&ctx->kroutes, rt)) == NULL)
473 				return false;
474 			if (!rt_cmp(rt, or))
475 				return false;
476 		} else {
477 			if (!rt_add(rt, NULL))
478 				return false;
479 		}
480 	}
481 
482 	return true;
483 }
484 
485 void
486 rt_build(struct dhcpcd_ctx *ctx, int af)
487 {
488 	struct rt_head routes, added;
489 	struct rt *rt, *rtn;
490 	unsigned long long o;
491 
492 	/* We need to have the interfaces in the correct order to ensure
493 	 * our routes are managed correctly. */
494 	if_sortinterfaces(ctx);
495 
496 	TAILQ_INIT(&routes);
497 	TAILQ_INIT(&added);
498 
499 	switch (af) {
500 #ifdef INET
501 	case AF_INET:
502 		if (!inet_getroutes(ctx, &routes))
503 			goto getfail;
504 		break;
505 #endif
506 #ifdef INET6
507 	case AF_INET6:
508 		if (!inet6_getroutes(ctx, &routes))
509 			goto getfail;
510 		break;
511 #endif
512 	}
513 
514 	TAILQ_FOREACH_SAFE(rt, &routes, rt_next, rtn) {
515 		if (rt->rt_dest.sa_family != af &&
516 		    rt->rt_gateway.sa_family != af)
517 			continue;
518 		/* Is this route already in our table? */
519 		if ((rt_find(&added, rt)) != NULL)
520 			continue;
521 		if (rt_doroute(rt)) {
522 			TAILQ_REMOVE(&routes, rt, rt_next);
523 			TAILQ_INSERT_TAIL(&added, rt, rt_next);
524 		}
525 	}
526 
527 	/* Remove old routes we used to manage. */
528 	TAILQ_FOREACH_SAFE(rt, &ctx->routes, rt_next, rtn) {
529 		if (rt->rt_dest.sa_family != af &&
530 		    rt->rt_gateway.sa_family != af)
531 			continue;
532 		TAILQ_REMOVE(&ctx->routes, rt, rt_next);
533 		if (rt_find(&added, rt) == NULL) {
534 			o = rt->rt_ifp->options ?
535 			    rt->rt_ifp->options->options :
536 			    ctx->options;
537 			if ((o &
538 				(DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
539 				(DHCPCD_EXITING | DHCPCD_PERSISTENT))
540 				rt_delete(rt);
541 		}
542 		TAILQ_INSERT_TAIL(&ctx->froutes, rt, rt_next);
543 	}
544 
545 	rt_headclear(&ctx->routes, af);
546 	TAILQ_CONCAT(&ctx->routes, &added, rt_next);
547 
548 getfail:
549 	rt_headclear(&routes, AF_UNSPEC);
550 }
551