2 Commits
patch ... main

Author SHA1 Message Date
tuna2134
249cee8375 fix 2026-07-11 12:42:36 +09:00
tuna2134
3eee844deb fix 2026-07-11 12:36:38 +09:00
2 changed files with 137 additions and 54 deletions

View File

@@ -37,9 +37,9 @@ sudo ip link set eip0 up
sudo ip addr add 192.0.2.1/30 dev eip0 sudo ip addr add 192.0.2.1/30 dev eip0
``` ```
`eip0` は TCP/UDP/SCTP の software GSO に対応します。GSO パケットは内側 TCP SYN に MSS オプションがある場合は、外側 IPv6 経路 MTU とトンネル MTU
Ethernet フレームとしてセグメント化してから、それぞれを EtherIP でカプセル化 小さい方に収まるよう、送信時に MSS を自動的に縮小します。IPv4/IPv6、
します 802.1Q/802.1ad VLAN、IPv6 拡張ヘッダーに対応し、既に小さい MSS は変更しません
## MTU と IPv6 フラグメント ## MTU と IPv6 フラグメント
@@ -67,12 +67,8 @@ sudo sysctl -w net.ipv6.ip6frag_time=10
この設定は経路上でのフラグメント欠落を防ぐものではありません。フラグメントを この設定は経路上でのフラグメント欠落を防ぐものではありません。フラグメントを
避けるには、外側インターフェースと経路の MTU を 1556 以上にするか、`eip0` 避けるには、外側インターフェースと経路の MTU を 1556 以上にするか、`eip0`
MTU を外側 MTU から 56 引いた値以下(外側 MTU 1500 なら 1444 以下)に設定して MTU を外側 MTU から 56 引いた値以下(外側 MTU 1500 なら 1444 以下)に設定して
ください。 ください。TCP は上記の MSS clamping でフラグメントを回避できますが、TCP 以外の
Ethernet フレームには適用されません。
一方、`eip0` の MTU は最大 9000 に設定できます。外側経路の MTU より大きい
非 GSO パケット、および GSO の各セグメントは、外側 IPv6 でフラグメント化して
送信されます。したがって MTU 9000 のフレームも転送できますが、経路上で IPv6
フラグメントが欠落しないことと、受信側に十分な再構成キューが必要です。
## トンネルの削除 ## トンネルの削除

View File

