TCP Segmentation Offload の罠

MicroServer のネットワークの性能テスト中、以前より発生していたある自宅サーバの怪現象に再び悩まされていたのですが、昨日ようやくその謎が解けました。

怪現象サーバには ASUS P5NT WS という nForce 680i チップセットのマザーボードを使っており、オンボードで MCP55 の GbE が載っています。Linux カーネルは Debian lenny の 2.6.26-2-openvz-amd64、ドライバは forcedeth 0.61 というちょっと古いものです。

このサーバ server1 から、他のサーバ server2 に HTTP でファイル転送するときに、server1 でキャプチャした送信パケットと、server2 でキャプチャした受信パケットが一致しないのです。

server1 10.101.0.254:80 (送信側)

00:47:59.394889 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 26577:32369, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 5792
00:47:59.394896 IP 10.101.0.74.35984 > 10.101.0.254.80: Flags [.], ack 18137, win 340, options [nop,nop,TS val 1853855 ecr 1753443287], length 0
00:47:59.394924 IP 10.101.0.74.35984 > 10.101.0.254.80: Flags [.], ack 19585, win 360, options [nop,nop,TS val 1853855 ecr 1753443287], length 0
00:47:59.394938 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 32369:35265, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 2896

server2 10.101.0.74:35984 (受信側)

00:47:59.401642 IP 10.101.0.74.35984 > 10.101.0.254.80: Flags [.], ack 18137, win 340, options [nop,nop,TS val 1853855 ecr 1753443287], length 0
00:47:59.401668 IP 10.101.0.74.35984 > 10.101.0.254.80: Flags [.], ack 19585, win 360, options [nop,nop,TS val 1853855 ecr 1753443287], length 0
00:47:59.401782 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 26577:28025, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 1448
00:47:59.401794 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 28025:29473, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 1448
00:47:59.401805 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 29473:30921, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 1448
00:47:59.401819 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 30921:32369, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 1448
00:47:59.401836 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 32369:33817, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 1448
00:47:59.401852 IP 10.101.0.254.80 > 10.101.0.74.35984: Flags [.], seq 33817:35265, ack 104, win 140, options [nop,nop,TS val 1753443287 ecr 1853855], length 1448

なお server1 server2 ともに MTU は 9000 に設定しています。server1 で送信した長さ 5792 や 2896 の TCP パケットが、長さ 1448 バイトのパケットに分割されて server2 に届いているように見えます。IP フラグメント化はしておらず、各パケットにはきちんと TCP ヘッダがついているので、どうやら MSS? 1448 に合わせて送受信しているようです。

いったい誰がこんな小細工をしているのでしょうか。当初はキャプチャ結果がこうなのだから、サーバの間で何者かが勝手にやっているに違いないと考え、2 台の間に入ってるスイッチ (AirMac Extreme とか) を疑って交換したり、直結してみたりといろいろ試しましたが、現象はおさまらず長い間原因不明でした。

それが昨日、これの犯人は MCP55 GbE の TCP Segmentation Offload という機能らしいということが判明しました。この機能の有無は ethtool -k で調べることができます。

% sudo ethtool -k eth0
Offload parameters for eth0:
Cannot get device flags: Operation not supported
rx-checksumming: on
tx-checksumming: on
scatter-gather: on
tcp segmentation offload: on
udp fragmentation offload: off
generic segmentation offload: on
large receive offload: off

tcp segmentation offload: on となっています。そして当然これは ethtool -K eth0 tso off とすれば無効にできる、と考えたいのですが、実際に forcedeth.c を読んだところこの TSO を無効にする方法はないようです。つまりネットワークインタフェースの MTU の値を、実際のハードウェアの MTU よりも大きい値を指定したときに自動的に MCP55 の TSO を使うようです。

% sudo ifconfig eth0 mtu 9000

しかしながらこの eth0 の実際の MTU は 1500 です。したがって他のホストからジャンボパケットを受信しても処理できず捨てます。eth0 の MTU に 9000 を設定できるにもかかわらずです。これまでこの現象の真の原因がわからず非常に悩みました。

これだけでも十分に迷惑ですが、他にも問題があります。TSO を使うために MTU 設定値を大きくしていると、TCP 以外の巨大な IP パケットを送ろうとしても送れないのです。たとえば server1 から ping -c1 -s 10000 server2 とすると、IP フラグメント化してフレームサイズが 1514 以下になった最後のパケットひとつだけが届くという結果になります。

server1 10.101.0.254 (送信側)

05:01:41.532898 00:1a:92:b2:c5:de > 00:1b:21:76:0b:39, ethertype IPv4 (0x0800), length 9010: 10.101.0.254 > 10.101.0.74: ICMP echo request, id 19016, seq 1, length 8976
05:01:41.532909 00:1a:92:b2:c5:de > 00:1b:21:76:0b:39, ethertype IPv4 (0x0800), length 1066: 10.101.0.254 > 10.101.0.74: icmp

server2 10.101.0.74 (受信側)

05:01:41.533036 00:1a:92:b2:c5:de > 00:1b:21:76:0b:39, ethertype IPv4 (0x0800), length 1066: 10.101.0.254 > 10.101.0.74: icmp

というわけで、からくりさえわかってしまえば簡単な話でしたが、これにはとにかく長い間悩まされてきたので、原因が判明してとてもすっきりしました。

はっきりいって forcedeth の TSO は、性能改善のメリットよりもトラブルと混乱によるデメリットのほうが大きい気がするので、今は身分相応に MTU 1500 にして使っています。ただしここで試したドライバは古いので、最新のドライバではまともになっているかもしれません。

2010/10/09 05:04:00 JST