「ソースコードで体感するネットワークの仕組み」のサンプルコードを動かすためにVagrantを用いてネットワーク構成環境を整える

# Vagrantを用いて書籍のプログラムのネットワーク構成環境を整える

## 動機

[ソースコードで体感するネットワークの仕組み ~手を動かしながら基礎からTCP/IPの実装までがわかる](https://www.amazon.co.jp/s/ref=nb_sb_noss?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&url=search-alias%3Daps&field-keywords=%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)の本を読んでいるが、サンプルコード動かすのに、Vagrant用いて試行錯誤的にネットワーク構成してみた。

------

+ ホストOS .. macOS X
+ ゲストOS(複数台とも) .. ubuntu16.04

## 第2章 仮想IPホストプログラム

事前にサンプルプログラム[^sample_p]をダウンロードしておく。

[^sample_p]: http://gihyo.jp/book/2018/978-4-7741-9744-9/support#supportDownload にある

Vagrantでは、NATのネットワーク(デフォルトのネットワークアドレスは`10.0.2.0/24`)がデフォルトで構成されていて、`10.0.2.2`がルータとして、すでに配置されているので、一番単純に構成するならば、

```.ruby:Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.synced_folder ".", "/home/vagrant/test"
  config.vm.provider :virtualbox do |vb|
    # change 10.0.2.0/24 with what you want
    # See also https://www.virtualbox.org/manual/ch09.html#changenat
    vb.customize ['modifyvm', :id, '--natnet1', '192.168.111.0/24']
  end
end
```

となる。`vb.customize ..`の部分は、デフォルトの`10.0.2.0/24`ネットワークを変更しに行っている:


```.bash:ゲストOS
$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.111.2   0.0.0.0         UG    0      0        0 eth0
192.168.111.0   *               255.255.255.0   U     0      0        0 eth0
```

```./MyEth.ini
IP-TTL=64
device=eth0
# ホストOS:  `ip a`のeth0ネットワークインターフェース。
vmac=08:00:27:16:16:86
vip=192.168.111.15
vmask=255.255.255.0
# `route | grep default` or `netstat -rn`でGenmaskが0.0.0.0のやつ or `ip route` のdefaultの行
gateway=192.168.111.2
```

で「とりあえずは」OK。(./MyEth.iniはサンプルプログラムの構成ファイル) 
ゲスト側OS(ubuntu)で、`ping 8.8.8.8`をうつと、`192.168.111.2`を経由して、8.8.8.8(googleのDNS)に疎通する[^traceroute]

[^traceroute]: `traceroute 8.8.8.8`で詳細を確かめられる

### 自力でroutingできるようにする

#### 課題点

上で基本的にOKだと思うんだけど、複数台のゲストOSに対しては、この構成は本質的に問題がある。というのは、NATのネットワークはゲストOSごとにネットワークを構成するので、
ゲストOSは複数台構成しても同じIPアドレスになってしまっているから。http://zorinos.seesaa.net/article/450304938.html のNATの図が非常にわかりやすい。

やりたいこととしては、client PCで`ping 8.8.8.8`すると、master PC(デフォルトゲートウェイとして機能)の`192.168.111.2`を経由して[^route]、`8.8.8.8`に疎通させたい。
そのためには以下のような構成(内部ネットワーク)にする。


[^route]: この後、natネットワークのルータ`10.0.2.2`を経由する。その後、ホストOS(MAC OS X)の "en0: Wi-Fi (AirPort)"のネットワークのルータを経由する。

```.ruby:Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.define "master" do |conf|
    conf.vm.box = "ubuntu/trusty64"
    conf.vm.network "private_network", ip: "192.168.111.2", virtualbox__intnet: "mynet"
  end


  config.vm.define "client" do |conf|
    conf.vm.box = "ubuntu/trusty64"
    conf.vm.network "private_network", ip: "192.168.111.100", virtualbox__intnet: "mynet"
    conf.vm.synced_folder ".", "/home/vagrant/test"
  end
end
```

Note) 複数のゲストOSに`virtualbox__intnet`で同名を指定すると、内部ネットワークが構成できる。このネットワークはホストOS(MacOS X)から見えない。
ホストOS(macOS X)から`ifconfig`してもvboxnetのnetwork interfaceが見えないこことで確認可能だ。[^host_only]

[^host_only]: `virtualbox__intnet`なしだと、clientのネットワークがhostonly networkになるので、ホストOS(macOS X)から`ifconfig`すると、`vboxnet`のネットワークインターフェースがあらわれると思います。


----

