]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
geneve: Introduce IFLA_GENEVE_LOCAL and IFLA_GENEVE_LOCAL6.
authorKuniyuki Iwashima <kuniyu@google.com>
Tue, 2 Jun 2026 19:03:45 +0000 (19:03 +0000)
committerJakub Kicinski <kuba@kernel.org>
Thu, 4 Jun 2026 01:07:41 +0000 (18:07 -0700)
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.

Let's introduce new options, IFLA_GENEVE_LOCAL and IFLA_GENEVE_LOCAL6,
to allow specifying a local IPv4/IPv6 address for the backend UDP
socket.

By default, when collect metadata mode (IFLA_GENEVE_COLLECT_METADATA)
is enabled, both IPv4 and IPv6 sockets are created.  However, if a
source address is specified via the new attributes, only a single
socket corresponding to that specific address family is created.

Accordingly, geneve_find_sock() and geneve_find_dev() are updated to
take the source address into account, ensuring that multiple devices
and sockets configured with different source addresses can coexist
without conflict.

In addition, the source address is validated in geneve_xmit_skb()
and geneve6_xmit_skb(), so the BPF prog must set it in bpf_tunnel_key.

With this change, multiple GENEVE devices can be successfully created
and bound to their respective local IP addresses:

  (*) "local" is the keyword for IFLA_GENEVE_LOCAL / IFLA_GENEVE_LOCAL6

  # for i in $(seq 1 2);
  do
          ip link add geneve4_${i} type geneve local 192.168.0.${i} external
          ip addr add 192.168.0.${i}/24 dev geneve4_${i}
          ip link set geneve4_${i} up

          ip link add geneve6_${i} type geneve local 2001:9292::${i} external
          ip addr add 2001:9292::${i}/64 dev geneve6_${i} nodad
          ip link set geneve6_${i} up
  done

  # ip -d l | grep geneve
  9: geneve4_1: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
      geneve external id 0 local 192.168.0.1 ...
  10: geneve6_1: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
      geneve external id 0 local 2001:9292::1 ...
  11: geneve4_2: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
      geneve external id 0 local 192.168.0.2 ...
  12: geneve6_2: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
      geneve external id 0 local 2001:9292::2 ...

  # ss -ua | grep geneve
  UNCONN 0      0         192.168.0.2:geneve      0.0.0.0:*
  UNCONN 0      0         192.168.0.1:geneve      0.0.0.0:*
  UNCONN 0      0      [2001:9292::2]:geneve            *:*
  UNCONN 0      0      [2001:9292::1]:geneve            *:*

Note that even if the local address is explicitly configured with
the wildcard address, kernel does not dump it except for devices with
IFLA_GENEVE_COLLECT_METADATA.  This is consistent with the behaviour
of is_tnl_info_zero(), which treats the wildcard remote address as not
configured.

  ## ynl example.
  # ./tools/net/ynl/pyynl/cli.py \
    --spec ./Documentation/netlink/specs/rt-link.yaml \
    --do newlink --create \
    --json '{"ifname": "geneve0",
             "linkinfo": {"kind":"geneve",
                  "data": {"local": "0.0.0.0",
           "collect-metadata": true}}}'

  # ./tools/net/ynl/pyynl/cli.py \
    --spec ./Documentation/netlink/specs/rt-link.yaml \
    --do getlink \
    --json '{"ifname": "geneve0"}' --output-json | \
    jq .linkinfo.data.local
  "0.0.0.0"

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20260602190436.139591-6-kuniyu@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Documentation/netlink/specs/rt-link.yaml
drivers/net/geneve.c
include/uapi/linux/if_link.h

index a5c0297818c623bd5172c14a167cf0e13c1bcad5..7f8e3ad3a40562198488e4b9b08346e5e5e3b04d 100644 (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-
index 3a62d132a8c4a4d9d22417d4e812eb8c49e355cb..da17b4f6fda502f21c3be5fcbb7a30526a1802e3 100644 (file)
@@ -775,9 +775,10 @@ static struct sock *geneve_create_sock(struct net *net,
                udp_conf.family = AF_INET6;
                udp_conf.ipv6_v6only = 1;
                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 = info->key.tp_dst;
@@ -1061,6 +1062,16 @@ static struct geneve_sock *geneve_find_sock(struct net *net,
                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;
        }
 
@@ -1327,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),
@@ -1438,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),
@@ -1729,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[],
@@ -1788,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)
@@ -1798,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 &&
@@ -1815,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,
@@ -1846,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;
 
@@ -1864,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;
                }
        }
@@ -1917,9 +1987,10 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[],
                cfg->dualstack = true;
        }
 
-       if (data[IFLA_GENEVE_REMOTE] && data[IFLA_GENEVE_REMOTE6]) {
+       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;
        }
 
@@ -1972,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];
@@ -2265,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 */
@@ -2320,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))
index 46413392b402cba3d970eec56adb25250e3a9e17..363526549a01cb1d956f2a8d337a22c10975e53a 100644 (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)