PPPoE ルータ同志で IPsec を構築するときの問題

はてブ数 2007/01/31Linux

IPsecで一部の通信が遮断される謎

IPsec構築時の問題点に書いたことですが、一部の通信が遮断される問題の原因を追求しました。一言で言えば、IPsecパケットが PPPoE(フレッツ網) を通るには大きすぎてフラグメントしていることが原因でした。

より詳細な原因

ネットワーク図
192.168.10.0/24 <--LAN--> FreeBSD  <--Internet--> Linux 2.6 <--LAN--> 192.168.20.0/24

192.168.10.0 内のWindowsマシンから出るパケットは MTU(パケット最大サイズ)として 1454 が設定されています。しかし、フレッツ網を PPPoE で抜ける際に PPP でくるまれ、さらに IPsec でくるまれるためにこのサイズのパケットは通過できません(参考)。

次のようにIPパケットの断片化が発生しています。

# tcpdump -i tun0 esp
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x450), length 268
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x451), length 1432
21:35:40 IP FreeBSD > Linux: esp
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x452), length 1044
21:35:40 IP Linux > FreeBSD: ESP(spi=0x0129b031,seq=0x402), length 76
21:35:40 IP FreeBSD > Linux: ESP(spi=0x01053609,seq=0x453), length 1432
21:35:40 IP FreeBSD > Linux: esp

本来、IPパケットが断片化されようと、相手側のLinuxマシンで再構築され復元されるため、断片化により通信速度は低下しますが、通信自体は問題ありません。

 生パケット → IPsecカプセル化 → IPフラグメント(分割)  → Internet
 → IPパケット再構築 → IPsecのカプセルを解く → 生パケット

しかし 192.168.20.0 内にはパケットが届いておりません。Linuxマシンのインターフェイスまでは届いているのですが、iptables なのか、その先の IPsec なのか、ともかくうまく行っていないようです。

tcpmss を設定する

tcpはコネクション時に、TCPの中に含まれるデータサイズMSS(Maximum Segment Size、通常 MTU-40byte)をネゴシエーションします。

# tcpdump -i fxp0 port 8080
21:33:25 IP pc11.5955 > pc22.8080: S xxx(0) win 5840 <mss 1414,sackOK,timestamp 4032556 0,nop,wscale 2>
21:33:25 IP pc22.8080 > pc11.5955: S xxx(0) ack yyy win 65535 <mss 1460,nop,nop,sackOK>

このときの小さい方の数字をTCPの通信において使用します。これを tcpmss と言います。

IPsecやPPPoEのトンネル化などによりヘッダサイズが増大してしまうなら、この mss の値を MTU に対して小さく設定してあげればいいことになります。この操作をルータ側で強制的に行ってしまえば、結果的にIPsecにおける断片化(フラグメント)を防ぐことができます。

MTU > mss + IPsec & IP & TCPヘッダサイズ

通信路MTUの調査方法

ping コマンドでパケットサイズを指定することで、通信路のMTUを調べることができます。

# Linux/FreeBSDの場合
$ ping -l 1370 192.168.20.2
# Windowsの場合
> ping -s 1370 192.168.20.2

この場合、どちらも1370バイトのpingパケットを 192.168.20.2 に投げます。無事パケットが返ってくる限界値+28byteが経路MTUになります。mssはMTU-40になります。

Linux側による解決策

必要なmssの値はMTUから計算で出せるハズですが、なぜかそれではうまく行かず*1、結局試行錯誤で次のように設定しました。

iptables -A FORWARD -p tcp -s 192.168.10.0/24 --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1344
iptables -A FORWARD -p tcp -d 192.168.10.0/24 --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1344

試行錯誤は、tcpdump を用いて esp の断片化が起きないサイズを探り出すことにしました。IPsecのフッタの一部がサイズ可変らしく(?*2)、pingによる調査では不十分でした(それよりも低い値になった)。

FreeBSD側による解決策

FreeBSDのpppデーモンには、すでにtcpmssfixupという機能が付いているのですか、pppd を通過する段階ではすでにIPsecでカプセル化されているため無力です*3。同じ理由で、ipnat.conf でも、mssを調整する機能があるのですが、パケットが出て行くときしか書き換えられないためにIPsec では使用出来ません。

