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