diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 657ba66245..a9a9b13d42 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1150,6 +1150,29 @@ + + + + [NextHop] Section Options + The [NextHop] section accepts the + following keys. Specify several [NextHop] + sections to configure several nexthop. Nexthop is used to manipulate entries in the kernel's nexthop + tables. + + + + Gateway= + + As in the [Network] section. This is mandatory. + + + + Id= + + The id of the nexthop (an unsigned integer). If unspecified or '0' then automatically chosen by kernel. + + + diff --git a/src/basic/linux/nexthop.h b/src/basic/linux/nexthop.h new file mode 100644 index 0000000000..d51b6e1d08 --- /dev/null +++ b/src/basic/linux/nexthop.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_NEXTHOP_H +#define _LINUX_NEXTHOP_H + +#include + +struct nhmsg { + unsigned char nh_family; + unsigned char nh_scope; /* return only */ + unsigned char nh_protocol; /* Routing protocol that installed nh */ + unsigned char resvd; + unsigned int nh_flags; /* RTNH_F flags */ +}; + +/* entry in a nexthop group */ +struct nexthop_grp { + __u32 id; /* nexthop id - must exist */ + __u8 weight; /* weight of this nexthop */ + __u8 resvd1; + __u16 resvd2; +}; + +enum { + NEXTHOP_GRP_TYPE_MPATH, /* default type if not specified */ + __NEXTHOP_GRP_TYPE_MAX, +}; + +#define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1) + +enum { + NHA_UNSPEC, + NHA_ID, /* u32; id for nexthop. id == 0 means auto-assign */ + + NHA_GROUP, /* array of nexthop_grp */ + NHA_GROUP_TYPE, /* u16 one of NEXTHOP_GRP_TYPE */ + /* if NHA_GROUP attribute is added, no other attributes can be set */ + + NHA_BLACKHOLE, /* flag; nexthop used to blackhole packets */ + /* if NHA_BLACKHOLE is added, OIF, GATEWAY, ENCAP can not be set */ + + NHA_OIF, /* u32; nexthop device */ + NHA_GATEWAY, /* be32 (IPv4) or in6_addr (IPv6) gw address */ + NHA_ENCAP_TYPE, /* u16; lwt encap type */ + NHA_ENCAP, /* lwt encap data */ + + /* NHA_OIF can be appended to dump request to return only + * nexthops using given device + */ + NHA_GROUPS, /* flag; only return nexthop groups in dump */ + NHA_MASTER, /* u32; only return nexthops with given master dev */ + + __NHA_MAX, +}; + +#define NHA_MAX (__NHA_MAX - 1) +#endif diff --git a/src/basic/linux/rtnetlink.h b/src/basic/linux/rtnetlink.h index 4639936762..80ad27fcc0 100644 --- a/src/basic/linux/rtnetlink.h +++ b/src/basic/linux/rtnetlink.h @@ -157,6 +157,13 @@ enum { RTM_GETCHAIN, #define RTM_GETCHAIN RTM_GETCHAIN + RTM_NEWNEXTHOP = 104, +#define RTM_NEWNEXTHOP RTM_NEWNEXTHOP + RTM_DELNEXTHOP, +#define RTM_DELNEXTHOP RTM_DELNEXTHOP + RTM_GETNEXTHOP, +#define RTM_GETNEXTHOP RTM_GETNEXTHOP + __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; @@ -165,7 +172,7 @@ enum { #define RTM_NR_FAMILIES (RTM_NR_MSGTYPES >> 2) #define RTM_FAM(cmd) (((cmd) - RTM_BASE) >> 2) -/* +/* Generic structure for encapsulation of optional route information. It is reminiscent of sockaddr, but with sa_family replaced with attribute type. @@ -205,7 +212,7 @@ struct rtmsg { unsigned char rtm_table; /* Routing table id */ unsigned char rtm_protocol; /* Routing protocol; see below */ - unsigned char rtm_scope; /* See below */ + unsigned char rtm_scope; /* See below */ unsigned char rtm_type; /* See below */ unsigned rtm_flags; @@ -342,6 +349,7 @@ enum rtattr_type_t { RTA_IP_PROTO, RTA_SPORT, RTA_DPORT, + RTA_NH_ID, __RTA_MAX }; @@ -515,7 +523,7 @@ struct ifinfomsg { }; /******************************************************************** - * prefix information + * prefix information ****/ struct prefixmsg { @@ -529,7 +537,7 @@ struct prefixmsg { unsigned char prefix_pad3; }; -enum +enum { PREFIX_UNSPEC, PREFIX_ADDRESS, @@ -704,6 +712,8 @@ enum rtnetlink_groups { #define RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV4_MROUTE_R RTNLGRP_IPV6_MROUTE_R, #define RTNLGRP_IPV6_MROUTE_R RTNLGRP_IPV6_MROUTE_R + RTNLGRP_NEXTHOP, +#define RTNLGRP_NEXTHOP RTNLGRP_NEXTHOP __RTNLGRP_MAX }; #define RTNLGRP_MAX (__RTNLGRP_MAX - 1) diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 16964712c9..58ec1efdd4 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -88,7 +88,8 @@ int sd_netlink_message_request_dump(sd_netlink_message *m, int dump) { assert_return(m, -EINVAL); assert_return(m->hdr, -EINVAL); - assert_return(IN_SET(m->hdr->nlmsg_type, RTM_GETLINK, RTM_GETADDR, RTM_GETROUTE, RTM_GETNEIGH, RTM_GETRULE, RTM_GETADDRLABEL), -EINVAL); + assert_return(IN_SET(m->hdr->nlmsg_type, RTM_GETLINK, RTM_GETADDR, RTM_GETROUTE, RTM_GETNEIGH, + RTM_GETRULE, RTM_GETADDRLABEL, RTM_GETNEXTHOP), -EINVAL); SET_FLAG(m->hdr->nlmsg_flags, NLM_F_DUMP, dump); diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index de9b8b21ab..6db0ffd13f 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -716,6 +717,17 @@ static const NLTypeSystem rtnl_routing_policy_rule_type_system = { .types = rtnl_routing_policy_rule_types, }; +static const NLType rtnl_nexthop_types[] = { + [NHA_ID] = { .type = NETLINK_TYPE_U32 }, + [NHA_OIF] = { .type = NETLINK_TYPE_U32 }, + [NHA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, +}; + +static const NLTypeSystem rtnl_nexthop_type_system = { + .count = ELEMENTSOF(rtnl_nexthop_types), + .types = rtnl_nexthop_types, +}; + static const NLType rtnl_types[] = { [NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 }, [NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) }, @@ -738,6 +750,9 @@ static const NLType rtnl_types[] = { [RTM_NEWRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, [RTM_DELRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, [RTM_GETRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, + [RTM_NEWNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) }, + [RTM_DELNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) }, + [RTM_GETNEXTHOP] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_nexthop_type_system, .size = sizeof(struct nhmsg) }, }; const NLTypeSystem rtnl_type_system_root = { diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h index 0d01a4bd0e..923accb7e3 100644 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once +#include + #include "sd-netlink.h" #include "in-addr-util.h" @@ -19,6 +21,10 @@ static inline bool rtnl_message_type_is_route(uint16_t type) { return IN_SET(type, RTM_NEWROUTE, RTM_GETROUTE, RTM_DELROUTE); } +static inline bool rtnl_message_type_is_nexthop(uint16_t type) { + return IN_SET(type, RTM_NEWNEXTHOP, RTM_GETNEXTHOP, RTM_DELNEXTHOP); +} + static inline bool rtnl_message_type_is_link(uint16_t type) { return IN_SET(type, RTM_NEWLINK, RTM_SETLINK, RTM_GETLINK, RTM_DELLINK); } diff --git a/src/libsystemd/sd-netlink/rtnl-message.c b/src/libsystemd/sd-netlink/rtnl-message.c index 751bf53d64..8cba0cc57e 100644 --- a/src/libsystemd/sd-netlink/rtnl-message.c +++ b/src/libsystemd/sd-netlink/rtnl-message.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -285,6 +286,70 @@ int sd_rtnl_message_new_route(sd_netlink *rtnl, sd_netlink_message **ret, return 0; } +int sd_rtnl_message_new_nexthop(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nhmsg_type, int nh_family, + unsigned char nh_protocol) { + struct nhmsg *nhm; + int r; + + assert_return(rtnl_message_type_is_nexthop(nhmsg_type), -EINVAL); + assert_return((nhmsg_type == RTM_GETNEXTHOP && nh_family == AF_UNSPEC) || + IN_SET(nh_family, AF_INET, AF_INET6), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nhmsg_type); + if (r < 0) + return r; + + if (nhmsg_type == RTM_NEWNEXTHOP) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + nhm = NLMSG_DATA((*ret)->hdr); + + nhm->nh_family = nh_family; + nhm->nh_scope = RT_SCOPE_UNIVERSE; + nhm->nh_protocol = nh_protocol; + + return 0; +} + +int sd_rtnl_message_nexthop_set_flags(sd_netlink_message *m, uint8_t flags) { + struct nhmsg *nhm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_nexthop(m->hdr->nlmsg_type), -EINVAL); + + nhm = NLMSG_DATA(m->hdr); + nhm->nh_flags |= flags; + + return 0; +} + +int sd_rtnl_message_nexthop_set_family(sd_netlink_message *m, uint8_t family) { + struct nhmsg *nhm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + + nhm = NLMSG_DATA(m->hdr); + nhm->nh_family = family; + + return 0; +} + +int sd_rtnl_message_nexthop_get_family(sd_netlink_message *m, uint8_t *family) { + struct nhmsg *nhm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + + nhm = NLMSG_DATA(m->hdr); + *family = nhm->nh_family ; + + return 0; +} + int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags) { struct ndmsg *ndm; @@ -713,6 +778,14 @@ int sd_rtnl_message_get_family(sd_netlink_message *m, int *family) { *family = rtm->rtm_family; + return 0; + } else if (rtnl_message_type_is_nexthop(m->hdr->nlmsg_type)) { + struct nhmsg *nhm; + + nhm = NLMSG_DATA(m->hdr); + + *family = nhm->nh_family; + return 0; } diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index 02e9e9a26b..f3366d1bf7 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -896,6 +896,13 @@ int sd_netlink_add_match( if (r < 0) return r; break; + case RTM_NEWNEXTHOP: + case RTM_DELNEXTHOP: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_NEXTHOP); + if (r < 0) + return r; + break; + default: return -EOPNOTSUPP; } diff --git a/src/network/meson.build b/src/network/meson.build index 6bed37a170..c16e095c2c 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -93,6 +93,8 @@ sources = files(''' networkd-network-bus.h networkd-network.c networkd-network.h + networkd-nexthop.c + networkd-nexthop.h networkd-route.c networkd-route.h networkd-routing-policy-rule.c diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index a23bddde9b..897a2b2fc9 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -672,6 +672,9 @@ static Link *link_free(Link *link) { link->routes = set_free_with_destructor(link->routes, route_free); link->routes_foreign = set_free_with_destructor(link->routes_foreign, route_free); + link->nexthops = set_free_with_destructor(link->nexthops, nexthop_free); + link->nexthops_foreign = set_free_with_destructor(link->nexthops_foreign, nexthop_free); + link->neighbors = set_free_with_destructor(link->neighbors, neighbor_free); link->neighbors_foreign = set_free_with_destructor(link->neighbors_foreign, neighbor_free); @@ -903,6 +906,58 @@ static int link_request_set_routing_policy_rule(Link *link) { return 0; } +static int nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(link); + assert(link->nexthop_messages > 0); + assert(IN_SET(link->state, LINK_STATE_CONFIGURING, + LINK_STATE_FAILED, LINK_STATE_LINGER)); + + link->nexthop_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) { + log_link_warning_errno(link, r, "Could not set nexthop: %m"); + link_enter_failed(link); + return 1; + } + + if (link->nexthop_messages == 0) { + log_link_debug(link, "Nexthop set"); + link->static_nexthops_configured = true; + link_check_ready(link); + } + + return 1; +} + +int link_request_set_nexthop(Link *link) { + NextHop *nh; + int r; + + LIST_FOREACH(nexthops, nh, link->network->static_nexthops) { + r = nexthop_configure(nh, link, nexthop_handler); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set nexthop: %m"); + if (r > 0) + link->nexthop_messages++; + } + + if (link->nexthop_messages == 0) { + link->static_nexthops_configured = true; + link_check_ready(link); + } else { + log_link_debug(link, "Setting nexthop"); + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 1; +} + static int route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { int r; @@ -948,6 +1003,7 @@ int link_request_set_routes(Link *link) { assert(link->state != _LINK_STATE_INVALID); link->static_routes_configured = false; + link->static_routes_ready = false; if (!link_has_carrier(link) && !link->network->configure_without_carrier) /* During configuring addresses, the link lost its carrier. As networkd is dropping @@ -1017,6 +1073,17 @@ void link_check_ready(Link *link) { if (!link->static_routes_configured) return; + if (!link->static_routes_ready) { + link->static_routes_ready = true; + r = link_request_set_nexthop(link); + if (r < 0) + link_enter_failed(link); + return; + } + + if (!link->static_nexthops_configured) + return; + if (!link->routing_policy_rules_configured) return; @@ -1134,6 +1201,8 @@ static int link_request_set_addresses(Link *link) { link->addresses_ready = false; link->neighbors_configured = false; link->static_routes_configured = false; + link->static_routes_ready = false; + link->static_nexthops_configured = false; link->routing_policy_rules_configured = false; r = link_set_bridge_fdb(link); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 446c042fb9..d6604c9120 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -69,6 +69,7 @@ typedef struct Link { unsigned address_label_messages; unsigned neighbor_messages; unsigned route_messages; + unsigned nexthop_messages; unsigned routing_policy_rule_messages; unsigned routing_policy_rule_remove_messages; unsigned enslaving; @@ -79,9 +80,8 @@ typedef struct Link { Set *neighbors_foreign; Set *routes; Set *routes_foreign; - - bool addresses_configured; - bool addresses_ready; + Set *nexthops; + Set *nexthops_foreign; sd_dhcp_client *dhcp_client; sd_dhcp_lease *dhcp_lease, *dhcp_lease_old; @@ -100,8 +100,12 @@ typedef struct Link { sd_ipv4ll *ipv4ll; bool ipv4ll_address:1; + bool addresses_configured:1; + bool addresses_ready:1; bool neighbors_configured:1; bool static_routes_configured:1; + bool static_routes_ready:1; + bool static_nexthops_configured:1; bool routing_policy_rules_configured:1; bool setting_mtu:1; @@ -198,6 +202,7 @@ uint32_t link_get_vrf_table(Link *link); uint32_t link_get_dhcp_route_table(Link *link); uint32_t link_get_ipv6_accept_ra_route_table(Link *link); int link_request_set_routes(Link *link); +int link_request_set_nexthop(Link *link); #define ADDRESS_FMT_VAL(address) \ be32toh((address).s_addr) >> 24, \ diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 546bb2375c..c70194f1f8 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "sd-daemon.h" #include "sd-netlink.h" @@ -1153,6 +1154,118 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, voi return 1; } +int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) { + _cleanup_(nexthop_freep) NextHop *tmp = NULL; + _cleanup_free_ char *gateway = NULL; + NextHop *nexthop = NULL; + Manager *m = userdata; + Link *link = NULL; + uint16_t type; + int r; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_warning_errno(r, "rtnl: failed to receive rule message, ignoring: %m"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWNEXTHOP, RTM_DELNEXTHOP)) { + log_warning("rtnl: received unexpected message type %u when processing nexthop, ignoring.", type); + return 0; + } + + r = nexthop_new(&tmp); + if (r < 0) + return log_oom(); + + r = sd_rtnl_message_get_family(message, &tmp->family); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get nexthop family, ignoring: %m"); + return 0; + } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + log_debug("rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family); + return 0; + } + + switch (tmp->family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(message, NHA_GATEWAY, &tmp->gw.in); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); + return 0; + } + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(message, NHA_GATEWAY, &tmp->gw.in6); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); + return 0; + } + break; + + default: + assert_not_reached("Received rule message with unsupported address family"); + } + + r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get NHA_ID attribute, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_read_u32(message, NHA_OIF, &tmp->oif); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m"); + return 0; + } + + r = link_get(m, tmp->oif, &link); + if (r < 0 || !link) { + if (!m->enumerating) + log_warning("rtnl: received nexthop message for link (%d) we do not know about, ignoring", tmp->oif); + return 0; + } + + (void) nexthop_get(link, tmp, &nexthop); + + if (DEBUG_LOGGING) + (void) in_addr_to_string(tmp->family, &tmp->gw, &gateway); + + switch (type) { + case RTM_NEWNEXTHOP: + if (!nexthop) { + log_debug("Remembering foreign nexthop: %s, oif: %d, id: %d", gateway, tmp->oif, tmp->id); + r = nexthop_add_foreign(link, tmp, &nexthop); + if (r < 0) { + log_warning_errno(r, "Could not remember foreign nexthop, ignoring: %m"); + return 0; + } + } + break; + case RTM_DELNEXTHOP: + log_debug("Forgetting foreign nexthop: %s, oif: %d, id: %d", gateway, tmp->oif, tmp->id); + nexthop_free(nexthop); + + break; + + default: + assert_not_reached("Received invalid RTNL message type"); + } + + return 1; +} + static int systemd_netlink_fd(void) { int n, fd, rtnl_fd = -EINVAL; @@ -1253,6 +1366,14 @@ static int manager_connect_rtnl(Manager *m) { if (r < 0) return r; + r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWNEXTHOP, &manager_rtnl_process_nexthop, NULL, m, "network-rtnl_process_nexthop"); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELNEXTHOP, &manager_rtnl_process_nexthop, NULL, m, "network-rtnl_process_nexthop"); + if (r < 0) + return r; + return 0; } @@ -1931,6 +2052,47 @@ int manager_rtnl_enumerate_rules(Manager *m) { return r; } +int manager_rtnl_enumerate_nexthop(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *nexthop; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_nexthop(m->rtnl, &req, RTM_GETNEXTHOP, 0, 0); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) { + if (r == -EOPNOTSUPP) { + log_debug("Nexthop are not supported by the kernel. Ignoring."); + return 0; + } + + return r; + } + + for (nexthop = reply; nexthop; nexthop = sd_netlink_message_next(nexthop)) { + int k; + + m->enumerating = true; + + k = manager_rtnl_process_nexthop(m->rtnl, nexthop, m); + if (k < 0) + r = k; + + m->enumerating = false; + } + + return r; +} + int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found) { AddressPool *p; int r; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index d5049df868..f2f309ffb0 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -83,11 +83,13 @@ int manager_rtnl_enumerate_addresses(Manager *m); int manager_rtnl_enumerate_neighbors(Manager *m); int manager_rtnl_enumerate_routes(Manager *m); int manager_rtnl_enumerate_rules(Manager *m); +int manager_rtnl_enumerate_nexthop(Manager *m); int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, void *userdata); int manager_rtnl_process_neighbor(sd_netlink *nl, sd_netlink_message *message, void *userdata); int manager_rtnl_process_route(sd_netlink *nl, sd_netlink_message *message, void *userdata); int manager_rtnl_process_rule(sd_netlink *nl, sd_netlink_message *message, void *userdata); +int manager_rtnl_process_nexthop(sd_netlink *nl, sd_netlink_message *message, void *userdata); void manager_dirty(Manager *m); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 689b1a123e..490a0a38a3 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -141,6 +141,8 @@ Route.InitialAdvertisedReceiveWindow, config_parse_tcp_window, Route.QuickAck, config_parse_quickack, 0, 0 Route.FastOpenNoCookie, config_parse_fast_open_no_cookie, 0, 0 Route.TTLPropagate, config_parse_route_ttl_propagate, 0, 0 +NextHop.Id, config_parse_nexthop_id, 0, 0 +NextHop.Gateway, config_parse_nexthop_gateway, 0, 0 DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns) DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3ea76a034a..d949cc0f93 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -151,6 +151,7 @@ int network_verify(Network *network) { AddressLabel *label, *label_next; Prefix *prefix, *prefix_next; RoutingPolicyRule *rule, *rule_next; + NextHop *nexthop, *nextnop_next; assert(network); assert(network->filename); @@ -282,6 +283,10 @@ int network_verify(Network *network) { if (route_section_verify(route, network) < 0) route_free(route); + LIST_FOREACH_SAFE(nexthops, nexthop, nextnop_next, network->static_nexthops) + if (nexthop_section_verify(nexthop) < 0) + nexthop_free(nexthop); + LIST_FOREACH_SAFE(static_fdb_entries, fdb, fdb_next, network->static_fdb_entries) if (section_is_invalid(fdb->section)) fdb_entry_free(fdb); @@ -453,6 +458,7 @@ int network_load_one(Manager *manager, const char *filename) { "IPv6AddressLabel\0" "RoutingPolicyRule\0" "Route\0" + "NextHop\0" "DHCP\0" "DHCPv4\0" /* compat */ "DHCPv6\0" @@ -525,8 +531,9 @@ static Network *network_free(Network *network) { FdbEntry *fdb_entry; Neighbor *neighbor; AddressLabel *label; - Prefix *prefix; Address *address; + NextHop *nexthop; + Prefix *prefix; Route *route; if (!network) @@ -573,6 +580,9 @@ static Network *network_free(Network *network) { while ((route = network->static_routes)) route_free(route); + while ((nexthop = network->static_nexthops)) + nexthop_free(nexthop); + while ((address = network->static_addresses)) address_free(address); @@ -596,6 +606,7 @@ static Network *network_free(Network *network) { hashmap_free(network->addresses_by_section); hashmap_free(network->routes_by_section); + hashmap_free(network->nexthops_by_section); hashmap_free(network->fdb_entries_by_section); hashmap_free(network->neighbors_by_section); hashmap_free(network->address_labels_by_section); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 35469c05ed..668cc0d348 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -19,6 +19,7 @@ #include "networkd-lldp-rx.h" #include "networkd-lldp-tx.h" #include "networkd-neighbor.h" +#include "networkd-nexthop.h" #include "networkd-radv.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" @@ -228,6 +229,7 @@ struct Network { LIST_HEAD(Address, static_addresses); LIST_HEAD(Route, static_routes); + LIST_HEAD(NextHop, static_nexthops); LIST_HEAD(FdbEntry, static_fdb_entries); LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses); LIST_HEAD(Neighbor, neighbors); @@ -238,6 +240,7 @@ struct Network { unsigned n_static_addresses; unsigned n_static_routes; + unsigned n_static_nexthops; unsigned n_static_fdb_entries; unsigned n_ipv6_proxy_ndp_addresses; unsigned n_neighbors; @@ -248,6 +251,7 @@ struct Network { Hashmap *addresses_by_section; Hashmap *routes_by_section; + Hashmap *nexthops_by_section; Hashmap *fdb_entries_by_section; Hashmap *neighbors_by_section; Hashmap *address_labels_by_section; diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c new file mode 100644 index 0000000000..9658fe30c0 --- /dev/null +++ b/src/network/networkd-nexthop.c @@ -0,0 +1,473 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2019 VMware, Inc. + */ + +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "in-addr-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-nexthop.h" +#include "parse-util.h" +#include "set.h" +#include "string-util.h" +#include "util.h" + +int nexthop_new(NextHop **ret) { + _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + + nexthop = new(NextHop, 1); + if (!nexthop) + return -ENOMEM; + + *nexthop = (NextHop) { + .family = AF_UNSPEC, + }; + + *ret = TAKE_PTR(nexthop); + + return 0; +} + +static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) { + _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL; + _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + int r; + + assert(network); + assert(ret); + assert(!!filename == (section_line > 0)); + + if (filename) { + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + nexthop = hashmap_get(network->nexthops_by_section, n); + if (nexthop) { + *ret = TAKE_PTR(nexthop); + + return 0; + } + } + + r = nexthop_new(&nexthop); + if (r < 0) + return r; + + nexthop->protocol = RTPROT_STATIC; + nexthop->network = network; + LIST_PREPEND(nexthops, network->static_nexthops, nexthop); + network->n_static_nexthops++; + + if (filename) { + nexthop->section = TAKE_PTR(n); + + r = hashmap_ensure_allocated(&network->nexthops_by_section, &network_config_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(network->nexthops_by_section, nexthop->section, nexthop); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(nexthop); + + return 0; +} + +void nexthop_free(NextHop *nexthop) { + if (!nexthop) + return; + + if (nexthop->network) { + LIST_REMOVE(nexthops, nexthop->network->static_nexthops, nexthop); + + assert(nexthop->network->n_static_nexthops > 0); + nexthop->network->n_static_nexthops--; + + if (nexthop->section) + hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section); + } + + network_config_section_free(nexthop->section); + + if (nexthop->link) { + set_remove(nexthop->link->nexthops, nexthop); + set_remove(nexthop->link->nexthops_foreign, nexthop); + } + + free(nexthop); +} + +static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) { + assert(nexthop); + + siphash24_compress(&nexthop->id, sizeof(nexthop->id), state); + siphash24_compress(&nexthop->oif, sizeof(nexthop->oif), state); + siphash24_compress(&nexthop->family, sizeof(nexthop->family), state); + + switch (nexthop->family) { + case AF_INET: + case AF_INET6: + siphash24_compress(&nexthop->gw, FAMILY_ADDRESS_SIZE(nexthop->family), state); + + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } +} + +static int nexthop_compare_func(const NextHop *a, const NextHop *b) { + int r; + + r = CMP(a->id, b->id); + if (r != 0) + return r; + + r = CMP(a->oif, b->oif); + if (r != 0) + return r; + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + switch (a->family) { + case AF_INET: + case AF_INET6: + + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + return 0; + default: + /* treat any other address family as AF_UNSPEC */ + return 0; + } +} + +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + nexthop_hash_ops, + NextHop, + nexthop_hash_func, + nexthop_compare_func, + nexthop_free); + +bool nexthop_equal(NextHop *r1, NextHop *r2) { + if (r1 == r2) + return true; + + if (!r1 || !r2) + return false; + + return nexthop_compare_func(r1, r2) == 0; +} + +int nexthop_get(Link *link, NextHop *in, NextHop **ret) { + NextHop *existing; + + assert(link); + assert(in); + + existing = set_get(link->nexthops, in); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + + existing = set_get(link->nexthops_foreign, in); + if (existing) { + if (ret) + *ret = existing; + return 0; + } + + return -ENOENT; +} + +static int nexthop_add_internal(Link *link, Set **nexthops, NextHop *in, NextHop **ret) { + _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + int r; + + assert(link); + assert(nexthops); + assert(in); + + r = nexthop_new(&nexthop); + if (r < 0) + return r; + + nexthop->id = in->id; + nexthop->oif = in->oif; + nexthop->family = in->family; + nexthop->gw = in->gw; + + r = set_ensure_allocated(nexthops, &nexthop_hash_ops); + if (r < 0) + return r; + + r = set_put(*nexthops, nexthop); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + nexthop->link = link; + + if (ret) + *ret = nexthop; + + nexthop = NULL; + + return 0; +} + +int nexthop_add_foreign(Link *link, NextHop *in, NextHop **ret) { + return nexthop_add_internal(link, &link->nexthops_foreign, in, ret); +} + +int nexthop_add(Link *link, NextHop *in, NextHop **ret) { + NextHop *nexthop; + int r; + + r = nexthop_get(link, in, &nexthop); + if (r == -ENOENT) { + /* NextHop does not exist, create a new one */ + r = nexthop_add_internal(link, &link->nexthops, in, &nexthop); + if (r < 0) + return r; + } else if (r == 0) { + /* Take over a foreign nexthop */ + r = set_ensure_allocated(&link->nexthops, &nexthop_hash_ops); + if (r < 0) + return r; + + r = set_put(link->nexthops, nexthop); + if (r < 0) + return r; + + set_remove(link->nexthops_foreign, nexthop); + } else if (r == 1) { + /* NextHop exists, do nothing */ + ; + } else + return r; + + if (ret) + *ret = nexthop; + + return 0; +} + +static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -ESRCH) + log_link_warning_errno(link, r, "Could not drop nexthop: %m"); + + return 1; +} + +int nexthop_remove(NextHop *nexthop, Link *link, + link_netlink_message_handler_t callback) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + assert(IN_SET(nexthop->family, AF_INET, AF_INET6)); + + r = sd_rtnl_message_new_nexthop(link->manager->rtnl, &req, + RTM_DELNEXTHOP, nexthop->family, + nexthop->protocol); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_DELNEXTHOP message: %m"); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *gw = NULL; + + if (!in_addr_is_null(nexthop->family, &nexthop->gw)) + (void) in_addr_to_string(nexthop->family, &nexthop->gw, &gw); + + log_link_debug(link, "Removing nexthop: gw: %s", strna(gw)); + } + + if (in_addr_is_null(nexthop->family, &nexthop->gw) == 0) { + r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, nexthop->family, &nexthop->gw); + if (r < 0) + return log_link_error_errno(link, r, "Could not append RTA_GATEWAY attribute: %m"); + } + + r = netlink_call_async(link->manager->rtnl, NULL, req, + callback ?: nexthop_remove_handler, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +int nexthop_configure( + NextHop *nexthop, + Link *link, + link_netlink_message_handler_t callback) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + assert(IN_SET(nexthop->family, AF_INET, AF_INET6)); + assert(callback); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *gw = NULL; + + if (!in_addr_is_null(nexthop->family, &nexthop->gw)) + (void) in_addr_to_string(nexthop->family, &nexthop->gw, &gw); + + log_link_debug(link, "Configuring nexthop: gw: %s", strna(gw)); + } + + r = sd_rtnl_message_new_nexthop(link->manager->rtnl, &req, + RTM_NEWNEXTHOP, nexthop->family, + nexthop->protocol); + if (r < 0) + return log_link_error_errno(link, r, "Could not create RTM_NEWNEXTHOP message: %m"); + + r = sd_netlink_message_append_u32(req, NHA_ID, nexthop->id); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m"); + + r = sd_netlink_message_append_u32(req, NHA_OIF, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NHA_OIF attribute: %m"); + + if (in_addr_is_null(nexthop->family, &nexthop->gw) == 0) { + r = netlink_message_append_in_addr_union(req, NHA_GATEWAY, nexthop->family, &nexthop->gw); + if (r < 0) + return log_link_error_errno(link, r, "Could not append NHA_GATEWAY attribute: %m"); + + r = sd_rtnl_message_nexthop_set_family(req, nexthop->family); + if (r < 0) + return log_link_error_errno(link, r, "Could not set nexthop family: %m"); + } + + r = netlink_call_async(link->manager->rtnl, NULL, req, callback, + link_netlink_destroy_callback, link); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + r = nexthop_add(link, nexthop, &nexthop); + if (r < 0) + return log_link_error_errno(link, r, "Could not add nexthop: %m"); + + return 1; +} + +int nexthop_section_verify(NextHop *nh) { + if (section_is_invalid(nh->section)) + return -EINVAL; + + if (in_addr_is_null(nh->family, &nh->gw) < 0) + return -EINVAL; + + return 0; +} + +int 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) { + + _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = nexthop_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = safe_atou32(rvalue, &n->id); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Could not parse nexthop id \"%s\", ignoring assignment: %m", rvalue); + return 0; + } + + TAKE_PTR(n); + return 0; +} + +int 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) { + + _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = nexthop_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = in_addr_from_string_auto(rvalue, &n->family, &n->gw); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + TAKE_PTR(n); + return 0; +} diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h new file mode 100644 index 0000000000..28cbdad738 --- /dev/null +++ b/src/network/networkd-nexthop.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1+ + * Copyright © 2019 VMware, Inc. + */ + +#pragma once + +#include "conf-parser.h" +#include "macro.h" + +typedef struct NextHop NextHop; +typedef struct NetworkConfigSection NetworkConfigSection; + +#include "networkd-network.h" +#include "networkd-util.h" + +struct NextHop { + Network *network; + NetworkConfigSection *section; + + Link *link; + + unsigned char protocol; + + int family; + uint32_t oif; + uint32_t id; + + union in_addr_union gw; + + LIST_FIELDS(NextHop, nexthops); +}; + +extern const struct hash_ops nexthop_hash_ops; + +int nexthop_new(NextHop **ret); +void nexthop_free(NextHop *nexthop); +int nexthop_configure(NextHop *nexthop, Link *link, link_netlink_message_handler_t callback); +int nexthop_remove(NextHop *nexthop, Link *link, link_netlink_message_handler_t callback); + +int nexthop_get(Link *link, NextHop *in, NextHop **ret); +int nexthop_add(Link *link, NextHop *in, NextHop **ret); +int nexthop_add_foreign(Link *link, NextHop *in, NextHop **ret); +bool nexthop_equal(NextHop *r1, NextHop *r2); + +int nexthop_section_verify(NextHop *nexthop); + +DEFINE_NETWORK_SECTION_FUNCTIONS(NextHop, nexthop_free); + +CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_id); +CONFIG_PARSER_PROTOTYPE(config_parse_nexthop_gateway); diff --git a/src/network/networkd.c b/src/network/networkd.c index 38bd9ff1ff..c7ce64b90b 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -107,6 +107,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Could not enumerate rules: %m"); + r = manager_rtnl_enumerate_nexthop(m); + if (r < 0) + return log_error_errno(r, "Could not enumerate nexthop: %m"); + r = manager_start(m); if (r < 0) return log_error_errno(r, "Could not start manager: %m"); diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 33cd82ba5a..d5b74e62e1 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -167,6 +167,12 @@ int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len); int sd_rtnl_message_route_get_type(sd_netlink_message *m, unsigned char *type); +int sd_rtnl_message_new_nexthop(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nhmsg_type, int nh_family, unsigned char nh_protocol); + +int sd_rtnl_message_nexthop_set_flags(sd_netlink_message *m, uint8_t flags); +int sd_rtnl_message_nexthop_set_family(sd_netlink_message *m, uint8_t family); +int sd_rtnl_message_nexthop_get_family(sd_netlink_message *m, uint8_t *family); + int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags); int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state); int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family); diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 24d94033fc..bd6a127ac5 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -255,3 +255,6 @@ MaxLeaseTimeSec= DefaultLeaseTimeSec= EmitTimezone= DNS= +[NextHop] +Id= +Gateway=