Network Namespaceを使ってみた

はじめに

LinuxのNetwork Namespaceを使って簡単なネットワークを構築してみました。

環境

  • 20.04.5 LTS

構築する環境


namespace( 仮装ノード)作成

$ sudo ip netns add host1
$ sudo ip netns add router
$ sudo ip netns add host2

namespaceが作成されたことを確認

$ ip netns list
host2
router
host1

veth(仮装ネットワークインターフェース)作成

host1とrouter、routerとhost2を疎通させるためにvethを作成します

$ sudo ip link add host1-veth0 type veth peer name router-veth0
$ sudo ip link add host2-veth1 type veth peer name router-veth1

vethが作成されたことを確認

$ip link
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 02:cd:6a:13:84:49 brd ff:ff:ff:ff:ff:ff
3: router-veth0@host1-veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 9a:6c:9f:8a:2f:d4 brd ff:ff:ff:ff:ff:ff
4: host1-veth0@router-veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether da:dd:38:f8:83:70 brd ff:ff:ff:ff:ff:ff
5: router-veth1@host2-veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 06:90:17:48:f0:ff brd ff:ff:ff:ff:ff:ff
6: host2-veth1@router-veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 1a:ee:a4:59:b6:4f brd ff:ff:ff:ff:ff:ff

vethをnamespaceに所属させる

$ sudo ip link set host1-veth0 netns host1
veth一覧からhost1-veth0@router-veth0が消えていることを確認
$ ip link
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
link/ether 02:cd:6a:13:84:49 brd ff:ff:ff:ff:ff:ff
3: router-veth0@if4: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 9a:6c:9f:8a:2f:d4 brd ff:ff:ff:ff:ff:ff link-netns host1
5: router-veth1@host2-veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 06:90:17:48:f0:ff brd ff:ff:ff:ff:ff:ff
6: host2-veth1@router-veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 1a:ee:a4:59:b6:4f brd ff:ff:ff:ff:ff:ff
他のvethも同様に所属させます
$ sudo ip link set router-veth0 netns router
$ sudo ip link set router-veth1 netns router
$ sudo ip link set host2-veth1 netns host2

vethを有効化する

$ sudo ip netns exec host1 ip link set host1-veth0 up
$ sudo ip netns exec router ip link set router-veth0 up
$ sudo ip netns exec router ip link set router-veth1 up
$ sudo ip netns exec host2 ip link set host2-veth1 up

IPアドレスを付与

各vethにそれぞれのセグメント内のIPアドレスを付与させます

sudo ip netns exec host1 ip address add 192.0.1.1/24 dev host1-veth0
host1-veth0にIPアドレスが付与されていることを確認
inetはIPv4 アドレスを表すのでIPアドレスが付与されていることがわかります
sudo ip netns exec host1 ip address show
1: lo: mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: host1-veth0@if3: mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether da:dd:38:f8:83:70 brd ff:ff:ff:ff:ff:ff link-netns router
inet 192.0.1.1/24 scope global host1-veth0
valid_lft forever preferred_lft forever
inet6 fe80::d8dd:38ff:fef8:8370/64 scope link
valid_lft forever preferred_lft forever
他のvethも同様にIPアドレスを付与します
$ sudo ip netns exec router ip address add 192.0.1.254/24 dev router-veth0
$ sudo ip netns exec router ip address add 196.0.1.254/24 dev router-veth1
$ sudo ip netns exec host2 ip address add 196.0.1.1/24 dev host2-veth1

疎通確認

host1からrouterへの疎通を確認

$ sudo ip netns exec host1 ping -c 3 192.0.1.254 -I 192.0.1.1
PING 192.0.1.254 (192.0.1.254) from 192.0.1.1 : 56(84) bytes of data.
64 bytes from 192.0.1.254: icmp_seq=1 ttl=64 time=0.031 ms
64 bytes from 192.0.1.254: icmp_seq=2 ttl=64 time=0.034 ms
64 bytes from 192.0.1.254: icmp_seq=3 ttl=64 time=0.033 ms

--- 192.0.1.254 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2032ms
rtt min/avg/max/mdev = 0.031/0.032/0.034/0.001 ms

疎通が通れていることがわかったのでrouterからhost2への疎通を確認します
$ sudo ip netns exec router ping -c 3 196.0.1.1 -I 196.0.1.254
PING 196.0.1.1 (196.0.1.1) from 196.0.1.254 : 56(84) bytes of data.
64 bytes from 196.0.1.1: icmp_seq=1 ttl=64 time=0.135 ms
64 bytes from 196.0.1.1: icmp_seq=2 ttl=64 time=0.051 ms
64 bytes from 196.0.1.1: icmp_seq=3 ttl=64 time=0.115 ms

--- 196.0.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2054ms
rtt min/avg/max/mdev = 0.051/0.100/0.135/0.035 ms

routerを介して通信する

