Linuxで動かしながら学ぶtcp/ipネットワーク入門 輪読1

# Linuxで動かしながら学ぶtcp/ipネットワーク入門 輪読

「Linuxで動かしながら学ぶtcp/ipネットワーク入門」をdocker ubuntuを使って、動かしながら読み進めた備忘録。

## Goal
この回では、ubuntu内で仮想的なネームスペースを二つ作って、互いに通信できるところまでを目標にします。この回では**同一セグメント内での二つのdeviceでPingできる**がGoalです。


## macに実行環境構築

環境はmac環境でdocker ubuntu 20.04を使用。
```
% docker run -it --privileged ubuntu:20.04 bash
```


docker内のnamespace作成には、`--privideged`でdocker containerを`sysctrl`権限付きで起動する必要があります。

<details><summary>参考</summary>
`docker run`せずに`docker pull`して、`docker container create --name learn-tcpip ubuntu:20.04`などとしてdownloadした\[repository:tag\](=image名？)を指定し、containerに好きな名前をつけるもよしです。


<details><summary>dockerのimage名とは[repository:tag]のこと？</summary>
https://ja.stackoverflow.com/questions/32345/docker%E3%81%AE%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E5%90%8D%E3%81%A8%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E5%90%8D%E3%81%A3%E3%81%A6%E5%90%8C%E3%81%98%E3%82%82%E3%81%AE　
</details>

```
% docker create --name learn-tcpip ubuntu:20.04
f57fa4120e1e0dde140109a2cec6eed8f521b7f997cde5aa2fabbb440ff537a4
```
or
```
% docker container create --name learn-tcpip ubuntu:20.04
f57fa4120e1e0dde140109a2cec6eed8f521b7f997cde5aa2fabbb440ff537a4
```

containerを作成したら、停止中のcontainerリストを下記コマンドで表示して、containerを起動します。
```
% docker container ls -a
CONTAINER ID   IMAGE                                   COMMAND                   CREATED         STATUS                    PORTS     NAMES
f57fa4120e1e   ubuntu:20.04                            "bash"  
```

```
% docker container start f57fa4120e1e
f57fa4120e1e
```

docker containerの内部で実行していきます。
```
% docker container exec -it --privileged f57fa4120e1e bash
root@f57fa4120e1e:/#
```

</details>






## interface接続までの流れ（物理的な接続）

Network Namespaceとは、独立したネットワークと思ってよく、ネットワークインターフェイスやルーティングテーブルを持ちます。
手順は下記。
1. Namespaceを2つ作成, ns1, ns2とする
2. Network interfaceを2つ作成、ns1-veth0、ns2-veth0とする
3. interfaceをそれぞれnamespaceに接続する

一度daemonを切ると、namespaceは有効でないとエラーが表示されます。
このためnamespaceを削除して作り直します。

