1 /* SPDX-License-Identifier: LGPL-2.1-or-later
2 * Copyright © 2019 VMware, Inc.
5 /* Make sure the net/if.h header is included before any linux/ one */
7 #include <linux/nexthop.h>
9 #include "alloc-util.h"
10 #include "netlink-util.h"
11 #include "networkd-link.h"
12 #include "networkd-manager.h"
13 #include "networkd-network.h"
14 #include "networkd-nexthop.h"
15 #include "networkd-queue.h"
16 #include "networkd-route.h"
17 #include "networkd-route-util.h"
18 #include "parse-util.h"
20 #include "stdio-util.h"
21 #include "string-util.h"
23 static void nexthop_detach_from_group_members(NextHop
*nexthop
) {
25 assert(nexthop
->manager
);
26 assert(nexthop
->id
> 0);
28 struct nexthop_grp
*nhg
;
29 HASHMAP_FOREACH(nhg
, nexthop
->group
) {
32 if (nexthop_get_by_id(nexthop
->manager
, nhg
->id
, &nh
) < 0)
35 set_remove(nh
->nexthops
, UINT32_TO_PTR(nexthop
->id
));
39 static void nexthop_attach_to_group_members(NextHop
*nexthop
) {
43 assert(nexthop
->manager
);
44 assert(nexthop
->id
> 0);
46 struct nexthop_grp
*nhg
;
47 HASHMAP_FOREACH(nhg
, nexthop
->group
) {
50 r
= nexthop_get_by_id(nexthop
->manager
, nhg
->id
, &nh
);
52 if (nexthop
->manager
->manage_foreign_nexthops
)
53 log_debug_errno(r
, "Nexthop (id=%"PRIu32
") has unknown group member (%"PRIu32
"), ignoring.",
54 nexthop
->id
, nhg
->id
);
58 r
= set_ensure_put(&nh
->nexthops
, NULL
, UINT32_TO_PTR(nexthop
->id
));
60 log_debug_errno(r
, "Failed to save nexthop ID (%"PRIu32
") to group member (%"PRIu32
"), ignoring: %m",
61 nexthop
->id
, nhg
->id
);
65 static NextHop
* nexthop_detach_impl(NextHop
*nexthop
) {
67 assert(!nexthop
->manager
|| !nexthop
->network
);
69 if (nexthop
->network
) {
70 assert(nexthop
->section
);
71 ordered_hashmap_remove(nexthop
->network
->nexthops_by_section
, nexthop
->section
);
72 nexthop
->network
= NULL
;
76 if (nexthop
->manager
) {
77 assert(nexthop
->id
> 0);
79 nexthop_detach_from_group_members(nexthop
);
81 hashmap_remove(nexthop
->manager
->nexthops_by_id
, UINT32_TO_PTR(nexthop
->id
));
82 nexthop
->manager
= NULL
;
89 static void nexthop_detach(NextHop
*nexthop
) {
90 nexthop_unref(nexthop_detach_impl(nexthop
));
93 static NextHop
* nexthop_free(NextHop
*nexthop
) {
97 nexthop_detach_impl(nexthop
);
99 config_section_free(nexthop
->section
);
100 hashmap_free_free(nexthop
->group
);
101 set_free(nexthop
->nexthops
);
102 set_free(nexthop
->routes
);
104 return mfree(nexthop
);
107 DEFINE_TRIVIAL_REF_UNREF_FUNC(NextHop
, nexthop
, nexthop_free
);
108 DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop
, nexthop_unref
);
110 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
114 trivial_compare_func
,
118 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
119 nexthop_section_hash_ops
,
121 config_section_hash_func
,
122 config_section_compare_func
,
126 static int nexthop_new(NextHop
**ret
) {
127 _cleanup_(nexthop_unrefp
) NextHop
*nexthop
= NULL
;
129 nexthop
= new(NextHop
, 1);
133 *nexthop
= (NextHop
) {
138 *ret
= TAKE_PTR(nexthop
);
143 static int nexthop_new_static(Network
*network
, const char *filename
, unsigned section_line
, NextHop
**ret
) {
144 _cleanup_(config_section_freep
) ConfigSection
*n
= NULL
;
145 _cleanup_(nexthop_unrefp
) NextHop
*nexthop
= NULL
;
151 assert(section_line
> 0);
153 r
= config_section_new(filename
, section_line
, &n
);
157 nexthop
= ordered_hashmap_get(network
->nexthops_by_section
, n
);
159 *ret
= TAKE_PTR(nexthop
);
163 r
= nexthop_new(&nexthop
);
167 nexthop
->protocol
= RTPROT_STATIC
;
168 nexthop
->network
= network
;
169 nexthop
->section
= TAKE_PTR(n
);
170 nexthop
->source
= NETWORK_CONFIG_SOURCE_STATIC
;
172 r
= ordered_hashmap_ensure_put(&network
->nexthops_by_section
, &nexthop_section_hash_ops
, nexthop
->section
, nexthop
);
176 *ret
= TAKE_PTR(nexthop
);
180 static void nexthop_hash_func(const NextHop
*nexthop
, struct siphash
*state
) {
184 siphash24_compress_typesafe(nexthop
->id
, state
);
187 static int nexthop_compare_func(const NextHop
*a
, const NextHop
*b
) {
191 return CMP(a
->id
, b
->id
);
194 static int nexthop_compare_full(const NextHop
*a
, const NextHop
*b
) {
200 /* This compares detailed configs, except for ID and ifindex. */
202 r
= CMP(a
->protocol
, b
->protocol
);
206 r
= CMP(a
->flags
, b
->flags
);
210 r
= CMP(hashmap_size(a
->group
), hashmap_size(b
->group
));
214 if (!hashmap_isempty(a
->group
)) {
215 struct nexthop_grp
*ga
;
217 HASHMAP_FOREACH(ga
, a
->group
) {
218 struct nexthop_grp
*gb
;
220 gb
= hashmap_get(b
->group
, UINT32_TO_PTR(ga
->id
));
224 r
= CMP(ga
->weight
, gb
->weight
);
230 r
= CMP(a
->blackhole
, b
->blackhole
);
234 r
= CMP(a
->family
, b
->family
);
238 if (IN_SET(a
->family
, AF_INET
, AF_INET6
)) {
239 r
= memcmp(&a
->gw
, &b
->gw
, FAMILY_ADDRESS_SIZE(a
->family
));
247 static int nexthop_dup(const NextHop
*src
, NextHop
**ret
) {
248 _cleanup_(nexthop_unrefp
) NextHop
*dest
= NULL
;
249 struct nexthop_grp
*nhg
;
255 dest
= newdup(NextHop
, src
, 1);
259 /* clear the reference counter and all pointers */
261 dest
->manager
= NULL
;
262 dest
->network
= NULL
;
263 dest
->section
= NULL
;
266 HASHMAP_FOREACH(nhg
, src
->group
) {
267 _cleanup_free_
struct nexthop_grp
*g
= NULL
;
269 g
= newdup(struct nexthop_grp
, nhg
, 1);
273 r
= hashmap_ensure_put(&dest
->group
, NULL
, UINT32_TO_PTR(g
->id
), g
);
280 *ret
= TAKE_PTR(dest
);
284 static bool nexthop_bound_to_link(const NextHop
*nexthop
) {
286 return !nexthop
->blackhole
&& hashmap_isempty(nexthop
->group
);
289 int nexthop_get_by_id(Manager
*manager
, uint32_t id
, NextHop
**ret
) {
297 nh
= hashmap_get(manager
->nexthops_by_id
, UINT32_TO_PTR(id
));
306 static int nexthop_get(Link
*link
, const NextHop
*in
, NextHop
**ret
) {
311 assert(link
->manager
);
315 return nexthop_get_by_id(link
->manager
, in
->id
, ret
);
317 /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by
318 * nexthop_section_verify(). */
319 assert(link
->manager
->manage_foreign_nexthops
);
321 ifindex
= nexthop_bound_to_link(in
) ? link
->ifindex
: 0;
323 HASHMAP_FOREACH(nexthop
, link
->manager
->nexthops_by_id
) {
324 if (nexthop
->ifindex
!= ifindex
)
326 if (nexthop_compare_full(nexthop
, in
) != 0)
329 /* Even if the configuration matches, it may be configured with another [NextHop] section
330 * that has an explicit ID. If so, the assigned nexthop is not the one we are looking for. */
331 if (set_contains(link
->manager
->nexthop_ids
, UINT32_TO_PTR(nexthop
->id
)))
342 static int nexthop_get_request_by_id(Manager
*manager
, uint32_t id
, Request
**ret
) {
350 req
= ordered_set_get(
351 manager
->request_queue
,
353 .type
= REQUEST_TYPE_NEXTHOP
,
354 .userdata
= (void*) &(const NextHop
) { .id
= id
},
355 .hash_func
= (hash_func_t
) nexthop_hash_func
,
356 .compare_func
= (compare_func_t
) nexthop_compare_func
,
366 static int nexthop_get_request(Link
*link
, const NextHop
*in
, Request
**ret
) {
371 assert(link
->manager
);
375 return nexthop_get_request_by_id(link
->manager
, in
->id
, ret
);
377 /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by
378 * nexthop_section_verify(). */
379 assert(link
->manager
->manage_foreign_nexthops
);
381 ifindex
= nexthop_bound_to_link(in
) ? link
->ifindex
: 0;
383 ORDERED_SET_FOREACH(req
, link
->manager
->request_queue
) {
384 if (req
->type
!= REQUEST_TYPE_NEXTHOP
)
387 NextHop
*nexthop
= ASSERT_PTR(req
->userdata
);
388 if (nexthop
->ifindex
!= ifindex
)
390 if (nexthop_compare_full(nexthop
, in
) != 0)
393 /* Even if the configuration matches, it may be requested by another [NextHop] section
394 * that has an explicit ID. If so, the request is not the one we are looking for. */
395 if (set_contains(link
->manager
->nexthop_ids
, UINT32_TO_PTR(nexthop
->id
)))
406 static int nexthop_add_new(Manager
*manager
, uint32_t id
, NextHop
**ret
) {
407 _cleanup_(nexthop_unrefp
) NextHop
*nexthop
= NULL
;
413 r
= nexthop_new(&nexthop
);
419 r
= hashmap_ensure_put(&manager
->nexthops_by_id
, &nexthop_hash_ops
, UINT32_TO_PTR(nexthop
->id
), nexthop
);
425 nexthop
->manager
= manager
;
434 static int nexthop_acquire_id(Manager
*manager
, NextHop
*nexthop
) {
441 /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by
442 * nexthop_section_verify(). */
443 assert(manager
->manage_foreign_nexthops
);
445 /* Find the lowest unused ID. */
447 for (uint32_t id
= 1; id
< UINT32_MAX
; id
++) {
448 if (nexthop_get_by_id(manager
, id
, NULL
) >= 0)
450 if (nexthop_get_request_by_id(manager
, id
, NULL
) >= 0)
452 if (set_contains(manager
->nexthop_ids
, UINT32_TO_PTR(id
)))
462 static void log_nexthop_debug(const NextHop
*nexthop
, const char *str
, Manager
*manager
) {
463 _cleanup_free_
char *state
= NULL
, *group
= NULL
, *flags
= NULL
;
464 struct nexthop_grp
*nhg
;
474 (void) link_get_by_index(manager
, nexthop
->ifindex
, &link
);
475 (void) network_config_state_to_string_alloc(nexthop
->state
, &state
);
476 (void) route_flags_to_string_alloc(nexthop
->flags
, &flags
);
478 HASHMAP_FOREACH(nhg
, nexthop
->group
)
479 (void) strextendf_with_separator(&group
, ",", "%"PRIu32
":%"PRIu32
, nhg
->id
, nhg
->weight
+1u);
481 log_link_debug(link
, "%s %s nexthop (%s): id: %"PRIu32
", gw: %s, blackhole: %s, group: %s, flags: %s",
482 str
, strna(network_config_source_to_string(nexthop
->source
)), strna(state
),
484 IN_ADDR_TO_STRING(nexthop
->family
, &nexthop
->gw
),
485 yes_no(nexthop
->blackhole
), strna(group
), strna(flags
));
488 static int nexthop_remove_dependents(NextHop
*nexthop
, Manager
*manager
) {
494 /* If a nexthop is removed, the kernel silently removes nexthops and routes that depend on the
495 * removed nexthop. Let's remove them for safety (though, they are already removed in the kernel,
496 * hence that should fail), and forget them. */
499 SET_FOREACH(id
, nexthop
->nexthops
) {
502 if (nexthop_get_by_id(manager
, PTR_TO_UINT32(id
), &nh
) < 0)
505 RET_GATHER(r
, nexthop_remove(nh
, manager
));
509 SET_FOREACH(route
, nexthop
->routes
)
510 RET_GATHER(r
, route_remove(route
, manager
));
515 static int nexthop_remove_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, RemoveRequest
*rreq
) {
521 Manager
*manager
= ASSERT_PTR(rreq
->manager
);
522 NextHop
*nexthop
= ASSERT_PTR(rreq
->userdata
);
524 r
= sd_netlink_message_get_errno(m
);
526 log_message_full_errno(m
,
527 (r
== -ENOENT
|| !nexthop
->manager
) ? LOG_DEBUG
: LOG_WARNING
,
528 r
, "Could not drop nexthop, ignoring");
530 (void) nexthop_remove_dependents(nexthop
, manager
);
532 if (nexthop
->manager
) {
533 /* If the nexthop cannot be removed, then assume the nexthop is already removed. */
534 log_nexthop_debug(nexthop
, "Forgetting", manager
);
537 if (nexthop_get_request_by_id(manager
, nexthop
->id
, &req
) >= 0)
538 nexthop_enter_removed(req
->userdata
);
540 nexthop_detach(nexthop
);
547 int nexthop_remove(NextHop
*nexthop
, Manager
*manager
) {
548 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*m
= NULL
;
553 assert(nexthop
->id
> 0);
556 /* If the nexthop is remembered, then use the remembered object. */
557 (void) nexthop_get_by_id(manager
, PTR_TO_UINT32(nexthop
->id
), &nexthop
);
559 /* link may be NULL. */
560 (void) link_get_by_index(manager
, nexthop
->ifindex
, &link
);
562 log_nexthop_debug(nexthop
, "Removing", manager
);
564 r
= sd_rtnl_message_new_nexthop(manager
->rtnl
, &m
, RTM_DELNEXTHOP
, AF_UNSPEC
, RTPROT_UNSPEC
);
566 return log_link_error_errno(link
, r
, "Could not create RTM_DELNEXTHOP message: %m");
568 r
= sd_netlink_message_append_u32(m
, NHA_ID
, nexthop
->id
);
570 return log_link_error_errno(link
, r
, "Could not append NHA_ID attribute: %m");
572 r
= manager_remove_request_add(manager
, nexthop
, nexthop
, manager
->rtnl
, m
, nexthop_remove_handler
);
574 return log_link_error_errno(link
, r
, "Could not queue rtnetlink message: %m");
576 nexthop_enter_removing(nexthop
);
580 static int nexthop_configure(NextHop
*nexthop
, Link
*link
, Request
*req
) {
581 _cleanup_(sd_netlink_message_unrefp
) sd_netlink_message
*m
= NULL
;
585 assert(nexthop
->id
> 0);
586 assert(IN_SET(nexthop
->family
, AF_UNSPEC
, AF_INET
, AF_INET6
));
588 assert(link
->manager
);
589 assert(link
->manager
->rtnl
);
590 assert(link
->ifindex
> 0);
593 log_nexthop_debug(nexthop
, "Configuring", link
->manager
);
595 r
= sd_rtnl_message_new_nexthop(link
->manager
->rtnl
, &m
, RTM_NEWNEXTHOP
, nexthop
->family
, nexthop
->protocol
);
599 r
= sd_netlink_message_append_u32(m
, NHA_ID
, nexthop
->id
);
603 if (!hashmap_isempty(nexthop
->group
)) {
604 _cleanup_free_
struct nexthop_grp
*group
= NULL
;
605 struct nexthop_grp
*p
, *nhg
;
607 group
= new(struct nexthop_grp
, hashmap_size(nexthop
->group
));
612 HASHMAP_FOREACH(nhg
, nexthop
->group
)
615 r
= sd_netlink_message_append_data(m
, NHA_GROUP
, group
, sizeof(struct nexthop_grp
) * hashmap_size(nexthop
->group
));
619 } else if (nexthop
->blackhole
) {
620 r
= sd_netlink_message_append_flag(m
, NHA_BLACKHOLE
);
624 assert(nexthop
->ifindex
== link
->ifindex
);
626 r
= sd_netlink_message_append_u32(m
, NHA_OIF
, nexthop
->ifindex
);
630 if (in_addr_is_set(nexthop
->family
, &nexthop
->gw
)) {
631 r
= netlink_message_append_in_addr_union(m
, NHA_GATEWAY
, nexthop
->family
, &nexthop
->gw
);
635 r
= sd_rtnl_message_nexthop_set_flags(m
, nexthop
->flags
& RTNH_F_ONLINK
);
641 return request_call_netlink_async(link
->manager
->rtnl
, m
, req
);
644 static int static_nexthop_handler(sd_netlink
*rtnl
, sd_netlink_message
*m
, Request
*req
, Link
*link
, NextHop
*nexthop
) {
650 r
= sd_netlink_message_get_errno(m
);
651 if (r
< 0 && r
!= -EEXIST
) {
652 log_link_message_warning_errno(link
, m
, r
, "Could not set nexthop");
653 link_enter_failed(link
);
657 if (link
->static_nexthop_messages
== 0) {
658 log_link_debug(link
, "Nexthops set");
659 link
->static_nexthops_configured
= true;
660 link_check_ready(link
);
666 int nexthop_is_ready(Manager
*manager
, uint32_t id
, NextHop
**ret
) {
674 if (nexthop_get_request_by_id(manager
, id
, NULL
) >= 0)
677 if (nexthop_get_by_id(manager
, id
, &nexthop
) < 0)
680 if (!nexthop_exists(nexthop
))
695 static bool nexthop_is_ready_to_configure(Link
*link
, const NextHop
*nexthop
) {
696 struct nexthop_grp
*nhg
;
701 assert(nexthop
->id
> 0);
703 if (!link_is_ready_to_configure(link
, false))
706 if (nexthop_bound_to_link(nexthop
)) {
707 assert(nexthop
->ifindex
== link
->ifindex
);
709 /* TODO: fdb nexthop does not require IFF_UP. The conditions below needs to be updated
710 * when fdb nexthop support is added. See rtm_to_nh_config() in net/ipv4/nexthop.c of
712 if (link
->set_flags_messages
> 0)
714 if (!FLAGS_SET(link
->flags
, IFF_UP
))
718 /* All group members must be configured first. */
719 HASHMAP_FOREACH(nhg
, nexthop
->group
) {
720 r
= nexthop_is_ready(link
->manager
, nhg
->id
, NULL
);
725 return gateway_is_ready(link
, FLAGS_SET(nexthop
->flags
, RTNH_F_ONLINK
), nexthop
->family
, &nexthop
->gw
);
728 static int nexthop_process_request(Request
*req
, Link
*link
, NextHop
*nexthop
) {
734 assert(link
->manager
);
737 if (!nexthop_is_ready_to_configure(link
, nexthop
))
740 r
= nexthop_configure(nexthop
, link
, req
);
742 return log_link_warning_errno(link
, r
, "Failed to configure nexthop");
744 nexthop_enter_configuring(nexthop
);
745 if (nexthop_get_by_id(link
->manager
, nexthop
->id
, &existing
) >= 0)
746 nexthop_enter_configuring(existing
);
751 static int link_request_nexthop(Link
*link
, const NextHop
*nexthop
) {
752 _cleanup_(nexthop_unrefp
) NextHop
*tmp
= NULL
;
753 NextHop
*existing
= NULL
;
757 assert(link
->manager
);
759 assert(nexthop
->source
!= NETWORK_CONFIG_SOURCE_FOREIGN
);
761 if (nexthop_get_request(link
, nexthop
, NULL
) >= 0)
762 return 0; /* already requested, skipping. */
764 r
= nexthop_dup(nexthop
, &tmp
);
768 if (nexthop_get(link
, nexthop
, &existing
) < 0) {
769 r
= nexthop_acquire_id(link
->manager
, tmp
);
774 assert(tmp
->id
== 0 || tmp
->id
== existing
->id
);
775 tmp
->id
= existing
->id
;
777 /* Copy state for logging below. */
778 tmp
->state
= existing
->state
;
781 if (nexthop_bound_to_link(tmp
))
782 tmp
->ifindex
= link
->ifindex
;
784 log_nexthop_debug(tmp
, "Requesting", link
->manager
);
785 r
= link_queue_request_safe(link
, REQUEST_TYPE_NEXTHOP
,
789 nexthop_compare_func
,
790 nexthop_process_request
,
791 &link
->static_nexthop_messages
,
792 static_nexthop_handler
,
797 nexthop_enter_requesting(tmp
);
799 nexthop_enter_requesting(existing
);
805 int link_request_static_nexthops(Link
*link
, bool only_ipv4
) {
810 assert(link
->network
);
812 link
->static_nexthops_configured
= false;
814 ORDERED_HASHMAP_FOREACH(nh
, link
->network
->nexthops_by_section
) {
815 if (only_ipv4
&& nh
->family
!= AF_INET
)
818 r
= link_request_nexthop(link
, nh
);
820 return log_link_warning_errno(link
, r
, "Could not request nexthop: %m");
823 if (link
->static_nexthop_messages
== 0) {
824 link
->static_nexthops_configured
= true;
825 link_check_ready(link
);
827 log_link_debug(link
, "Requesting nexthops");
828 link_set_state(link
, LINK_STATE_CONFIGURING
);
834 static bool nexthop_can_update(const NextHop
*assigned_nexthop
, const NextHop
*requested_nexthop
) {
835 assert(assigned_nexthop
);
836 assert(assigned_nexthop
->manager
);
837 assert(requested_nexthop
);
838 assert(requested_nexthop
->network
);
840 /* A group nexthop cannot be replaced with a non-group nexthop, and vice versa.
841 * See replace_nexthop_grp() and replace_nexthop_single() in net/ipv4/nexthop.c of the kernel. */
842 if (hashmap_isempty(assigned_nexthop
->group
) != hashmap_isempty(requested_nexthop
->group
))
845 /* There are several more conditions if we can replace a group nexthop, e.g. hash threshold and
846 * resilience. But, currently we do not support to modify that. Let's add checks for them in the
847 * future when we support to configure them.*/
849 /* When a nexthop is replaced with a blackhole nexthop, and a group nexthop has multiple nexthops
850 * including this nexthop, then the kernel refuses to replace the existing nexthop.
851 * So, here, for simplicity, let's unconditionally refuse to replace a non-blackhole nexthop with
852 * a blackhole nexthop. See replace_nexthop() in net/ipv4/nexthop.c of the kernel. */
853 if (!assigned_nexthop
->blackhole
&& requested_nexthop
->blackhole
)
859 static void link_mark_nexthops(Link
*link
, bool foreign
) {
864 assert(link
->manager
);
866 /* First, mark all nexthops. */
867 HASHMAP_FOREACH(nexthop
, link
->manager
->nexthops_by_id
) {
868 /* do not touch nexthop created by the kernel */
869 if (nexthop
->protocol
== RTPROT_KERNEL
)
872 /* When 'foreign' is true, mark only foreign nexthops, and vice versa. */
873 if (nexthop
->source
!= (foreign
? NETWORK_CONFIG_SOURCE_FOREIGN
: NETWORK_CONFIG_SOURCE_STATIC
))
876 /* Ignore nexthops not assigned yet or already removed. */
877 if (!nexthop_exists(nexthop
))
880 /* Ignore nexthops bound to other links. */
881 if (nexthop
->ifindex
> 0 && nexthop
->ifindex
!= link
->ifindex
)
884 nexthop_mark(nexthop
);
887 /* Then, unmark all nexthops requested by active links. */
888 HASHMAP_FOREACH(other
, link
->manager
->links_by_index
) {
889 if (!foreign
&& other
== link
)
892 if (!IN_SET(other
->state
, LINK_STATE_CONFIGURING
, LINK_STATE_CONFIGURED
))
895 ORDERED_HASHMAP_FOREACH(nexthop
, other
->network
->nexthops_by_section
) {
898 if (nexthop_get(other
, nexthop
, &existing
) < 0)
901 if (!nexthop_can_update(existing
, nexthop
))
904 /* Found matching static configuration. Keep the existing nexthop. */
905 nexthop_unmark(existing
);
910 int link_drop_nexthops(Link
*link
, bool foreign
) {
915 assert(link
->manager
);
917 link_mark_nexthops(link
, foreign
);
919 HASHMAP_FOREACH(nexthop
, link
->manager
->nexthops_by_id
) {
920 if (!nexthop_is_marked(nexthop
))
923 RET_GATHER(r
, nexthop_remove(nexthop
, link
->manager
));
929 void link_foreignize_nexthops(Link
*link
) {
933 assert(link
->manager
);
935 link_mark_nexthops(link
, /* foreign = */ false);
937 HASHMAP_FOREACH(nexthop
, link
->manager
->nexthops_by_id
) {
938 if (!nexthop_is_marked(nexthop
))
941 nexthop
->source
= NETWORK_CONFIG_SOURCE_FOREIGN
;
945 static int nexthop_update_group(NextHop
*nexthop
, sd_netlink_message
*message
) {
946 _cleanup_hashmap_free_free_ Hashmap
*h
= NULL
;
947 _cleanup_free_
struct nexthop_grp
*group
= NULL
;
948 size_t size
= 0, n_group
;
954 r
= sd_netlink_message_read_data(message
, NHA_GROUP
, &size
, (void**) &group
);
955 if (r
< 0 && r
!= -ENODATA
)
956 return log_debug_errno(r
, "rtnl: could not get NHA_GROUP attribute, ignoring: %m");
958 nexthop_detach_from_group_members(nexthop
);
960 if (size
% sizeof(struct nexthop_grp
) != 0)
961 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
),
962 "rtnl: received nexthop message with invalid nexthop group size, ignoring.");
964 if ((uintptr_t) group
% alignof(struct nexthop_grp
) != 0)
965 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL
),
966 "rtnl: received nexthop message with invalid alignment, ignoring.");
968 n_group
= size
/ sizeof(struct nexthop_grp
);
969 for (size_t i
= 0; i
< n_group
; i
++) {
970 _cleanup_free_
struct nexthop_grp
*nhg
= NULL
;
972 if (group
[i
].id
== 0) {
973 log_debug("rtnl: received nexthop message with invalid ID in group, ignoring.");
977 if (group
[i
].weight
> 254) {
978 log_debug("rtnl: received nexthop message with invalid weight in group, ignoring.");
982 nhg
= newdup(struct nexthop_grp
, group
+ i
, 1);
986 r
= hashmap_ensure_put(&h
, NULL
, UINT32_TO_PTR(nhg
->id
), nhg
);
990 log_debug_errno(r
, "Failed to store nexthop group, ignoring: %m");
997 hashmap_free_free(nexthop
->group
);
998 nexthop
->group
= TAKE_PTR(h
);
1000 nexthop_attach_to_group_members(nexthop
);
1004 int manager_rtnl_process_nexthop(sd_netlink
*rtnl
, sd_netlink_message
*message
, Manager
*m
) {
1006 uint32_t id
, ifindex
;
1007 NextHop
*nexthop
= NULL
;
1008 Request
*req
= NULL
;
1009 bool is_new
= false;
1016 if (sd_netlink_message_is_error(message
)) {
1017 r
= sd_netlink_message_get_errno(message
);
1019 log_message_warning_errno(message
, r
, "rtnl: failed to receive rule message, ignoring");
1024 r
= sd_netlink_message_get_type(message
, &type
);
1026 log_warning_errno(r
, "rtnl: could not get message type, ignoring: %m");
1028 } else if (!IN_SET(type
, RTM_NEWNEXTHOP
, RTM_DELNEXTHOP
)) {
1029 log_warning("rtnl: received unexpected message type %u when processing nexthop, ignoring.", type
);
1033 r
= sd_netlink_message_read_u32(message
, NHA_ID
, &id
);
1034 if (r
== -ENODATA
) {
1035 log_warning_errno(r
, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m");
1038 log_warning_errno(r
, "rtnl: could not get NHA_ID attribute, ignoring: %m");
1040 } else if (id
== 0) {
1041 log_warning("rtnl: received nexthop message with invalid nexthop ID, ignoring: %m");
1045 (void) nexthop_get_by_id(m
, id
, &nexthop
);
1046 (void) nexthop_get_request_by_id(m
, id
, &req
);
1048 if (type
== RTM_DELNEXTHOP
) {
1050 nexthop_enter_removed(nexthop
);
1051 log_nexthop_debug(nexthop
, "Forgetting removed", m
);
1052 (void) nexthop_remove_dependents(nexthop
, m
);
1053 nexthop_detach(nexthop
);
1055 log_nexthop_debug(&(const NextHop
) { .id
= id
}, "Kernel removed unknown", m
);
1058 nexthop_enter_removed(req
->userdata
);
1063 /* If we did not know the nexthop, then save it. */
1065 r
= nexthop_add_new(m
, id
, &nexthop
);
1067 log_warning_errno(r
, "Failed to add received nexthop, ignoring: %m");
1074 /* Also update information that cannot be obtained through netlink notification. */
1075 if (req
&& req
->waiting_reply
) {
1076 NextHop
*n
= ASSERT_PTR(req
->userdata
);
1078 nexthop
->source
= n
->source
;
1081 r
= sd_rtnl_message_get_family(message
, &nexthop
->family
);
1083 log_debug_errno(r
, "rtnl: could not get nexthop family, ignoring: %m");
1085 r
= sd_rtnl_message_nexthop_get_protocol(message
, &nexthop
->protocol
);
1087 log_debug_errno(r
, "rtnl: could not get nexthop protocol, ignoring: %m");
1089 r
= sd_rtnl_message_nexthop_get_flags(message
, &nexthop
->flags
);
1091 log_debug_errno(r
, "rtnl: could not get nexthop flags, ignoring: %m");
1093 (void) nexthop_update_group(nexthop
, message
);
1095 if (nexthop
->family
!= AF_UNSPEC
) {
1096 r
= netlink_message_read_in_addr_union(message
, NHA_GATEWAY
, nexthop
->family
, &nexthop
->gw
);
1098 nexthop
->gw
= IN_ADDR_NULL
;
1100 log_debug_errno(r
, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m");
1103 r
= sd_netlink_message_has_flag(message
, NHA_BLACKHOLE
);
1105 log_debug_errno(r
, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m");
1107 nexthop
->blackhole
= r
;
1109 r
= sd_netlink_message_read_u32(message
, NHA_OIF
, &ifindex
);
1111 nexthop
->ifindex
= 0;
1113 log_debug_errno(r
, "rtnl: could not get NHA_OIF attribute, ignoring: %m");
1114 else if (ifindex
> INT32_MAX
)
1115 log_debug_errno(r
, "rtnl: received invalid NHA_OIF attribute, ignoring: %m");
1117 nexthop
->ifindex
= (int) ifindex
;
1119 /* All blackhole or group nexthops are managed by Manager. Note that the linux kernel does not
1120 * set NHA_OID attribute when NHA_BLACKHOLE or NHA_GROUP is set. Just for safety. */
1121 if (!nexthop_bound_to_link(nexthop
))
1122 nexthop
->ifindex
= 0;
1124 nexthop_enter_configured(nexthop
);
1126 nexthop_enter_configured(req
->userdata
);
1128 log_nexthop_debug(nexthop
, is_new
? "Remembering" : "Received remembered", m
);
1132 static int nexthop_section_verify(NextHop
*nh
) {
1133 if (section_is_invalid(nh
->section
))
1136 if (!nh
->network
->manager
->manage_foreign_nexthops
&& nh
->id
== 0)
1137 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
1138 "%s: [NextHop] section without specifying Id= is not supported "
1139 "if ManageForeignNextHops=no is set in networkd.conf. "
1140 "Ignoring [NextHop] section from line %u.",
1141 nh
->section
->filename
, nh
->section
->line
);
1143 if (!hashmap_isempty(nh
->group
)) {
1144 if (in_addr_is_set(nh
->family
, &nh
->gw
))
1145 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
1146 "%s: nexthop group cannot have gateway address. "
1147 "Ignoring [NextHop] section from line %u.",
1148 nh
->section
->filename
, nh
->section
->line
);
1150 if (nh
->family
!= AF_UNSPEC
)
1151 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
1152 "%s: nexthop group cannot have Family= setting. "
1153 "Ignoring [NextHop] section from line %u.",
1154 nh
->section
->filename
, nh
->section
->line
);
1157 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
1158 "%s: nexthop group cannot be a blackhole. "
1159 "Ignoring [NextHop] section from line %u.",
1160 nh
->section
->filename
, nh
->section
->line
);
1163 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
1164 "%s: nexthop group cannot have on-link flag. "
1165 "Ignoring [NextHop] section from line %u.",
1166 nh
->section
->filename
, nh
->section
->line
);
1167 } else if (nh
->family
== AF_UNSPEC
)
1168 /* When neither Family=, Gateway=, nor Group= is specified, assume IPv4. */
1169 nh
->family
= AF_INET
;
1171 if (nh
->blackhole
) {
1172 if (in_addr_is_set(nh
->family
, &nh
->gw
))
1173 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
1174 "%s: blackhole nexthop cannot have gateway address. "
1175 "Ignoring [NextHop] section from line %u.",
1176 nh
->section
->filename
, nh
->section
->line
);
1179 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL
),
1180 "%s: blackhole nexthop cannot have on-link flag. "
1181 "Ignoring [NextHop] section from line %u.",
1182 nh
->section
->filename
, nh
->section
->line
);
1185 if (nh
->onlink
< 0 && in_addr_is_set(nh
->family
, &nh
->gw
) &&
1186 ordered_hashmap_isempty(nh
->network
->addresses_by_section
)) {
1187 /* If no address is configured, in most cases the gateway cannot be reachable.
1188 * TODO: we may need to improve the condition above. */
1189 log_warning("%s: Gateway= without static address configured. "
1190 "Enabling OnLink= option.",
1191 nh
->section
->filename
);
1195 if (nh
->onlink
>= 0)
1196 SET_FLAG(nh
->flags
, RTNH_F_ONLINK
, nh
->onlink
);
1201 int network_drop_invalid_nexthops(Network
*network
) {
1202 _cleanup_hashmap_free_ Hashmap
*nexthops
= NULL
;
1208 ORDERED_HASHMAP_FOREACH(nh
, network
->nexthops_by_section
) {
1209 if (nexthop_section_verify(nh
) < 0) {
1217 /* Always use the setting specified later. So, remove the previously assigned setting. */
1218 NextHop
*dup
= hashmap_remove(nexthops
, UINT32_TO_PTR(nh
->id
));
1220 log_warning("%s: Duplicated nexthop settings for ID %"PRIu32
" is specified at line %u and %u, "
1221 "dropping the nexthop setting specified at line %u.",
1222 dup
->section
->filename
,
1223 nh
->id
, nh
->section
->line
,
1224 dup
->section
->line
, dup
->section
->line
);
1225 /* nexthop_detach() will drop the nexthop from nexthops_by_section. */
1226 nexthop_detach(dup
);
1229 r
= hashmap_ensure_put(&nexthops
, NULL
, UINT32_TO_PTR(nh
->id
), nh
);
1238 int manager_build_nexthop_ids(Manager
*manager
) {
1244 if (!manager
->manage_foreign_nexthops
)
1247 manager
->nexthop_ids
= set_free(manager
->nexthop_ids
);
1249 ORDERED_HASHMAP_FOREACH(network
, manager
->networks
) {
1252 ORDERED_HASHMAP_FOREACH(nh
, network
->nexthops_by_section
) {
1256 r
= set_ensure_put(&manager
->nexthop_ids
, NULL
, UINT32_TO_PTR(nh
->id
));
1265 int config_parse_nexthop_id(
1267 const char *filename
,
1269 const char *section
,
1270 unsigned section_line
,
1277 _cleanup_(nexthop_unref_or_set_invalidp
) NextHop
*n
= NULL
;
1278 Network
*network
= userdata
;
1288 r
= nexthop_new_static(network
, filename
, section_line
, &n
);
1292 if (isempty(rvalue
)) {
1298 r
= safe_atou32(rvalue
, &id
);
1300 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1301 "Could not parse nexthop id \"%s\", ignoring assignment: %m", rvalue
);
1305 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
1306 "Invalid nexthop id \"%s\", ignoring assignment: %m", rvalue
);
1315 int config_parse_nexthop_gateway(
1317 const char *filename
,
1319 const char *section
,
1320 unsigned section_line
,
1327 _cleanup_(nexthop_unref_or_set_invalidp
) NextHop
*n
= NULL
;
1328 Network
*network
= userdata
;
1337 r
= nexthop_new_static(network
, filename
, section_line
, &n
);
1341 if (isempty(rvalue
)) {
1342 n
->family
= AF_UNSPEC
;
1343 n
->gw
= IN_ADDR_NULL
;
1349 r
= in_addr_from_string_auto(rvalue
, &n
->family
, &n
->gw
);
1351 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1352 "Invalid %s='%s', ignoring assignment: %m", lvalue
, rvalue
);
1360 int config_parse_nexthop_family(
1362 const char *filename
,
1364 const char *section
,
1365 unsigned section_line
,
1372 _cleanup_(nexthop_unref_or_set_invalidp
) NextHop
*n
= NULL
;
1373 Network
*network
= userdata
;
1383 r
= nexthop_new_static(network
, filename
, section_line
, &n
);
1387 if (isempty(rvalue
) &&
1388 !in_addr_is_set(n
->family
, &n
->gw
)) {
1389 /* Accept an empty string only when Gateway= is null or not specified. */
1390 n
->family
= AF_UNSPEC
;
1395 a
= nexthop_address_family_from_string(rvalue
);
1397 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
1398 "Invalid %s='%s', ignoring assignment: %m", lvalue
, rvalue
);
1402 if (in_addr_is_set(n
->family
, &n
->gw
) &&
1403 ((a
== ADDRESS_FAMILY_IPV4
&& n
->family
== AF_INET6
) ||
1404 (a
== ADDRESS_FAMILY_IPV6
&& n
->family
== AF_INET
))) {
1405 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
1406 "Specified family '%s' conflicts with the family of the previously specified Gateway=, "
1407 "ignoring assignment.", rvalue
);
1412 case ADDRESS_FAMILY_IPV4
:
1413 n
->family
= AF_INET
;
1415 case ADDRESS_FAMILY_IPV6
:
1416 n
->family
= AF_INET6
;
1419 assert_not_reached();
1426 int config_parse_nexthop_onlink(
1428 const char *filename
,
1430 const char *section
,
1431 unsigned section_line
,
1438 _cleanup_(nexthop_unref_or_set_invalidp
) NextHop
*n
= NULL
;
1439 Network
*network
= userdata
;
1448 r
= nexthop_new_static(network
, filename
, section_line
, &n
);
1452 r
= parse_tristate(rvalue
, &n
->onlink
);
1454 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1455 "Failed to parse %s=, ignoring assignment: %s", lvalue
, rvalue
);
1463 int config_parse_nexthop_blackhole(
1465 const char *filename
,
1467 const char *section
,
1468 unsigned section_line
,
1475 _cleanup_(nexthop_unref_or_set_invalidp
) NextHop
*n
= NULL
;
1476 Network
*network
= userdata
;
1485 r
= nexthop_new_static(network
, filename
, section_line
, &n
);
1489 r
= parse_boolean(rvalue
);
1491 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1492 "Failed to parse %s=, ignoring assignment: %s", lvalue
, rvalue
);
1502 int config_parse_nexthop_group(
1504 const char *filename
,
1506 const char *section
,
1507 unsigned section_line
,
1514 _cleanup_(nexthop_unref_or_set_invalidp
) NextHop
*n
= NULL
;
1515 Network
*network
= userdata
;
1524 r
= nexthop_new_static(network
, filename
, section_line
, &n
);
1528 if (isempty(rvalue
)) {
1529 n
->group
= hashmap_free_free(n
->group
);
1534 for (const char *p
= rvalue
;;) {
1535 _cleanup_free_
struct nexthop_grp
*nhg
= NULL
;
1536 _cleanup_free_
char *word
= NULL
;
1540 r
= extract_first_word(&p
, &word
, NULL
, 0);
1544 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1545 "Invalid %s=, ignoring assignment: %s", lvalue
, rvalue
);
1551 nhg
= new0(struct nexthop_grp
, 1);
1555 sep
= strchr(word
, ':');
1558 r
= safe_atou32(sep
, &w
);
1560 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1561 "Failed to parse weight for nexthop group, ignoring assignment: %s:%s",
1565 if (w
== 0 || w
> 256) {
1566 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
1567 "Invalid weight for nexthop group, ignoring assignment: %s:%s",
1571 /* See comments in config_parse_multipath_route(). */
1572 nhg
->weight
= w
- 1;
1575 r
= safe_atou32(word
, &nhg
->id
);
1577 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1578 "Failed to parse nexthop ID in %s=, ignoring assignment: %s%s%s",
1579 lvalue
, word
, sep
? ":" : "", strempty(sep
));
1583 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0,
1584 "Nexthop ID in %s= must be positive, ignoring assignment: %s%s%s",
1585 lvalue
, word
, sep
? ":" : "", strempty(sep
));
1589 r
= hashmap_ensure_put(&n
->group
, NULL
, UINT32_TO_PTR(nhg
->id
), nhg
);
1593 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
1594 "Nexthop ID %"PRIu32
" is specified multiple times in %s=, ignoring assignment: %s%s%s",
1595 nhg
->id
, lvalue
, word
, sep
? ":" : "", strempty(sep
));