ルーター自作でわかるパケットの流れ/小俣 光之を読みました。
本文にほとんどがソースコードのためひたすら写経しました。 ちなみに後半に差し掛かったあたりで気づいたのですが、サポートページでソースコードは公開されています。
「せっかく書いたのだから動かしたい!」という欲求が生まれたものの、本書に書かれているような物理機器が手元になかったので、netnsで仮想ネットワークを構築しました。
構成
ネットワーク構成図は以下のとおりです。
環境
手元にあった環境のため古いですが、vagrantで立てたlinux環境を使いました。
$ cat /etc/redhat-release CentOS release 6.6 (Final)
netnsを使ってネットワークを構築する
基本的にroot権限が必要です。
namespaceを区切る
host
、RT
、NextRouter
をnamespaceを区切ることで実現します。
sudo ip netns add host sudo ip netns add RT sudo ip netns add NextRouter
以下で作成されたことが確認できます。
$ ip netns show RT host NextRouter
ケーブルをつなぐ
Linux Network Namespaceでは、vethを物理ケーブルだと思うとわかりやすいです。
構成図の通り繋いでいきます。
命名規則は<ホスト名>_<I/F名>
としました。NextRouter
だけ文字制限なのか設定できなかったのでNR
と略しています。
I/Fは、本書中でルータのLAN側をeth0
、WAN側をeth1
と指定されていたので、その他の機器でも統一しています。
sudo ip link add host_veth1 type veth peer name RT_veth0 sudo ip link add RT_veth1 type veth peer name NR_veth0 sudo ip link add NR_veth1 type veth peer name Linux_veth0
上記でケーブルは繋いだので、各機器に所属させます。
sudo ip link set host_veth1 netns host sudo ip link set RT_veth0 netns RT sudo ip link set RT_veth1 netns RT sudo ip link set NR_veth0 netns NextRouter sudo ip link set NR_veth1 netns NextRouter
上記のip link set
を実行する前は、ホストOS(CentOS)上でip link show
とすると見えていたリンクが以下のようになっていると思います。
$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 08:00:27:2d:82:3a brd ff:ff:ff:ff:ff:ff 10: Linux_veth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state DOWN qlen 1000 link/ether 1a:dd:55:e7:f4:bd brd ff:ff:ff:ff:ff:ff
ifconfig
でもOKですが、この時点ではifconfig -a
としないとDOWN
しているI/Fは表示されないはずです。
lo
とeth0
はvagrantが割り当てているのもので今回の対象でないので、Linux_veth0
だけになっていることに注目してください。
言い換えると、host_veth1
、RT_veth0
、RT_veth1
、NR_veth0
、NR_veth1
がそれぞれのnamespaceに所属したので、ホストOS(CentOS)から見えなくなっています。
IPアドレスを採番する
/24
単位でセグメントを分ける設計としました。また、host
は拡張性を見据え(今は関係ないですが。。。)、若番から採番します。
next hopになる機器は老番からの採番としています。
sudo ip netns exec host ip addr add 192.168.0.1/24 dev host_veth1 sudo ip netns exec RT ip addr add 192.168.0.254/24 dev RT_veth0 sudo ip netns exec RT ip addr add 192.168.1.1/24 dev RT_veth1 sudo ip netns exec NextRouter ip addr add 192.168.1.254/24 dev NR_veth0 sudo ip netns exec NextRouter ip addr add 192.168.2.1/24 dev NR_veth1 sudo ip addr add 192.168.2.254/24 dev Linux_veth0
確認コマンドのため、ここでは省略しますが、ip addr show
で採番は確認できます。
I/Fを立ち上げる
各機器のI/Fをupさせます。lo
も忘れずに。
sudo ip netns exec host ip link set lo up sudo ip netns exec RT ip link set lo up sudo ip netns exec NextRouter ip link set lo up sudo ip netns exec host ip link set host_veth1 up sudo ip netns exec RT ip link set RT_veth0 up sudo ip netns exec RT ip link set RT_veth1 up sudo ip netns exec NextRouter ip link set NR_veth0 up sudo ip netns exec NextRouter ip link set NR_veth1 up sudo ip link set Linux_veth0 up
確認コマンドのため、ここでは省略しますが、ip link show
でstateがUP
になっていることが確認でき、ifconfig
でも見えるようになります。
ルーティングを設定する
各機器のWAN側にデフォルトゲートウェイを設定します。
ホストOS(CentOS)とNextRouterには、LAN側へstatic routeを設定します。
- ホストOS(CentOS):
192.168.0.0/24
,192.168.1.0/24
- NextRouter:
192.168.0.0/24
Direct Connectedになるセグメントはルーティング不要です。
sudo ip netns exec host ip route add default via 192.168.0.254 sudo ip netns exec RT ip route add default via 192.168.1.254 sudo ip netns exec NextRouter ip route add default via 192.168.2.254 sudo ip netns exec NextRouter ip route add 192.168.0.0/24 via 192.168.1.1 sudo ip route add 192.168.0.0/24 via 192.168.2.1 sudo ip route add 192.168.1.0/24 via 192.168.2.1
ip_forwardを有効化する
パケット転送を行えるようにip_forward
を有効化します。
ただし、RTは自作ルータのため転送機能があるためLinuxと機能が重複しないよう無効化します。
つまり、hostとNextRouterがip_forward
を有効化する対象です。
しかしnetnsでは、ネットワークまわりの設定だけがnamespaceごとに区切られていますが、ファイルシステムは共通のため、
sudo sysctl -w net.ipv4.ip_forward=1
で設定するとRTも含めて全機器で有効化されてしまいます。
netnsでは、/etc/netns/<namespace名>
が優先的に読み込まれるので、
RTについては、net.ipv4.ip_forward=0
とした/etc/sysctl.conf
を/etc/netns/RT/sysctl.conf
としてコピーしておけばOKです。
デフォルト値は、0
のはずなので、念の為sysctl net.ipv4.ip_forward
でnet.ipv4.ip_forward = 0
になっていることを確認したあと、以下のようにコピーしましょう。
sudo cp /etc/sysctl.conf /etc/netns/RT/
RT以外は共通で有効化するので以下を実行しましょう。
sudo sysctl -w net.ipv4.ip_forward=1
sysctl net.ipv4.ip_forward
でnet.ipv4.ip_forward = 1
になっていればOKです。
自作ルータ
自作ルータのコードは、サポートページで公開されています。 もしくは本書を見ていただくのがよいと思います。
パラメータ
main.c
中のパラメータは以下のように設定します。
PARAM Param = {"RT_veth0", "RT_veth1", 1, "192.168.0.254"};
実行してみる
hostからホストOS(CentOS)のveth0(192.168.2.254)
にpingを打ってみましょう。
先に自作ルータを 起動せず にpingを打ってみましょう。
sudo ip netns exec RT bash #hostにログイン $ ping 192.168.2.254 -c 1 PING 192.168.2.254 (192.168.2.254) 56(84) bytes of data. --- 192.168.2.254 ping statistics --- 1 packets transmitted, 0 received, 100% packet loss, time 10000ms
予想通り、タイムアウトします。
では、いよいよ自作ルータを試します。
sudo ip netns exec RT bash
でRTにログインしてからmake
した./router
を実行します。
(ログインしないとI/Fがないと怒られるはずです)
ping 192.168.2.254 -c 1 PING 192.168.2.254 (192.168.2.254) 56(84) bytes of data. 64 bytes from 192.168.2.254: icmp_seq=1 ttl=62 time=0.304 ms --- 192.168.2.254 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.304/0.304/0.304/0.000 ms
ちゃんとping応答がありました!
ルータ側のログ
おまけです。ルータ側のログを見てみましょう。
本書に記載のものに加えていくつか自分で見やすいようにデバッグログを追加しています。
<INFO>
となっているものが追加したものです(デバッグしながらだったので他にもあるかも)。
$ ./router NextRouter=192.168.1.254 RT_veth0 OK addr=192.168.0.254 subnet=192.168.0.0 netmask=255.255.255.0 RT_veth1 OK addr=192.168.1.1 subnet=192.168.1.0 netmask=255.255.255.0 router start
ここまでがルータ起動処理です。RT_veth0
とRT_veth1
を自作ルータに割り当て、NextRouter
のIPを設定しています。
--------AnalyzePacket------- <INFO> dst IP: 192.168.2.254 <INFO> subnet: 255.255.255.0 [0]:192.168.2.254 to NextRouter ----Ip2MacSearch---- Ip2Mac ADD [1] 192.168.1.254 = 0 <INFO> dst MAC: 00:00:00:00:00:00 Ip2Mac(192.168.1.254):NG Ip2Mac(192.168.1.254):Send Arp Request [0]:Ip2Mac:error or sending AppendSendData:[1] 192.168.1.254 98bytes(Total=1:98bytes)
上記が192.168.2.254
宛にpingを打ったログです。
192.168.2.0/24
は自作ルータのI/Fが持っているセグメントではないので、NextRouterへパケットを転送しようとしています。
しかし、NextRouterのIP192.168.1.254
に紐づくMACアドレスを知らないので、Arp Request
を投げ、pingパケットはバッファに積んでいます。
--------AnalyzePacket------- [1]recv: ARP REPLY: 42bytes ----Ip2MacSearch---- AppendSendReqData:[1] 0 Ip2Mac EXIST [1] 192.168.1.254 = 0 Ip2Mac(192.168.1.254):OK dst MAC(Ip2Mac): 7e:85:60:2f:76:fb GetSendReqData:[1] 0 GetSendData:[1] 192.168.1.254 98bytes iphdr.ttl 64->63 write:BufferSendOne:[1] 98bytes
Arp Reply
が返ってきました。これでNextRouterのMACアドレスを知ることができたのでバッファにあったpingパケットを[1](RT_veth1)
から送出しています。
L3ホップになるのでttl
も減算しています。
--------AnalyzePacket------- <INFO> dst IP: 192.168.0.1 <INFO> subnet: 255.255.255.0 [1]:192.168.0.1 to TargetSegment ----Ip2MacSearch---- Ip2Mac ADD [0] 192.168.0.1 = 0 <INFO> dst MAC: 00:00:00:00:00:00 Ip2Mac(192.168.0.1):NG Ip2Mac(192.168.0.1):Send Arp Request [1]:Ip2Mac:error or sending AppendSendData:[1] 192.168.0.1 98bytes(Total=1:98bytes)
宛先が192.168.0.1
となっていることがからもわかるように、pingの戻りパケットです。
先程と同じようにArp Request
でIP-MACアドレス解決を図っています。
--------AnalyzePacket------- [0]recv: ARP REPLY: 42bytes ----Ip2MacSearch---- AppendSendReqData:[0] 0 Ip2Mac EXIST [0] 192.168.0.1 = 0 Ip2Mac(192.168.0.1):OK dst MAC(Ip2Mac): 02:01:1d:da:db:04 GetSendReqData:[0] 0 GetSendData:[0] 192.168.0.1 98bytes iphdr.ttl 63->62 write:BufferSendOne:[0] 98bytes
Arp Reply
で192.168.0.1
に紐づくMACアドレスが学習できたので、パケットを送出します。
ここでも、L3ホップになるのでttl
も減算しています。
これらの処理が行われた結果、hostでもping応答を確認できたわけです。
最後に
Cでechoサーバやチャットサーバを書いたことがあったものの、写経時にtypoが多すぎてデバッグに時間取られました。 奇しくも動作をより理解する結果に繋がったので悪くはなかったかなと思っています。 本記事のnetnsも普段dockerがいい感じにやってくれる部分を知ることができたので、いい勉強になりました。
ちなみに、本記事タイトルは、本書タイトルそのままだと長くなりすぎなので、略しましたが通称あるんでしょうか。

- 作者: 小俣 光之
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/09
- メディア: 単行本(ソフトカバー)
- 購入: 4人 クリック: 130回
- この商品を含むブログ (12件) を見る