IPVS (LVS/NAT) とiptables DNAT targetの共存について調べた
TL;DR
- IPVSはデフォルトで
conntrack(標準のconnection tracking)を利用しない - そのせいでIPVSに処理させるパケットをNATしたりすると期待通り動かないことがある
- 必要があれば
/proc/sys/net/ipv4/vs/conntrack(net.ipv4.vs.conntrack) を設定しよう
背景
- iptablesの
DNATtargetと IPVS(LVS/NAT構成)を共存させると上手く行かない- PREROUTING chainにおいて
REDIRECTtarget によりポート番号をIPVSのvirtual serviceのものに変換するように設定した場合, REDIRECTされるポートに対し接続すると, 戻り通信(SYNに対するSYN+ACK)の送信元ポートがvirtual serviceのものになる(期待されるのは, 変換前のもの) - (PREROUTING chainの)
REDIRECTtargetは内部的にはDNATと同等の動きをするようで,DNATでも起きる
- PREROUTING chainにおいて
- 以下のようにすれば再現する
テスト用ホストでnetcatなどで適当にListenしてくれるポートを2つ作り, IPVSでバランシング設定
(while true;do nc -l -p 1001; echo 1001; done)& (while true;do nc -l -p 1002; echo 1002; done)&ipvsadm -A -t 172.16.1.8:1000 -s rr ipvsadm -a -t 172.16.1.8:1000 -r 172.16.1.8:1001 -m ipvsadm -a -t 172.16.1.8:1000 -r 172.16.1.8:1002 -m別ホストから接続を確認 (期待通り2つのnetcatプロセスに分散されて動いている)
echo hoge | nc -N 172.16.1.8 1000 echo hoge | nc -N 172.16.1.8 1000テストホストでの出力
hoge 1001 hoge 1002
テスト用ホストでREDIRECTターゲットを設定
iptables -t nat -A PREROUTING -p tcp --dport 1010 -j REDIRECT --to-port 1000別ホストから接続を確認 (接続できず, tcpdumpで見ると戻りパケットの送信元ポート番号がおかしい)
echo hoge | nc -N 172.16.1.8 100023:01:43.223963 IP 172.16.1.2.54996 > 172.16.1.8.1010: Flags [S], seq 3333715063, win 29200, options [mss 1460,sackOK,TS val 535608746 ecr 0,nop,wscale 7], length 0 23:01:43.224307 IP 172.16.1.8.1000 > 172.16.1.2.54996: Flags [S.], seq 3354407097, ack 3333715064, win 28960, options [mss 1460,sackOK,TS val 128663562 ecr 535608746,nop,wscale 7], length 0 23:01:43.224330 IP 172.16.1.2.54996 > 172.16.1.8.1000: Flags [R], seq 3333715064, win 0, length 0テスト用ホストでDNATターゲットを設定しても, 同様
iptables -t nat -A PREROUTING -p tcp --dport 1020 -j DNAT --to-destination :1000( Linux kernel 4.14.83 で確認 )
調べたこと
netfilterの動きについて, ソースコードを中心に確認し, 前述の事象が起きる理由を調査した。
前提知識
- iptablesの操作やtable, chain, targetなどの概念は知っている前提
- 仕組みの概要は A Deep Dive into Iptables and Netfilter Architecture がiptablesとnetfilterの関係を含め説明している
- Linux kernelのネットワーク処理の中のhookを提供する枠組み(=netfilter)を利用したfirewall softwareがiptables
- netfilterの用意するhookは何種類かあり, それに対応するのがiptablesのchain(の中の初期から存在するもの)
- chainに紐づくtableは複数存在しうるが, 所定の順で呼び出される
- IPVS も netfilter を利用して作られている
ソースコードリーディング
- 取っ掛かりとして REDIRECT target について調べる
/net/netfilter/xt_REDIRECT.cが実装で, ぱっと見redirect_tg_regで target の定義をしているredirect_tg_initがmodule初期化時(多分)に呼び出され,xt_register_targetを介して AddressFamilyごとのtargetのリスト に追加されていく
- targetを呼び出す箇所が気になるので, パケットに対し各ルールを評価する場所を探す
ipt_do_tableがそれipt_do_tableは最終的にはnf_hook_ops構造体 としてhookに登録されるipt_do_tableはiptable_filter_hookなどに呼び出されているiptable_filter_hookなどは ここ のように 初期化時にxt_hook_ops_allocによりnf_hook_ops構造体 に詰められipt_register_tableによって登録されるipt_regsiter_table->nf_register_net_hooks->nf_register_net_hookとたどり,nf_hook_entries_growでhook関数のリストに追加される- 登録時にはpriorityを見てhook関数の順序を決定しており,
元は
xt_tableとして設定された.priorityがnf_hook_opsの.priorityに引き継がれているxt_proto_init- 大元になる priority は このへん であり, A Deep Dive into Iptables and Netfilter Architecture の表に合致する
- IPVSについては, ここを中心に実装がある
- 受け付ける通信はSNATより1つ高い優先度で, 戻りの通信はDNATより1つ低い優先度で処理される
- 気になっている事象は, 戻りの通信自体はあるがsource portがおかしく, 戻り通信の処理がおかしいように感じる
DNATやREDIRECTについては PREROUTING chain にしかルールを書かなくても通常は戻りの通信も上手く行くが, これはconntrackによるconnection trackingのおかげconntrackも netfilterを用いて実装されているconntrackの情報は skbに紐づけて管理される
仮説: conntrack と IPVS の評価順序の問題
- 仮説: 評価順序からすると, ingress traffic も egress traffic も
conntrack-> IPVS の順序であり, IPVSによるpacket書き換えの影響で egress traffic の connection tracking がマッチしないのではないか - IPVSによるoutput通信の書き換えの優先度を上げて実験
- ここについて,
NF_IP_PRI_CONNTRACK - 1としてkernelをビルドし直す
- ここについて,
- 結果: 変化なし
- 事象は全く変わりなく発生した
conntrack -Eコマンドでconntrackについて確認してみると, そもそも IPVSのvirtual serviceへの通信はconntrack情報が乗ってこない事がわかる
さらに調査
- そもそも IPVSは独自にconnection trackingを実装していて, 標準の
conntrackを利用しない- ここなどで触れられている
conntrackの情報は削除している- ここ で設定された
ip_vs_nat_xmitが ここ で呼び出され,ip_vs_nat_send_or_contを経由してIP_VS_CONN_F_NFCTフラグが立っていなければip_vs_notrackによりconntrackの情報を削除される
- ここ で設定された
- 先に触れていたように,
conntrackの情報はskbに紐付けられており,DNATREDIRECTがどう動いていても 情報が消されてしまっては戻りの通信を正しく処理できるはずがない- IPVS独自実装のconnection trackingだけが働くため, IPVSのvirtual serviceのportが送信元になる
- しかし, よく見ると ここ が
真であれば
IP_VS_CONN_F_NFCTフラグが立ち,conntrackの情報削除を免れる- 直前のコメント文にある, “
conntrackを保持することが有用なケース” がまさに今回のはず ip_vs_conntrack_enabledが真になるのはたどっていくとsysctlでnet/ipv4/vs配下 のconntrack
- 直前のコメント文にある, “
再実験
テストホストで見つけた設定を試す
sysctl net.ipv4.vs.conntrack=1別のホストから
REDIRECTtagetが働くようにアクセスecho hoge | nc -N 172.16.1.8 1000 echo hoge | nc -N 172.16.1.8 1000テストホストでの出力
hoge 1001 hoge 1002
無事解決。副作用が無いかについてはまだ調べられていない。