2012-09-24

RHEL6: multi NIC in the same subnet


タイトルは「NIC」としているが、正確には「インターフェイス」の意味。つまり、複数 LAN ポートを同一サブネットに繋ぎたい。

例として、次のように設定する。

  • eth0: 192.168.1.11/24 (00-0c-29-11-11-11)
  • eth1: 192.168.1.12/24 (00-0c-29-22-22-22)

このとき双方に ping を打つと、実際には eth0 だけが応答する。

>ping 192.168.1.11
>ping 192.168.1.12
>arp -a
インターフェイス: 192.168.1.101 --- 0xc
  インターネット アドレス      物理アドレス      種類
  192.168.1.11          00-0c-29-11-11-11     動的
  192.168.1.12          00-0c-29-11-11-11     動的

これは Linux ネットワークの仕様。RHEL5 だと、次を設定することで両方から ping が返るようになる。

net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.all.arp_ignore = 1

>ping 192.168.1.11
>ping 192.168.1.12
>arp -a
インターフェイス: 192.168.1.101 --- 0xc
  インターネット アドレス      物理アドレス      種類
  192.168.1.11          00-0c-29-11-11-11     動的
  192.168.1.12          00-0c-29-22-22-22     動的

しかし、RHEL6 で同じ設定をしても動かない。具体的には、eth1 への ping が返ってこず、ARP テーブルにも登録されない。

>ping 192.168.1.11
>ping 192.168.1.12
>arp -a
インターフェイス: 192.168.1.101 --- 0xc
  インターネット アドレス      物理アドレス      種類
  192.168.1.11          00-0c-29-11-11-11     動的

この理由がずっと分からなかったが、どうやら RHEL6 から rp_filter の挙動が変わったようだ。結論から言うと、次の設定で RHEL6 でも動くようになる。

net.ipv4.conf.all.rp_filter = 2
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.all.arp_ignore = 1

理由はこの辺りを参照。


RHEL5 では、conf.all.rp_filter と conf.<interface>.rp_filter との論理積(AND)が rp_filter の実効値だったが、RHEL6 では最大値(MAX)が実効値になった。

RHEL5/RHEL6 のどちらにも、/etc/sysctl.conf に次の設定がある。

# Controls source route verification
net.ipv4.conf.default.rp_filter = 1

よって RHEL5 の場合、rp_filter の値は次のようになる。

[rhel5]# sysctl -a | grep '\.rp_filter' | sort
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.eth0.rp_filter = 1
net.ipv4.conf.eth1.rp_filter = 1
net.ipv4.conf.lo.rp_filter = 0

rp_filter の実効値は、eth0 であれば all.rp_filter AND eth0.rp_filter (=0)となり、eth0 の rp_filter は無効となる。つまり /etc/sysctl.conf にそれらしい設定が有りながら、実は全く効いていなかったということ。騙された。

RHEL6 の場合は、

[rhel6]# sysctl -a | grep '\.rp_filter' | sort
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.eth0.rp_filter = 1
net.ipv4.conf.eth1.rp_filter = 1
net.ipv4.conf.lo.rp_filter = 1

今度は MAX(all.rp_filter, eth0.rp_filter) (=1)となり、rp_filter は有効となる。RHEL5 と全く同じ動作にしたいなら、/etc/sysctl.conf で net.ipv4.conf.default.rp_filter = 0 と設定する必要がある。

今回の目的(同一サブネットに複数インターフェイス)では、rp_filter = 2 にすれば良い。これは loose mode というらしく、いずれかのインターフェイスから到達可能な宛先であればパケットを受信する、というモードのようだ。RFC3704 なんて読む気ないので詳しくは知らない。

そもそも今回の件で rp_filter が影響することが分かりづらいが、極力プライマリーインターフェイスで通信しようとする Linux の特性を考えると、何となく理解できなくもない。今回の場合、プライマリー側である eth0 が ARP パケットを処理する際に rp_filter が効くのだろう(と思う)。が、やはり Linux のこの特性は分かりにくい(直観的でない)と思う。

以上のように、Linux で複数インターフェイスを同一サブネットに繋ごうとすると、非常に微妙な領域に足を突っ込むことになる。やるなら一個のインターフェイスに仮想 IP アドレスを付ける方法(仮想インターフェイス)をお勧めする。じゃあ何故そうしないのかと言うと、現場 SE に「仮想 IP アドレス」とか言っても通じないから。少なくとも自力で設定できるとは思えない。しかし「1 つの LAN ポートに 1 つの IP アドレス」であれば、奴らにも理解してもらえる確率がグッと上がる。:-)

