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