[PATCH net-next] vxlan: add IFLA_VXLAN_IGNORE_DF

From: Daniel Golle

Date: Wed Sep 02 2026 - 07:40:09 EST


A vxlan whose underlay cannot carry the encapsulated frame answers the
sender with a path MTU message and drops it. That is the right default,
but it leaves no way to extend a segment across an underlay that is
merely smaller: every host behind the tunnel then learns a path MTU that
the hosts beside it do not have, so once extended the resulting MTU can
be too small to carry any IPv6 at all (<1280).

It also fails outright where the path forwards whole datagrams but
discards IP fragments, which is common on carrier NAT. There the
fragmenting has to happen inside the encapsulation to survive, and the
tunnel refuses to do it.

Add IFLA_VXLAN_IGNORE_DF, off by default, mirroring IFLA_GRE_IGNORE_DF:
carry the frame and let IP fragment the outer packet rather than report
the path MTU and drop it.

On IPv4 this means never setting DF. On IPv6 there is no DF bit to
clear, and ip6_fragment() refuses a packet that is not from a local
socket unless skb->ignore_df is set, so set it there. Without that the
existing df attribute has no expression on IPv6 at all: an oversized
frame is dropped whatever it is set to.

Signed-off-by: Daniel Golle <daniel@xxxxxxxxxxxxxx>
---
drivers/net/vxlan/vxlan_core.c | 20 +++++++++++++++++---
include/net/vxlan.h | 1 +
include/uapi/linux/if_link.h | 1 +
3 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 459f19f7071e..21cb7fbfb6e6 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -2510,7 +2510,9 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
if (err)
goto out_unlock;

- if (vxlan->cfg.df == VXLAN_DF_SET) {
+ if (vxlan->cfg.ignore_df) {
+ df = 0;
+ } else if (vxlan->cfg.df == VXLAN_DF_SET) {
df = htons(IP_DF);
} else if (vxlan->cfg.df == VXLAN_DF_INHERIT) {
struct ethhdr *eth = eth_hdr(skb);
@@ -2526,7 +2528,9 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
}

ndst = &rt->dst;
- err = skb_tunnel_check_pmtu(skb, ndst, vxlan_headroom(flags & VXLAN_F_GPE),
+ err = vxlan->cfg.ignore_df ? 0 :
+ skb_tunnel_check_pmtu(skb, ndst,
+ vxlan_headroom(flags & VXLAN_F_GPE),
netif_is_any_bridge_port(dev));
if (err < 0) {
goto tx_error;
@@ -2598,7 +2602,8 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
goto out_unlock;
}

- err = skb_tunnel_check_pmtu(skb, ndst,
+ err = vxlan->cfg.ignore_df ? 0 :
+ skb_tunnel_check_pmtu(skb, ndst,
vxlan_headroom((flags & VXLAN_F_GPE) | VXLAN_F_IPV6),
netif_is_any_bridge_port(dev));
if (err < 0) {
@@ -2630,6 +2635,9 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev,
goto tx_error;
}

+ if (vxlan->cfg.ignore_df)
+ skb->ignore_df = 1;
+
udp_tunnel6_xmit_skb(ndst, sock6->sk, skb, dev,
&saddr, &pkey->u.ipv6.dst, tos, ttl,
pkey->label, src_port, dst_port, !udp_sum,
@@ -3453,6 +3461,7 @@ static const struct nla_policy vxlan_policy[IFLA_VXLAN_MAX + 1] = {
[IFLA_VXLAN_REMCSUM_NOPARTIAL] = { .type = NLA_FLAG },
[IFLA_VXLAN_TTL_INHERIT] = { .type = NLA_FLAG },
[IFLA_VXLAN_DF] = { .type = NLA_U8 },
+ [IFLA_VXLAN_IGNORE_DF] = { .type = NLA_U8 },
[IFLA_VXLAN_VNIFILTER] = { .type = NLA_U8 },
[IFLA_VXLAN_LOCALBYPASS] = NLA_POLICY_MAX(NLA_U8, 1),
[IFLA_VXLAN_LABEL_POLICY] = NLA_POLICY_MAX(NLA_U32, VXLAN_LABEL_MAX),
@@ -4391,6 +4400,9 @@ static int vxlan_nl2conf(struct nlattr *tb[], struct nlattr *data[],
if (data[IFLA_VXLAN_DF])
conf->df = nla_get_u8(data[IFLA_VXLAN_DF]);

+ if (data[IFLA_VXLAN_IGNORE_DF])
+ conf->ignore_df = nla_get_u8(data[IFLA_VXLAN_IGNORE_DF]);
+
if (data[IFLA_VXLAN_VNIFILTER]) {
err = vxlan_nl2flag(conf, data, IFLA_VXLAN_VNIFILTER,
VXLAN_F_VNIFILTER, changelink, false,
@@ -4547,6 +4559,7 @@ static size_t vxlan_get_size(const struct net_device *dev)
nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_TTL_INHERIT */
nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_TOS */
nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_DF */
+ nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_IGNORE_DF */
nla_total_size(sizeof(__be32)) + /* IFLA_VXLAN_LABEL */
nla_total_size(sizeof(__u32)) + /* IFLA_VXLAN_LABEL_POLICY */
nla_total_size(sizeof(__u8)) + /* IFLA_VXLAN_LEARNING */
@@ -4623,6 +4636,7 @@ static int vxlan_fill_info(struct sk_buff *skb, const struct net_device *dev)
!!(vxlan->cfg.flags & VXLAN_F_TTL_INHERIT)) ||
nla_put_u8(skb, IFLA_VXLAN_TOS, vxlan->cfg.tos) ||
nla_put_u8(skb, IFLA_VXLAN_DF, vxlan->cfg.df) ||
+ nla_put_u8(skb, IFLA_VXLAN_IGNORE_DF, vxlan->cfg.ignore_df) ||
nla_put_be32(skb, IFLA_VXLAN_LABEL, vxlan->cfg.label) ||
nla_put_u32(skb, IFLA_VXLAN_LABEL_POLICY, vxlan->cfg.label_policy) ||
nla_put_u8(skb, IFLA_VXLAN_LEARNING,
diff --git a/include/net/vxlan.h b/include/net/vxlan.h
index 7b8207505523..85690b817e0e 100644
--- a/include/net/vxlan.h
+++ b/include/net/vxlan.h
@@ -228,6 +228,7 @@ struct vxlan_config {
unsigned int addrmax;
bool no_share;
enum ifla_vxlan_df df;
+ bool ignore_df;
struct vxlanhdr reserved_bits;
};

diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 43cecca49f01..5a2d3a92941a 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -1463,6 +1463,7 @@ enum {
IFLA_VXLAN_LABEL_POLICY, /* IPv6 flow label policy; ifla_vxlan_label_policy */
IFLA_VXLAN_RESERVED_BITS,
IFLA_VXLAN_MC_ROUTE,
+ IFLA_VXLAN_IGNORE_DF,
__IFLA_VXLAN_MAX
};
#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1)
--
2.55.0