Dockerネットワークで遊ぶ_その6_WEBアクセス編

## はじめに

前回までで、[ルーター自作でわかるパケットの流れ](https://gihyo.jp/book/2011/978-4-7741-4745-1)をDocker内のネットワークで試すことを一通り終えた

今回はPINGだけではなく、アプリレベルでの疎通確認を行う

具体的には、２つあるネットワークの片方にWEBサーバを立てて
他方のネットワークからルーターマシンを介してブラウジング（実態は`curl`）できることを確認する

https://gihyo.jp/book/2011/978-4-7741-4745-1

### 連載情報

連載|内容
---|---
[その１](https://qiita.com/t13801206/items/0fd95e85377098f996b3)|〈実践〉同じセグメント内のPING疎通確認
[その2](https://qiita.com/t13801206/items/0858f2af9c57a9db65aa)|〈コード〉イーサネットヘッダの中身をみてみる
[その3](https://qiita.com/t13801206/items/81459f9374bffcd293ec)|〈コード〉ARP,IP,ICMPの中身をみてみる
[その4](https://qiita.com/t13801206/items/478493c1527cbcf0511c)|〈実践〉IP,TCPをキャプチャする
[その5](https://qiita.com/t13801206/items/7fa2f7bf75021899e05e)|〈実践）ルーターを準備する
[その6](https://qiita.com/t13801206/items/0aa1d0f2b762d013e9f8)|〈実践〉別セグメントのWEBへアクセスをする


## Webサーバーとの通信

Dockerの[httpd](https://hub.docker.com/_/httpd)イメージを使ってWEBサーバを立てる
httpdは下記の通り中身はApacheであり、コンテナ起動してローカルホストへアクセスすれば簡単にページが見れる

> What is httpd?
The Apache HTTP Server, colloquially called Apache, is a Web server application notable for playing a key role in the initial growth of the World Wide Web.

コンテナ起動後に`localhost:8080`へアクセスして見える実際のページ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/5ca372ba-44ce-4acd-694e-04c6ae52d9bd.png)


### Webサーバをたてる

- ポートは`8080`
- ホスト名は`WebServer`
- 静的ルーティングを変更するために特権`--privileged`を与える

```powershell
docker pull httpd
docker container run -itd --privileged --mount type=bind,source=/d/200_Study/2304_Router_Jisaku,target=/app -p 8080:80 --net testnw --hostname WebServer --name web httpd:latest
docker container exec -u root -it web /bin/bash
```

#### 静的ルートを変更する

デフォルトルートが`x.x.x.1`を向いているので
`172.19.0.0/16`宛てのパケットをルーターマシン `172.18.0.2` へ流すよう変更する

```Powershell
apt update
apt install iproute2

# ルート変更
ip route add 172.19.0.0/16 via 172.18.0.2 dev eth0
```

## 同じネットワークからWEBサーバへ疎通確認

まずは本題へ入る前に、**同じ**ネットワーク内でWEBサーバへアクセスできるか確認する

```bash:curl WebServer
<html><body><h1>It works!</h1></body></html>
```

### サーバ側からはどう見えるか？

クライアントから `nc -v -w 1 172.18.0.5 80` をした後で
WEBサーバで`netstat -tan`をすると、最終行にクライアントからのアクセスが確認できる

```bash:netstat -tan
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 127.0.0.11:44109        0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 172.18.0.5:80           172.18.0.3:45656        SYN_RECV
```

<details><summary>パケットキャプチャ結果</summary><div>

```bash
Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:03
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=61199
frag_off=2,0,ttl=64,protocol=6(TCP),check=7ff3
saddr=172.18.0.3,daddr=172.18.0.5
bad tcp checksum
Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:03
ether_shost=02:42:ac:12:00:05
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=0
frag_off=2,0,ttl=64,protocol=6(TCP),check=8fe2
saddr=172.18.0.5,daddr=172.18.0.3
bad tcp checksum
Packet[66bytes]

ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:03
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=52,id=61200
frag_off=2,0,ttl=64,protocol=6(TCP),check=86f3
saddr=172.18.0.3,daddr=172.18.0.5
bad tcp checksum
Packet[140bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:03
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=126,id=61201
frag_off=2,0,ttl=64,protocol=6(TCP),check=3bf3
saddr=172.18.0.3,daddr=172.18.0.5
bad tcp checksum
Packet[66bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:03
ether_shost=02:42:ac:12:00:05
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=52,id=23601
frag_off=2,0,ttl=64,protocol=6(TCP),check=6686
saddr=172.18.0.5,daddr=172.18.0.3
bad tcp checksum
Packet[336bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:03
ether_shost=02:42:ac:12:00:05
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=322,id=23602
frag_off=2,0,ttl=64,protocol=6(TCP),check=5785
saddr=172.18.0.5,daddr=172.18.0.3
bad tcp checksum
Packet[66bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:03
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=52,id=61202
frag_off=2,0,ttl=64,protocol=6(TCP),check=84f3
saddr=172.18.0.3,daddr=172.18.0.5
bad tcp checksum
Packet[66bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:03
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=52,id=61203
frag_off=2,0,ttl=64,protocol=6(TCP),check=83f3
saddr=172.18.0.3,daddr=172.18.0.5
bad tcp checksum
Packet[66bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:03
ether_shost=02:42:ac:12:00:05
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=52,id=23603
frag_off=2,0,ttl=64,protocol=6(TCP),check=6486
saddr=172.18.0.5,daddr=172.18.0.3
bad tcp checksum
Packet[66bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:03
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=52,id=61204
frag_off=2,0,ttl=64,protocol=6(TCP),check=82f3
saddr=172.18.0.3,daddr=172.18.0.5
bad tcp checksum
```

</div></details>

## 【本題】別ネットワークからWEBサーバへ疎通確認

上記と同じ方法で別ネットワークからWebServerにアクセスしようとしても**応答なし**

```bash
nc -zv 172.18.0.5 80
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: TIMEOUT.
```

ここから１日以上かけて長い長い原因究明をした・・・

#### サーバ側で`nc`受信

- サーバ：　`nc -vul 172.18.0.5 8888`
- クライアント： ` nc -u 172.18.0.5 8888`

##### 結果

- 同一ネットワークからは受信可
- 他ネットワークからは不可
- なお他ネットワークからも、**サーバのNICまでは届いている**

#### traceroute -I (ICMP)

ICMPは通る

```bash
traceroute -I 172.18.0.5
traceroute to 172.18.0.5 (172.18.0.5), 30 hops max, 60 byte packets
 1  myrouter.testnw2 (172.19.0.2)  0.264 ms  0.234 ms  0.311 ms
 2  172.18.0.5 (172.18.0.5)  0.696 ms  0.876 ms  1.023 ms
 ```

#### TCP Traceroute

`tcptraceroute`も通る

```bash
tcptraceroute 172.18.0.5 80
Running:
        traceroute -T -O info -p 80 172.18.0.5
traceroute to 172.18.0.5 (172.18.0.5), 30 hops max, 60 byte packets
 1  myrouter.testnw2 (172.19.0.2)  0.258 ms  0.146 ms  0.298 ms
 2  172.18.0.5 (172.18.0.5) <syn,ack>  0.558 ms  0.639 ms  0.916 ms
```

:::note info 
これは traceroute に `-T` オプションを付けたものと同等
:::

※（[前回](https://qiita.com/t13801206/items/7fa2f7bf75021899e05e)確認済だった）

##### tracerouteのオプション

`traceroute --help`より一部抜粋

>  -I  --icmp                  Use ICMP ECHO for tracerouting
>  -T  --tcp                   Use TCP SYN for tracerouting (default port is 80)
>  -U  --udp                   Use UDP to particular port for tracerouting

#### traceroute -U と traceroute (無印) が通らない

章題のとおり。
ICMPは通るが、TCP,UDPが通らない

#### サーバーのインターフェイス監視

サーバーに対するパケットしか見当たらない（＝戻りのパケットが無い！）
`saddr=172.19.0.3,daddr=172.18.0.5`

つまり
**ルーターが悪さをしているわけではなく**
**サーバマシン自体がパケットを送出していない**ということ・・・！？

<details><summary>パケットキャプチャ結果</summary><div>

```bash
Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:02
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=46342
frag_off=2,0,ttl=63,protocol=6(TCP),check=882e
saddr=172.19.0.3,daddr=172.18.0.5
bad tcp checksum

Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:02
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=46343
frag_off=2,0,ttl=63,protocol=6(TCP),check=872e
saddr=172.19.0.3,daddr=172.18.0.5
bad tcp checksum

Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:02
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=46344
frag_off=2,0,ttl=63,protocol=6(TCP),check=862e
saddr=172.19.0.3,daddr=172.18.0.5
bad tcp checksum

Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:02
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=46345
frag_off=2,0,ttl=63,protocol=6(TCP),check=852e
saddr=172.19.0.3,daddr=172.18.0.5
bad tcp checksum

Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:02
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=46346
frag_off=2,0,ttl=63,protocol=6(TCP),check=842e
saddr=172.19.0.3,daddr=172.18.0.5
bad tcp checksum

Packet[74bytes]
ether_header----------------------------
ether_dhost=02:42:ac:12:00:05
ether_shost=02:42:ac:12:00:02
ether_type=800(IP)
ip--------------------------------------
version=4,ihl=5,tos=0,tot_len=60,id=46347
frag_off=2,0,ttl=63,protocol=6(TCP),check=832e
saddr=172.19.0.3,daddr=172.18.0.5
bad tcp checksum
```

</div></details>



##### TCP Dump

`sudo tcpdump -w sample.pcap`

`pcap`ファイルはWireSharkで読める

#### （寄り道）WEBサーバのアクセスログを確認する

`/usr/local/apache2`の階層で、`vi conf/httpd.conf`
もともと # でコメントアウトされている行の # を外して、logs以下にファイルが生成されるようにする

```bash
CustomLog "logs/access_log" combined
```

`apachectl -k stop` でサーバをいったん停止して、再度コンテナに入りなおす
アクセスログにアクセスされた形跡はなかった

:::note info 
Dockerデスクトップがあれば、コンソールにログが流れるので、上記操作をしなくてもログを監視することは可能
:::

### WireSharkで詳細確認

[WireSharkでチェックサムの検証を有効化する](http://soratokumodoitnow724.blog43.fc2.com/blog-entry-308.html)

#### ip_forward有効｜別ネットワークからcurl

立ち戻って、Linux標準機能のIPフォワード機能を使ったらWEBサーバまでたどり着くか検証した

チェックサムはすべて**誤り**
だけど**通信はうまくいっている**
これは本書にも書いてあるとおり、TCPオフロード機能によってNICでチェックサムが挿入されるから、、（なのか？）

いずれにせよ、IPフォワード機能を使えば通信はできている
チェックサムは誤っているが・・（二度目）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/747e8043-dc3e-2e35-3d58-4d4e1a9dfaf8.png)

#### ip_forward無効｜自作ルーターで別ネットワークからcurl

何度もSYNパケットの再送がされており、**サーバーからのSYNACKが返ってこない**
サーバーでパケットが処理されていない
これは本当にチェックサムが誤っている？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/887dc22c-a26e-07ff-bca9-11b4020b82dc.png)

##### TCPオフロードを無効化した。うまくいった。

[こちらの記事](https://qiita.com/semind/items/9b48c6655ced95a42b03)を参考にサーバ・クライアント双方のNICのTCPオフロードを無効化した

**通信がうまくいった！！**

```bash:サーバとクライアントの両方で設定変更が必要
ethtool -K eth0 rx off
ethtool -K eth0 tx off
ethtool -K eth0 tso off
ethtool -K eth0 gro off
```

<details><summary>curl成功時のターミナル</summary><div>

このときはIPアドレスが振り替わって、クライアントが`172.19.0.3`でWEBサーバが`172.18.0.2`

```Powershell
root@Client2:/app/Src/Pcap# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
17: eth0@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:13:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.19.0.3/16 brd 172.19.255.255 scope global eth0
       valid_lft forever preferred_lft forever

root@Client2:/app/Src/Pcap# curl -v 172.18.0.2
*   Trying 172.18.0.2:80...
* Connected to 172.18.0.2 (172.18.0.2) port 80 (#0)
> GET / HTTP/1.1
> Host: 172.18.0.2
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 07 May 2023 13:22:53 GMT
< Server: Apache/2.4.57 (Unix)
< Last-Modified: Mon, 11 Jun 2007 18:53:14 GMT
< ETag: "2d-432a5e4a73a80"
< Accept-Ranges: bytes
< Content-Length: 45
< Content-Type: text/html
<
<html><body><h1>It works!</h1></body></html>
* Connection #0 to host 172.18.0.2 left intact
```

</div></details>

WireSharkにも`TCP CHECKSUM INCORRECT`が表示されなくなった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/d17ad697-e234-f884-13da-c37a880f31b7.png)

## まとめ

Dockerネットワークを2つ構築して、AネットワークのクライアントからBネットワークのWebサーバへ接続しようとした

全然つながらないまま
その結果、`nc`や`traceroute`の使い方がよく分かっていった

最終的には**TCPオフロードが邪魔をしていたようで**
WEBサーバがACKを返してこなかったことが原因（たぶん）と分かった

本書の題名の通り、まさにルータ自作でパケットの気持ちになれた気がする

## 余談～Linux標準のIPフォワードはすごい

**TCPオフロードをOFFにした状態**で、Linuxの標準IPフォワード機能を有効にしてみた
（自作ルーターは無効化）

ちゃんと`curl`でWEBページがとってこれた
当たり前かもしれないけど、すごい

## 参考

https://qiita.com/semind/items/9b48c6655ced95a42b03

## 糸冬了！！