なお、今回使用したコマンドたちは[こちらのリンク先](https://gist.github.com/kokeshiM0chi/8119117b7e5efc4b5404ac442c9cbb94#file-scripts-learn-tcpip-md)にまとめてあります。dockerのcontainerが一旦停止するとnamespaceは無効化されますので、復旧などに。

<details><summary>daemonを再起動するとnamespaceは無効になるっぽい</summary>

```
root@f57fa4120e1e:/# ip netns list
Error: Peer netns reference is invalid.
Error: Peer netns reference is invalid.
Error: Peer netns reference is invalid.
Error: Peer netns reference is invalid.
Error: Peer netns reference is invalid.
helloworld
Error: Peer netns reference is invalid.
ns3
Error: Peer netns reference is invalid.
ns2
Error: Peer netns reference is invalid.
ns1
```

```
root@f57fa4120e1e:/# ip netns delete ns1
root@f57fa4120e1e:/# ip netns delete ns2
root@f57fa4120e1e:/# ip netns delete ns3
```
</details>


```
root@f57fa4120e1e:/# sudo ip netns add ns1
root@f57fa4120e1e:/# sudo ip netns add ns2
root@f57fa4120e1e:/# sudo ip netns add ns3
```

`ns1-veth0`と`ns2-veth0`でvethのnetwork interfaceを作ります。
interfaceの実体はファイルである。すでにある場合はファイルが既にあるとエラーメッセージが出力されます。
```
root@f57fa4120e1e:/# sudo ip link add ns1-veth0 type veth peer name ns2-veth0 
```

```
root@f57fa4120e1e:/# sudo ip link show | grep veth
4: ns2-veth0@ns1-veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
6: ns1-veth0@ns2-veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
root@b
```

作成したinterfaceをnetwork namespaceの持ち物にします。
```
root@f57fa4120e1e:/# sudo ip link set ns1-veth0 netns ns1
root@f57fa4120e1e:/# sudo ip link set ns2-veth0 netns ns2
```

namespace内のinterfaceになるので、linkをshowしても何も表示されなくなります。
（スコープが区切られたみたいな感じ）
```
root@f57fa4120e1e:/# sudo ip link show | grep veth
root@f57fa4120e1e:/# 
```

namespaceを指定して、ip linkを表示します。
```
root@f57fa4120e1e:/# sudo ip netns exec ns1 ip link show | grep veth
6: ns1-veth0@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
root@f57fa4120e1e:/# sudo ip netns exec ns2 ip link show | grep veth
4: ns2-veth0@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
```

作成済みのnamespace ns1, ns2に対して、ip linkが作成されています。

これでveth interfaceは仮想的なLANケーブルの役割を担います。なので、二つのnamespaceはこの仮想的なLANケーブルで繋がっています。
これはLANケーブルで二つのコンピュータが接続されているのと同じような感じです。
veth interfaceにip addressを付与すれば通信ができます。

ここまでで以下のように接続ができました。LANケーブルでコンピュータ同士が接続されたように、仮想ネットワークネームスペースがインターフェースを通じて接続されています。

![network1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/443312/e1f3cbf2-d640-2a0e-cbb7-d2d7200a3d21.png)

これはLANケーブルでコンピュータを接続している状態です。ip addressを設定して通信ができるようになります。

## interfaceにip addressを付与（論理的な設定）

各ネットワークスペースそれぞれでip addressを割り当てます。
```
root@f57fa4120e1e:/# sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0
root@f57fa4120e1e:/# sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0
```

interfaceの状態を確認します。
```
root@f57fa4120e1e:/# sudo ip netns exec ns1 ip link show ns1-veth0 | grep state
6: ns1-veth0@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
root@f57fa4120e1e:/# sudo ip netns exec ns2 ip link show ns2-veth0 | grep state
4: ns2-veth0@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
```
network interfaceの`STATE`の項目がDOWNになっています。明示的にUPにすることで通信ができます。

```
root@f57fa4120e1e:/# sudo ip netns exec ns1 ip link set ns1-veth0 up
root@f57fa4120e1e:/# sudo ip netns exec ns2 ip link set ns2-veth0 up
```

`state UP`になりました。
```
root@f57fa4120e1e:/# sudo ip netns exec ns1 ip link show ns1-veth0 | grep state
6: ns1-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
root@f57fa4120e1e:/# sudo ip netns exec ns2 ip link show ns2-veth0 | grep state
4: ns2-veth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
root@f57fa4120e1e:/# 
```

## pingしてみむとす

ns1からns2のIPに向けてpingしてみむとす。

<details><summary>dockerにping install</summary>

docker imageに`ping`がないときは次のコマンドで入れましょう。
```
apt-get install iputils-ping net-tools
```
https://qiita.com/manabuishiirb/items/26de8c9740a1d2c7cfdd
　
</details>

```
root@b27d3bad4e97:/# sudo ip netns exec ns1 ping -c 3 192.0.2.2
PING 192.0.2.2 (192.0.2.2) 56(84) bytes of data.
64 bytes from 192.0.2.2: icmp_seq=1 ttl=64 time=0.724 ms
64 bytes from 192.0.2.2: icmp_seq=2 ttl=64 time=0.211 ms
64 bytes from 192.0.2.2: icmp_seq=3 ttl=64 time=0.147 ms

--- 192.0.2.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2084ms
rtt min/avg/max/mdev = 0.147/0.360/0.724/0.258 ms
```

ns1からns2にpingを送信して返ってくることが確認できました。
これでPingの疎通ができました。

┗(｀･∀･´)ﾖｯｼｬ!

**物理的な配線を論理的に設定する**ことで通信することができるってことです。

論理構成されたネットワークはこのようになってます。

![network1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/443312/a20cc6a1-930d-d579-8884-677af74d97de.png)

ここまででTCP/IPの実験環境の準備ができました。

ルータがいなくても通信できるのは、同じセグメント内同士だからです。
ルータは異なるセグメントと通信する為の機能です。
セグメントとは何か、ルータとは何かをよりよく理解するためにはIP Addressの設計をよく知ることが必要です。次のページではIP Addressについてみていきます。

## IP Addressで理解するセグメント

先ほど、ip addressを付与した時のコマンドをもう一度見てみます。
```
root@f57fa4120e1e:/# sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0
root@f57fa4120e1e:/# sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0
```

1行目のコマンドでは、`dev`でdevice名を指定し、`ns1-veth0`としました。
また、ip addressは`192.0.2.1/24`が値として割り当てられています。
2行目のコマンドについても同様です。まとめると次のような値の割り当てが行われています、

|namespace name  |device  |ip address  |
|---|---|---|
|ns1  |ns1-veth0  |192.0.2.1/24  |
|ns2  |ns2-veth0  |192.0.2.2/24  |

ip addressは32bitの2進数表現を8bitで区切り、10進数に変換されたものです。
今回は、共通部分は`192.0.2`です。これがセグメントを表します。`/24`は32bitsのうち、何bitsをセグメントに割り当てるかを表します。ですから、この場合、残る8bitを10進数表現した`1`及び`2`が同一セグメント内におけるip addressを指します。

次の図のような構成でip addressを割り当てるコマンドだったのです。

![ipaddress.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/443312/d0fe74e4-7cf6-3905-f428-9c6bd8829f31.png)

今回は、ルータを使わず、同一セグメント内で通信できるネットワークを作りました。次はルータを使用して、異なるセグメント同士で通信できるネットワークを作ります。