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