ipコマンドとnetnsでお手軽なテストクライアント作成

この記事はLinux Advent Calendar 2014の9日目の記事です。

ネットワークを使う機能でなにかしらテストしたいときに複数のクライアントが欲しい時がありますよね。大量アクセスをしたい場合はjmeterとかありますが、クライアントのIPアドレス複数あったほうが良いケースもあると思います。kvm等でクライアントを複数作ってbridgeするという方法もありますが、それはちょっと重量級なのでもうちょい手軽な方法がないかなーというところです。 そこでお手軽な方法はなにかというところでネットワークネームスペース(netns)を使って見たいと思います。
ネットワークネームスペースとはなんぞや?という方はten_forwardさんがgihyo.jpで連載している「LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術」の「第6回 Linuxカーネルのコンテナ機能[5] ─ネットワーク」が参考になると思います。

まず名称を決めたいと思います。ネットワークネームスペースに参加している方をゲスト、デフォルトのネットワークネームスペースに参加している方をホストと呼んでいきます。 ちなみに、テストした環境はkvmのゲストなので適当な図で書くと↓のような感じになってます。

kvm host ----------------|---- -------------------- hostbr0  -------------------- host-veth  ---------|-----------  guest-veth
192.168.11.3/24   |        192.168.122.149/24              |    192.168.122.150/24

kvmのネットワークは192.168.122.0/24です。hostbr0はkvmゲストで作ったbridgeです。host-vethとguest-vethはvethでこの2個はペアになってます。

今回やりたいのはクライアントに対してIPアドレスを個別に振りたいのでNATではなくてbridgeを使います。 では、まずはbridgeを作ります。

hostbr0というbridge作成。

[root@dockertest ~]# ip link add hostbr0 type bridge

bridgeをlink upします。

[root@dockertest ~]# ip link set hostbr0 up

物理nicもlink upします。

[root@dockertest ~]# ip link set ens3 up

作成したbridgeにnic追加します。brctl addifと同じです。

[root@dockertest ~]# ip link set dev ens3 master hostbr0

bridgeの確認してみます。

[root@dockertest ~]# bridge link show
2: ens3 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master hostbr0 state forwarding priority 32 cost 19

bridgeにipアドレス設定

[root@dockertest ~]# ip addr add 192.168.122.149/24 dev hostbr0

デフォルトルートの設定

[root@dockertest ~]# ip route add default via 192.168.122.1

kvmホスト側にpingが打てるかチェック

[root@dockertest ~]# ping -c4 192.168.11.3
PING 192.168.11.3 (192.168.11.3) 56(84) bytes of data.
64 bytes from 192.168.11.3: icmp_seq=1 ttl=64 time=0.558 ms
64 bytes from 192.168.11.3: icmp_seq=2 ttl=64 time=0.361 ms
64 bytes from 192.168.11.3: icmp_seq=3 ttl=64 time=0.411 ms
64 bytes from 192.168.11.3: icmp_seq=4 ttl=64 time=0.370 ms

ここまででnamespaceを特に設定していないLinux環境にてbrdigeの設定ができたので、次にネットワークネームスペースを作り、テスト用クライアントにIPアドレスを降ってみたいと思います。

最初にvethを作成します。guest-vethがこれから作るネームスペースに入ります。

[root@dockertest ~]# ip link add host-veth type veth peer name guest-veth

host-vethをhostbr0に追加します。

root@dockertest ~]#  ip link set dev host-veth master hostbr0

追加できたことを確認しましょう。

[root@dockertest ~]# bridge link show
2: ens3 state UP : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master hostbr0 state forwarding priority 32 cost 19
6: host-veth state DOWN : <BROADCAST,MULTICAST> mtu 1500 master hostbr0 state disabled priority 32 cost 2

ネットワークネームスペースの確認。何も表示されないのでデフォルト以外はありません。

[root@dockertest ~]# ip netns