最初後述するように ipfw を使っていたのですが、pf できました。*4

デフォルトのカーネルで使用出来ると思います。rc.conf には次のように書きます。

pf_enable="YES"

pf.conf には次のように書きました。LAN側インターフェイスの 入力時 と 出力時 にそれぞれフィルタすることに注意してください。

int_if="fxp0"		# LAN interface
scrub in  on $int_if from 192.168.10.0/24 to 192.168.20.0/24 max-mss 1344
scrub out on $int_if from 192.168.20.0/24 to 192.168.10.0/24 max-mss 1344

/etc/rc.d/pf start」 する際に ALTQ がどうのと怒られますが、ALTQ関連機能を使用しなければ問題ないので気にしないでください。

# /etc/rc.d/pf start
Enabling pf.
No ALTQ support in kernel
ALTQ related functions disabled

FreeBSD側による別の解決策(非推奨)

今更 ipfw の時代でもないので非推奨の方法です(動作自体は問題ないのですが煩雑です)。

結局、ipfw + tcpmssd を使用しました。tcpmssd は /usr/ports/net/tcpmssd よりインストールしました(パッケージでも構いません)。起動スクリプトがありませんので、tcpmssd.sh.txt/usr/local/etc/rc.d/tcpmss.sh として保存し使用してください。

カーネルのコンパイルオプションです。

options         IPFIREWALL
options         IPFIREWALL_VERBOSE
options         IPDIVERT

rc.conf です。mssではなくMTUを与えることに注意。

firewall_enable="YES"
firewall_type="/etc/ipfw.sh"
tcpmssd_enable="YES"
tcpmssd_flags="-p 50 -m 1372"

/etc/ipfw.sh です。IPsecを通過する tcpコネクションパケット を tcpmssd に飛ばし mss を書き換えます。

add 64900 divert 50 tcp from 192.168.10.0/24 to 192.168.20.0/24 tcpflags syn
add 64902 divert 50 tcp from 192.168.20.0/24 to 192.168.10.0/24 tcpflags syn

pfによる方法と同様に、FreeBSDマシンから直接 192.168.20.0/24 へ向かうパケットは ipfw のルールを通過せず、tcpmss を設定することは不可能です。

*1 : 時折パケットがロストする

*2 : 詳しい人補足願います

*3 : 出て行くときにはすでにIPsecでカプセル化されているので、書き換えようがない

*4 : IPFilter の機能不足で困ることが多かったのですが、今度から pf 使おう(汗)

さらなる疑問

IPsecにおいて、カプセル化後にIPパケットのフラグメントが発生した場合、本来ならば受信側がパケットを再構築して通信できるはずだと思うのですが……(速度が遅くなるのは仕方ないとしても)。

  • FreeBSD ←→ FreeBSDなら問題ないのか?
  • Linux ←→ Linuxなら問題ないのか?
  • FreeBSD(6.x) ←→ Linux(2.6.x)で通信可能にする手段は存在しないのか?

そもそも、Linux側には ip_conntrack (コネクション追跡)がインストールされているのでフィルタリングより前にESPパケットが再構築されているハズなのですが*5、フラグメントしたESPパケットの到着は確認できても、IPsecのカプセル化を解いた後のパケットは確認できていません。

色々調べたところ、Linuxはカプセル化する前にパケットを分割しますが、FreeBSDはカプセル化してからパケットを分割しています。この辺のミスマッチが怪しいですね。ip_conntrack がESPのフラグメントパケットを修復する際に、ヘッダ書き換え(?)か何か悪さしてるんじゃないかと思いますが……。

*5 : Linux 側 iptablesで確認しましたが、フラグメントパケットは記録されていませんでした。iptables にESPパケットが届いているのは確認できます

2007/01/27(土)VMware player (vmplayer)のハマりどころ(on ubuntu)

vmware のうち VMware Player(無償版) について

vmplayer とは、vmware のうち仮想マシン実行環境のコア部分のみを無償で提供しているものです。VMware Workstation などは使ったことがないので比較は難しいですか、仮想マシンを利用する上での便利機能などが削られ、単純に仮想マシンを使うのみとなるようです。

