[NetBSD]

MTU blackhole 問題 / 2004-12-16 (木)

10月頃の話。

うちでは NetBSD1.6.2 を積んだ PCにオンボード含めネットワークインターフェイスを三つつけてルータにしており、PPPoE で FletsADSL に接続、内部マシンへの NA(P)Tを提供しておりました。

ところが、あるマシンからだけ何故かWebのフォームで大容量のテキストを送ろうとすると通信に失敗するという問題がありました。以下はその調査結果です。

結局、よーわからんので週末持ち越し。会社から PowerBook を持って帰ってきて比較検証をやってみる。

対象は mixi の日記の書き込み。

これが正常な PowerBook

21:43:18.058803 IP dhcp09.core.nest.or.jp.49307 > sumbawa.mixi.jp.http: S 3598307457:3598307457(0) win 65535 <mss 1460,nop,wscale 0,nop,nop,timestamp 4277980427 0> 
21:43:18.075370 IP sumbawa.mixi.jp.http > dhcp09.core.nest.or.jp.49307: S 1217852595:1217852595(0) ack 3598307458 win 5792 <mss 1460,nop,nop,timestamp 758981033 4277980427,nop,wscale 0> 
21:43:18.075523 IP dhcp09.core.nest.or.jp.49307 > sumbawa.mixi.jp.http: . ack 1 win 65535 <nop,nop,timestamp 4277980427 758981033> 
21:43:18.075790 IP dhcp09.core.nest.or.jp.49307 > sumbawa.mixi.jp.http: . 1:1449(1448) ack 1 win 65535 <nop,nop,timestamp 4277980427 758981033> 
21:43:18.092521 IP puresnow.core.nest.or.jp > dhcp09.core.nest.or.jp: icmp 36: sumbawa.mixi.jp unreachable - need to frag (mtu 1400) 
21:43:18.092653 IP dhcp09.core.nest.or.jp.49307 > sumbawa.mixi.jp.http: . 1:1349(1348) ack 1 win 65535 <nop,nop,timestamp 4277980427 758981033> 
3way handshake の時の MSSに注目。PowerBook が 1460 を送るのはともかくとして帰ってきたパケットも 1460 とほざいてやがる。で、安心して1446 バイト突っ込んで送ったら ICMP で「MTUは1400までにしてくれ」と泣きをいれて、でもって 1348 バイトにまけてやらぁと流れていく訳です。

で、問題の方。

sumbawa.mixi.jp.http: S 139593446:139593446(0) win 65535 <mss 1460,nop,wscale 0,nop,nop,timestamp 761340946 0> 
21:46:51.442298 IP sumbawa.mixi.jp.http > dhcp03.core.nest.or.jp.49217: S 1438025936:1438025936(0) ack 139593447 win 5792 <mss 1460,nop,nop,timestamp 759193408 761340946,nop,wscale 0> 
21:46:51.442483 IP dhcp03.core.nest.or.jp.49217 > sumbawa.mixi.jp.http: . ack 1 win 65535 <nop,nop,timestamp 761340946 759193408> 
(中略) 
21:46:51.464313 IP dhcp03.core.nest.or.jp.49217 > sumbawa.mixi.jp.http: . 668:2116(1448) ack 1 win 65535 <nop,nop,timestamp 761340946 759193428> 
21:46:51.464417 IP dhcp03.core.nest.or.jp.49217 > sumbawa.mixi.jp.http: . 2116:3564(1448) ack 1 win 65535 <nop,nop,timestamp 761340946 759193428> 
21:46:51.466627 IP puresnow.core.nest.or.jp > dhcp03.core.nest.or.jp: icmp 36: sumbawa.mixi.jp unreachable - need to frag (mtu 1400) 
21:46:51.466917 IP puresnow.core.nest.or.jp > dhcp03.core.nest.or.jp: icmp 36: sumbawa.mixi.jp unreachable - need to frag (mtu 1400) 
21:46:52.922657 IP dhcp03.core.nest.or.jp.49217 > sumbawa.mixi.jp.http: . 668:2116(1448) ack 1 win 65535 <nop,nop,timestamp 761340948 759193428> 

最初の 3 way handshake はまぁいいとしよう、 問題は次だ。1448バイト詰めたところでやっぱり泣きが入るが、こいつ泣いてきてもMSSを直さない!

だからここで通信が途絶え、MTU path black hole っぽい挙動になるのでした。

まぁ、後者の ICMP を聞かないのはバグだろう(なおこいつ、ファイアーウォールは切ってます)。これは報告するとして、もう一つ気にかかるのは puresnow( NetBSD1.6.2 )が MSSが1460 なやりとりを看過している事だ。ipnat で mssclamp しているのだからここで小さくすべきじゃないのか?

