summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-06-03 18:07:44 -0700
committerJakub Kicinski <kuba@kernel.org>2026-06-03 18:07:45 -0700
commit1de680c1c4757a246366a4576189b50437354b81 (patch)
treeca51deb01c7df7b22471444a617f0ee05879fa14
parenta87533a8083185bbf0d450c017662109d8a5bfc7 (diff)
parentafabbb56a7262979a75259e2710019aa8ced3e8a (diff)
downloadlinux-1de680c1c4757a246366a4576189b50437354b81.tar.gz
linux-1de680c1c4757a246366a4576189b50437354b81.zip
Merge branch 'geneve-allow-binding-udp-socket-to-a-specific-address'
Kuniyuki Iwashima says: ==================== geneve: Allow binding UDP socket to a specific address. By default, a GENEVE device bind()s its underlying UDP socket(s) to the IPv4 or IPv6 wildcard address because there is no way to specify a specific local IP address to bind() to. This prevents deploying multiple GENEVE devices on a multi-homed host where each device should be isolated and bound to a different local IP address on the same UDP port. This series introduces two options to specify local IPv4 or IPv6 addresses for a GENEVE device. ==================== Link: https://patch.msgid.link/20260602190436.139591-1-kuniyu@google.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--Documentation/netlink/specs/rt-link.yaml9
-rw-r--r--drivers/net/geneve.c254
-rw-r--r--include/uapi/linux/if_link.h2
3 files changed, 219 insertions, 46 deletions
diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml
index a5c0297818c6..7f8e3ad3a405 100644
--- a/Documentation/netlink/specs/rt-link.yaml
+++ b/Documentation/netlink/specs/rt-link.yaml
@@ -1941,6 +1941,15 @@ attribute-sets:
-
name: gro-hint
type: flag
+ -
+ name: local
+ type: u32
+ byte-order: big-endian
+ display-hint: ipv4
+ -
+ name: local6
+ type: binary
+ display-hint: ipv6
-
name: linkinfo-hsr-attrs
name-prefix: ifla-hsr-
diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c
index e8ff03ed87dc..da17b4f6fda5 100644
--- a/drivers/net/geneve.c
+++ b/drivers/net/geneve.c
@@ -73,6 +73,7 @@ struct geneve_dev_node {
struct geneve_config {
bool collect_md;
+ bool dualstack;
bool use_udp6_rx_checksums;
bool ttl_inherit;
bool gro_hint;
@@ -760,9 +761,10 @@ static int geneve_udp_encap_err_lookup(struct sock *sk, struct sk_buff *skb)
return -EPFNOSUPPORT;
}
-static struct sock *geneve_create_sock(struct net *net, bool ipv6,
- __be16 port, bool ipv6_rx_csum)
+static struct sock *geneve_create_sock(struct net *net,
+ struct geneve_dev *geneve, bool ipv6)
{
+ struct ip_tunnel_info *info = &geneve->cfg.info;
struct udp_port_cfg udp_conf;
struct socket *sock;
int err;
@@ -772,13 +774,14 @@ static struct sock *geneve_create_sock(struct net *net, bool ipv6,
if (ipv6) {
udp_conf.family = AF_INET6;
udp_conf.ipv6_v6only = 1;
- udp_conf.use_udp6_rx_checksums = ipv6_rx_csum;
+ udp_conf.use_udp6_rx_checksums = geneve->cfg.use_udp6_rx_checksums;
+ udp_conf.local_ip6 = info->key.u.ipv6.src;
} else {
udp_conf.family = AF_INET;
- udp_conf.local_ip.s_addr = htonl(INADDR_ANY);
+ udp_conf.local_ip.s_addr = info->key.u.ipv4.src;
}
- udp_conf.local_udp_port = port;
+ udp_conf.local_udp_port = info->key.tp_dst;
/* Open UDP socket */
err = udp_sock_create(net, &udp_conf, &sock);
@@ -970,8 +973,8 @@ static int geneve_gro_complete(struct sock *sk, struct sk_buff *skb,
}
/* Create new listen socket if needed */
-static struct geneve_sock *geneve_socket_create(struct net *net, __be16 port,
- bool ipv6, bool ipv6_rx_csum)
+static struct geneve_sock *geneve_socket_create(struct net *net,
+ struct geneve_dev *geneve, bool ipv6)
{
struct geneve_net *gn = net_generic(net, geneve_net_id);
struct udp_tunnel_sock_cfg tunnel_cfg;
@@ -983,7 +986,7 @@ static struct geneve_sock *geneve_socket_create(struct net *net, __be16 port,
if (!gs)
return ERR_PTR(-ENOMEM);
- sk = geneve_create_sock(net, ipv6, port, ipv6_rx_csum);
+ sk = geneve_create_sock(net, geneve, ipv6);
if (IS_ERR(sk)) {
kfree(gs);
return ERR_CAST(sk);
@@ -1039,48 +1042,63 @@ static void geneve_sock_release(struct geneve_dev *geneve)
#endif
}
-static struct geneve_sock *geneve_find_sock(struct geneve_net *gn,
- sa_family_t family,
- __be16 dst_port,
- bool gro_hint)
+static struct geneve_sock *geneve_find_sock(struct net *net,
+ struct geneve_dev *geneve, bool ipv6)
{
+ struct geneve_net *gn = net_generic(net, geneve_net_id);
+ struct ip_tunnel_info *info = &geneve->cfg.info;
+ sa_family_t family = ipv6 ? AF_INET6 : AF_INET;
+ bool gro_hint = geneve->cfg.gro_hint;
+ __be16 dst_port = info->key.tp_dst;
struct geneve_sock *gs;
list_for_each_entry(gs, &gn->sock_list, list) {
- if (inet_sk(gs->sk)->inet_sport == dst_port &&
- geneve_get_sk_family(gs) == family &&
- gs->gro_hint == gro_hint) {
- return gs;
- }
+ if (inet_sk(gs->sk)->inet_sport != dst_port)
+ continue;
+
+ if (geneve_get_sk_family(gs) != family)
+ continue;
+
+ if (gs->gro_hint != gro_hint)
+ continue;
+
+ if (family == AF_INET &&
+ inet_sk(gs->sk)->inet_saddr != info->key.u.ipv4.src)
+ continue;
+
+#if IS_ENABLED(CONFIG_IPV6)
+ if (family == AF_INET6 &&
+ !ipv6_addr_equal(&gs->sk->sk_v6_rcv_saddr, &info->key.u.ipv6.src))
+ continue;
+#endif
+
+ return gs;
}
+
return NULL;
}
static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6)
{
struct net *net = geneve->net;
- struct geneve_net *gn = net_generic(net, geneve_net_id);
- bool gro_hint = geneve->cfg.gro_hint;
struct geneve_dev_node *node;
struct geneve_sock *gs;
__u8 vni[3];
__u32 hash;
- gs = geneve_find_sock(gn, ipv6 ? AF_INET6 : AF_INET,
- geneve->cfg.info.key.tp_dst, gro_hint);
+ gs = geneve_find_sock(net, geneve, ipv6);
if (gs) {
gs->refcnt++;
goto out;
}
- gs = geneve_socket_create(net, geneve->cfg.info.key.tp_dst, ipv6,
- geneve->cfg.use_udp6_rx_checksums);
+ gs = geneve_socket_create(net, geneve, ipv6);
if (IS_ERR(gs))
return PTR_ERR(gs);
out:
gs->collect_md = geneve->cfg.collect_md;
- gs->gro_hint = gro_hint;
+ gs->gro_hint = geneve->cfg.gro_hint;
#if IS_ENABLED(CONFIG_IPV6)
if (ipv6) {
rcu_assign_pointer(geneve->sock6, gs);
@@ -1102,12 +1120,12 @@ out:
static int geneve_open(struct net_device *dev)
{
struct geneve_dev *geneve = netdev_priv(dev);
- bool metadata = geneve->cfg.collect_md;
+ bool dualstack = geneve->cfg.dualstack;
bool ipv4, ipv6;
int ret = 0;
- ipv6 = geneve->cfg.info.mode & IP_TUNNEL_INFO_IPV6 || metadata;
- ipv4 = !ipv6 || metadata;
+ ipv6 = geneve->cfg.info.mode & IP_TUNNEL_INFO_IPV6 || dualstack;
+ ipv4 = !ipv6 || dualstack;
#if IS_ENABLED(CONFIG_IPV6)
if (ipv6) {
ret = geneve_sock_add(geneve, true);
@@ -1320,6 +1338,12 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev,
if (IS_ERR(rt))
return PTR_ERR(rt);
+ if (geneve->cfg.info.key.u.ipv4.src &&
+ saddr != geneve->cfg.info.key.u.ipv4.src) {
+ dst_release(&rt->dst);
+ return -EADDRNOTAVAIL;
+ }
+
err = skb_tunnel_check_pmtu(skb, &rt->dst,
GENEVE_IPV4_HLEN + info->options_len +
geneve_build_gro_hint_opt(geneve, skb),
@@ -1431,6 +1455,12 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev,
if (IS_ERR(dst))
return PTR_ERR(dst);
+ if (!ipv6_addr_any(&geneve->cfg.info.key.u.ipv6.src) &&
+ !ipv6_addr_equal(&saddr, &geneve->cfg.info.key.u.ipv6.src)) {
+ dst_release(dst);
+ return -EADDRNOTAVAIL;
+ }
+
err = skb_tunnel_check_pmtu(skb, dst,
GENEVE_IPV6_HLEN + info->options_len +
geneve_build_gro_hint_opt(geneve, skb),
@@ -1722,6 +1752,8 @@ static const struct nla_policy geneve_policy[IFLA_GENEVE_MAX + 1] = {
[IFLA_GENEVE_INNER_PROTO_INHERIT] = { .type = NLA_FLAG },
[IFLA_GENEVE_PORT_RANGE] = NLA_POLICY_EXACT_LEN(sizeof(struct ifla_geneve_port_range)),
[IFLA_GENEVE_GRO_HINT] = { .type = NLA_FLAG },
+ [IFLA_GENEVE_LOCAL] = { .type = NLA_BE32 },
+ [IFLA_GENEVE_LOCAL6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
};
static int geneve_validate(struct nlattr *tb[], struct nlattr *data[],
@@ -1781,7 +1813,45 @@ static int geneve_validate(struct nlattr *tb[], struct nlattr *data[],
return 0;
}
+static bool geneve_saddr_wildcard(const struct ip_tunnel_info *info)
+{
+ if (ip_tunnel_info_af(info) == AF_INET) {
+ if (!info->key.u.ipv4.src)
+ return true;
+#if IS_ENABLED(CONFIG_IPV6)
+ } else {
+ if (ipv6_addr_any(&info->key.u.ipv6.src))
+ return true;
+#endif
+ }
+
+ return false;
+}
+
+static bool geneve_saddr_conflict(const struct ip_tunnel_info *a,
+ const struct ip_tunnel_info *b)
+{
+ if (ip_tunnel_info_af(a) != ip_tunnel_info_af(b))
+ return false;
+
+ if (geneve_saddr_wildcard(a) || geneve_saddr_wildcard(b))
+ return true;
+
+ if (ip_tunnel_info_af(a) == AF_INET) {
+ if (a->key.u.ipv4.src == b->key.u.ipv4.src)
+ return true;
+#if IS_ENABLED(CONFIG_IPV6)
+ } else {
+ if (ipv6_addr_equal(&a->key.u.ipv6.src, &b->key.u.ipv6.src))
+ return true;
+#endif
+ }
+
+ return false;
+}
+
static struct geneve_dev *geneve_find_dev(struct geneve_net *gn,
+ const struct geneve_config *cfg,
const struct ip_tunnel_info *info,
bool *tun_on_same_port,
bool *tun_collect_md)
@@ -1791,8 +1861,10 @@ static struct geneve_dev *geneve_find_dev(struct geneve_net *gn,
*tun_on_same_port = false;
*tun_collect_md = false;
list_for_each_entry(geneve, &gn->geneve_list, next) {
- if (info->key.tp_dst == geneve->cfg.info.key.tp_dst) {
- *tun_collect_md = geneve->cfg.collect_md;
+ if (info->key.tp_dst == geneve->cfg.info.key.tp_dst &&
+ (cfg->dualstack || geneve->cfg.dualstack ||
+ geneve_saddr_conflict(info, &geneve->cfg.info))) {
+ *tun_collect_md |= geneve->cfg.collect_md;
*tun_on_same_port = true;
}
if (info->key.tun_id == geneve->cfg.info.key.tun_id &&
@@ -1808,7 +1880,12 @@ static bool is_tnl_info_zero(const struct ip_tunnel_info *info)
return !(info->key.tun_id || info->key.tos ||
!ip_tunnel_flags_empty(info->key.tun_flags) ||
info->key.ttl || info->key.label || info->key.tp_src ||
- memchr_inv(&info->key.u, 0, sizeof(info->key.u)));
+#if IS_ENABLED(CONFIG_IPV6)
+ (ip_tunnel_info_af(info) == AF_INET6 &&
+ !ipv6_addr_any(&info->key.u.ipv6.dst)) ||
+#endif
+ (ip_tunnel_info_af(info) == AF_INET &&
+ info->key.u.ipv4.dst));
}
static bool geneve_dst_addr_equal(struct ip_tunnel_info *a,
@@ -1839,7 +1916,7 @@ static int geneve_configure(struct net *net, struct net_device *dev,
geneve->net = net;
geneve->dev = dev;
- t = geneve_find_dev(gn, info, &tun_on_same_port, &tun_collect_md);
+ t = geneve_find_dev(gn, cfg, info, &tun_on_same_port, &tun_collect_md);
if (t)
return -EBUSY;
@@ -1857,13 +1934,13 @@ static int geneve_configure(struct net *net, struct net_device *dev,
if (cfg->collect_md) {
if (tun_on_same_port) {
NL_SET_ERR_MSG(extack,
- "There can be only one externally controlled device on a destination port");
+ "There can be only one externally controlled device on a destination port and a source address");
return -EPERM;
}
} else {
if (tun_collect_md) {
NL_SET_ERR_MSG(extack,
- "There already exists an externally controlled device on this destination port");
+ "There already exists an externally controlled device on this destination port and the source address");
return -EPERM;
}
}
@@ -1900,9 +1977,20 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[],
struct ip_tunnel_info *info = &cfg->info;
int attrtype;
- if (data[IFLA_GENEVE_REMOTE] && data[IFLA_GENEVE_REMOTE6]) {
+ if (data[IFLA_GENEVE_COLLECT_METADATA]) {
+ if (changelink) {
+ attrtype = IFLA_GENEVE_COLLECT_METADATA;
+ goto change_notsup;
+ }
+
+ cfg->collect_md = true;
+ cfg->dualstack = true;
+ }
+
+ if ((data[IFLA_GENEVE_LOCAL] || data[IFLA_GENEVE_REMOTE]) &&
+ (data[IFLA_GENEVE_LOCAL6] || data[IFLA_GENEVE_REMOTE6])) {
NL_SET_ERR_MSG(extack,
- "Cannot specify both IPv4 and IPv6 Remote addresses");
+ "Cannot specify both IPv4/IPv6 Remote/Local addresses");
return -EINVAL;
}
@@ -1924,6 +2012,8 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[],
if (data[IFLA_GENEVE_REMOTE6]) {
#if IS_ENABLED(CONFIG_IPV6)
+ int addr_type;
+
if (changelink && (ip_tunnel_info_af(info) == AF_INET)) {
attrtype = IFLA_GENEVE_REMOTE6;
goto change_notsup;
@@ -1933,13 +2023,13 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[],
info->key.u.ipv6.dst =
nla_get_in6_addr(data[IFLA_GENEVE_REMOTE6]);
- if (ipv6_addr_type(&info->key.u.ipv6.dst) &
- IPV6_ADDR_LINKLOCAL) {
+ addr_type = ipv6_addr_type(&info->key.u.ipv6.dst);
+ if (addr_type & IPV6_ADDR_LINKLOCAL) {
NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_REMOTE6],
"Remote IPv6 address cannot be link-local");
return -EINVAL;
}
- if (ipv6_addr_is_multicast(&info->key.u.ipv6.dst)) {
+ if (addr_type & IPV6_ADDR_MULTICAST) {
NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_REMOTE6],
"Remote IPv6 address cannot be Multicast");
return -EINVAL;
@@ -1953,6 +2043,65 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[],
#endif
}
+ if (data[IFLA_GENEVE_LOCAL]) {
+ if (changelink) {
+ __be32 src = nla_get_in_addr(data[IFLA_GENEVE_LOCAL]);
+
+ if (ip_tunnel_info_af(info) == AF_INET6 ||
+ src != info->key.u.ipv4.src) {
+ attrtype = IFLA_GENEVE_LOCAL;
+ goto change_notsup;
+ }
+ } else {
+ info->key.u.ipv4.src = nla_get_in_addr(data[IFLA_GENEVE_LOCAL]);
+
+ if (ipv4_is_multicast(info->key.u.ipv4.src)) {
+ NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL],
+ "Local IPv4 address cannot be Multicast");
+ return -EINVAL;
+ }
+
+ cfg->dualstack = false;
+ }
+ }
+
+ if (data[IFLA_GENEVE_LOCAL6]) {
+#if IS_ENABLED(CONFIG_IPV6)
+ if (changelink) {
+ struct in6_addr src = nla_get_in6_addr(data[IFLA_GENEVE_LOCAL6]);
+
+ if (ip_tunnel_info_af(info) == AF_INET ||
+ !ipv6_addr_equal(&src, &info->key.u.ipv6.src)) {
+ attrtype = IFLA_GENEVE_LOCAL6;
+ goto change_notsup;
+ }
+ } else {
+ int addr_type;
+
+ info->mode = IP_TUNNEL_INFO_IPV6;
+ info->key.u.ipv6.src = nla_get_in6_addr(data[IFLA_GENEVE_LOCAL6]);
+
+ addr_type = ipv6_addr_type(&info->key.u.ipv6.src);
+ if (addr_type & IPV6_ADDR_LINKLOCAL) {
+ NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL6],
+ "Local IPv6 address cannot be link-local");
+ return -EINVAL;
+ }
+ if (addr_type & IPV6_ADDR_MULTICAST) {
+ NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL6],
+ "Local IPv6 address cannot be Multicast");
+ return -EINVAL;
+ }
+
+ cfg->dualstack = false;
+ }
+#else
+ NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL6],
+ "IPv6 support not enabled in the kernel");
+ return -EPFNOSUPPORT;
+#endif
+ }
+
if (data[IFLA_GENEVE_ID]) {
__u32 vni;
__u8 tvni[3];
@@ -2017,14 +2166,6 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[],
cfg->port_max = ntohs(p->high);
}
- if (data[IFLA_GENEVE_COLLECT_METADATA]) {
- if (changelink) {
- attrtype = IFLA_GENEVE_COLLECT_METADATA;
- goto change_notsup;
- }
- cfg->collect_md = true;
- }
-
if (data[IFLA_GENEVE_UDP_CSUM]) {
if (changelink) {
attrtype = IFLA_GENEVE_UDP_CSUM;
@@ -2145,6 +2286,7 @@ static int geneve_newlink(struct net_device *dev,
.use_udp6_rx_checksums = false,
.ttl_inherit = false,
.collect_md = false,
+ .dualstack = false,
.port_min = 1,
.port_max = USHRT_MAX,
};
@@ -2253,6 +2395,7 @@ static size_t geneve_get_size(const struct net_device *dev)
{
return nla_total_size(sizeof(__u32)) + /* IFLA_GENEVE_ID */
nla_total_size(sizeof(struct in6_addr)) + /* IFLA_GENEVE_REMOTE{6} */
+ nla_total_size(sizeof(struct in6_addr)) + /* IFLA_GENEVE_LOCAL{6} */
nla_total_size(sizeof(__u8)) + /* IFLA_GENEVE_TTL */
nla_total_size(sizeof(__u8)) + /* IFLA_GENEVE_TOS */
nla_total_size(sizeof(__u8)) + /* IFLA_GENEVE_DF */
@@ -2308,6 +2451,24 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev)
#endif
}
+ if (!geneve->cfg.dualstack) {
+ if (ip_tunnel_info_af(info) == AF_INET) {
+ if ((info->key.u.ipv4.src ||
+ geneve->cfg.collect_md) &&
+ nla_put_in_addr(skb, IFLA_GENEVE_LOCAL,
+ info->key.u.ipv4.src))
+ goto nla_put_failure;
+#if IS_ENABLED(CONFIG_IPV6)
+ } else {
+ if ((!ipv6_addr_any(&info->key.u.ipv6.src) ||
+ geneve->cfg.collect_md) &&
+ nla_put_in6_addr(skb, IFLA_GENEVE_LOCAL6,
+ &info->key.u.ipv6.src))
+ goto nla_put_failure;
+#endif
+ }
+ }
+
if (nla_put_u8(skb, IFLA_GENEVE_TTL, info->key.ttl) ||
nla_put_u8(skb, IFLA_GENEVE_TOS, info->key.tos) ||
nla_put_be32(skb, IFLA_GENEVE_LABEL, info->key.label))
@@ -2374,6 +2535,7 @@ struct net_device *geneve_dev_create_fb(struct net *net, const char *name,
.use_udp6_rx_checksums = true,
.ttl_inherit = false,
.collect_md = true,
+ .dualstack = true,
.port_min = 1,
.port_max = USHRT_MAX,
};
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 46413392b402..363526549a01 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -1506,6 +1506,8 @@ enum {
IFLA_GENEVE_INNER_PROTO_INHERIT,
IFLA_GENEVE_PORT_RANGE,
IFLA_GENEVE_GRO_HINT,
+ IFLA_GENEVE_LOCAL,
+ IFLA_GENEVE_LOCAL6,
__IFLA_GENEVE_MAX
};
#define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1)