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