]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-route-nexthop.c
Merge pull request #31082 from yuwata/network-cleanups-for-removing-routes
[thirdparty/systemd.git] / src / network / networkd-route-nexthop.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <linux/nexthop.h>
4
5 #include "alloc-util.h"
6 #include "extract-word.h"
7 #include "netlink-util.h"
8 #include "networkd-manager.h"
9 #include "networkd-network.h"
10 #include "networkd-nexthop.h"
11 #include "networkd-route.h"
12 #include "networkd-route-nexthop.h"
13 #include "networkd-route-util.h"
14 #include "parse-util.h"
15 #include "string-util.h"
16
17 void route_detach_from_nexthop(Route *route) {
18 NextHop *nh;
19
20 assert(route);
21 assert(route->manager);
22
23 if (route->nexthop_id == 0)
24 return;
25
26 if (nexthop_get_by_id(route->manager, route->nexthop_id, &nh) < 0)
27 return;
28
29 route_unref(set_remove(nh->routes, route));
30 }
31
32 void route_attach_to_nexthop(Route *route) {
33 NextHop *nh;
34 int r;
35
36 assert(route);
37 assert(route->manager);
38
39 if (route->nexthop_id == 0)
40 return;
41
42 r = nexthop_get_by_id(route->manager, route->nexthop_id, &nh);
43 if (r < 0) {
44 if (route->manager->manage_foreign_nexthops)
45 log_debug_errno(r, "Route has unknown nexthop ID (%"PRIu32"), ignoring.",
46 route->nexthop_id);
47 return;
48 }
49
50 r = set_ensure_put(&nh->routes, &route_hash_ops_unref, route);
51 if (r < 0)
52 return (void) log_debug_errno(r, "Failed to save route to nexthop, ignoring: %m");
53 if (r == 0)
54 return (void) log_debug("Duplicated route assigned to nexthop, ignoring.");
55
56 route_ref(route);
57 }
58
59 static void route_nexthop_done(RouteNextHop *nh) {
60 assert(nh);
61
62 free(nh->ifname);
63 }
64
65 RouteNextHop* route_nexthop_free(RouteNextHop *nh) {
66 if (!nh)
67 return NULL;
68
69 route_nexthop_done(nh);
70
71 return mfree(nh);
72 }
73
74 void route_nexthops_done(Route *route) {
75 assert(route);
76
77 route_nexthop_done(&route->nexthop);
78 ordered_set_free(route->nexthops);
79 }
80
81 static void route_nexthop_hash_func_full(const RouteNextHop *nh, struct siphash *state, bool with_weight) {
82 assert(nh);
83 assert(state);
84
85 /* See nh_comp() in net/ipv4/fib_semantics.c of the kernel. */
86
87 siphash24_compress_typesafe(nh->family, state);
88 if (!IN_SET(nh->family, AF_INET, AF_INET6))
89 return;
90
91 in_addr_hash_func(&nh->gw, nh->family, state);
92 if (with_weight)
93 siphash24_compress_typesafe(nh->weight, state);
94 siphash24_compress_typesafe(nh->ifindex, state);
95 if (nh->ifindex == 0)
96 siphash24_compress_string(nh->ifname, state); /* For Network or Request object. */
97 }
98
99 static int route_nexthop_compare_func_full(const RouteNextHop *a, const RouteNextHop *b, bool with_weight) {
100 int r;
101
102 assert(a);
103 assert(b);
104
105 r = CMP(a->family, b->family);
106 if (r != 0)
107 return r;
108
109 if (!IN_SET(a->family, AF_INET, AF_INET6))
110 return 0;
111
112 r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
113 if (r != 0)
114 return r;
115
116 if (with_weight) {
117 r = CMP(a->weight, b->weight);
118 if (r != 0)
119 return r;
120 }
121
122 r = CMP(a->ifindex, b->ifindex);
123 if (r != 0)
124 return r;
125
126 if (a->ifindex == 0) {
127 r = strcmp_ptr(a->ifname, b->ifname);
128 if (r != 0)
129 return r;
130 }
131
132 return 0;
133 }
134
135 static void route_nexthop_hash_func(const RouteNextHop *nh, struct siphash *state) {
136 route_nexthop_hash_func_full(nh, state, /* with_weight = */ true);
137 }
138
139 static int route_nexthop_compare_func(const RouteNextHop *a, const RouteNextHop *b) {
140 return route_nexthop_compare_func_full(a, b, /* with_weight = */ true);
141 }
142
143 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
144 route_nexthop_hash_ops,
145 RouteNextHop,
146 route_nexthop_hash_func,
147 route_nexthop_compare_func,
148 route_nexthop_free);
149
150 static size_t route_n_nexthops(const Route *route) {
151 if (route->nexthop_id != 0 || route_type_is_reject(route))
152 return 0;
153
154 if (ordered_set_isempty(route->nexthops))
155 return 1;
156
157 return ordered_set_size(route->nexthops);
158 }
159
160 void route_nexthops_hash_func(const Route *route, struct siphash *state) {
161 assert(route);
162
163 size_t nhs = route_n_nexthops(route);
164 siphash24_compress_typesafe(nhs, state);
165
166 switch (nhs) {
167 case 0:
168 siphash24_compress_typesafe(route->nexthop_id, state);
169 return;
170
171 case 1:
172 route_nexthop_hash_func_full(&route->nexthop, state, /* with_weight = */ false);
173 return;
174
175 default: {
176 RouteNextHop *nh;
177 ORDERED_SET_FOREACH(nh, route->nexthops)
178 route_nexthop_hash_func(nh, state);
179 }}
180 }
181
182 int route_nexthops_compare_func(const Route *a, const Route *b) {
183 int r;
184
185 assert(a);
186 assert(b);
187
188 size_t a_nhs = route_n_nexthops(a);
189 size_t b_nhs = route_n_nexthops(b);
190 r = CMP(a_nhs, b_nhs);
191 if (r != 0)
192 return r;
193
194 switch (a_nhs) {
195 case 0:
196 return CMP(a->nexthop_id, b->nexthop_id);
197
198 case 1:
199 return route_nexthop_compare_func_full(&a->nexthop, &b->nexthop, /* with_weight = */ false);
200
201 default: {
202 RouteNextHop *nh;
203 ORDERED_SET_FOREACH(nh, a->nexthops) {
204 r = CMP(nh, (RouteNextHop*) ordered_set_get(a->nexthops, nh));
205 if (r != 0)
206 return r;
207 }
208 return 0;
209 }}
210 }
211
212 static int route_nexthop_copy(const RouteNextHop *src, RouteNextHop *dest) {
213 assert(src);
214 assert(dest);
215
216 *dest = *src;
217
218 /* unset pointer copied in the above. */
219 dest->ifname = NULL;
220
221 return strdup_or_null(src->ifindex > 0 ? NULL : src->ifname, &dest->ifname);
222 }
223
224 static int route_nexthop_dup(const RouteNextHop *src, RouteNextHop **ret) {
225 _cleanup_(route_nexthop_freep) RouteNextHop *dest = NULL;
226 int r;
227
228 assert(src);
229 assert(ret);
230
231 dest = new(RouteNextHop, 1);
232 if (!dest)
233 return -ENOMEM;
234
235 r = route_nexthop_copy(src, dest);
236 if (r < 0)
237 return r;
238
239 *ret = TAKE_PTR(dest);
240 return 0;
241 }
242
243 int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) {
244 int r;
245
246 assert(src);
247 assert(dest);
248
249 if (src->nexthop_id != 0 || route_type_is_reject(src))
250 return 0;
251
252 if (nh)
253 return route_nexthop_copy(nh, &dest->nexthop);
254
255 if (ordered_set_isempty(src->nexthops))
256 return route_nexthop_copy(&src->nexthop, &dest->nexthop);
257
258 ORDERED_SET_FOREACH(nh, src->nexthops) {
259 _cleanup_(route_nexthop_freep) RouteNextHop *nh_dup = NULL;
260
261 r = route_nexthop_dup(nh, &nh_dup);
262 if (r < 0)
263 return r;
264
265 r = ordered_set_ensure_put(&dest->nexthops, &route_nexthop_hash_ops, nh_dup);
266 if (r < 0)
267 return r;
268 assert(r > 0);
269
270 TAKE_PTR(nh_dup);
271 }
272
273 return 0;
274 }
275
276 static bool multipath_routes_needs_adjust(const Route *route) {
277 assert(route);
278
279 RouteNextHop *nh;
280 ORDERED_SET_FOREACH(nh, route->nexthops)
281 if (route->nexthop.ifindex == 0)
282 return true;
283
284 return false;
285 }
286
287 bool route_nexthops_needs_adjust(const Route *route) {
288 assert(route);
289
290 if (route->nexthop_id != 0)
291 /* At this stage, the nexthop may not be configured, or may be under reconfiguring.
292 * Hence, we cannot know if the nexthop is blackhole or not. */
293 return route->type != RTN_BLACKHOLE;
294
295 if (route_type_is_reject(route))
296 return false;
297
298 if (ordered_set_isempty(route->nexthops))
299 return route->nexthop.ifindex == 0;
300
301 return multipath_routes_needs_adjust(route);
302 }
303
304 static bool route_nexthop_set_ifindex(RouteNextHop *nh, Link *link) {
305 assert(nh);
306 assert(link);
307 assert(link->manager);
308
309 if (nh->ifindex > 0) {
310 nh->ifname = mfree(nh->ifname);
311 return false;
312 }
313
314 /* If an interface name is specified, use it. Otherwise, use the interface that requests this route. */
315 if (nh->ifname && link_get_by_name(link->manager, nh->ifname, &link) < 0)
316 return false;
317
318 nh->ifindex = link->ifindex;
319 nh->ifname = mfree(nh->ifname);
320 return true; /* updated */
321 }
322
323 int route_adjust_nexthops(Route *route, Link *link) {
324 int r;
325
326 assert(route);
327 assert(link);
328 assert(link->manager);
329
330 /* When an IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel sends
331 * RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type fib_rt_info::type
332 * may not be blackhole. Thus, we cannot know the internal value. Moreover, on route removal, the
333 * matching is done with the hidden value if we set non-zero type in RTM_DELROUTE message. So,
334 * here let's set route type to BLACKHOLE when the nexthop is blackhole. */
335 if (route->nexthop_id != 0) {
336 NextHop *nexthop;
337
338 r = nexthop_is_ready(link->manager, route->nexthop_id, &nexthop);
339 if (r <= 0)
340 return r; /* r == 0 means the nexthop is under (re-)configuring.
341 * We cannot use the currently remembered information. */
342
343 if (!nexthop->blackhole)
344 return false;
345
346 if (route->type == RTN_BLACKHOLE)
347 return false;
348
349 route->type = RTN_BLACKHOLE;
350 return true; /* updated */
351 }
352
353 if (route_type_is_reject(route))
354 return false;
355
356 if (ordered_set_isempty(route->nexthops))
357 return route_nexthop_set_ifindex(&route->nexthop, link);
358
359 if (!multipath_routes_needs_adjust(route))
360 return false;
361
362 _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL;
363 for (;;) {
364 _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
365
366 nh = ordered_set_steal_first(route->nexthops);
367 if (!nh)
368 break;
369
370 (void) route_nexthop_set_ifindex(nh, link);
371
372 r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh);
373 if (r == -EEXIST)
374 continue; /* Duplicated? Let's drop the nexthop. */
375 if (r < 0)
376 return r;
377 assert(r > 0);
378
379 TAKE_PTR(nh);
380 }
381
382 ordered_set_free(route->nexthops);
383 route->nexthops = TAKE_PTR(nexthops);
384 return true; /* updated */
385 }
386
387 int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret) {
388 assert(manager);
389 assert(nh);
390
391 if (nh->ifindex > 0)
392 return link_get_by_index(manager, nh->ifindex, ret);
393 if (nh->ifname)
394 return link_get_by_name(manager, nh->ifname, ret);
395
396 return -ENOENT;
397 }
398
399 static bool route_nexthop_is_ready_to_configure(const RouteNextHop *nh, Manager *manager, bool onlink) {
400 Link *link;
401
402 assert(nh);
403 assert(manager);
404
405 if (route_nexthop_get_link(manager, nh, &link) < 0)
406 return false;
407
408 if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ true))
409 return false;
410
411 /* If the interface is not managed by us, we request that the interface has carrier.
412 * That is, ConfigureWithoutCarrier=no is the default even for unamanaged interfaces. */
413 if (!link->network && !link_has_carrier(link))
414 return false;
415
416 return gateway_is_ready(link, onlink, nh->family, &nh->gw);
417 }
418
419 int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager) {
420 int r;
421
422 assert(route);
423 assert(manager);
424
425 if (route->nexthop_id != 0) {
426 struct nexthop_grp *nhg;
427 NextHop *nh;
428
429 r = nexthop_is_ready(manager, route->nexthop_id, &nh);
430 if (r <= 0)
431 return r;
432
433 HASHMAP_FOREACH(nhg, nh->group) {
434 r = nexthop_is_ready(manager, nhg->id, NULL);
435 if (r <= 0)
436 return r;
437 }
438
439 return true;
440 }
441
442 if (route_type_is_reject(route))
443 return true;
444
445 if (ordered_set_isempty(route->nexthops))
446 return route_nexthop_is_ready_to_configure(&route->nexthop, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK));
447
448 RouteNextHop *nh;
449 ORDERED_SET_FOREACH(nh, route->nexthops)
450 if (!route_nexthop_is_ready_to_configure(nh, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK)))
451 return false;
452
453 return true;
454 }
455
456 int route_nexthops_to_string(const Route *route, char **ret) {
457 _cleanup_free_ char *buf = NULL;
458 int r;
459
460 assert(route);
461 assert(ret);
462
463 if (route->nexthop_id != 0) {
464 if (asprintf(&buf, "nexthop: %"PRIu32, route->nexthop_id) < 0)
465 return -ENOMEM;
466
467 *ret = TAKE_PTR(buf);
468 return 0;
469 }
470
471 if (route_type_is_reject(route)) {
472 buf = strdup("gw: n/a");
473 if (!buf)
474 return -ENOMEM;
475
476 *ret = TAKE_PTR(buf);
477 return 0;
478 }
479
480 if (ordered_set_isempty(route->nexthops)) {
481 if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw))
482 buf = strjoin("gw: ", IN_ADDR_TO_STRING(route->nexthop.family, &route->nexthop.gw));
483 else if (route->gateway_from_dhcp_or_ra) {
484 if (route->nexthop.family == AF_INET)
485 buf = strdup("gw: _dhcp4");
486 else if (route->nexthop.family == AF_INET6)
487 buf = strdup("gw: _ipv6ra");
488 else
489 buf = strdup("gw: _dhcp");
490 } else
491 buf = strdup("gw: n/a");
492 if (!buf)
493 return -ENOMEM;
494
495 *ret = TAKE_PTR(buf);
496 return 0;
497 }
498
499 RouteNextHop *nh;
500 ORDERED_SET_FOREACH(nh, route->nexthops) {
501 const char *s = in_addr_is_set(nh->family, &nh->gw) ? IN_ADDR_TO_STRING(nh->family, &nh->gw) : NULL;
502
503 if (nh->ifindex > 0)
504 r = strextendf_with_separator(&buf, ",", "%s@%i:%"PRIu32, strempty(s), nh->ifindex, nh->weight + 1);
505 else if (nh->ifname)
506 r = strextendf_with_separator(&buf, ",", "%s@%s:%"PRIu32, strempty(s), nh->ifname, nh->weight + 1);
507 else
508 r = strextendf_with_separator(&buf, ",", "%s:%"PRIu32, strempty(s), nh->weight + 1);
509 if (r < 0)
510 return r;
511 }
512
513 char *p = strjoin("gw: ", strna(buf));
514 if (!p)
515 return -ENOMEM;
516
517 *ret = p;
518 return 0;
519 }
520
521 static int append_nexthop_one(const Route *route, const RouteNextHop *nh, struct rtattr **rta, size_t offset) {
522 struct rtnexthop *rtnh;
523 struct rtattr *new_rta;
524 int r;
525
526 assert(route);
527 assert(IN_SET(route->family, AF_INET, AF_INET6));
528 assert(nh);
529 assert(rta);
530 assert(*rta);
531
532 new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop)));
533 if (!new_rta)
534 return -ENOMEM;
535 *rta = new_rta;
536
537 rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
538 *rtnh = (struct rtnexthop) {
539 .rtnh_len = sizeof(*rtnh),
540 .rtnh_ifindex = nh->ifindex,
541 .rtnh_hops = nh->weight,
542 };
543
544 (*rta)->rta_len += sizeof(struct rtnexthop);
545
546 if (nh->family == route->family) {
547 r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family));
548 if (r < 0)
549 goto clear;
550
551 rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
552 rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family));
553
554 } else if (nh->family == AF_INET6) {
555 assert(route->family == AF_INET);
556
557 r = rtattr_append_attribute(rta, RTA_VIA,
558 &(RouteVia) {
559 .family = nh->family,
560 .address = nh->gw,
561 }, sizeof(RouteVia));
562 if (r < 0)
563 goto clear;
564
565 rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset);
566 rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia));
567
568 } else if (nh->family == AF_INET)
569 assert_not_reached();
570
571 return 0;
572
573 clear:
574 (*rta)->rta_len -= sizeof(struct rtnexthop);
575 return r;
576 }
577
578 static int netlink_message_append_multipath_route(const Route *route, sd_netlink_message *message) {
579 _cleanup_free_ struct rtattr *rta = NULL;
580 size_t offset;
581 int r;
582
583 assert(route);
584 assert(message);
585
586 rta = new(struct rtattr, 1);
587 if (!rta)
588 return -ENOMEM;
589
590 *rta = (struct rtattr) {
591 .rta_type = RTA_MULTIPATH,
592 .rta_len = RTA_LENGTH(0),
593 };
594 offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta;
595
596 if (ordered_set_isempty(route->nexthops)) {
597 r = append_nexthop_one(route, &route->nexthop, &rta, offset);
598 if (r < 0)
599 return r;
600
601 } else {
602 RouteNextHop *nh;
603 ORDERED_SET_FOREACH(nh, route->nexthops) {
604 struct rtnexthop *rtnh;
605
606 r = append_nexthop_one(route, nh, &rta, offset);
607 if (r < 0)
608 return r;
609
610 rtnh = (struct rtnexthop *)((uint8_t *) rta + offset);
611 offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta;
612 }
613 }
614
615 return sd_netlink_message_append_data(message, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta));
616 }
617
618 int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message) {
619 int r;
620
621 assert(route);
622 assert(IN_SET(route->family, AF_INET, AF_INET6));
623 assert(message);
624
625 if (route->nexthop_id != 0)
626 return sd_netlink_message_append_u32(message, RTA_NH_ID, route->nexthop_id);
627
628 if (route_type_is_reject(route))
629 return 0;
630
631 /* We request IPv6 multipath routes separately. Even though, if weight is non-zero, we need to use
632 * RTA_MULTIPATH, as we have no way to specify the weight of the nexthop. */
633 if (ordered_set_isempty(route->nexthops) && route->nexthop.weight == 0) {
634
635 if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) {
636 if (route->nexthop.family == route->family)
637 r = netlink_message_append_in_addr_union(message, RTA_GATEWAY, route->nexthop.family, &route->nexthop.gw);
638 else {
639 assert(route->family == AF_INET);
640 r = sd_netlink_message_append_data(message, RTA_VIA,
641 &(const RouteVia) {
642 .family = route->nexthop.family,
643 .address = route->nexthop.gw,
644 }, sizeof(RouteVia));
645 }
646 if (r < 0)
647 return r;
648 }
649
650 assert(route->nexthop.ifindex > 0);
651 return sd_netlink_message_append_u32(message, RTA_OIF, route->nexthop.ifindex);
652 }
653
654 return netlink_message_append_multipath_route(route, message);
655 }
656
657 static int route_parse_nexthops(Route *route, const struct rtnexthop *rtnh, size_t size) {
658 _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL;
659 int r;
660
661 assert(route);
662 assert(IN_SET(route->family, AF_INET, AF_INET6));
663 assert(rtnh);
664
665 if (size < sizeof(struct rtnexthop))
666 return -EBADMSG;
667
668 for (; size >= sizeof(struct rtnexthop); ) {
669 _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
670
671 if (NLMSG_ALIGN(rtnh->rtnh_len) > size)
672 return -EBADMSG;
673
674 if (rtnh->rtnh_len < sizeof(struct rtnexthop))
675 return -EBADMSG;
676
677 nh = new(RouteNextHop, 1);
678 if (!nh)
679 return -ENOMEM;
680
681 *nh = (RouteNextHop) {
682 .ifindex = rtnh->rtnh_ifindex,
683 .weight = rtnh->rtnh_hops,
684 };
685
686 if (rtnh->rtnh_len > sizeof(struct rtnexthop)) {
687 size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop);
688 bool have_gw = false;
689
690 for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
691
692 switch (attr->rta_type) {
693 case RTA_GATEWAY:
694 if (have_gw)
695 return -EBADMSG;
696
697 if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(route->family)))
698 return -EBADMSG;
699
700 nh->family = route->family;
701 memcpy(&nh->gw, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(nh->family));
702 have_gw = true;
703 break;
704
705 case RTA_VIA:
706 if (have_gw)
707 return -EBADMSG;
708
709 if (route->family != AF_INET)
710 return -EBADMSG;
711
712 if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia)))
713 return -EBADMSG;
714
715 RouteVia *via = RTA_DATA(attr);
716 if (via->family != AF_INET6)
717 return -EBADMSG;
718
719 nh->family = via->family;
720 nh->gw = via->address;
721 have_gw = true;
722 break;
723 }
724 }
725 }
726
727 r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh);
728 assert(r != 0);
729 if (r > 0)
730 TAKE_PTR(nh);
731 else if (r != -EEXIST)
732 return r;
733
734 size -= NLMSG_ALIGN(rtnh->rtnh_len);
735 rtnh = RTNH_NEXT(rtnh);
736 }
737
738 ordered_set_free(route->nexthops);
739 route->nexthops = TAKE_PTR(nexthops);
740 return 0;
741 }
742
743 int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message) {
744 int r;
745
746 assert(route);
747 assert(message);
748
749 r = sd_netlink_message_read_u32(message, RTA_NH_ID, &route->nexthop_id);
750 if (r < 0 && r != -ENODATA)
751 return log_warning_errno(r, "rtnl: received route message with invalid nexthop id, ignoring: %m");
752
753 if (route->nexthop_id != 0 || route_type_is_reject(route))
754 /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's
755 * fib6_nh_init() in net/ipv6/route.c. However, we'd like to make it consistent with IPv4
756 * routes. Hence, skip reading of RTA_OIF. */
757 return 0;
758
759 uint32_t ifindex;
760 r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex);
761 if (r >= 0)
762 route->nexthop.ifindex = (int) ifindex;
763 else if (r != -ENODATA)
764 return log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m");
765
766 if (route->nexthop.ifindex > 0) {
767 r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, route->family, &route->nexthop.gw);
768 if (r >= 0) {
769 route->nexthop.family = route->family;
770 return 0;
771 }
772 if (r != -ENODATA)
773 return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m");
774
775 if (route->family != AF_INET)
776 return 0;
777
778 RouteVia via;
779 r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via);
780 if (r >= 0) {
781 route->nexthop.family = via.family;
782 route->nexthop.gw = via.address;
783 return 0;
784 }
785 if (r != -ENODATA)
786 return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m");
787
788 return 0;
789 }
790
791 size_t rta_len;
792 _cleanup_free_ void *rta = NULL;
793 r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta);
794 if (r == -ENODATA)
795 return 0;
796 if (r < 0)
797 return log_warning_errno(r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m");
798
799 r = route_parse_nexthops(route, rta, rta_len);
800 if (r < 0)
801 return log_warning_errno(r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m");
802
803 return 0;
804 }
805
806 int route_section_verify_nexthops(Route *route) {
807 assert(route);
808 assert(route->section);
809
810 if (route->gateway_from_dhcp_or_ra) {
811 assert(route->network);
812
813 if (route->nexthop.family == AF_UNSPEC)
814 /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */
815 switch (route->family) {
816 case AF_UNSPEC:
817 log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
818 "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".",
819 route->section->filename, route->section->line);
820
821 route->nexthop.family = route->family = AF_INET;
822 break;
823 case AF_INET:
824 case AF_INET6:
825 log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. "
826 "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.",
827 route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra");
828
829 route->nexthop.family = route->family;
830 break;
831 default:
832 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
833 "%s: Invalid route family. Ignoring [Route] section from line %u.",
834 route->section->filename, route->section->line);
835 }
836
837 if (route->nexthop.family == AF_INET && !FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4))
838 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
839 "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. "
840 "Ignoring [Route] section from line %u.",
841 route->section->filename, route->section->line);
842
843 if (route->nexthop.family == AF_INET6 && !route->network->ipv6_accept_ra)
844 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
845 "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. "
846 "Ignoring [Route] section from line %u.",
847 route->section->filename, route->section->line);
848 }
849
850 /* When only Gateway= is specified, assume the route family based on the Gateway address. */
851 if (route->family == AF_UNSPEC)
852 route->family = route->nexthop.family;
853
854 if (route->family == AF_UNSPEC) {
855 assert(route->section);
856
857 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
858 "%s: Route section without Gateway=, Destination=, Source=, "
859 "or PreferredSource= field configured. "
860 "Ignoring [Route] section from line %u.",
861 route->section->filename, route->section->line);
862 }
863
864 if (route->gateway_onlink < 0 && in_addr_is_set(route->nexthop.family, &route->nexthop.gw) &&
865 route->network && ordered_hashmap_isempty(route->network->addresses_by_section)) {
866 /* If no address is configured, in most cases the gateway cannot be reachable.
867 * TODO: we may need to improve the condition above. */
868 log_warning("%s: Gateway= without static address configured. "
869 "Enabling GatewayOnLink= option.",
870 route->section->filename);
871 route->gateway_onlink = true;
872 }
873
874 if (route->gateway_onlink >= 0)
875 SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink);
876
877 if (route->family == AF_INET6) {
878 if (route->nexthop.family == AF_INET)
879 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
880 "%s: IPv4 gateway is configured for IPv6 route. "
881 "Ignoring [Route] section from line %u.",
882 route->section->filename, route->section->line);
883
884 RouteNextHop *nh;
885 ORDERED_SET_FOREACH(nh, route->nexthops)
886 if (nh->family == AF_INET)
887 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
888 "%s: IPv4 multipath route is specified for IPv6 route. "
889 "Ignoring [Route] section from line %u.",
890 route->section->filename, route->section->line);
891 }
892
893 if (route->nexthop_id != 0 &&
894 (route->gateway_from_dhcp_or_ra ||
895 in_addr_is_set(route->nexthop.family, &route->nexthop.gw) ||
896 !ordered_set_isempty(route->nexthops)))
897 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
898 "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. "
899 "Ignoring [Route] section from line %u.",
900 route->section->filename, route->section->line);
901
902 if (route_type_is_reject(route) &&
903 (route->gateway_from_dhcp_or_ra ||
904 in_addr_is_set(route->nexthop.family, &route->nexthop.gw) ||
905 !ordered_set_isempty(route->nexthops)))
906 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
907 "%s: reject type route cannot be specified with Gateway= or MultiPathRoute=. "
908 "Ignoring [Route] section from line %u.",
909 route->section->filename, route->section->line);
910
911 if ((route->gateway_from_dhcp_or_ra ||
912 in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) &&
913 !ordered_set_isempty(route->nexthops))
914 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
915 "%s: Gateway= cannot be specified with MultiPathRoute=. "
916 "Ignoring [Route] section from line %u.",
917 route->section->filename, route->section->line);
918
919 if (ordered_set_size(route->nexthops) == 1) {
920 _cleanup_(route_nexthop_freep) RouteNextHop *nh = ordered_set_steal_first(route->nexthops);
921
922 route_nexthop_done(&route->nexthop);
923 route->nexthop = TAKE_STRUCT(*nh);
924
925 assert(ordered_set_isempty(route->nexthops));
926 route->nexthops = ordered_set_free(route->nexthops);
927 }
928
929 return 0;
930 }
931
932 int config_parse_gateway(
933 const char *unit,
934 const char *filename,
935 unsigned line,
936 const char *section,
937 unsigned section_line,
938 const char *lvalue,
939 int ltype,
940 const char *rvalue,
941 void *data,
942 void *userdata) {
943
944 Network *network = userdata;
945 _cleanup_(route_unref_or_set_invalidp) Route *route = NULL;
946 int r;
947
948 assert(filename);
949 assert(section);
950 assert(lvalue);
951 assert(rvalue);
952 assert(data);
953
954 if (streq(section, "Network")) {
955 /* we are not in an Route section, so use line number instead */
956 r = route_new_static(network, filename, line, &route);
957 if (r == -ENOMEM)
958 return log_oom();
959 if (r < 0) {
960 log_syntax(unit, LOG_WARNING, filename, line, r,
961 "Failed to allocate route, ignoring assignment: %m");
962 return 0;
963 }
964 } else {
965 r = route_new_static(network, filename, section_line, &route);
966 if (r == -ENOMEM)
967 return log_oom();
968 if (r < 0) {
969 log_syntax(unit, LOG_WARNING, filename, line, r,
970 "Failed to allocate route, ignoring assignment: %m");
971 return 0;
972 }
973
974 if (isempty(rvalue)) {
975 route->gateway_from_dhcp_or_ra = false;
976 route->nexthop.family = AF_UNSPEC;
977 route->nexthop.gw = IN_ADDR_NULL;
978 TAKE_PTR(route);
979 return 0;
980 }
981
982 if (streq(rvalue, "_dhcp")) {
983 route->gateway_from_dhcp_or_ra = true;
984 route->nexthop.family = AF_UNSPEC;
985 route->nexthop.gw = IN_ADDR_NULL;
986 TAKE_PTR(route);
987 return 0;
988 }
989
990 if (streq(rvalue, "_dhcp4")) {
991 route->gateway_from_dhcp_or_ra = true;
992 route->nexthop.family = AF_INET;
993 route->nexthop.gw = IN_ADDR_NULL;
994 TAKE_PTR(route);
995 return 0;
996 }
997
998 if (streq(rvalue, "_ipv6ra")) {
999 route->gateway_from_dhcp_or_ra = true;
1000 route->nexthop.family = AF_INET6;
1001 route->nexthop.gw = IN_ADDR_NULL;
1002 TAKE_PTR(route);
1003 return 0;
1004 }
1005 }
1006
1007 r = in_addr_from_string_auto(rvalue, &route->nexthop.family, &route->nexthop.gw);
1008 if (r < 0) {
1009 log_syntax(unit, LOG_WARNING, filename, line, r,
1010 "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
1011 return 0;
1012 }
1013
1014 route->gateway_from_dhcp_or_ra = false;
1015 TAKE_PTR(route);
1016 return 0;
1017 }
1018
1019 int config_parse_route_gateway_onlink(
1020 const char *unit,
1021 const char *filename,
1022 unsigned line,
1023 const char *section,
1024 unsigned section_line,
1025 const char *lvalue,
1026 int ltype,
1027 const char *rvalue,
1028 void *data,
1029 void *userdata) {
1030
1031 Network *network = userdata;
1032 _cleanup_(route_unref_or_set_invalidp) Route *route = NULL;
1033 int r;
1034
1035 assert(filename);
1036 assert(section);
1037 assert(lvalue);
1038 assert(rvalue);
1039 assert(data);
1040
1041 r = route_new_static(network, filename, section_line, &route);
1042 if (r == -ENOMEM)
1043 return log_oom();
1044 if (r < 0) {
1045 log_syntax(unit, LOG_WARNING, filename, line, r,
1046 "Failed to allocate route, ignoring assignment: %m");
1047 return 0;
1048 }
1049
1050 r = config_parse_tristate(unit, filename, line, section, section_line, lvalue, ltype, rvalue,
1051 &route->gateway_onlink, network);
1052 if (r <= 0)
1053 return r;
1054
1055 TAKE_PTR(route);
1056 return 0;
1057 }
1058
1059 int config_parse_route_nexthop(
1060 const char *unit,
1061 const char *filename,
1062 unsigned line,
1063 const char *section,
1064 unsigned section_line,
1065 const char *lvalue,
1066 int ltype,
1067 const char *rvalue,
1068 void *data,
1069 void *userdata) {
1070
1071 Network *network = userdata;
1072 _cleanup_(route_unref_or_set_invalidp) Route *route = NULL;
1073 uint32_t id;
1074 int r;
1075
1076 assert(filename);
1077 assert(section);
1078 assert(lvalue);
1079 assert(rvalue);
1080 assert(data);
1081
1082 r = route_new_static(network, filename, section_line, &route);
1083 if (r == -ENOMEM)
1084 return log_oom();
1085 if (r < 0) {
1086 log_syntax(unit, LOG_WARNING, filename, line, r,
1087 "Failed to allocate route, ignoring assignment: %m");
1088 return 0;
1089 }
1090
1091 if (isempty(rvalue)) {
1092 route->nexthop_id = 0;
1093 TAKE_PTR(route);
1094 return 0;
1095 }
1096
1097 r = safe_atou32(rvalue, &id);
1098 if (r < 0) {
1099 log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue);
1100 return 0;
1101 }
1102 if (id == 0) {
1103 log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue);
1104 return 0;
1105 }
1106
1107 route->nexthop_id = id;
1108 TAKE_PTR(route);
1109 return 0;
1110 }
1111
1112 int config_parse_multipath_route(
1113 const char *unit,
1114 const char *filename,
1115 unsigned line,
1116 const char *section,
1117 unsigned section_line,
1118 const char *lvalue,
1119 int ltype,
1120 const char *rvalue,
1121 void *data,
1122 void *userdata) {
1123
1124 _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL;
1125 _cleanup_(route_unref_or_set_invalidp) Route *route = NULL;
1126 _cleanup_free_ char *word = NULL;
1127 Network *network = userdata;
1128 const char *p;
1129 char *dev;
1130 int r;
1131
1132 assert(filename);
1133 assert(section);
1134 assert(lvalue);
1135 assert(rvalue);
1136 assert(data);
1137
1138 r = route_new_static(network, filename, section_line, &route);
1139 if (r == -ENOMEM)
1140 return log_oom();
1141 if (r < 0) {
1142 log_syntax(unit, LOG_WARNING, filename, line, r,
1143 "Failed to allocate route, ignoring assignment: %m");
1144 return 0;
1145 }
1146
1147 if (isempty(rvalue)) {
1148 route->nexthops = ordered_set_free(route->nexthops);
1149 TAKE_PTR(route);
1150 return 0;
1151 }
1152
1153 nh = new0(RouteNextHop, 1);
1154 if (!nh)
1155 return log_oom();
1156
1157 p = rvalue;
1158 r = extract_first_word(&p, &word, NULL, 0);
1159 if (r == -ENOMEM)
1160 return log_oom();
1161 if (r <= 0) {
1162 log_syntax(unit, LOG_WARNING, filename, line, r,
1163 "Invalid multipath route option, ignoring assignment: %s", rvalue);
1164 return 0;
1165 }
1166
1167 dev = strchr(word, '@');
1168 if (dev) {
1169 *dev++ = '\0';
1170
1171 r = parse_ifindex(dev);
1172 if (r > 0)
1173 nh->ifindex = r;
1174 else {
1175 if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) {
1176 log_syntax(unit, LOG_WARNING, filename, line, 0,
1177 "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue);
1178 return 0;
1179 }
1180
1181 nh->ifname = strdup(dev);
1182 if (!nh->ifname)
1183 return log_oom();
1184 }
1185 }
1186
1187 r = in_addr_from_string_auto(word, &nh->family, &nh->gw);
1188 if (r < 0) {
1189 log_syntax(unit, LOG_WARNING, filename, line, r,
1190 "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue);
1191 return 0;
1192 }
1193
1194 if (!isempty(p)) {
1195 r = safe_atou32(p, &nh->weight);
1196 if (r < 0) {
1197 log_syntax(unit, LOG_WARNING, filename, line, r,
1198 "Invalid multipath route weight, ignoring assignment: %s", p);
1199 return 0;
1200 }
1201 /* ip command takes weight in the range 1…255, while kernel takes the value in the
1202 * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip
1203 * command uses, then networkd decreases by one and stores it to match the range which
1204 * kernel uses. */
1205 if (nh->weight == 0 || nh->weight > 256) {
1206 log_syntax(unit, LOG_WARNING, filename, line, 0,
1207 "Invalid multipath route weight, ignoring assignment: %s", p);
1208 return 0;
1209 }
1210 nh->weight--;
1211 }
1212
1213 r = ordered_set_ensure_put(&route->nexthops, &route_nexthop_hash_ops, nh);
1214 if (r == -ENOMEM)
1215 return log_oom();
1216 if (r < 0) {
1217 log_syntax(unit, LOG_WARNING, filename, line, r,
1218 "Failed to store multipath route, ignoring assignment: %m");
1219 return 0;
1220 }
1221
1222 TAKE_PTR(nh);
1223 TAKE_PTR(route);
1224 return 0;
1225 }