@@ -2,7 +2,9 @@
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/if_link.h> #include <linux/if_link.h>
#include <linux/if_vlan.h>
#include <linux/in6.h> #include <linux/in6.h>
#include <linux/ip.h>
#include <linux/ipv6.h> #include <linux/ipv6.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h> #include <linux/list.h>
@@ -13,13 +15,17 @@
#include <linux/rculist.h> #include <linux/rculist.h>
#include <linux/rtnetlink.h> #include <linux/rtnetlink.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/tcp.h>
#include <linux/unaligned.h>
#include <net/checksum.h>
#include <net/ip.h>
#include <net/ip6_route.h> #include <net/ip6_route.h>
#include <net/ipv6.h> #include <net/ipv6.h>
#include <net/net_namespace.h> #include <net/net_namespace.h>
#include <net/netns/generic.h> #include <net/netns/generic.h>
#include <net/gso.h>
#include <net/protocol.h> #include <net/protocol.h>
#include <net/rtnetlink.h> #include <net/rtnetlink.h>
#include <net/tcp.h>
#include "etherip6_uapi.h" #include "etherip6_uapi.h"
@@ -32,7 +38,7 @@
#define ETHERIP6_HLEN 2 #define ETHERIP6_HLEN 2
#define ETHERIP6_DEFAULT_HOP_LIMIT 64 #define ETHERIP6_DEFAULT_HOP_LIMIT 64
#define ETHERIP6_MAX_MTU 9000 #define ETHERIP6_MAX_MTU 9000
#define ETHERIP6_NEEDED_HEADROOM (sizeof(struct ipv6hdr) + ETHERIP6_HLEN) #define ETHERIP6_OUTER_HLEN (sizeof(struct ipv6hdr) + ETHERIP6_HLEN)
struct etherip6_tunnel { struct etherip6_tunnel {
struct list_head list; struct list_head list;
@@ -141,7 +147,123 @@ static struct etherip6_tunnel *etherip6_lookup_rx(struct net *net,
return etherip6_lookup_unique_rx(net, local, remote, iif, false, false); return etherip6_lookup_unique_rx(net, local, remote, iif, false, false);
} }
static void etherip6_xmit_one(struct sk_buff *skb, struct net_device *dev) /*
* Reduce an advertised MSS only when the encapsulated SYN would otherwise
* exceed the smaller of the tunnel MTU and the current outer path MTU.
* skb_header_pointer() keeps the common linear-skb path allocation-free while
* still handling cloned and non-linear packets correctly.
*/
static void etherip6_clamp_tcp_mss(struct sk_buff *skb, unsigned int path_mtu)
{
struct vlan_hdr vlan_buf, *vh;
struct tcphdr tcp_buf, *th;
struct ethhdr eth_buf, *eth;
unsigned int nhoff = ETH_HLEN;
unsigned int thoff, tcp_hlen;
unsigned int inner_mtu, ip_hlen;
unsigned char opt_buf[MAX_TCP_OPTION_SPACE], *opt;
unsigned int optlen;
unsigned int mss_offset;
__be16 proto;
u16 old_mss, new_mss;
u8 nexthdr;
eth = skb_header_pointer(skb, 0, sizeof(eth_buf), &eth_buf);
if (unlikely(!eth))
return;
proto = eth->h_proto;
while (eth_type_vlan(proto)) {
vh = skb_header_pointer(skb, nhoff, sizeof(vlan_buf), &vlan_buf);
if (unlikely(!vh))
return;
proto = vh->h_vlan_encapsulated_proto;
nhoff += sizeof(*vh);
}
if (unlikely(path_mtu <= ETHERIP6_OUTER_HLEN + nhoff))
return;
inner_mtu = min_t(unsigned int, skb->dev->mtu,
path_mtu - ETHERIP6_OUTER_HLEN - nhoff);
if (proto == htons(ETH_P_IP)) {
struct iphdr ip_buf, *iph;
iph = skb_header_pointer(skb, nhoff, sizeof(ip_buf), &ip_buf);
if (!iph || iph->version != 4 || iph->ihl < 5 ||
iph->protocol != IPPROTO_TCP ||
(iph->frag_off & htons(IP_MF | IP_OFFSET)))
return;
thoff = nhoff + iph->ihl * 4;
ip_hlen = sizeof(struct iphdr);
} else if (proto == htons(ETH_P_IPV6)) {
struct ipv6hdr ip6_buf, *ip6h;
__be16 frag_off = 0;
int offset;
ip6h = skb_header_pointer(skb, nhoff, sizeof(ip6_buf), &ip6_buf);
if (!ip6h || ip6h->version != 6)
return;
nexthdr = ip6h->nexthdr;
offset = ipv6_skip_exthdr(skb, nhoff + sizeof(*ip6h),
&nexthdr, &frag_off);
if (offset < 0 || nexthdr != IPPROTO_TCP || frag_off)
return;
thoff = offset;
ip_hlen = sizeof(struct ipv6hdr);
} else {
return;
}
th = skb_header_pointer(skb, thoff, sizeof(tcp_buf), &tcp_buf);
if (!th || !th->syn || th->doff < sizeof(*th) / 4)
return;
tcp_hlen = th->doff * 4;
if (tcp_hlen > MAX_TCP_HEADER ||
inner_mtu <= ip_hlen + sizeof(struct tcphdr))
return;
new_mss = min_t(unsigned int, U16_MAX,
inner_mtu - ip_hlen - sizeof(struct tcphdr));
optlen = tcp_hlen - sizeof(*th);
opt = skb_header_pointer(skb, thoff + sizeof(*th), optlen, opt_buf);
if (!opt)
return;
while (optlen) {
u8 kind = opt[0];
u8 len;
if (kind == TCPOPT_EOL)
return;
if (kind == TCPOPT_NOP) {
opt++;
optlen--;
continue;
}
if (optlen < 2 || (len = opt[1]) < 2 || len > optlen)
return;
if (kind == TCPOPT_MSS && len == TCPOLEN_MSS) {
old_mss = get_unaligned_be16(opt + 2);
if (old_mss > new_mss) {
mss_offset = thoff + tcp_hlen - optlen + 2;
if (skb_ensure_writable(skb, mss_offset + 2))
return;
th = (struct tcphdr *)(skb->data + thoff);
put_unaligned_be16(new_mss,
skb->data + mss_offset);
inet_proto_csum_replace2(&th->check, skb,
htons(old_mss), htons(new_mss),
false);
}
return;
}
opt += len;
optlen -= len;
}
}
static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev)
{ {
struct etherip6_tunnel *tun = netdev_priv(dev); struct etherip6_tunnel *tun = netdev_priv(dev);
struct net *net = dev_net(dev); struct net *net = dev_net(dev);
@@ -169,6 +291,8 @@ static void etherip6_xmit_one(struct sk_buff *skb, struct net_device *dev)
goto tx_error; goto tx_error;
} }
etherip6_clamp_tcp_mss(skb, dst_mtu(dst));
headroom = LL_RESERVED_SPACE(dst->dev) + sizeof(*ip6h) + ETHERIP6_HLEN; headroom = LL_RESERVED_SPACE(dst->dev) + sizeof(*ip6h) + ETHERIP6_HLEN;
err = skb_cow_head(skb, headroom); err = skb_cow_head(skb, headroom);
if (err) { if (err) {
@@ -209,7 +333,7 @@ static void etherip6_xmit_one(struct sk_buff *skb, struct net_device *dev)
err = ip6_local_out(net, NULL, skb); err = ip6_local_out(net, NULL, skb);
if (unlikely(net_xmit_eval(err))) { if (unlikely(net_xmit_eval(err))) {
atomic_long_inc(&tun->tx_dropped); atomic_long_inc(&tun->tx_dropped);
return; return NETDEV_TX_OK;
} }
stats = this_cpu_ptr(tun->stats); stats = this_cpu_ptr(tun->stats);
@@ -218,45 +342,11 @@ static void etherip6_xmit_one(struct sk_buff *skb, struct net_device *dev)
u64_stats_add(&stats->tx_bytes, inner_len); u64_stats_add(&stats->tx_bytes, inner_len);
u64_stats_update_end(&stats->syncp); u64_stats_update_end(&stats->syncp);
return; return NETDEV_TX_OK;
tx_error: tx_error:
atomic_long_inc(&tun->tx_dropped); atomic_long_inc(&tun->tx_dropped);
dev_kfree_skb(skb); dev_kfree_skb(skb);
return;
}
static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct etherip6_tunnel *tun = netdev_priv(dev);
struct sk_buff *segs, *nskb;
if (!skb_is_gso(skb)) {
etherip6_xmit_one(skb, dev);
return NETDEV_TX_OK;
}
/*
* EtherIP has no hardware GSO type. Segment the inner Ethernet frame
* before adding the EtherIP and outer IPv6 headers. Each resulting skb
* is then a normal IPv6 packet and can be fragmented by ip6_local_out()
* when the outer path MTU is smaller than the tunnel MTU.
*/
segs = skb_gso_segment(skb, 0);
if (IS_ERR_OR_NULL(segs)) {
atomic_long_inc(&tun->tx_dropped);
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
consume_skb(skb);
while (segs) {
nskb = segs;
segs = segs->next;
nskb->next = NULL;
etherip6_xmit_one(nskb, dev);
}
return NETDEV_TX_OK; return NETDEV_TX_OK;
} }
@@ -299,12 +389,9 @@ static void etherip6_setup(struct net_device *dev)
dev->needs_free_netdev = true; dev->needs_free_netdev = true;
dev->type = ARPHRD_ETHER; dev->type = ARPHRD_ETHER;
dev->flags &= ~IFF_NOARP; dev->flags &= ~IFF_NOARP;
dev->features |= NETIF_F_SG | NETIF_F_HW_CSUM | dev->features &= ~(NETIF_F_GSO_MASK | NETIF_F_CSUM_MASK);
NETIF_F_GSO_SOFTWARE; dev->hw_features = 0;
dev->hw_features |= NETIF_F_SG | NETIF_F_HW_CSUM | dev->vlan_features = 0;
NETIF_F_GSO_SOFTWARE;
dev->vlan_features = dev->features;
dev->needed_headroom = ETHERIP6_NEEDED_HEADROOM;
dev->min_mtu = ETH_MIN_MTU; dev->min_mtu = ETH_MIN_MTU;
dev->max_mtu = ETHERIP6_MAX_MTU; dev->max_mtu = ETHERIP6_MAX_MTU;
eth_hw_addr_random(dev); eth_hw_addr_random(dev);