同じサブネットへのNIC 2枚指し、またはソースルーティングのおはなし

NIC 2枚刺し」というと「IP masqueradeを使ってLinuxルーターを作ろう!」的な話を思い出す老害です。こんにちは。

NICを2枚生やしたサーバーから同じサブネットに両方の足をのばす機会があったのですが、じゃっかん躓いたのでそのメモです。

具体的にいうと、eth0の方は問題ないのですが、eth1についているIPアドレスへの疎通ができない、というものでした。

以下、

という体で読んでください。


さて、
別サーバーからpingしつつ、eth1でtcpdumpしてみると、echo requestは届いているのが観測できました。が、echo replyを返していません。

# tcpdump -i eth1 -nl icmp
listening on eth1, link-type EN10MB (Ethernet), capture size 96 bytes
15:19:07.469577 IP 10.0.0.100 > 10.0.0.11: ICMP echo request, id 55821, seq 551, length 64
15:19:08.469517 IP 10.0.0.100 > 10.0.0.11: ICMP echo request, id 55821, seq 552, length 64
15:19:09.469253 IP 10.0.0.100 > 10.0.0.11: ICMP echo request, id 55821, seq 553, length 64

同じようにeth0でtcpdumpしてみると、入ってきたeth1じゃなくて、eth0からecho replyを出しているのが観測できました。

# tcpdump -i eth0 -nl icmp
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
15:22:14.481126 IP 10.0.0.11 > 10.0.0.100: ICMP echo reply, id 55821, seq 738, length 64
15:22:15.480946 IP 10.0.0.11 > 10.0.0.100: ICMP echo reply, id 55821, seq 739, length 64
15:22:16.480895 IP 10.0.0.11 > 10.0.0.100: ICMP echo reply, id 55821, seq 740, length 64


なので、10.0.0.11宛のパケットは入ってきたeth1から出ていくようにすればOKそうです。

これをハンドリングするにはソースルーティングの設定をすればよくて、Linuxではiproute2の出番になります。

古来のrouteコマンドですと、宛先に応じてNICのデバイスやnext hop routerの指定を行いますが、Linuxではこのテーブルを複数持つことができ、さまざまな条件でどのテーブルを参照させるかを指定することができます。

「さまざまな条件」とは、例えば

といったものです。fwmarkとは、iptables(試したことはないですがebtablesでもできると思います)で--set-markで印をつけたパケットをひっかけるためのもので、iptablesの世界の柔軟なルールを使ってパケットにマーキングをし、それに応じてルーティングまでも切り替えられるというかなり(いい意味で)変態なものです。


話を元に戻します。

まず、なぜeth1から来たパケットがeth0から出ていくのか、iproute2のコマンドを使ってrule tableやroutingの情報を確認してみます。

rule tableはip ruleコマンドで確認できます。

# ip rule show
0:      from all lookup 255
32766:  from all lookup main
32767:  from all lookup default

左の数値はruleの優先順位で、数値の小さい順に参照されます。
from allはこのrule内のrouteを参照するかどうかの条件です。
lookupのあとの255かmainがrule tableのIDです。基本的にIDは数値なのですが、/etc/iproute2/rt_tablesにIDと文字列の対応を書いておく(/etc/hostsやservicesの様なものです)と、文字列で表示されるようになります。

255のrule tableはループバックやブロードキャストのルーティングが定義されています。defaultはどのtableのroutingにもマッチしなかった場合に参照されるtableですが、多分中身は空だと思います。

で、mainが、いつもnetstat -nrやroute -nで見ているルーティングが定義されているtableです。ipコマンドでruleの中のルーティングを確認するには、ip routeコマンドを使います。netstat -nrと比較してみたり、255やdefault tableも見てみるといいと思います。

# ip route show table main
10.0.0.0/18 dev eth0  proto kernel  scope link  src 10.0.0.10
169.254.0.0/16 dev eth0  scope link
default via 10.0.0.1 dev eth0


さて、10.0.0.11宛のパケットは、優先順位的に最初の255 tableの中のルーティングにはマッチしないので、続いてこのmain rule tableの中に入っていくわけですが、最初のルーティングルールにマッチし、そこには「dev eth0」とあるのでeth0から返りのパケットが出ていく、というあんばいなのがみてとれます。


というわけで、

  • mainより優先順位が高いrule tableで、
  • eth1から出ていくようなルーティングを定義

すればよさそうです。

rule tableの新規追加はip ruleコマンドでやります。
今回は、mainより優先ということで優先度200、tableのIDは100で、出るパケットの送信元アドレスがeth1の10.0.0.11のをひっかけるようなtableを作ります。

# ip rule add from 10.0.0.11 table 100 prio 200

そしてこのtableの中に、eth1から出ていくようなルーティングをip routeコマンドで定義します。

# ip route add dev eth1 src 10.0.0.11 table 100

これで10.0.0.11(eth1)宛のパケットもちゃんと返せるようになります。

ちなみに、どのrouteで出て行くか確認するには、ip route get IPADDRESS すればわかります。

落穂ひろい

今回はid 100というrule tableを作りました。途中でも触れましたが、/etc/iproute2/rt_tables に

100  ENI

とか書いておくと、ip rule showで数値でなく文字列(ENI)で表示されるので、わかりやすい名前を与えておくといいと思います。


ipコマンドのサブコマンドは特定できる文字まで省略できます。
例えば、ip route show table mainは、ip ro s t mainと書くこともできます。
この例はちょっとアレですが、ifconfig -a相当の情報はip addrで表示できるのですが、ip aと打つのはよくやります。