2012-09-02

Tomcat: Cannot stop in wrong hosts configuration

ある客先のサーバーだけ、Tomcat の停止に失敗する。

2012/08/29 15:56:51 org.apache.coyote.http11.Http11Protocol pause
情報: Coyote HTTP/1.1を http-8080 で一時停止します
2012/08/29 16:00:00 org.apache.catalina.connector.Connector pause
致命的: プロトコルハンドラの一時停止に失敗しました
java.net.ConnectException: Connection timed out
	at java.net.PlainSocketImpl.socketConnect(Native Method)
	at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:351)
	at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:213)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:200)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
	at java.net.Socket.connect(Socket.java:529)
	at java.net.Socket.connect(Socket.java:478)
	at java.net.Socket.<init>(Socket.java:375)
	at java.net.Socket.<init>(Socket.java:218)
	at org.apache.jk.common.ChannelSocket.unLockSocket(ChannelSocket.java:492)
	at org.apache.jk.common.ChannelSocket.pause(ChannelSocket.java:288)
	at org.apache.jk.server.JkMain.pause(JkMain.java:726)
	at org.apache.jk.server.JkCoyoteHandler.pause(JkCoyoteHandler.java:153)
	at org.apache.catalina.connector.Connector.pause(Connector.java:1064)
	at org.apache.catalina.core.StandardService.stop(StandardService.java:578)
	at org.apache.catalina.core.StandardServer.stop(StandardServer.java:788)
	at org.apache.catalina.startup.Catalina.stop(Catalina.java:662)
	at org.apache.catalina.startup.Catalina$CatalinaShutdownHook.run(Catalina.java:706)
2012/08/29 16:00:01 org.apache.catalina.core.StandardService stop
情報: サービス Catalina を停止します

Tomcat のバージョンも示しておくが、多分どのバージョンでも起こる。

[appserver]# service tomcat6 version | grep version
Server version: Apache Tomcat/6.0.35

ちなみにタイトルは「Cannot stop」としているが、正確には上記のタイムアウトに時間が掛かる(3 ~ 5 分)だけで、待ってさえいれば停止する。

最初は、何でシャットダウンするのに socket が timed out するのかと思ったが、shutdown hook を機に自身のシャットダウンポートへ通信しているんだろうと勘付けば、ネットワークに問題がありそうだと察しが付く。(ソースコードは見てないので、実際はどうだか知らない)

[appserver]# ifconfig | grep 'inet addr'
          inet addr:172.16.1.10  Bcast:172.16.1.255  Mask:255.255.255.0
          inet addr:127.0.0.1  Mask:255.0.0.0
[appserver]# cat /etc/hosts
# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1		localhost.localdomain localhost
::1		localhost6.localdomain6 localhost6
192.168.10.12	appserver

案の定、実際の IP アドレス(172.16.1.10)と /etc/hosts (192.168.10.12)が食い違っている。私が試した限り、それぞれが別ネットワーク(サブネット)だと今回のエラーになるようだ。食い違いがあっても同じネットワークだと起こらない。

/etc/hosts を修正して、ネットワークを再起動すれば解決する。

[appserver]# cat /etc/hosts
# Do not remove the following line, or various programs
# that require network functionality will fail.
127.0.0.1		appserver localhost.localdomain localhost
::1		localhost6.localdomain6 localhost6

所詮、現場 SE の程度なんてこんなもんだ。IP アドレスを変更する際に /etc/hosts に気を払うこともできない。ちなみに今回の場合、上記に加え /etc/resolve.conf も /etc/sysconfig/network もボロボロだったことを付け加えておこう。

そしてこういうことがあるから、特に理由のない限り、自ホスト名は「127.0.0.1」に追加するのが正しい。これについては以前のエントリで述べた通り。

127.0.0.1 ではなくサーバー IP アドレスで登録すべき、と言われそうだが、それでは DHCP 環境で使えない。固定 IP 環境でも、永遠に IP アドレスを変えないと保証できるはずもない。その時 IP アドレスを変更する SE が、/etc/hosts に気を回すスキルを持っていると期待するほど、私はお人好しじゃない。