この後、clientのPC(`192.168.111.100`)に対して、`192.168.111.2`をデフォルトゲートウェイとするために、masterのマシンを自力でrouterにする必要がある。
(clientのPCは複数台構成可能で、そうしたい場合は、Vagrantfileで`client0`, `client1`として、ipアドレスを一意にした上で、`client`の設定と同じように書けば良さそう。

```.bash:clientVM
$ vagrant ssh client
$ traceroute 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.0.2.2 (10.0.2.2)  0.298 ms  0.819 ms  0.553 ms
 2  192.168.150.1 (192.168.150.1)  58.173 ms  58.104 ms  59.045 ms
 ...
10  108.170.238.85 (108.170.238.85)  7.513 ms 72.14.236.129 (72.14.236.129)  7.435 ms 209.85.143.49 (209.85.143.49)  7.364 ms
11  google-public-dns-a.google.com (8.8.8.8)  7.398 ms  7.326 ms  7.916 ms
```

ということで`10.0.2.2`のrouterがdefault gatewayになっている。`route`コマンドでもわかるけど。(VM同士はpingで疎通可能になっている)

今の状態から、`sudo route add default gw 192.168.111.2 && sudo route del default gw 10.0.2.2`としてdefault gatewayを変更しても、`192.168.111.100`のPC(master)には、routing設定がされていないため、routingできない。(つまり、`ping 8.8.8.8`が応答しない) 

そこで、自力でmasterのPCをroutingできるようにしよう。

#### ルータ化する

とは言っても基本的には、http://maruchan-shiro123.hatenablog.com/entry/2015/04/05/181854 の通りにすれば良い。いくつか変更した点をば。

+ SNATの部分は、`sudo iptables -t nat -A POSTROUTING -o eth0 -s 192.168.111.0/24 -j MASQUERADE`として、sオプションを明示的にしるしたほうが良さそう。

+ client側のPCのデフォルトゲートウェイを変更する。つまり、

```.ruby:Vagrantfile
Vagrant.configure("2") do |config|
  # snip
  config.vm.define "client" do |conf|
    conf.vm.box = "ubuntu/trusty64"
    conf.vm.network "private_network", ip: "192.168.111.100", virtualbox__intnet: "mynet"
    conf.vm.synced_folder "../eth", "/home/vagrant/test"
    # see also https://www.vagrantup.com/docs/networking/public_network.html#default-router
    gw = "192.168.111.2"
    conf.vm.provision "shell",
      run: "always",
      inline: "route add default gw #{gw}"

    conf.vm.provision "shell",
      run: "always",
      inline: "eval `route -n | awk '{ if ($8 ==\"eth0\" && $2 != \"0.0.0.0\") print \"route del default gw \" $2; }'`"
  end
  # snip
end
```

と変更する。

Note) `route add default gw ...`とかはVMの再起動時に情報失われるので、`run: "always"`としている。

----

以上のように設定すると、clientでの`ping 8.8.8.8`がgatewayの`192.168.111.2`を経由して通る。`traceroute 8.8.8.8`で確認すると、

```clientVM
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  192.168.111.2 (192.168.111.2)  0.278 ms  0.209 ms  0.168 ms
 2  * * *
 3  * * *
 4  ... プロバイダのipアドレス
 ...
 11  google-public-dns-a.google.com (8.8.8.8)  18.878 ms  19.249 ms  12.319 ms
```

みたいな感じになる。[^ping_todo]

[^ping_todo]: todo: iptablesの設定を変更すれば良さそう

Note) 

2,3が`* * *`は、本当は、

```
 2  10.0.2.2 (10.0.2.2)  0.217 ms  0.178 ms  0.058 ms
 3  ルータ名 (192.168.10.221)  2.500 ms  2.952 ms  3.110 ms
```

みたいな感じなんだけど、フォワードの方向しかping許していないので、アスタリスク表示になる。

確認取れたので、

```./MyEth.ini
IP-TTL=64
device=eth1
# `ip a`でeth1インタフェースを確認して所用のMACアドレスに変更する。
vmac=08:00:27:e0:6a:24
vip=192.168.111.100
vmask=255.255.255.0
gateway=192.168.111.2
```

でプログラムを動かすと良い。

## 第3章: UDP通信に対応させ、DHCPクライアント機能を実装しよう

これは、素直に

```.ruby:Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/trusty64"
  # If err, see also the issue #3083, https://github.com/hashicorp/vagrant/issues/3083
  config.vm.network :private_network, type: "dhcp", ip: "192.168.111.0"
  config.vm.synced_folder ".", "/home/vagrant/test"
end
```

で良いと思う[^dhcp_error]。

[^dhcp_error]: 自分は途中で、`A host only network interface you're attempting to configure via DHCP`の文言のエラーが出てしまったので、https://github.com/hashicorp/vagrant/issues/3083 みて解決した

```bash
sudo grep -R "DHCPOFFER" /var/log/*
```

すると、DHCP serverの在り処が`192.168.111.2`とわかる。
ホストOSで`ifconfig`とすると、`192.168.111.1`となっているのがわかる。

```.bash:macOSX
$ ifconfig
vboxnet0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
	ether 0a:00:27:00:00:00
	inet 192.168.111.1 netmask 0xffffff00 broadcast 192.168.111.255
```

ゲストOSのデフォルトゲートウェイは`route`で確認すると、`10.0.2.2`となっている。

TODO) 今回は必要なかったが、ついでにDHCPサーバーを自力で構成するのもやってみたい

### 第4章： TCPの基本機能を追加しよう

これは、第2章のネットワーク構成と全く同じなので、そのままでよさそう。


## 感想

そもそも、ネットワーク自体しっかり理解していない部分も多かったので、結構時間がかかってしまったが、結果的には、自力でのネットワーク構築、nat, iptablesとかがわかって得るものが多いと感じた。