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