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