で、実はそこら辺、今日の昼休みに食後の気分転換でソースを読んでたのだ。対象は NetBSD1.6.2 の ip_nat.c と ipfilter 4.3.35 のソース。なお、NetBSD1.6.2 の ipfilter は 3.4.29ベースとあるがipfilterでは mssclamp は3.4.30 から実装となっている事から分かるように、ipfilter 本体で未実装の技術が先行投入されている。(ipfilter の作者は NetBSD の comitter らしいので、まぁそういう事なんだろう)

そして、この二つをざーっとみくらべて気がついて、以下のパッチを当ててカーネルを再構築、で、治りましたとさ。

puresnow# diff -rcN /sys/netinet/ip_nat.c.orig /sys/netinet/ip_nat.c 
*** /sys/netinet/ip_nat.c.orig Wed Dec 31 06:21:18 2003 
--- /sys/netinet/ip_nat.c Fri Oct 1 21:49:18 2004 
*************** 
*** 2804,2809 **** 
--- 2803,2817 ---- 
*/ 
if (nat->nat_age == fr_tcpclosed) 
nat->nat_age = fr_tcplastack; 
+ #if 1 /* added shiro */ 
+ /* 
+ * Do a MSS CLAMPING on a SYN packet, 
+ * only deal IPv4 for now. 
+ */ 
+ if (nat->nat_mssclamp && 
+ (tcp->th_flags & TH_SYN) != 0) 
+ tcp_mss_clamp(tcp, nat->nat_mssclamp, fin, csump); 
+ #endif 
MUTEX_EXIT(&nat->nat_lock); 
} else if (fin->fin_p == IPPROTO_UDP) { 
udphdr_t *udp = (udphdr_t *)tcp; 

断片だけだと分からんだろうからざっと説明すると、tcp_mss_clamp() がパケット内部の MSS がmssclamp で指定した値よりも大きい場合にその値に fix する関数。で、この修正は

ip_natin() 関数に追加している。ソースのコメントからだすと

* ip_natout() - changes ip_src and if required, sport

* - creates a new mapping, if required.

* ip_natin() - changes ip_dst and if required, dport

*

とある。で、対になる外部から内部へのパケットをやりとりする ip_natout() ではこの tcp_mss_clamp() が記載されているにもかかわらず内部から外部へのパケットを処理する ip_natin() ではそれが記載されてなかったため追加したという訳。これで外部からの返事も「clamp」されて、mssclamp で指定された値以上が出ないようになる。

え、なんでこれでうまくいくかって?

これは pppoe0 でとった 3 way handshake の

22:53:30.600441 PPPoE [ses 0x4a58] 61.199.200.64.10104 > sumbawa.mixi.jp.www: S 3935919247:3935919247(0) win 65535 <mss 1360,nop,wscale 0,nop,nop,timestamp 761348944 0> (DF) 
22:53:30.613527 PPPoE [ses 0x4a58] sumbawa.mixi.jp.www > 61.199.200.64.10104: S 1374318826:1374318826(0) ack 3935919248 win 5792 <mss 1460,nop,nop,timestamp 763194281 761348944,nop,wscale 0> (DF) 
22:53:30.617697 PPPoE [ses 0x4a58] 61.199.200.64.10104 > sumbawa.mixi.jp.www: . ack 1 win 65535 <nop,nop,timestamp 761348944 763194281> (DF) 

ここではお互いのmss を通知しているのだが、中から外に行く時は確かに「clamp」されて 1360 になってるけど、帰りは 1460 が通ってしまう。中のマシンは外部にパケットが流れる時に「clamp」されたと分かる訳がないので 1460 を信じちゃう訳だ。

通常、これは実際にパケットを流したところで ICMPで泣きが入るのでけりがつくのだが、泣きを無視するOSの場合、ここで信じちゃうのが致命傷になる訳だ。

なお、この修正は ipfilter 3.4.35 でははいっている。つまりこの問題を ipfilter の作者はしっているという訳だ。NetBSD 2.0 では ipfilter は 4.x ベースになるそうだし、NetBSD1.6.3 がでるとしたらこの修正は入るだろうから、NetBSD.org には報告しなくていいかなっと思っているけど...、しかし、ぐぐっても同じ問題を誰も報告してないところを見ると、NetBSD1.6.2 なんて誰も使ってないのかも知れない。ううぅ、-current に移行すべきなのかなぁ、私も(;______;)

まぁ、これにてこの問題は解決、っと。

...