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