summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/systemd.network.xml23
-rw-r--r--src/basic/linux/nexthop.h56
-rw-r--r--src/basic/linux/rtnetlink.h18
-rw-r--r--src/libsystemd/sd-netlink/netlink-message.c3
-rw-r--r--src/libsystemd/sd-netlink/netlink-types.c15
-rw-r--r--src/libsystemd/sd-netlink/netlink-util.h6
-rw-r--r--src/libsystemd/sd-netlink/rtnl-message.c73
-rw-r--r--src/libsystemd/sd-netlink/sd-netlink.c7
-rw-r--r--src/network/meson.build2
-rw-r--r--src/network/networkd-link.c69
-rw-r--r--src/network/networkd-link.h11
-rw-r--r--src/network/networkd-manager.c162
-rw-r--r--src/network/networkd-manager.h2
-rw-r--r--src/network/networkd-network-gperf.gperf2
-rw-r--r--src/network/networkd-network.c13
-rw-r--r--src/network/networkd-network.h4
-rw-r--r--src/network/networkd-nexthop.c473
-rw-r--r--src/network/networkd-nexthop.h50
-rw-r--r--src/network/networkd.c4
-rw-r--r--src/systemd/sd-netlink.h6
-rw-r--r--test/fuzz/fuzz-network-parser/directives.network3
21 files changed, 993 insertions, 9 deletions
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 @@
</listitem>
</varlistentry>
</variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>[NextHop] Section Options</title>
+ <para>The <literal>[NextHop]</literal> section accepts the
+ following keys. Specify several <literal>[NextHop]</literal>
+ sections to configure several nexthop. Nexthop is used to manipulate entries in the kernel's nexthop
+ tables.</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>Gateway=</varname></term>
+ <listitem>
+ <para>As in the <literal>[Network]</literal> section. This is mandatory.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><varname>Id=</varname></term>
+ <listitem>
+ <para>The id of the nexthop (an unsigned integer). If unspecified or '0' then automatically chosen by kernel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
</refsect1>
<refsect1>
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 <linux/types.h>
+
+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 <linux/if_link.h>
#include <linux/if_macsec.h>
#include <linux/if_tunnel.h>
+#include <linux/nexthop.h>
#include <linux/l2tp.h>
#include <linux/veth.h>
#include <linux/wireguard.h>
@@ -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 <linux/rtnetlink.h>
+
#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 <netinet/in.h>
#include <linux/if_addrlabel.h>
+#include <linux/nexthop.h>
#include <stdbool.h>
#include <unistd.h>
@@ -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;
@@ -714,6 +779,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;
}
return -EOPNOTSUPP;
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 <unistd.h>
#include <linux/if.h>
#include <linux/fib_rules.h>
+#include <linux/nexthop.h>
#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 <linux/nexthop.h>
+
+#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=