This commit is contained in:
tuna2134
2026-07-11 12:27:50 +09:00
parent 5ac8f37aca
commit fccfaf6249
2 changed files with 54 additions and 127 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
``` ```
TCP SYN に MSS オプションがある場合、外側 IPv6 経路の MTU を超えないよう、 `eip0` は TCP/UDP/SCTP の software GSO に対応します。GSO パケットは内側の
送信時に IPv4/IPv6 の MSS を自動的に縮小します。802.1Q/802.1ad VLAN と IPv6 Ethernet フレームとしてセグメント化してから、それぞれを EtherIP でカプセル化
拡張ヘッダーにも対応します。既に十分小さい MSS は変更しません します
## MTU と IPv6 フラグメント ## MTU と IPv6 フラグメント
@@ -67,8 +67,12 @@ 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 の自動縮小で回避できる場合がありますが、任意の ください。
Ethernet フレームには適用できません。
一方、`eip0` の MTU は最大 9000 に設定できます。外側経路の MTU より大きい
非 GSO パケット、および GSO の各セグメントは、外側 IPv6 でフラグメント化して
送信されます。したがって MTU 9000 のフレームも転送できますが、経路上で IPv6
フラグメントが欠落しないことと、受信側に十分な再構成キューが必要です。
## トンネルの削除 ## トンネルの削除

View File

@@ -3,9 +3,7 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/if_link.h> #include <linux/if_link.h>
#include <linux/in6.h> #include <linux/in6.h>
#include <linux/ip.h>
#include <linux/ipv6.h> #include <linux/ipv6.h>
#include <linux/if_vlan.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/module.h> #include <linux/module.h>
@@ -15,17 +13,13 @@
#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"
@@ -38,9 +32,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)
/* IPv6 + EtherIP headers added outside the encapsulated Ethernet frame. */
#define ETHERIP6_OUTER_HLEN (sizeof(struct ipv6hdr) + ETHERIP6_HLEN)
struct etherip6_tunnel { struct etherip6_tunnel {
struct list_head list; struct list_head list;
@@ -149,111 +141,7 @@ 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_clamp_tcp_mss(struct sk_buff *skb, unsigned int path_mtu) static void etherip6_xmit_one(struct sk_buff *skb, struct net_device *dev)
{
struct vlan_hdr _vh, *vh;
struct tcphdr _th, *th;
struct ethhdr _eth, *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 optlen;
__be16 proto;
u16 old_mss, new_mss;
u8 nexthdr;
eth = skb_header_pointer(skb, 0, sizeof(_eth), &_eth);
if (!eth)
return;
proto = eth->h_proto;
while (eth_type_vlan(proto)) {
vh = skb_header_pointer(skb, nhoff, sizeof(_vh), &_vh);
if (!vh)
return;
proto = vh->h_vlan_encapsulated_proto;
nhoff += sizeof(*vh);
}
if (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;
iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph);
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);
} else if (proto == htons(ETH_P_IPV6)) {
struct ipv6hdr _ip6h, *ip6h;
__be16 frag_off = 0;
int offset;
ip6h = skb_header_pointer(skb, nhoff, sizeof(_ip6h), &_ip6h);
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;
min_ip_hlen = sizeof(struct ipv6hdr);
} else {
return;
}
th = skb_header_pointer(skb, thoff, sizeof(_th), &_th);
if (!th || !th->syn || th->doff < sizeof(*th) / 4)
return;
tcp_hlen = th->doff * 4;
if (inner_mtu <= min_ip_hlen + sizeof(*th))
return;
new_mss = min_t(unsigned int, U16_MAX,
inner_mtu - min_ip_hlen - sizeof(*th));
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);
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) {
put_unaligned_be16(new_mss, opt + 2);
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);
@@ -281,8 +169,6 @@ static netdev_tx_t etherip6_xmit(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) {
@@ -323,7 +209,7 @@ static netdev_tx_t etherip6_xmit(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 NETDEV_TX_OK; return;
} }
stats = this_cpu_ptr(tun->stats); stats = this_cpu_ptr(tun->stats);
@@ -332,11 +218,45 @@ static netdev_tx_t etherip6_xmit(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 NETDEV_TX_OK; return;
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;
} }
@@ -379,9 +299,12 @@ 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_GSO_MASK | NETIF_F_CSUM_MASK); dev->features |= NETIF_F_SG | NETIF_F_HW_CSUM |
dev->hw_features = 0; NETIF_F_GSO_SOFTWARE;
dev->vlan_features = 0; dev->hw_features |= NETIF_F_SG | NETIF_F_HW_CSUM |
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);