ubuntu の上にインストールしてみたのですが、色々ハマリどころ(や疑問に思った点)があったので、記事にまとめておきます。

なお、vmware (vmplayer) が動作するOSをホストOS、vmware の上にインストールするOSをゲストOSと言います。(vmwareを調べていると)よく出てくる言葉なので覚えておきましょう。

vmxファイルを作る方法がない

vmplayer には、仮想マシンの定義ファイルである、拡張子 vmx のファイルを作成する機能が付いていません。このファイルはただのテキストファイルですので手書き可能ですが、一般の方にはややハードルが高いと言わざる得ないでしょう。適宜覚書はてな異本というサイトで、作成ツールが紹介されていますので参考にしてみてください。

vmdk を作ることができない

vmplayer には仮想HDDのイメージファイルである vmdk を作成する機能がありません。ベタファイルを仮想HDDとみなしてマウントすることは不可能ですqemuというフリーのエミュレーターを利用し、次のようにイメージファイルを作成します。

$ qemu-img create -f vmdk disk-img.vmdk 4G

強調部分はそれぞれファイル名、HDD容量となっています。ランレングスか何かで圧縮されているらしく、どんな容量のディスクイメージを作っても中身を使わない限り小さなファイルとなるのが特徴です。

vmware-toolsが必要

Linux や Windows などのゲストOSをより快適に動作させるためには、VMware用のドライバをインストールする必要があります。これらを vmware-tools と言います。vmplayer には vmware-tools は付属していないので、まず vmware-server などをダウンロードします(使用制限はありますがダウンロードは誰でもできます)。

そしたら、tools-key.pub というファイルのあるディレクトリを探してください。その中に、各OS用のデバイスドライバが ISOファイル(CDのイメージファイル)として格納されています。これらを、各OSにインストールします。VMware Workstation などではボタン操作一発でインストールされたりするようですが*1、vmplayer では地道に手動でインストールする必要があります。

VMware Shared Foldersについて

VMwareにはゲストOS上でホストOS上のファイルシステムを直接操作する機能があります。それを[gShared Folder]といい、vmxファイルで次のように指定します。

sharedFolder.option = "alwaysEnabled"
sharedFolder.maxNum = "1"
sharedFolder0.present = "TRUE"
sharedFolder0.enabled = "TRUE"
sharedFolder0.readAccess = "TRUE"
sharedFolder0.writeAccess = "TRUE"
sharedFolder0.hostPath = "/data"
sharedFolder0.hostName = "host"
sharedFolder0.guestName = "share"
sharedFolder0.expiration = "never"

ゲストOSがWindows 2000/XPなどの場合、ネットワーク全体の中身に「VMware Shared Folders」というフォルダが確認できますが、ダブルクリックすると「ネットワークを参照できません。ネットワークが存在しないか、起動されていません」と表示されます。結構ハマったのですが、結論としては「VMware Player 1.0.x(vmplayer)では Shared Folders は利用出来ない」ということになります。

これが利用出来るのは、VMware ServerやVMware Workstationのみのようです。sharedFolder0 の設定がエラーもなく読み込まれるので一瞬動くと勘違いするのですが、動きません。……警告ぐらい出してほしいところですけどね。

VMware Player 2.0からSharedFolderが利用できるようになりました。vmplayer のメニューに設定項目がありますので、そこから設定する方が楽でしょう。

ホストOSとゲストOS間の通信のみが異様に遅い

ホストOSがLinux等のときに発生するようですが、Samba や ftp などの通信が異様なほど遅くなることがあります。しかも、ゲストOS←→外部ネットワークはなんの問題もなく、何十Mbps も出るにもかかわらず、ホストOSとの通信のみが遅くなります。

この現象の解説と解決策は、VMware on AMD Athlon 64 X2のブリッジ接続時の通信性能に書かれていますが、VMware起動前に次のコマンドを実行してください(すでに起動済ならば、vmplayer の再起動でokです)。

# ethtool -K eth0 tso off

いちいち実行するのが面倒ならば、rc.localなどに書いてしまってもよいでしょう。この現象はかなり意味不明で、ものすごくハマりました。

アンダースコア(_) が入力出来ない

