]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/network/networkd-nexthop.c
Merge pull request #28423 from dvdhrm/pr/memfd
[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 *m = 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, &m, 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(m, 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, m, 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 r = 0;
691
692 assert(manager);
693
694 SET_FOREACH(nexthop, manager->nexthops) {
695 if (!nexthop_is_marked(nexthop))
696 continue;
697
698 RET_GATHER(r, nexthop_remove(nexthop));
699 }
700
701 return r;
702 }
703
704 int link_drop_foreign_nexthops(Link *link) {
705 NextHop *nexthop;
706 int r = 0;
707
708 assert(link);
709 assert(link->manager);
710 assert(link->network);
711
712 /* First, mark all nexthops. */
713 SET_FOREACH(nexthop, link->nexthops) {
714 /* do not touch nexthop created by the kernel */
715 if (nexthop->protocol == RTPROT_KERNEL)
716 continue;
717
718 /* Do not remove nexthops we configured. */
719 if (nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN)
720 continue;
721
722 /* Ignore nexthops not assigned yet or already removed. */
723 if (!nexthop_exists(nexthop))
724 continue;
725
726 nexthop_mark(nexthop);
727 }
728
729 /* Then, unmark all nexthops requested by active links. */
730 HASHMAP_FOREACH(nexthop, link->network->nexthops_by_section) {
731 NextHop *existing;
732
733 if (nexthop_get(NULL, link, nexthop, &existing) >= 0)
734 nexthop_unmark(existing);
735 }
736
737 /* Finally, remove all marked rules. */
738 SET_FOREACH(nexthop, link->nexthops) {
739 if (!nexthop_is_marked(nexthop))
740 continue;
741
742 RET_GATHER(r, nexthop_remove(nexthop));
743 }
744
745 manager_mark_nexthops(link->manager, /* foreign = */ true, NULL);
746
747 return RET_GATHER(r, manager_drop_marked_nexthops(link->manager));
748 }
749
750 int link_drop_managed_nexthops(Link *link) {
751 NextHop *nexthop;
752 int r = 0;
753
754 assert(link);
755 assert(link->manager);
756
757 SET_FOREACH(nexthop, link->nexthops) {
758 /* do not touch nexthop created by the kernel */
759 if (nexthop->protocol == RTPROT_KERNEL)
760 continue;
761
762 /* Do not touch addresses managed by kernel or other tools. */
763 if (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN)
764 continue;
765
766 /* Ignore nexthops not assigned yet or already removing. */
767 if (!nexthop_exists(nexthop))
768 continue;
769
770 RET_GATHER(r, nexthop_remove(nexthop));
771 }
772
773 manager_mark_nexthops(link->manager, /* foreign = */ false, link);
774
775 return RET_GATHER(r, manager_drop_marked_nexthops(link->manager));
776 }
777
778 void link_foreignize_nexthops(Link *link) {
779 NextHop *nexthop;
780
781 assert(link);
782
783 SET_FOREACH(nexthop, link->nexthops)
784 nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN;
785
786 manager_mark_nexthops(link->manager, /* foreign = */ false, link);
787
788 SET_FOREACH(nexthop, link->manager->nexthops) {
789 if (!nexthop_is_marked(nexthop))
790 continue;
791
792 nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN;
793 }
794 }
795
796 int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) {
797 _cleanup_(nexthop_freep) NextHop *tmp = NULL;
798 _cleanup_free_ void *raw_group = NULL;
799 NextHop *nexthop = NULL;
800 size_t raw_group_size;
801 uint32_t ifindex;
802 uint16_t type;
803 Link *link = NULL;
804 int r;
805
806 assert(rtnl);
807 assert(message);
808 assert(m);
809
810 if (sd_netlink_message_is_error(message)) {
811 r = sd_netlink_message_get_errno(message);
812 if (r < 0)
813 log_message_warning_errno(message, r, "rtnl: failed to receive rule message, ignoring");
814
815 return 0;
816 }
817
818 r = sd_netlink_message_get_type(message, &type);
819 if (r < 0) {
820 log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
821 return 0;
822 } else if (!IN_SET(type, RTM_NEWNEXTHOP, RTM_DELNEXTHOP)) {
823 log_warning("rtnl: received unexpected message type %u when processing nexthop, ignoring.", type);
824 return 0;
825 }
826
827 r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex);
828 if (r < 0 && r != -ENODATA) {
829 log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m");
830 return 0;
831 } else if (r >= 0) {
832 if (ifindex <= 0) {
833 log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex);
834 return 0;
835 }
836
837 r = link_get_by_index(m, ifindex, &link);
838 if (r < 0) {
839 if (!m->enumerating)
840 log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex);
841 return 0;
842 }
843 }
844
845 r = nexthop_new(&tmp);
846 if (r < 0)
847 return log_oom();
848
849 r = sd_rtnl_message_get_family(message, &tmp->family);
850 if (r < 0) {
851 log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m");
852 return 0;
853 } else if (!IN_SET(tmp->family, AF_UNSPEC, AF_INET, AF_INET6)) {
854 log_link_debug(link, "rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family);
855 return 0;
856 }
857
858 r = sd_rtnl_message_nexthop_get_protocol(message, &tmp->protocol);
859 if (r < 0) {
860 log_link_warning_errno(link, r, "rtnl: could not get nexthop protocol, ignoring: %m");
861 return 0;
862 }
863
864 r = sd_rtnl_message_nexthop_get_flags(message, &tmp->flags);
865 if (r < 0) {
866 log_link_warning_errno(link, r, "rtnl: could not get nexthop flags, ignoring: %m");
867 return 0;
868 }
869
870 r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group);
871 if (r < 0 && r != -ENODATA) {
872 log_link_warning_errno(link, r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
873 return 0;
874 } else if (r >= 0) {
875 struct nexthop_grp *group = raw_group;
876 size_t n_group;
877
878 if (raw_group_size == 0 || raw_group_size % sizeof(struct nexthop_grp) != 0) {
879 log_link_warning(link, "rtnl: received nexthop message with invalid nexthop group size, ignoring.");
880 return 0;
881 }
882
883 assert((uintptr_t) group % alignof(struct nexthop_grp) == 0);
884
885 n_group = raw_group_size / sizeof(struct nexthop_grp);
886 for (size_t i = 0; i < n_group; i++) {
887 _cleanup_free_ struct nexthop_grp *nhg = NULL;
888
889 if (group[i].id == 0) {
890 log_link_warning(link, "rtnl: received nexthop message with invalid ID in group, ignoring.");
891 return 0;
892 }
893 if (group[i].weight > 254) {
894 log_link_warning(link, "rtnl: received nexthop message with invalid weight in group, ignoring.");
895 return 0;
896 }
897
898 nhg = newdup(struct nexthop_grp, group + i, 1);
899 if (!nhg)
900 return log_oom();
901
902 r = hashmap_ensure_put(&tmp->group, NULL, UINT32_TO_PTR(nhg->id), nhg);
903 if (r == -ENOMEM)
904 return log_oom();
905 if (r < 0) {
906 log_link_warning_errno(link, r, "Failed to store nexthop group, ignoring: %m");
907 return 0;
908 }
909 if (r > 0)
910 TAKE_PTR(nhg);
911 }
912 }
913
914 if (tmp->family != AF_UNSPEC) {
915 r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw);
916 if (r < 0 && r != -ENODATA) {
917 log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m");
918 return 0;
919 }
920 }
921
922 r = sd_netlink_message_has_flag(message, NHA_BLACKHOLE);
923 if (r < 0) {
924 log_link_warning_errno(link, r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m");
925 return 0;
926 }
927 tmp->blackhole = r;
928
929 r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id);
930 if (r == -ENODATA) {
931 log_link_warning_errno(link, r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m");
932 return 0;
933 } else if (r < 0) {
934 log_link_warning_errno(link, r, "rtnl: could not get NHA_ID attribute, ignoring: %m");
935 return 0;
936 } else if (tmp->id == 0) {
937 log_link_warning(link, "rtnl: received nexthop message with invalid nexthop ID, ignoring: %m");
938 return 0;
939 }
940
941 /* All blackhole or group nexthops are managed by Manager. Note that the linux kernel does not
942 * set NHA_OID attribute when NHA_BLACKHOLE or NHA_GROUP is set. Just for safety. */
943 if (!nexthop_owned_by_link(tmp))
944 link = NULL;
945
946 (void) nexthop_get(m, link, tmp, &nexthop);
947
948 switch (type) {
949 case RTM_NEWNEXTHOP:
950 if (nexthop) {
951 nexthop->flags = tmp->flags;
952 nexthop_enter_configured(nexthop);
953 log_nexthop_debug(tmp, "Received remembered", link);
954 } else {
955 nexthop_enter_configured(tmp);
956 log_nexthop_debug(tmp, "Remembering", link);
957
958 r = nexthop_add(m, link, tmp);
959 if (r < 0) {
960 log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m");
961 return 0;
962 }
963
964 TAKE_PTR(tmp);
965 }
966
967 break;
968 case RTM_DELNEXTHOP:
969 if (nexthop) {
970 nexthop_enter_removed(nexthop);
971 if (nexthop->state == 0) {
972 log_nexthop_debug(nexthop, "Forgetting", link);
973 nexthop_free(nexthop);
974 } else
975 log_nexthop_debug(nexthop, "Removed", link);
976 } else
977 log_nexthop_debug(tmp, "Kernel removed unknown", link);
978 break;
979
980 default:
981 assert_not_reached();
982 }
983
984 return 1;
985 }
986
987 static int nexthop_section_verify(NextHop *nh) {
988 if (section_is_invalid(nh->section))
989 return -EINVAL;
990
991 if (!hashmap_isempty(nh->group)) {
992 if (in_addr_is_set(nh->family, &nh->gw))
993 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
994 "%s: nexthop group cannot have gateway address. "
995 "Ignoring [NextHop] section from line %u.",
996 nh->section->filename, nh->section->line);
997
998 if (nh->family != AF_UNSPEC)
999 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
1000 "%s: nexthop group cannot have Family= setting. "
1001 "Ignoring [NextHop] section from line %u.",
1002 nh->section->filename, nh->section->line);
1003
1004 if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
1005 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
1006 "%s: nexthop group cannot be a blackhole. "
1007 "Ignoring [NextHop] section from line %u.",
1008 nh->section->filename, nh->section->line);
1009 } else if (nh->family == AF_UNSPEC)
1010 /* When neither Family=, Gateway=, nor Group= is specified, assume IPv4. */
1011 nh->family = AF_INET;
1012
1013 if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw))
1014 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
1015 "%s: blackhole nexthop cannot have gateway address. "
1016 "Ignoring [NextHop] section from line %u.",
1017 nh->section->filename, nh->section->line);
1018
1019 if (nh->onlink < 0 && in_addr_is_set(nh->family, &nh->gw) &&
1020 ordered_hashmap_isempty(nh->network->addresses_by_section)) {
1021 /* If no address is configured, in most cases the gateway cannot be reachable.
1022 * TODO: we may need to improve the condition above. */
1023 log_warning("%s: Gateway= without static address configured. "
1024 "Enabling OnLink= option.",
1025 nh->section->filename);
1026 nh->onlink = true;
1027 }
1028
1029 if (nh->onlink >= 0)
1030 SET_FLAG(nh->flags, RTNH_F_ONLINK, nh->onlink);
1031
1032 return 0;
1033 }
1034
1035 void network_drop_invalid_nexthops(Network *network) {
1036 NextHop *nh;
1037
1038 assert(network);
1039
1040 HASHMAP_FOREACH(nh, network->nexthops_by_section)
1041 if (nexthop_section_verify(nh) < 0)
1042 nexthop_free(nh);
1043 }
1044
1045 int config_parse_nexthop_id(
1046 const char *unit,
1047 const char *filename,
1048 unsigned line,
1049 const char *section,
1050 unsigned section_line,
1051 const char *lvalue,
1052 int ltype,
1053 const char *rvalue,
1054 void *data,
1055 void *userdata) {
1056
1057 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1058 Network *network = userdata;
1059 uint32_t id;
1060 int r;
1061
1062 assert(filename);
1063 assert(section);
1064 assert(lvalue);
1065 assert(rvalue);
1066 assert(data);
1067
1068 r = nexthop_new_static(network, filename, section_line, &n);
1069 if (r < 0)
1070 return log_oom();
1071
1072 if (isempty(rvalue)) {
1073 n->id = 0;
1074 TAKE_PTR(n);
1075 return 0;
1076 }
1077
1078 r = safe_atou32(rvalue, &id);
1079 if (r < 0) {
1080 log_syntax(unit, LOG_WARNING, filename, line, r,
1081 "Could not parse nexthop id \"%s\", ignoring assignment: %m", rvalue);
1082 return 0;
1083 }
1084 if (id == 0) {
1085 log_syntax(unit, LOG_WARNING, filename, line, 0,
1086 "Invalid nexthop id \"%s\", ignoring assignment: %m", rvalue);
1087 return 0;
1088 }
1089
1090 n->id = id;
1091 TAKE_PTR(n);
1092 return 0;
1093 }
1094
1095 int config_parse_nexthop_gateway(
1096 const char *unit,
1097 const char *filename,
1098 unsigned line,
1099 const char *section,
1100 unsigned section_line,
1101 const char *lvalue,
1102 int ltype,
1103 const char *rvalue,
1104 void *data,
1105 void *userdata) {
1106
1107 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1108 Network *network = userdata;
1109 int r;
1110
1111 assert(filename);
1112 assert(section);
1113 assert(lvalue);
1114 assert(rvalue);
1115 assert(data);
1116
1117 r = nexthop_new_static(network, filename, section_line, &n);
1118 if (r < 0)
1119 return log_oom();
1120
1121 if (isempty(rvalue)) {
1122 n->family = AF_UNSPEC;
1123 n->gw = IN_ADDR_NULL;
1124
1125 TAKE_PTR(n);
1126 return 0;
1127 }
1128
1129 r = in_addr_from_string_auto(rvalue, &n->family, &n->gw);
1130 if (r < 0) {
1131 log_syntax(unit, LOG_WARNING, filename, line, r,
1132 "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
1133 return 0;
1134 }
1135
1136 TAKE_PTR(n);
1137 return 0;
1138 }
1139
1140 int config_parse_nexthop_family(
1141 const char *unit,
1142 const char *filename,
1143 unsigned line,
1144 const char *section,
1145 unsigned section_line,
1146 const char *lvalue,
1147 int ltype,
1148 const char *rvalue,
1149 void *data,
1150 void *userdata) {
1151
1152 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1153 Network *network = userdata;
1154 AddressFamily a;
1155 int r;
1156
1157 assert(filename);
1158 assert(section);
1159 assert(lvalue);
1160 assert(rvalue);
1161 assert(data);
1162
1163 r = nexthop_new_static(network, filename, section_line, &n);
1164 if (r < 0)
1165 return log_oom();
1166
1167 if (isempty(rvalue) &&
1168 !in_addr_is_set(n->family, &n->gw)) {
1169 /* Accept an empty string only when Gateway= is null or not specified. */
1170 n->family = AF_UNSPEC;
1171 TAKE_PTR(n);
1172 return 0;
1173 }
1174
1175 a = nexthop_address_family_from_string(rvalue);
1176 if (a < 0) {
1177 log_syntax(unit, LOG_WARNING, filename, line, 0,
1178 "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue);
1179 return 0;
1180 }
1181
1182 if (in_addr_is_set(n->family, &n->gw) &&
1183 ((a == ADDRESS_FAMILY_IPV4 && n->family == AF_INET6) ||
1184 (a == ADDRESS_FAMILY_IPV6 && n->family == AF_INET))) {
1185 log_syntax(unit, LOG_WARNING, filename, line, 0,
1186 "Specified family '%s' conflicts with the family of the previously specified Gateway=, "
1187 "ignoring assignment.", rvalue);
1188 return 0;
1189 }
1190
1191 switch (a) {
1192 case ADDRESS_FAMILY_IPV4:
1193 n->family = AF_INET;
1194 break;
1195 case ADDRESS_FAMILY_IPV6:
1196 n->family = AF_INET6;
1197 break;
1198 default:
1199 assert_not_reached();
1200 }
1201
1202 TAKE_PTR(n);
1203 return 0;
1204 }
1205
1206 int config_parse_nexthop_onlink(
1207 const char *unit,
1208 const char *filename,
1209 unsigned line,
1210 const char *section,
1211 unsigned section_line,
1212 const char *lvalue,
1213 int ltype,
1214 const char *rvalue,
1215 void *data,
1216 void *userdata) {
1217
1218 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1219 Network *network = userdata;
1220 int r;
1221
1222 assert(filename);
1223 assert(section);
1224 assert(lvalue);
1225 assert(rvalue);
1226 assert(data);
1227
1228 r = nexthop_new_static(network, filename, section_line, &n);
1229 if (r < 0)
1230 return log_oom();
1231
1232 if (isempty(rvalue)) {
1233 n->onlink = -1;
1234 TAKE_PTR(n);
1235 return 0;
1236 }
1237
1238 r = parse_boolean(rvalue);
1239 if (r < 0) {
1240 log_syntax(unit, LOG_WARNING, filename, line, r,
1241 "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
1242 return 0;
1243 }
1244
1245 n->onlink = r;
1246
1247 TAKE_PTR(n);
1248 return 0;
1249 }
1250
1251 int config_parse_nexthop_blackhole(
1252 const char *unit,
1253 const char *filename,
1254 unsigned line,
1255 const char *section,
1256 unsigned section_line,
1257 const char *lvalue,
1258 int ltype,
1259 const char *rvalue,
1260 void *data,
1261 void *userdata) {
1262
1263 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1264 Network *network = userdata;
1265 int r;
1266
1267 assert(filename);
1268 assert(section);
1269 assert(lvalue);
1270 assert(rvalue);
1271 assert(data);
1272
1273 r = nexthop_new_static(network, filename, section_line, &n);
1274 if (r < 0)
1275 return log_oom();
1276
1277 r = parse_boolean(rvalue);
1278 if (r < 0) {
1279 log_syntax(unit, LOG_WARNING, filename, line, r,
1280 "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
1281 return 0;
1282 }
1283
1284 n->blackhole = r;
1285
1286 TAKE_PTR(n);
1287 return 0;
1288 }
1289
1290 int config_parse_nexthop_group(
1291 const char *unit,
1292 const char *filename,
1293 unsigned line,
1294 const char *section,
1295 unsigned section_line,
1296 const char *lvalue,
1297 int ltype,
1298 const char *rvalue,
1299 void *data,
1300 void *userdata) {
1301
1302 _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL;
1303 Network *network = userdata;
1304 int r;
1305
1306 assert(filename);
1307 assert(section);
1308 assert(lvalue);
1309 assert(rvalue);
1310 assert(data);
1311
1312 r = nexthop_new_static(network, filename, section_line, &n);
1313 if (r < 0)
1314 return log_oom();
1315
1316 if (isempty(rvalue)) {
1317 n->group = hashmap_free_free(n->group);
1318 TAKE_PTR(n);
1319 return 0;
1320 }
1321
1322 for (const char *p = rvalue;;) {
1323 _cleanup_free_ struct nexthop_grp *nhg = NULL;
1324 _cleanup_free_ char *word = NULL;
1325 uint32_t w;
1326 char *sep;
1327
1328 r = extract_first_word(&p, &word, NULL, 0);
1329 if (r == -ENOMEM)
1330 return log_oom();
1331 if (r < 0) {
1332 log_syntax(unit, LOG_WARNING, filename, line, r,
1333 "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
1334 return 0;
1335 }
1336 if (r == 0)
1337 break;
1338
1339 nhg = new0(struct nexthop_grp, 1);
1340 if (!nhg)
1341 return log_oom();
1342
1343 sep = strchr(word, ':');
1344 if (sep) {
1345 *sep++ = '\0';
1346 r = safe_atou32(sep, &w);
1347 if (r < 0) {
1348 log_syntax(unit, LOG_WARNING, filename, line, r,
1349 "Failed to parse weight for nexthop group, ignoring assignment: %s:%s",
1350 word, sep);
1351 continue;
1352 }
1353 if (w == 0 || w > 256) {
1354 log_syntax(unit, LOG_WARNING, filename, line, 0,
1355 "Invalid weight for nexthop group, ignoring assignment: %s:%s",
1356 word, sep);
1357 continue;
1358 }
1359 /* See comments in config_parse_multipath_route(). */
1360 nhg->weight = w - 1;
1361 }
1362
1363 r = safe_atou32(word, &nhg->id);
1364 if (r < 0) {
1365 log_syntax(unit, LOG_WARNING, filename, line, r,
1366 "Failed to parse nexthop ID in %s=, ignoring assignment: %s%s%s",
1367 lvalue, word, sep ? ":" : "", strempty(sep));
1368 continue;
1369 }
1370 if (nhg->id == 0) {
1371 log_syntax(unit, LOG_WARNING, filename, line, 0,
1372 "Nexthop ID in %s= must be positive, ignoring assignment: %s%s%s",
1373 lvalue, word, sep ? ":" : "", strempty(sep));
1374 continue;
1375 }
1376
1377 r = hashmap_ensure_put(&n->group, NULL, UINT32_TO_PTR(nhg->id), nhg);
1378 if (r == -ENOMEM)
1379 return log_oom();
1380 if (r == -EEXIST) {
1381 log_syntax(unit, LOG_WARNING, filename, line, r,
1382 "Nexthop ID %"PRIu32" is specified multiple times in %s=, ignoring assignment: %s%s%s",
1383 nhg->id, lvalue, word, sep ? ":" : "", strempty(sep));
1384 continue;
1385 }
1386 assert(r > 0);
1387 TAKE_PTR(nhg);
1388 }
1389
1390 TAKE_PTR(n);
1391 return 0;
1392 }