]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-nexthop.c
Merge pull request #27113 from keszybz/variable-expansion-rework
[thirdparty/systemd.git] / src / network / networkd-nexthop.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later
2 * Copyright © 2019 VMware, Inc.
3 */
4
5 #include <net/if.h>
6 #include <linux/nexthop.h>
7
8 #include "alloc-util.h"
9 #include "netlink-util.h"
10 #include "networkd-link.h"
11 #include "networkd-manager.h"
12 #include "networkd-network.h"
13 #include "networkd-nexthop.h"
14 #include "networkd-queue.h"
15 #include "networkd-route-util.h"
16 #include "parse-util.h"
17 #include "set.h"
18 #include "stdio-util.h"
19 #include "string-util.h"
20
21 NextHop *nexthop_free(NextHop *nexthop) {
22 if (!nexthop)
23 return NULL;
24
25 if (nexthop->network) {
26 assert(nexthop->section);
27 hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section);
28 }
29
30 config_section_free(nexthop->section);
31
32 if (nexthop->link) {
33 set_remove(nexthop->link->nexthops, nexthop);
34
35 if (nexthop->link->manager && nexthop->id > 0)
36 hashmap_remove(nexthop->link->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
37 }
38
39 if (nexthop->manager) {
40 set_remove(nexthop->manager->nexthops, nexthop);
41
42 if (nexthop->id > 0)
43 hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id));
44 }
45
46 hashmap_free_free(nexthop->group);
47
48 return mfree(nexthop);
49 }
50
51 DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_free);
52
53 static int nexthop_new(NextHop **ret) {
54 _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
55
56 nexthop = new(NextHop, 1);
57 if (!nexthop)
58 return -ENOMEM;
59
60 *nexthop = (NextHop) {
61 .family = AF_UNSPEC,
62 .onlink = -1,
63 };
64
65 *ret = TAKE_PTR(nexthop);
66
67 return 0;
68 }
69
70 static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) {
71 _cleanup_(config_section_freep) ConfigSection *n = NULL;
72 _cleanup_(nexthop_freep) NextHop *nexthop = NULL;
73 int r;
74
75 assert(network);
76 assert(ret);
77 assert(filename);
78 assert(section_line > 0);
79
80 r = config_section_new(filename, section_line, &n);
81 if (r < 0)
82 return r;
83
84 nexthop = hashmap_get(network->nexthops_by_section, n);
85 if (nexthop) {
86 *ret = TAKE_PTR(nexthop);
87 return 0;
88 }
89
90 r = nexthop_new(&nexthop);
91 if (r < 0)
92 return r;
93
94 nexthop->protocol = RTPROT_STATIC;
95 nexthop->network = network;
96 nexthop->section = TAKE_PTR(n);
97 nexthop->source = NETWORK_CONFIG_SOURCE_STATIC;
98
99 r = hashmap_ensure_put(&network->nexthops_by_section, &config_section_hash_ops, nexthop->section, nexthop);
100 if (r < 0)
101 return r;
102
103 *ret = TAKE_PTR(nexthop);
104 return 0;
105 }
106
107 static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) {
108 assert(nexthop);
109
110 siphash24_compress(&nexthop->protocol, sizeof(nexthop->protocol), state);
111 siphash24_compress(&nexthop->id, sizeof(nexthop->id), state);
112 siphash24_compress(&nexthop->blackhole, sizeof(nexthop->blackhole), state);
113 siphash24_compress(&nexthop->family, sizeof(nexthop->family), state);
114
115 switch (nexthop->family) {
116 case AF_INET:
117 case AF_INET6:
118 siphash24_compress(&nexthop->gw, FAMILY_ADDRESS_SIZE(nexthop->family), state);
119
120 break;
121 default:
122 /* treat any other address family as AF_UNSPEC */
123 break;
124 }
125 }
126
127 static int nexthop_compare_func(const NextHop *a, const NextHop *b) {
128 int r;
129
130 r = CMP(a->protocol, b->protocol);
131 if (r != 0)
132 return r;
133
134 r = CMP(a->id, b->id);
135 if (r != 0)
136 return r;
137
138 r = CMP(a->blackhole, b->blackhole);
139 if (r != 0)
140 return r;
141
142 r = CMP(a->family, b->family);
143 if (r != 0)
144 return r;
145
146 if (IN_SET(a->family, AF_INET, AF_INET6))
147 return memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family));
148
149 return 0;
150 }
151
152 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
153 nexthop_hash_ops,
154 NextHop,
155 nexthop_hash_func,
156 nexthop_compare_func,
157 nexthop_free);
158
159 static bool nexthop_equal(const NextHop *a, const NextHop *b) {
160 if (a == b)
161 return true;
162
163 if (!a || !b)
164 return false;
165
166 return nexthop_compare_func(a, b) == 0;
167 }
168
169 static int nexthop_dup(const NextHop *src, NextHop **ret) {
170 _cleanup_(nexthop_freep) NextHop *dest = NULL;
171 struct nexthop_grp *nhg;
172 int r;
173
174 assert(src);
175 assert(ret);
176
177 dest = newdup(NextHop, src, 1);
178 if (!dest)
179 return -ENOMEM;
180
181 /* unset all pointers */
182 dest->manager = NULL;
183 dest->link = NULL;
184 dest->network = NULL;
185 dest->section = NULL;
186 dest->group = NULL;
187
188 HASHMAP_FOREACH(nhg, src->group) {
189 _cleanup_free_ struct nexthop_grp *g = NULL;
190
191 g = newdup(struct nexthop_grp, nhg, 1);
192 if (!g)
193 return -ENOMEM;
194
195 r = hashmap_ensure_put(&dest->group, NULL, UINT32_TO_PTR(g->id), g);
196 if (r < 0)
197 return r;
198 if (r > 0)
199 TAKE_PTR(g);
200 }
201
202 *ret = TAKE_PTR(dest);
203 return 0;
204 }
205
206 int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) {
207 NextHop *nh;
208
209 assert(manager);
210
211 if (id == 0)
212 return -EINVAL;
213
214 nh = hashmap_get(manager->nexthops_by_id, UINT32_TO_PTR(id));
215 if (!nh)
216 return -ENOENT;
217
218 if (ret)
219 *ret = nh;
220 return 0;
221 }
222
223 static bool nexthop_owned_by_link(const NextHop *nexthop) {
224 return !nexthop->blackhole && hashmap_isempty(nexthop->group);
225 }
226
227 static int nexthop_get(Manager *manager, Link *link, NextHop *in, NextHop **ret) {
228 NextHop *nexthop;
229 Set *nexthops;
230
231 assert(in);
232
233 if (nexthop_owned_by_link(in)) {
234 if (!link)
235 return -ENOENT;
236
237 nexthops = link->nexthops;
238 } else {
239 if (!manager)
240 return -ENOENT;
241
242 nexthops = manager->nexthops;
243 }
244
245 nexthop = set_get(nexthops, in);
246 if (nexthop) {
247 if (ret)
248 *ret = nexthop;
249 return 0;
250 }
251
252 if (in->id > 0)
253 return -ENOENT;
254
255 /* Also find nexthop configured without ID. */
256 SET_FOREACH(nexthop, nexthops) {
257 uint32_t id;
258 bool found;
259
260 id = nexthop->id;
261 nexthop->id = 0;
262 found = nexthop_equal(nexthop, in);
263 nexthop->id = id;
264
265 if (!found)
266 continue;
267
268 if (ret)
269 *ret = nexthop;
270 return 0;
271 }
272
273 return -ENOENT;
274 }
275
276 static int nexthop_add(Manager *manager, Link *link, NextHop *nexthop) {
277 int r;
278
279 assert(nexthop);
280 assert(nexthop->id > 0);
281
282 if (nexthop_owned_by_link(nexthop)) {
283 assert(link);
284
285 r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop);
286 if (r < 0)
287 return r;
288 if (r == 0)
289 return -EEXIST;
290
291 nexthop->link = link;
292
293 manager = link->manager;
294 } else {
295 assert(manager);
296
297 r = set_ensure_put(&manager->nexthops, &nexthop_hash_ops, nexthop);
298 if (r < 0)
299 return r;
300 if (r == 0)
301 return -EEXIST;
302
303 nexthop->manager = manager;
304 }
305
306 return hashmap_ensure_put(&manager->nexthops_by_id, NULL, UINT32_TO_PTR(nexthop->id), nexthop);
307 }
308
309 static int nexthop_acquire_id(Manager *manager, NextHop *nexthop) {
310 _cleanup_set_free_ Set *ids = NULL;
311 Network *network;
312 uint32_t id;
313 int r;
314
315 assert(manager);
316 assert(nexthop);
317
318 if (nexthop->id > 0)
319 return 0;
320
321 /* Find the lowest unused ID. */
322
323 ORDERED_HASHMAP_FOREACH(network, manager->networks) {
324 NextHop *tmp;
325
326 HASHMAP_FOREACH(tmp, network->nexthops_by_section) {
327 if (tmp->id == 0)
328 continue;
329
330 r = set_ensure_put(&ids, NULL, UINT32_TO_PTR(tmp->id));
331 if (r < 0)
332 return r;
333 }
334 }
335
336 for (id = 1; id < UINT32_MAX; id++) {
337 if (manager_get_nexthop_by_id(manager, id, NULL) >= 0)
338 continue;
339 if (set_contains(ids, UINT32_TO_PTR(id)))
340 continue;
341 break;
342 }
343
344 nexthop->id = id;
345 return 0;
346 }
347
348 static void log_nexthop_debug(const NextHop *nexthop, const char *str, const Link *link) {
349 _cleanup_free_ char *state = NULL, *group = NULL, *flags = NULL;
350 struct nexthop_grp *nhg;
351
352 assert(nexthop);
353 assert(str);
354
355 /* link may be NULL. */
356
357 if (!DEBUG_LOGGING)
358 return;
359
360 (void) network_config_state_to_string_alloc(nexthop->state, &state);
361 (void) route_flags_to_string_alloc(nexthop->flags, &flags);
362
363 HASHMAP_FOREACH(nhg, nexthop->group)
364 (void) strextendf_with_separator(&group, ",", "%"PRIu32":%"PRIu32, nhg->id, nhg->weight+1u);
365
366 log_link_debug(link, "%s %s nexthop (%s): id: %"PRIu32", gw: %s, blackhole: %s, group: %s, flags: %s",
367 str, strna(network_config_source_to_string(nexthop->source)), strna(state),
368 nexthop->id,
369 IN_ADDR_TO_STRING(nexthop->family, &nexthop->gw),
370 yes_no(nexthop->blackhole), strna(group), strna(flags));
371 }
372
373 static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
374 int r;
375
376 assert(m);
377
378 /* link may be NULL. */
379
380 if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
381 return 1;
382
383 r = sd_netlink_message_get_errno(m);
384 if (r < 0 && r != -ENOENT)
385 log_link_message_warning_errno(link, m, r, "Could not drop nexthop, ignoring");
386
387 return 1;
388 }
389
390 static int nexthop_remove(NextHop *nexthop) {
391 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
392 Manager *manager;
393 Link *link;
394 int r;
395
396 assert(nexthop);
397 assert(nexthop->manager || (nexthop->link && nexthop->link->manager));
398
399 /* link may be NULL. */
400 link = nexthop->link;
401 manager = nexthop->manager ?: nexthop->link->manager;
402
403 if (nexthop->id == 0) {
404 log_link_debug(link, "Cannot remove nexthop without valid ID, ignoring.");
405 return 0;
406 }
407
408 log_nexthop_debug(nexthop, "Removing", link);
409
410 r = sd_rtnl_message_new_nexthop(manager->rtnl, &req, RTM_DELNEXTHOP, AF_UNSPEC, RTPROT_UNSPEC);
411 if (r < 0)
412 return log_link_error_errno(link, r, "Could not create RTM_DELNEXTHOP message: %m");
413
414 r = sd_netlink_message_append_u32(req, NHA_ID, nexthop->id);
415 if (r < 0)
416 return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m");
417
418 r = netlink_call_async(manager->rtnl, NULL, req, nexthop_remove_handler,
419 link ? link_netlink_destroy_callback : NULL, link);
420 if (r < 0)
421 return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
422
423 link_ref(link); /* link may be NULL, link_ref() is OK with that */
424
425 nexthop_enter_removing(nexthop);
426 return 0;
427 }
428
429 static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) {
430 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
431 int r;
432
433 assert(nexthop);
434 assert(IN_SET(nexthop->family, AF_UNSPEC, AF_INET, AF_INET6));
435 assert(link);
436 assert(link->manager);
437 assert(link->manager->rtnl);
438 assert(link->ifindex > 0);
439 assert(req);
440
441 log_nexthop_debug(nexthop, "Configuring", link);
442
443 r = sd_rtnl_message_new_nexthop(link->manager->rtnl, &m, RTM_NEWNEXTHOP, nexthop->family, nexthop->protocol);
444 if (r < 0)
445 return r;
446
447 if (nexthop->id > 0) {
448 r = sd_netlink_message_append_u32(m, NHA_ID, nexthop->id);
449 if (r < 0)
450 return r;
451 }
452
453 if (!hashmap_isempty(nexthop->group)) {
454 _cleanup_free_ struct nexthop_grp *group = NULL;
455 struct nexthop_grp *p, *nhg;
456
457 group = new(struct nexthop_grp, hashmap_size(nexthop->group));
458 if (!group)
459 return log_oom();
460
461 p = group;
462 HASHMAP_FOREACH(nhg, nexthop->group)
463 *p++ = *nhg;
464
465 r = sd_netlink_message_append_data(m, NHA_GROUP, group, sizeof(struct nexthop_grp) * hashmap_size(nexthop->group));
466 if (r < 0)
467 return r;
468
469 } else if (nexthop->blackhole) {
470 r = sd_netlink_message_append_flag(m, NHA_BLACKHOLE);
471 if (r < 0)
472 return r;
473 } else {
474 r = sd_netlink_message_append_u32(m, NHA_OIF, link->ifindex);
475 if (r < 0)
476 return r;
477
478 if (in_addr_is_set(nexthop->family, &nexthop->gw)) {
479 r = netlink_message_append_in_addr_union(m, NHA_GATEWAY, nexthop->family, &nexthop->gw);
480 if (r < 0)
481 return r;
482
483 r = sd_rtnl_message_nexthop_set_flags(m, nexthop->flags & RTNH_F_ONLINK);
484 if (r < 0)
485 return r;
486 }
487 }
488
489 return request_call_netlink_async(link->manager->rtnl, m, req);
490 }
491
492 static int static_nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, NextHop *nexthop) {
493 int r;
494
495 assert(m);
496 assert(link);
497
498 r = sd_netlink_message_get_errno(m);
499 if (r < 0 && r != -EEXIST) {
500 log_link_message_warning_errno(link, m, r, "Could not set nexthop");
501 link_enter_failed(link);
502 return 1;
503 }
504
505 if (link->static_nexthop_messages == 0) {
506 log_link_debug(link, "Nexthops set");
507 link->static_nexthops_configured = true;
508 link_check_ready(link);
509 }
510
511 return 1;
512 }
513
514 static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) {
515 struct nexthop_grp *nhg;
516
517 assert(link);
518 assert(nexthop);
519
520 if (!link_is_ready_to_configure(link, false))
521 return false;
522
523 if (nexthop_owned_by_link(nexthop)) {
524 /* TODO: fdb nexthop does not require IFF_UP. The conditions below needs to be updated
525 * when fdb nexthop support is added. See rtm_to_nh_config() in net/ipv4/nexthop.c of
526 * kernel. */
527 if (link->set_flags_messages > 0)
528 return false;
529 if (!FLAGS_SET(link->flags, IFF_UP))
530 return false;
531 }
532
533 /* All group members must be configured first. */
534 HASHMAP_FOREACH(nhg, nexthop->group) {
535 NextHop *g;
536
537 if (manager_get_nexthop_by_id(link->manager, nhg->id, &g) < 0)
538 return false;
539
540 if (!nexthop_exists(g))
541 return false;
542 }
543
544 if (nexthop->id == 0) {
545 Request *req;
546
547 ORDERED_SET_FOREACH(req, link->manager->request_queue) {
548 if (req->type != REQUEST_TYPE_NEXTHOP)
549 continue;
550 if (((NextHop*) req->userdata)->id != 0)
551 return false; /* first configure nexthop with id. */
552 }
553 }
554
555 return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw);
556 }
557
558 static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) {
559 int r;
560
561 assert(req);
562 assert(link);
563 assert(nexthop);
564
565 if (!nexthop_is_ready_to_configure(link, nexthop))
566 return 0;
567
568 r = nexthop_configure(nexthop, link, req);
569 if (r < 0)
570 return log_link_warning_errno(link, r, "Failed to configure nexthop");
571
572 nexthop_enter_configuring(nexthop);
573 return 1;
574 }
575
576 static int link_request_nexthop(Link *link, NextHop *nexthop) {
577 NextHop *existing;
578 int r;
579
580 assert(link);
581 assert(nexthop);
582 assert(nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN);
583
584 if (nexthop_get(link->manager, link, nexthop, &existing) < 0) {
585 _cleanup_(nexthop_freep) NextHop *tmp = NULL;
586
587 r = nexthop_dup(nexthop, &tmp);
588 if (r < 0)
589 return r;
590
591 r = nexthop_acquire_id(link->manager, tmp);
592 if (r < 0)
593 return r;
594
595 r = nexthop_add(link->manager, link, tmp);
596 if (r < 0)
597 return r;
598
599 existing = TAKE_PTR(tmp);
600 } else
601 existing->source = nexthop->source;
602
603 log_nexthop_debug(existing, "Requesting", link);
604 r = link_queue_request_safe(link, REQUEST_TYPE_NEXTHOP,
605 existing, NULL,
606 nexthop_hash_func,
607 nexthop_compare_func,
608 nexthop_process_request,
609 &link->static_nexthop_messages,
610 static_nexthop_handler,
611 NULL);
612 if (r <= 0)
613 return r;
614
615 nexthop_enter_requesting(existing);
616 return 1;
617 }
618
619 int link_request_static_nexthops(Link *link, bool only_ipv4) {
620 NextHop *nh;
621 int r;
622
623 assert(link);
624 assert(link->network);
625
626 link->static_nexthops_configured = false;
627
628 HASHMAP_FOREACH(nh, link->network->nexthops_by_section) {
629 if (only_ipv4 && nh->family != AF_INET)
630 continue;
631
632 r = link_request_nexthop(link, nh);
633 if (r < 0)
634 return log_link_warning_errno(link, r, "Could not request nexthop: %m");
635 }
636
637 if (link->static_nexthop_messages == 0) {
638 link->static_nexthops_configured = true;
639 link_check_ready(link);
640 } else {
641 log_link_debug(link, "Requesting nexthops");
642 link_set_state(link, LINK_STATE_CONFIGURING);
643 }
644
645 return 0;
646 }
647
648 static void manager_mark_nexthops(Manager *manager, bool foreign, const Link *except) {
649 NextHop *nexthop;
650 Link *link;
651
652 assert(manager);
653
654 /* First, mark all nexthops. */
655 SET_FOREACH(nexthop, manager->nexthops) {
656 /* do not touch nexthop created by the kernel */
657 if (nexthop->protocol == RTPROT_KERNEL)
658 continue;
659
660 /* When 'foreign' is true, mark only foreign nexthops, and vice versa. */
661 if (foreign != (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN))
662 continue;
663
664 /* Ignore nexthops not assigned yet or already removed. */
665 if (!nexthop_exists(nexthop))
666 continue;
667
668 nexthop_mark(nexthop);
669 }
670
671 /* Then, unmark all nexthops requested by active links. */
672 HASHMAP_FOREACH(link, manager->links_by_index) {
673 if (link == except)
674 continue;
675
676 if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
677 continue;
678
679 HASHMAP_FOREACH(nexthop, link->network->nexthops_by_section) {
680 NextHop *existing;
681
682 if (nexthop_get(manager, NULL, nexthop, &existing) >= 0)
683 nexthop_unmark(existing);
684 }
685 }
686 }
687
688 static int manager_drop_marked_nexthops(Manager *manager) {
689 NextHop *nexthop;
690 int k, r = 0;
691
692 assert(manager);
693
694 SET_FOREACH(nexthop, manager->nexthops) {
695 if (!nexthop_is_marked(nexthop))
696 continue;
697
698 k = nexthop_remove(nexthop);
699 if (k < 0 && r >= 0)
700 r = k;
701 }
702
703 return r;
704 }
705
706 int link_drop_foreign_nexthops(Link *link) {
707 NextHop *nexthop;
708 int k, r = 0;
709
710 assert(link);
711 assert(link->manager);
712 assert(link->network);
713
714 /* First, mark all nexthops. */
715 SET_FOREACH(nexthop, link->nexthops) {
716 /* do not touch nexthop created by the kernel */
717 if (nexthop->protocol == RTPROT_KERNEL)
718 continue;
719
720 /* Do not remove nexthops we configured. */
721 if (nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN)
722 continue;
723
724 /* Ignore nexthops not assigned yet or already removed. */
725 if (!nexthop_exists(nexthop))
726 continue;
727
728 nexthop_mark(nexthop);
729 }
730
731 /* Then, unmark all nexthops requested by active links. */
732 HASHMAP_FOREACH(nexthop, link->network->nexthops_by_section) {
733 NextHop *existing;
734
735 if (nexthop_get(NULL, link, nexthop, &existing) >= 0)
736 nexthop_unmark(existing);
737 }
738
739 /* Finally, remove all marked rules. */
740 SET_FOREACH(nexthop, link->nexthops) {
741 if (!nexthop_is_marked(nexthop))
742 continue;
743
744 k = nexthop_remove(nexthop);
745 if (k < 0 && r >= 0)
746 r = k;
747 }
748
749 manager_mark_nexthops(link->manager, /* foreign = */ true, NULL);
750
751 k = manager_drop_marked_nexthops(link->manager);
752 if (k < 0 && r >= 0)
753 r = k;
754
755 return r;
756 }
757
758 int link_drop_managed_nexthops(Link *link) {
759 NextHop *nexthop;
760 int k, r = 0;
761
762 assert(link);
763 assert(link->manager);
764
765 SET_FOREACH(nexthop, link->nexthops) {
766 /* do not touch nexthop created by the kernel */
767 if (nexthop->protocol == RTPROT_KERNEL)
768 continue;
769
770 /* Do not touch addresses managed by kernel or other tools. */
771 if (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN)
772 continue;
773
774 /* Ignore nexthops not assigned yet or already removing. */
775 if (!nexthop_exists(nexthop))
776 continue;
777
778 k = nexthop_remove(nexthop);
779 if (k < 0 && r >= 0)
780 r = k;
781 }
782
783 manager_mark_nexthops(link->manager, /* foreign = */ false, link);
784
785 k = manager_drop_marked_nexthops(link->manager);
786 if (k < 0 && r >= 0)
787 r = k;
788
789 return r;
790 }
791
792 void link_foreignize_nexthops(Link *link) {
793 NextHop *nexthop;
794
795 assert(link);
796
797 SET_FOREACH(nexthop, link->nexthops)
798 nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN;
799
800 manager_mark_nexthops(link->manager, /* foreign = */ false, link);
801
802 SET_FOREACH(nexthop, link->manager->nexthops) {
803 if (!nexthop_is_marked(nexthop))
804 continue;
805
806 nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN;
807 }
808 }
809
810 int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
811 _cleanup_(nexthop_freep) NextHop *tmp = NULL;
812 _cleanup_free_ void *raw_group = NULL;
813 NextHop *nexthop = NULL;
814 size_t raw_group_size;
815 uint32_t ifindex;
816 uint16_t type;
817 Link *link = NULL;
818 int r;
819
820 assert(rtnl);
821 assert(message);
822 assert(m);
823
824 if (sd_netlink_message_is_error(message)) {
825 r = sd_netlink_message_get_errno(message);
826 if (r < 0)
827 log_message_warning_errno(message, r, "rtnl: failed to receive rule message, ignoring");
828
829 return 0;
830 }
831
832 r = sd_netlink_message_get_type(message, &type);
833 if (r < 0) {
834 log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
835 return 0;
836 } else if (!IN_SET(type, RTM_NEWNEXTHOP, RTM_DELNEXTHOP)) {
837 log_warning("rtnl: received unexpected message type %u when processing nexthop, ignoring.", type);
838 return 0;
839 }
840
841 r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex);
842 if (r < 0 && r != -ENODATA) {
843 log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m");
844 return 0;
845 } else if (r >= 0) {
846 if (ifindex <= 0) {
847 log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex);
848 return 0;
849 }
850
851 r = link_get_by_index(m, ifindex, &link);
852 if (r < 0 || !link) {
853 if (!m->enumerating)
854 log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex);
855 return 0;
856 }
857 }
858
859 r = nexthop_new(&tmp);
860 if (r < 0)
861 return log_oom();
862
863 r = sd_rtnl_message_get_family(message, &tmp->family);
864 if (r < 0) {
865 log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m");
866 return 0;
867 } else if (!IN_SET(tmp->family, AF_UNSPEC, AF_INET, AF_INET6)) {
868 log_link_debug(link, "rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family);
869 return 0;
870 }
871
872 r = sd_rtnl_message_nexthop_get_protocol(message, &tmp->protocol);
873 if (r < 0) {
874 log_link_warning_errno(link, r, "rtnl: could not get nexthop protocol, ignoring: %m");
875 return 0;
876 }
877
878 r = sd_rtnl_message_nexthop_get_flags(message, &tmp->flags);
879 if (r < 0) {
880 log_link_warning_errno(link, r, "rtnl: could not get nexthop flags, ignoring: %m");
881 return 0;
882 }
883
884 r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group);
885 if (r < 0 && r != -ENODATA) {
886 log_link_warning_errno(link, r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
887 return 0;
888 } else if (r >= 0) {
889 struct nexthop_grp *group = raw_group;
890 size_t n_group;
891
892 if (raw_group_size == 0 || raw_group_size % sizeof(struct nexthop_grp) != 0) {
893 log_link_warning(link, "rtnl: received nexthop message with invalid nexthop group size, ignoring.");
894 return 0;
895 }
896
897 assert((uintptr_t) group % alignof(struct nexthop_grp) == 0);
898
899 n_group = raw_group_size / sizeof(struct nexthop_grp);
900 for (size_t i = 0; i < n_group; i++) {
901 _cleanup_free_ struct nexthop_grp *nhg = NULL;
902
903 if (group[i].id == 0) {
904 log_link_warning(link, "rtnl: received nexthop message with invalid ID in group, ignoring.");
905 return 0;
906 }
907 if (group[i].weight > 254) {
908 log_link_warning(link, "rtnl: received nexthop message with invalid weight in group, ignoring.");
909 return 0;
910 }
911
912 nhg = newdup(struct nexthop_grp, group + i, 1);
913 if (!nhg)
914 return log_oom();
915
916 r = hashmap_ensure_put(&tmp->group, NULL, UINT32_TO_PTR(nhg->id), nhg);
917 if (r == -ENOMEM)
918 return log_oom();
919 if (r < 0) {
920 log_link_warning_errno(link, r, "Failed to store nexthop group, ignoring: %m");
921 return 0;
922 }
923 if (r > 0)
924 TAKE_PTR(nhg);
925 }
926 }
927
928 if (tmp->family != AF_UNSPEC) {
929 r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw);
930 if (r < 0 && r != -ENODATA) {
931 log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m");
932 return 0;
933 }
934 }
935
936 r = sd_netlink_message_has_flag(message, NHA_BLACKHOLE);
937 if (r < 0) {
938 log_link_warning_errno(link, r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m");
939 return 0;
940 }
941 tmp->blackhole = r;
942
943 r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id);
944 if (r == -ENODATA) {
945 log_link_warning_errno(link, r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m");
946 return 0;
947 } else if (r < 0) {
948 log_link_warning_errno(link, r, "rtnl: could not get NHA_ID attribute, ignoring: %m");
949 return 0;
950 } else if (tmp->id == 0) {
951 log_link_warning(link, "rtnl: received nexthop message with invalid nexthop ID, ignoring: %m");
952 return 0;
953 }
954
955 /* All blackhole or group nexthops are managed by Manager. Note that the linux kernel does not
956 * set NHA_OID attribute when NHA_BLACKHOLE or NHA_GROUP is set. Just for safety. */
957 if (!nexthop_owned_by_link(tmp))
958 link = NULL;
959
960 (void) nexthop_get(m, link, tmp, &nexthop);
961
962 switch (type) {
963 case RTM_NEWNEXTHOP:
964 if (nexthop) {
965 nexthop->flags = tmp->flags;
966 nexthop_enter_configured(nexthop);
967 log_nexthop_debug(tmp, "Received remembered", link);
968 } else {
969 nexthop_enter_configured(tmp);
970 log_nexthop_debug(tmp, "Remembering", link);
971
972 r = nexthop_add(m, link, tmp);
973 if (r < 0) {
974 log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m");
975 return 0;
976 }
977
978 TAKE_PTR(tmp);
979 }
980
981 break;
982 case RTM_DELNEXTHOP:
983 if (nexthop) {
984 nexthop_enter_removed(nexthop);
985 if (nexthop->state == 0) {
986 log_nexthop_debug(nexthop, "Forgetting", link);
987 nexthop_free(nexthop);
988 } else
989 log_nexthop_debug(nexthop, "Removed", link);
990 } else
991 log_nexthop_debug(tmp, "Kernel removed unknown", link);
992 break;
993
994 default:
995 assert_not_reached();
996 }
997
998 return 1;
999 }
1000
1001 static int nexthop_section_verify(NextHop *nh) {
1002 if (section_is_invalid(nh->section))
1003 return -EINVAL;
1004
1005 if (!hashmap_isempty(nh->group)) {
1006 if (in_addr_is_set(nh->family, &nh->gw))
1007 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
1008 "%s: nexthop group cannot have gateway address. "
1009 "Ignoring [NextHop] section from line %u.",
1010 nh->section->filename, nh->section->line);
1011
1012 if (nh->family != AF_UNSPEC)
1013 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
1014 "%s: nexthop group cannot have Family= setting. "
1015 "Ignoring [NextHop] section from line %u.",
1016 nh->section->filename, nh->section->line);
1017
1018 if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
1019 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
1020 "%s: nexthop group cannot be a blackhole. "
1021 "Ignoring [NextHop] section from line %u.",
1022 nh->section->filename, nh->section->line);
1023 } else if (nh->family == AF_UNSPEC)
1024 /* When neither Family=, Gateway=, nor Group= is specified, assume IPv4. */
1025 nh->family = AF_INET;
1026
1027 if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
1028 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
1029 "%s: blackhole nexthop cannot have gateway address. "
1030 "Ignoring [NextHop] section from line %u.",
1031 nh->section->filename, nh->section->line);
1032
1033 if (nh->onlink < 0 && in_addr_is_set(nh->family, &nh->gw) &&
1034 ordered_hashmap_isempty(nh->network->addresses_by_section)) {
1035 /* If no address is configured, in most cases the gateway cannot be reachable.
1036 * TODO: we may need to improve the condition above. */
1037 log_warning("%s: Gateway= without static address configured. "
1038 "Enabling OnLink= option.",
1039 nh->section->filename);
1040 nh->onlink = true;
1041 }
1042
1043 if (nh->onlink >= 0)
1044 SET_FLAG(nh->flags, RTNH_F_ONLINK, nh->onlink);
1045
1046 return 0;
1047 }
1048
1049 void network_drop_invalid_nexthops(Network *network) {
1050 NextHop *nh;
1051
1052 assert(network);
1053
1054 HASHMAP_FOREACH(nh, network->nexthops_by_section)
1055 if (nexthop_section_verify(nh) < 0)
1056 nexthop_free(nh);
1057 }
1058
1059 int config_parse_nexthop_id(
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 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1072 Network *network = userdata;
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 = nexthop_new_static(network, filename, section_line, &n);
1083 if (r < 0)
1084 return log_oom();
1085
1086 if (isempty(rvalue)) {
1087 n->id = 0;
1088 TAKE_PTR(n);
1089 return 0;
1090 }
1091
1092 r = safe_atou32(rvalue, &id);
1093 if (r < 0) {
1094 log_syntax(unit, LOG_WARNING, filename, line, r,
1095 "Could not parse nexthop id \"%s\", ignoring assignment: %m", rvalue);
1096 return 0;
1097 }
1098 if (id == 0) {
1099 log_syntax(unit, LOG_WARNING, filename, line, 0,
1100 "Invalid nexthop id \"%s\", ignoring assignment: %m", rvalue);
1101 return 0;
1102 }
1103
1104 n->id = id;
1105 TAKE_PTR(n);
1106 return 0;
1107 }
1108
1109 int config_parse_nexthop_gateway(
1110 const char *unit,
1111 const char *filename,
1112 unsigned line,
1113 const char *section,
1114 unsigned section_line,
1115 const char *lvalue,
1116 int ltype,
1117 const char *rvalue,
1118 void *data,
1119 void *userdata) {
1120
1121 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1122 Network *network = userdata;
1123 int r;
1124
1125 assert(filename);
1126 assert(section);
1127 assert(lvalue);
1128 assert(rvalue);
1129 assert(data);
1130
1131 r = nexthop_new_static(network, filename, section_line, &n);
1132 if (r < 0)
1133 return log_oom();
1134
1135 if (isempty(rvalue)) {
1136 n->family = AF_UNSPEC;
1137 n->gw = IN_ADDR_NULL;
1138
1139 TAKE_PTR(n);
1140 return 0;
1141 }
1142
1143 r = in_addr_from_string_auto(rvalue, &n->family, &n->gw);
1144 if (r < 0) {
1145 log_syntax(unit, LOG_WARNING, filename, line, r,
1146 "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
1147 return 0;
1148 }
1149
1150 TAKE_PTR(n);
1151 return 0;
1152 }
1153
1154 int config_parse_nexthop_family(
1155 const char *unit,
1156 const char *filename,
1157 unsigned line,
1158 const char *section,
1159 unsigned section_line,
1160 const char *lvalue,
1161 int ltype,
1162 const char *rvalue,
1163 void *data,
1164 void *userdata) {
1165
1166 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1167 Network *network = userdata;
1168 AddressFamily a;
1169 int r;
1170
1171 assert(filename);
1172 assert(section);
1173 assert(lvalue);
1174 assert(rvalue);
1175 assert(data);
1176
1177 r = nexthop_new_static(network, filename, section_line, &n);
1178 if (r < 0)
1179 return log_oom();
1180
1181 if (isempty(rvalue) &&
1182 !in_addr_is_set(n->family, &n->gw)) {
1183 /* Accept an empty string only when Gateway= is null or not specified. */
1184 n->family = AF_UNSPEC;
1185 TAKE_PTR(n);
1186 return 0;
1187 }
1188
1189 a = nexthop_address_family_from_string(rvalue);
1190 if (a < 0) {
1191 log_syntax(unit, LOG_WARNING, filename, line, 0,
1192 "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
1193 return 0;
1194 }
1195
1196 if (in_addr_is_set(n->family, &n->gw) &&
1197 ((a == ADDRESS_FAMILY_IPV4 && n->family == AF_INET6) ||
1198 (a == ADDRESS_FAMILY_IPV6 && n->family == AF_INET))) {
1199 log_syntax(unit, LOG_WARNING, filename, line, 0,
1200 "Specified family '%s' conflicts with the family of the previously specified Gateway=, "
1201 "ignoring assignment.", rvalue);
1202 return 0;
1203 }
1204
1205 switch (a) {
1206 case ADDRESS_FAMILY_IPV4:
1207 n->family = AF_INET;
1208 break;
1209 case ADDRESS_FAMILY_IPV6:
1210 n->family = AF_INET6;
1211 break;
1212 default:
1213 assert_not_reached();
1214 }
1215
1216 TAKE_PTR(n);
1217 return 0;
1218 }
1219
1220 int config_parse_nexthop_onlink(
1221 const char *unit,
1222 const char *filename,
1223 unsigned line,
1224 const char *section,
1225 unsigned section_line,
1226 const char *lvalue,
1227 int ltype,
1228 const char *rvalue,
1229 void *data,
1230 void *userdata) {
1231
1232 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1233 Network *network = userdata;
1234 int r;
1235
1236 assert(filename);
1237 assert(section);
1238 assert(lvalue);
1239 assert(rvalue);
1240 assert(data);
1241
1242 r = nexthop_new_static(network, filename, section_line, &n);
1243 if (r < 0)
1244 return log_oom();
1245
1246 if (isempty(rvalue)) {
1247 n->onlink = -1;
1248 TAKE_PTR(n);
1249 return 0;
1250 }
1251
1252 r = parse_boolean(rvalue);
1253 if (r < 0) {
1254 log_syntax(unit, LOG_WARNING, filename, line, r,
1255 "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
1256 return 0;
1257 }
1258
1259 n->onlink = r;
1260
1261 TAKE_PTR(n);
1262 return 0;
1263 }
1264
1265 int config_parse_nexthop_blackhole(
1266 const char *unit,
1267 const char *filename,
1268 unsigned line,
1269 const char *section,
1270 unsigned section_line,
1271 const char *lvalue,
1272 int ltype,
1273 const char *rvalue,
1274 void *data,
1275 void *userdata) {
1276
1277 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1278 Network *network = userdata;
1279 int r;
1280
1281 assert(filename);
1282 assert(section);
1283 assert(lvalue);
1284 assert(rvalue);
1285 assert(data);
1286
1287 r = nexthop_new_static(network, filename, section_line, &n);
1288 if (r < 0)
1289 return log_oom();
1290
1291 r = parse_boolean(rvalue);
1292 if (r < 0) {
1293 log_syntax(unit, LOG_WARNING, filename, line, r,
1294 "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
1295 return 0;
1296 }
1297
1298 n->blackhole = r;
1299
1300 TAKE_PTR(n);
1301 return 0;
1302 }
1303
1304 int config_parse_nexthop_group(
1305 const char *unit,
1306 const char *filename,
1307 unsigned line,
1308 const char *section,
1309 unsigned section_line,
1310 const char *lvalue,
1311 int ltype,
1312 const char *rvalue,
1313 void *data,
1314 void *userdata) {
1315
1316 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1317 Network *network = userdata;
1318 int r;
1319
1320 assert(filename);
1321 assert(section);
1322 assert(lvalue);
1323 assert(rvalue);
1324 assert(data);
1325
1326 r = nexthop_new_static(network, filename, section_line, &n);
1327 if (r < 0)
1328 return log_oom();
1329
1330 if (isempty(rvalue)) {
1331 n->group = hashmap_free_free(n->group);
1332 TAKE_PTR(n);
1333 return 0;
1334 }
1335
1336 for (const char *p = rvalue;;) {
1337 _cleanup_free_ struct nexthop_grp *nhg = NULL;
1338 _cleanup_free_ char *word = NULL;
1339 uint32_t w;
1340 char *sep;
1341
1342 r = extract_first_word(&p, &word, NULL, 0);
1343 if (r == -ENOMEM)
1344 return log_oom();
1345 if (r < 0) {
1346 log_syntax(unit, LOG_WARNING, filename, line, r,
1347 "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
1348 return 0;
1349 }
1350 if (r == 0)
1351 break;
1352
1353 nhg = new0(struct nexthop_grp, 1);
1354 if (!nhg)
1355 return log_oom();
1356
1357 sep = strchr(word, ':');
1358 if (sep) {
1359 *sep++ = '\0';
1360 r = safe_atou32(sep, &w);
1361 if (r < 0) {
1362 log_syntax(unit, LOG_WARNING, filename, line, r,
1363 "Failed to parse weight for nexthop group, ignoring assignment: %s:%s",
1364 word, sep);
1365 continue;
1366 }
1367 if (w == 0 || w > 256) {
1368 log_syntax(unit, LOG_WARNING, filename, line, 0,
1369 "Invalid weight for nexthop group, ignoring assignment: %s:%s",
1370 word, sep);
1371 continue;
1372 }
1373 /* See comments in config_parse_multipath_route(). */
1374 nhg->weight = w - 1;
1375 }
1376
1377 r = safe_atou32(word, &nhg->id);
1378 if (r < 0) {
1379 log_syntax(unit, LOG_WARNING, filename, line, r,
1380 "Failed to parse nexthop ID in %s=, ignoring assignment: %s%s%s",
1381 lvalue, word, sep ? ":" : "", strempty(sep));
1382 continue;
1383 }
1384 if (nhg->id == 0) {
1385 log_syntax(unit, LOG_WARNING, filename, line, 0,
1386 "Nexthop ID in %s= must be positive, ignoring assignment: %s%s%s",
1387 lvalue, word, sep ? ":" : "", strempty(sep));
1388 continue;
1389 }
1390
1391 r = hashmap_ensure_put(&n->group, NULL, UINT32_TO_PTR(nhg->id), nhg);
1392 if (r == -ENOMEM)
1393 return log_oom();
1394 if (r == -EEXIST) {
1395 log_syntax(unit, LOG_WARNING, filename, line, r,
1396 "Nexthop ID %"PRIu32" is specified multiple times in %s=, ignoring assignment: %s%s%s",
1397 nhg->id, lvalue, word, sep ? ":" : "", strempty(sep));
1398 continue;
1399 }
1400 assert(r > 0);
1401 TAKE_PTR(nhg);
1402 }
1403
1404 TAKE_PTR(n);
1405 return 0;
1406 }