testnsというネームスペースを作ります。

[root@dockertest ~]# ip netns add testns

guest-vethをtestnsに所属させます。

[root@dockertest ~]# ip link set guest-veth netns testns

testnsにguest-vethが存在するか確認。

root@dockertest ~]# ip netns exec testns ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: guest-veth: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 22:e4:31:f1:a8:f9 brd ff:ff:ff:ff:ff:ff

ipコマンドでnetnsを指定しながら操作するのが面倒なのでネットワークネームスペースをtestnsに指定してbashを起動します。

[root@dockertest ~]# ip netns exec testns bash

本当にtestnsに入っているか確認。ちゃんとtestnsにいます。

[root@dockertest ~]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: guest-veth: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 22:e4:31:f1:a8:f9 brd ff:ff:ff:ff:ff:ff

guest-vethにipを振ります。

[root@dockertest ~]# ip addr add 192.168.122.150/24 dev guest-veth

guest-vethをlink up。

[root@dockertest ~]# ip link set guest-veth up

デフォルトゲートウェイを設定。

[root@dockertest ~]# ip route add default via 192.168.122.1

tesutnsの環境からhostbr0に対してpingを実行

[root@dockertest ~]# ping -c 2 192.168.122.149
PING 192.168.122.149 (192.168.122.149) 56(84) bytes of data.
64 bytes from 192.168.122.149: icmp_seq=1 ttl=64 time=0.147 ms
64 bytes from 192.168.122.149: icmp_seq=2 ttl=64 time=0.061 ms

--- 192.168.122.149 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.061/0.104/0.147/0.043 ms

kvmホストに対してもpingが届くか確認

[root@dockertest ~]# ping -c 2 192.168.11.3
PING 192.168.11.3 (192.168.11.3) 56(84) bytes of data.
64 bytes from 192.168.11.3: icmp_seq=1 ttl=64 time=0.630 ms
64 bytes from 192.168.11.3: icmp_seq=2 ttl=64 time=0.342 ms

--- 192.168.11.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms

そうしたらkvmホスト側でhttpサーバを起動してみてアクセスログでどのIPからリクエストが来たか確認しましょう。

まずはデフォルトネームスペース側から。IPは192.168.122.149になるはずです。

[root@dockertest ~]# ip -4 a l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    inet 172.17.42.1/16 scope global docker0
       valid_lft forever preferred_lft forever
4: hostbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    inet 192.168.122.149/24 scope global hostbr0
       valid_lft forever preferred_lft forever
[root@dockertest ~]# curl  http://192.168.11.3:8080/index.html
hello, world

サーバ側のログで192.168.122.149からのアクセスが確認できます。

masami@saga:~/test$  python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 ...
192.168.122.149 - - [07/Dec/2014 12:08:36] "GET /index.html HTTP/1.1" 200 -

次にtesnsから実行します。

[root@dockertest ~]# ip netns exec testns bash
[root@dockertest ~]# ip -4 a l
5: guest-veth: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 192.168.122.150/24 scope global guest-veth
       valid_lft forever preferred_lft forever
[root@dockertest ~]# curl http://192.168.11.3:8080/index2.html
hello, world

サーバ側のログでindex2.htmlへ192.168.122.150からアクセスが来てますね。期待通りです( ´∀`)bグッ!

masami@saga:~/test$  python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 ...
192.168.122.149 - - [07/Dec/2014 12:08:36] "GET /index.html HTTP/1.1" 200 -
192.168.122.150 - - [07/Dec/2014 12:10:41] "GET /index2.html HTTP/1.1" 200 -

ipコマンドとネットワークネームスペースを使うことで比較的簡単にクライアントを作れました。

p.s. Linux Kernel側でのnamespaceの実装についてはこちらに資料を上げてあります。

Linux Namespace

ルーター自作でわかるパケットの流れ

ルーター自作でわかるパケットの流れ