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>
This commit is contained in:
Jakub Kicinski 2026-06-03 18:07:44 -07:00
commit 1de680c1c4
3 changed files with 219 additions and 46 deletions

View File

@ -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-

View File

@ -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 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6)
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,
};

View File

@ -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)