同じセグメント内での疎通は確認できたので、host1からhost2へ疎通がとれるようにしていきます
現在は通信をしようとするとエラーが起きます

$ sudo ip netns exec host1 ping -c 3 196.0.1.1 -I 192.0.1.1
PING 196.0.1.1 (196.0.1.1) from 192.0.1.1 : 56(84) bytes of data.
ping: sendmsg: Network is unreachable
ping: sendmsg: Network is unreachable
ping: sendmsg: Network is unreachable

--- 196.0.1.1 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2059ms

host1にデフォルトルートを追加

現在のhost1のルーティングテーブルを確認するとhost1と違うセグメントに対する設定がされていないようです
そのためデフォルートをrouterへと向けます

$ sudo ip netns exec host1 ip route show
192.0.1.0/24 dev host1-veth0 proto kernel scope link src 192.0.1.1

デフォルトルートを追加できました

$ sudo ip netns exec host1 ip route show
default via 192.0.1.254 dev host1-veth0
192.0.1.0/24 dev host1-veth0 proto kernel scope link src 192.0.1.1
host2にも同様にデフォルトルートを追加します
$ sudo ip netns exec host2 ip route add default via 196.0.1.254

routerとして設定

最後にrouterをrouterとして動くように設定します

$ sudo ip netns exec router sysctl net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

疎通確認

host1からhost2への疎通確認をします

$ sudo ip netns exec host1 ping -c 3 196.0.1.1 -I 192.0.1.1
PING 196.0.1.1 (196.0.1.1) from 192.0.1.1 : 56(84) bytes of data.
64 bytes from 196.0.1.1: icmp_seq=1 ttl=63 time=0.029 ms
64 bytes from 196.0.1.1: icmp_seq=2 ttl=63 time=0.060 ms
64 bytes from 196.0.1.1: icmp_seq=3 ttl=63 time=0.044 ms

--- 196.0.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2051ms
rtt min/avg/max/mdev = 0.029/0.044/0.060/0.012 ms

無事に疎通を確認できました

最後に

今回はrouterを用いた簡単なネットワークを構築してみました。
Network Namespaceは手軽にネットワークに関する実験ができるのでネットワーク学習に最適だなと思いました。
これ以外にもWANやNATの設定などもできるので試してみてはいかがでしょうか。

テスト投稿

値オブジェクトの利点

ふるまいを持つ

お金を計算する場面を考えてみる。

お金には量と通貨がある。これを値オブジェクトとして定義すると以下のようになる。(ソースコードはLaravel)
お金は足す場面があるので、addメソッドとして定義する。

お金を表現するmoneyクラス

class Money 
{
    public function __construct(string $currency, int $amount)
    {
        $this->currency = $currency;
        $this->amount = $amount;
    }

    public function add(Money $money)
    {
        if($this->currency != $money->currency) {
            throw new \Exception('通貨が一致しません');
        }

        return new Money($this->currency, $this->amount + $money->amount);
    }
}

実際に計算を行う
class MoneyController extends Controller
{
    public function index()
    {
        $jpy1 = new Money('JPY', 100);
        $jpy2 = new Money('JPY', 200);
        $usd1 = new Money('USD', 1);

        // 日本円どうし
        $sumJpy = $jpy1->add($jpy2);

        // 別の通貨どうし 
        $sumMoney = $jpy1->add($usd1);
        
    }
}

値オブジェクトに別の通貨どうしの場合は例外を投げるように定義することで、別の通貨どうしが足されるというバグを防ぐことができる。
値オブジェクトはただのデータの入れ物ではなく、オブジェクトに対するふるまいとして自身に関するルールを持つことができる。

表現力を増す

userについての情報を持ったUserクラスを考える
苗字と名前を合わせてフルネームを取得したい場合、値オブジェクトに持たせることでフルネームを取得することができる。
「性:Tanaka 名:Taro」のようなフォーマットでフルネームを取得したい場合もFullNameメソッドを変更するだけで対応ができる。

class User 
{
    public function __construct(string $firstName, string $lastName)
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
        
        // 文字数チェック
        if (mb_strlen($firstName) < 3 || mb_strlen($firstName) < 3) {
            throw new \Exception('3文字以上にしてください');
        }
    }

    public function FullName() 
    {
        return $this->lastName . ' ' . $this->firstName;
    }
}
class UserController extends Controller
{
    public function index() {
        $user1 = new User('Taro', 'Tanaka');

        $FullName = $user1->FullName();
        
    }
}

不正な値を存在させない・ロジックの散在を防ぐ

先ほどのUserクラスを例に取る。
システム上では苗字・名前ともに3文字以上にしたいという場合などがある。
しかし、プログラム上では苗字・名前が2文字以下でも問題はない。
一度2文字が許容されるようのコード上で許可されてしまうと至る所で文字数チェックをする必要が出てくる。
値オブジェクトはインスタンス化する時にチェックをすることでそもそもこのような以上な値を防ぐことができる。
これは同じロジックをいたるところに書くことも防いでくれる。