Compare commits

6 Commits

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
5ac8f37aca Merge pull request 'mss' (#1) from mss into main
Reviewed-on: #1
2026-07-06 14:10:04 +00:00
tuna2134
1333e033bb fix 2026-07-06 23:02:49 +09:00
tuna2134
b6240ba235 fix 2026-07-06 22:51:20 +09:00
tuna2134
5fa3637a5b fix 2026-07-06 22:50:56 +09:00
2 changed files with 81 additions and 34 deletions

View File

@@ -37,9 +37,38 @@ sudo ip link set eip0 up
sudo ip addr add 192.0.2.1/30 dev eip0
```
TCP SYN に MSS オプションがある場合、外側 IPv6 経路の MTU を超えないよう、
送信時に IPv4/IPv6 の MSS を自動的に縮小します。802.1Q/802.1ad VLAN と IPv6
拡張ヘッダーに対応します。既に十分小さい MSS は変更しません。
TCP SYN に MSS オプションがある場合、外側 IPv6 経路 MTU とトンネル MTU の
小さい方に収まるよう、送信時に MSS を自動的に縮小します。IPv4/IPv6
802.1Q/802.1ad VLAN、IPv6 拡張ヘッダーに対応し、既に小さい MSS は変更しません。
## MTU と IPv6 フラグメント
EtherIP over IPv6 では、内側 Ethernet ヘッダー 14 bytes、EtherIP ヘッダー
2 bytes、外側 IPv6 ヘッダー 40 bytes の合計 56 bytes が追加されます。そのため、
`eip0` と外側インターフェースの MTU がともに 1500 の場合、最大サイズの内側
フレームは外側で 1556 bytes となり、IPv6 フラグメントが必ず発生します。
フラグメントの一部が欠落した場合、受信側ではパケット全体を再構成できません。
再構成の状態は次のコマンドで確認できます。
```sh
nstat -az | grep Ip6Reasm
```
`Ip6ReasmFails` または `Ip6ReasmTimeout` が転送中に増える場合は、フラグメントの
欠落、または受信側の再構成キュー不足が発生しています。再構成キュー不足に対する
緩和策として、受信側で次の値を設定できます。
```sh
sudo sysctl -w net.ipv6.ip6frag_high_thresh=268435456
sudo sysctl -w net.ipv6.ip6frag_time=10
```
この設定は経路上でのフラグメント欠落を防ぐものではありません。フラグメントを
避けるには、外側インターフェースと経路の MTU を 1556 以上にするか、`eip0`
MTU を外側 MTU から 56 引いた値以下(外側 MTU 1500 なら 1444 以下)に設定して
ください。TCP は上記の MSS clamping でフラグメントを回避できますが、TCP 以外の
Ethernet フレームには適用されません。
## トンネルの削除

View File

@@ -2,10 +2,10 @@
#include <linux/etherdevice.h>
#include <linux/if_ether.h>
#include <linux/if_link.h>
#include <linux/if_vlan.h>
#include <linux/in6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/if_vlan.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
@@ -38,8 +38,6 @@
#define ETHERIP6_HLEN 2
#define ETHERIP6_DEFAULT_HOP_LIMIT 64
#define ETHERIP6_MAX_MTU 9000
/* IPv6 + EtherIP headers added outside the encapsulated Ethernet frame. */
#define ETHERIP6_OUTER_HLEN (sizeof(struct ipv6hdr) + ETHERIP6_HLEN)
struct etherip6_tunnel {
@@ -149,55 +147,61 @@ static struct etherip6_tunnel *etherip6_lookup_rx(struct net *net,
return etherip6_lookup_unique_rx(net, local, remote, iif, false, false);
}
/*
* 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 _vh, *vh;
struct tcphdr _th, *th;
struct ethhdr _eth, *eth;
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;
unsigned int min_ip_hlen;
unsigned char *opt;
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), &_eth);
if (!eth)
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(_vh), &_vh);
if (!vh)
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 (path_mtu <= ETHERIP6_OUTER_HLEN + nhoff)
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 _iph, *iph;
struct iphdr ip_buf, *iph;
iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_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;
min_ip_hlen = sizeof(struct iphdr);
ip_hlen = sizeof(struct iphdr);
} else if (proto == htons(ETH_P_IPV6)) {
struct ipv6hdr _ip6h, *ip6h;
struct ipv6hdr ip6_buf, *ip6h;
__be16 frag_off = 0;
int offset;
ip6h = skb_header_pointer(skb, nhoff, sizeof(_ip6h), &_ip6h);
ip6h = skb_header_pointer(skb, nhoff, sizeof(ip6_buf), &ip6_buf);
if (!ip6h || ip6h->version != 6)
return;
nexthdr = ip6h->nexthdr;
@@ -206,25 +210,25 @@ static void etherip6_clamp_tcp_mss(struct sk_buff *skb, unsigned int path_mtu)
if (offset < 0 || nexthdr != IPPROTO_TCP || frag_off)
return;
thoff = offset;
min_ip_hlen = sizeof(struct ipv6hdr);
ip_hlen = sizeof(struct ipv6hdr);
} else {
return;
}
th = skb_header_pointer(skb, thoff, sizeof(_th), &_th);
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 (inner_mtu <= min_ip_hlen + sizeof(*th))
if (tcp_hlen > MAX_TCP_HEADER ||
inner_mtu <= ip_hlen + sizeof(struct tcphdr))
return;
new_mss = min_t(unsigned int, U16_MAX,
inner_mtu - min_ip_hlen - sizeof(*th));
inner_mtu - ip_hlen - sizeof(struct tcphdr));
if (skb_ensure_writable(skb, thoff + tcp_hlen))
return;
th = (struct tcphdr *)(skb->data + thoff);
opt = (unsigned char *)(th + 1);
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];
@@ -242,9 +246,15 @@ static void etherip6_clamp_tcp_mss(struct sk_buff *skb, unsigned int path_mtu)
if (kind == TCPOPT_MSS && len == TCPOLEN_MSS) {
old_mss = get_unaligned_be16(opt + 2);
if (old_mss > new_mss) {
put_unaligned_be16(new_mss, opt + 2);
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);
htons(old_mss), htons(new_mss),
false);
}
return;
}
@@ -263,6 +273,7 @@ static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev)
struct pcpu_sw_netstats *stats;
__be16 *etherip;
unsigned int payload_len;
unsigned int inner_len;
int headroom;
int err;
@@ -290,6 +301,7 @@ static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev)
}
payload_len = skb->len + ETHERIP6_HLEN;
inner_len = skb->len;
etherip = skb_push(skb, ETHERIP6_HLEN);
*etherip = ETHERIP6_HDR;
@@ -318,13 +330,19 @@ static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev)
*/
memset(skb->cb, 0, sizeof(struct inet6_skb_parm));
err = ip6_local_out(net, NULL, skb);
if (unlikely(net_xmit_eval(err))) {
atomic_long_inc(&tun->tx_dropped);
return NETDEV_TX_OK;
}
stats = this_cpu_ptr(tun->stats);
u64_stats_update_begin(&stats->syncp);
u64_stats_inc(&stats->tx_packets);
u64_stats_add(&stats->tx_bytes, payload_len - ETHERIP6_HLEN);
u64_stats_add(&stats->tx_bytes, inner_len);
u64_stats_update_end(&stats->syncp);
return ip6_local_out(net, NULL, skb) == 0 ? NETDEV_TX_OK : NETDEV_TX_OK;
return NETDEV_TX_OK;
tx_error:
atomic_long_inc(&tun->tx_dropped);