Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce3fd3cc28 | ||
|
|
20cf4124c8 |
5
Makefile
5
Makefile
@@ -10,6 +10,11 @@ all: module tools
|
||||
module:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) modules
|
||||
|
||||
tools: etherip6ctl
|
||||
|
||||
etherip6ctl: etherip6ctl.c etherip6_uapi.h
|
||||
$(CC) $(CFLAGS) -Wall -Wextra -O2 -o $@ etherip6ctl.c
|
||||
|
||||
clean:
|
||||
@if [ -d "$(KDIR)" ]; then \
|
||||
$(MAKE) -C "$(KDIR)" M="$(PWD)" clean; \
|
||||
|
||||
33
README.md
33
README.md
@@ -37,39 +37,14 @@ sudo ip link set eip0 up
|
||||
sudo ip addr add 192.0.2.1/30 dev eip0
|
||||
```
|
||||
|
||||
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 フラグメントが必ず発生します。
|
||||
|
||||
フラグメントの一部が欠落した場合、受信側ではパケット全体を再構成できません。
|
||||
再構成の状態は次のコマンドで確認できます。
|
||||
Overlay MTUのデフォルトは1500です。Underlayの経路MTUも1500の場合、
|
||||
カプセル化後の外側IPv6パケットはMTUを超えるため、送信元で経路MTU以下の
|
||||
IPv6フラグメントに分割します。
|
||||
|
||||
```sh
|
||||
nstat -az | grep Ip6Reasm
|
||||
sudo ip link set eip0 mtu 1500
|
||||
```
|
||||
|
||||
`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 フレームには適用されません。
|
||||
|
||||
## トンネルの削除
|
||||
|
||||
```sh
|
||||
|
||||
156
etherip6.c
156
etherip6.c
@@ -2,7 +2,6 @@
|
||||
#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>
|
||||
@@ -15,17 +14,12 @@
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/rtnetlink.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/ipv6.h>
|
||||
#include <net/net_namespace.h>
|
||||
#include <net/netns/generic.h>
|
||||
#include <net/protocol.h>
|
||||
#include <net/rtnetlink.h>
|
||||
#include <net/tcp.h>
|
||||
|
||||
#include "etherip6_uapi.h"
|
||||
|
||||
@@ -38,7 +32,7 @@
|
||||
#define ETHERIP6_HLEN 2
|
||||
#define ETHERIP6_DEFAULT_HOP_LIMIT 64
|
||||
#define ETHERIP6_MAX_MTU 9000
|
||||
#define ETHERIP6_OUTER_HLEN (sizeof(struct ipv6hdr) + ETHERIP6_HLEN)
|
||||
#define ETHERIP6_ENCAP_HLEN (sizeof(struct ipv6hdr) + ETHERIP6_HLEN)
|
||||
|
||||
struct etherip6_tunnel {
|
||||
struct list_head list;
|
||||
@@ -147,122 +141,6 @@ 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 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), ð_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);
|
||||
@@ -273,7 +151,6 @@ 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;
|
||||
|
||||
@@ -291,8 +168,6 @@ static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
goto tx_error;
|
||||
}
|
||||
|
||||
etherip6_clamp_tcp_mss(skb, dst_mtu(dst));
|
||||
|
||||
headroom = LL_RESERVED_SPACE(dst->dev) + sizeof(*ip6h) + ETHERIP6_HLEN;
|
||||
err = skb_cow_head(skb, headroom);
|
||||
if (err) {
|
||||
@@ -301,7 +176,6 @@ 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,31 +192,24 @@ static netdev_tx_t etherip6_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
|
||||
skb->protocol = htons(ETH_P_IPV6);
|
||||
skb->dev = dst->dev;
|
||||
/*
|
||||
* This skb used to contain an inner Ethernet frame. Clear its control
|
||||
* block before handing it to IPv6, then pin source fragmentation to the
|
||||
* current underlay PMTU. ip6_local_out() adds the Fragment Header and
|
||||
* splits oversized packets in ip6_finish_output().
|
||||
*/
|
||||
memset(IP6CB(skb), 0, sizeof(*IP6CB(skb)));
|
||||
IP6CB(skb)->frag_max_size = dst_mtu(dst);
|
||||
skb->ignore_df = 1;
|
||||
skb_dst_set(skb, dst);
|
||||
|
||||
/*
|
||||
* skb->cb belongs to the protocol currently processing the skb. The
|
||||
* encapsulated frame may leave bridge, qdisc, or inner IPv6 state in it;
|
||||
* in particular, stale inet6_skb_parm flags or frag_max_size corrupt the
|
||||
* outer IPv6 fragmentation path. Native IPv6 tunnels clear this state
|
||||
* in ip6tunnel_xmit() before calling ip6_local_out().
|
||||
*/
|
||||
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, inner_len);
|
||||
u64_stats_add(&stats->tx_bytes, payload_len - ETHERIP6_HLEN);
|
||||
u64_stats_update_end(&stats->syncp);
|
||||
|
||||
return NETDEV_TX_OK;
|
||||
return ip6_local_out(net, NULL, skb) == 0 ? NETDEV_TX_OK : NETDEV_TX_OK;
|
||||
|
||||
tx_error:
|
||||
atomic_long_inc(&tun->tx_dropped);
|
||||
@@ -394,6 +261,7 @@ static void etherip6_setup(struct net_device *dev)
|
||||
dev->vlan_features = 0;
|
||||
dev->min_mtu = ETH_MIN_MTU;
|
||||
dev->max_mtu = ETHERIP6_MAX_MTU;
|
||||
dev->needed_headroom = ETHERIP6_ENCAP_HLEN;
|
||||
eth_hw_addr_random(dev);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user