ホストOSがLinux等のときに発生するようですが、アンダースコアキーを押しても全く反応がありません。これを解決するには、~/.vmware/config というファイルを作成し、次の設定を書き込んでください。

xkeymap.keycode.211 = 0x073

*1 : 過去にそんな光景を見た気がする

2007/01/25(木)HTTP asf (wmv/wma)ストリームのpush配信プロトコル

Windows Media Player 9(WMV9) などによるHTTPストリームデータの仕様は以下のサイトに書かれていますが、push配信については記述がありません。その点について解析を行いました。

http://sdp.ppona.com/

匿名push配信時のプロトコル解析

クライアントからサーバへのリクエスト。

POST /test HTTP/1.1
Content-Type: application/x-wms-pushsetup
X-Accept-Authentication: NTLM, Digest
User-Agent: WMEncoder/9.0.0.3287
Host: 192.168.0.1:8080
Content-Length: 0
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: push-id=0

サーバからクライアントへの応答。

HTTP/1.1 204 No Content
Server: Servet-agent
Content-Length: 0
Date: Tue, 09 Jan 2007 10:02:58 GMT
Pragma: no-cache, timeout=60000
Cache-Control: no-cache
Set-Cookie: push-id=35201712
Supported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.predstrm, 
com.microsoft.wm.fastcache, com.microsoft.wm.startupprofile

注意点

  • (サーバ側では)Connection が keep alive される
  • クライアントが接続テストだけを行ってきた場合は、この時点で終了する。

配信時の接続ではさらに次のように続きます。

POST /test HTTP/1.1
Content-Type: application/x-wms-pushstart
X-Accept-Authentication: NTLM, Digest
User-Agent: WMEncoder/9.0.0.3287
Host: 192.168.0.1:8000
Content-Length: 2147483647
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: push-id=35201712

$H~以下ストリームデータ~

ストリームデータの取り扱い注意

push配信によって渡されるストリームデータは、HTTP Protocol (Pesudo-Stream) のうち「Type Header」のあとに含まれる、「MSS Pre-header」(8byte)がありません。よってこのヘッダに含まれるシーケンス番号やフラグなどを中継サーバ側で生成し追加してやる必要があります(この際、Type Headerにあるパケットサイズも書き換える必要があります)。

  • MSS Pre-header の先頭2byteは、$Hや$Eの場合 "00 0C"、$Dの場合 "00 00" で問題ありません。
  • MSS Pre-header の Length は $D などのパケットヘッダの Length と同じ値を設定します。

Digest認証時のプロトコル解析

Digest認証を求める場合、サーバ側は次のような応答を返します。

HTTP/1.1 401 Unauthorized
Server: Servet-agent
WWW-Authenticate: Digest qop="auth",algorithm=MD5-sess,nonce="XX~XX",charset=utf-8,realm="Digest"
Date: Tue, 09 Jan 2007 10:34:57 GMT
Pragma: no-cache, timeout=60000
Set-Cookie: push-id=98422531
Supported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.predstrm, 
com.microsoft.wm.fastcache, com.microsoft.wm.startupprofile
Content-Length: 0

2007/01/23(火)FreeBSD をアップデート

make buildworld

久しぶりの make buildworld だったのですっかりやり方を忘れていましたが、6.1PREバージョンから 6.2-STABLE にアップデートしました。このサーバに関してはもう入れ直すことはないでしょう(故障するまで)。

OpenLDAP + Samba でドメインを構築しているのですが、slapd の起動順序がどうにも変更できず再起動のたびに参っていたのですが、調べてみると 5.x から起動順序ルールか変わったようで(それ以前は起動スクリプトの名前順)。nss_ldap を入れている都合上、どのデーモンよりも先に slapd を起動しないと色々大変なことになるんですが(デーモンを起動する度に ladp に接続に行って数分止まってしまう)、標準状態では一般デーモンと同じ優先度で起動するようになっていました。

というわけで、ネットワークの初期設定直後に起動するように変更。

# /usr/local/etc/rc.d/slapd.sh 変更後の設定
#
# PROVIDE: slapd
# REQUIRE: NETWORKING
# BEFORE: securelevel SERVERS
# KEYWORD: FreeBSD shutdown

次の記事がとても参考になりました。書かれた方には感謝。