Closed29

Linuxで動かしながら学ぶTCP/IP

ganyariyaganyariya

第 2 章 What is tcp/ip ?

root@1a09944cbb48:/# ping -c 10 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=36 time=36.9 ms
...
64 bytes from 8.8.8.8: icmp_seq=10 ttl=36 time=14.9 ms

--- 8.8.8.8 ping statistics ---
10 packets transmitted, 10 received, 0% packet loss,

ping は IP 上で ICMP で通信するコマンド。
10 回のパケットを送信して、そのうちすべての応答が帰ってきている。
ttl は何台のルータを経由したかを表し、通るごとに減少していく。 (127.0.0.1 だと ttl=64 なので、 64-36 個通ってきたっぽい。)

以下で、今のネットワークインターフェース一覧を表示する

ip address show
ganyariyaganyariya

https://knowledge.sakura.ad.jp/23899/

Docker には 3 種類のネットワークモデルが存在する。

  • bridge が最も基本的なもの。コンテナ内からの通信はインターネット側にルーティングできる。
    • docker-compose では linux-tcpip_default のように、directory name から自動で固有のインターネットが作成される。同じインターネット内はサービス名で ip address が自動解決される。
  • host はホストマシンのインターフェースを共有するもの。
  • none はインターフェースがないため、外部のネットワークとは通信できない。

ganyariyaganyariya

tcpdump はコンピュータに流れる通信をキャプチャできるプログラム。

それぞれの行が1つのパケットに相当する。

172.19.0.2 (docker ubuntu container) から google 8.8.8.8 に icmp を送信している。

tcpdump -tn -i any icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
IP 172.19.0.2 > 8.8.8.8: ICMP echo request, id 3, seq 1, length 64
IP 8.8.8.8 > 172.19.0.2: ICMP echo reply, id 3, seq 1, length 64
IP 172.19.0.2 > 8.8.8.8: ICMP echo request, id 3, seq 2, length 64
IP 8.8.8.8 > 172.19.0.2: ICMP echo reply, id 3, seq 2, length 64

traceroute は経由してきたルータを取得するコマンド。
TTL を 1, 2, 3 など小さい値に設定して順番に送信する。
0 になるとルータはパケットを破棄し「うまくいかなかったよパケット」を返送してくる。
このうまくいかなかったよパケットの送信元IPアドレスを見て、どのルータを通ってきているかを調べられる。

root@10a73e451191:/# traceroute -I 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  172.19.0.1 (172.19.0.1)  0.344 ms  0.019 ms  0.011 ms
 2  192.168.65.5 (192.168.65.5)  0.225 ms  0.025 ms  0.019 ms
 3  192.168.11.1 (192.168.11.1)  10.518 ms  11.034 ms  11.197 ms
 4  210.147.178.49 (210.147.178.49)  18.505 ms  18.753 ms  18.882 ms
 5  210.147.178.50 (210.147.178.50)  19.213 ms  19.344 ms  19.607 ms
 6  72.14.205.0 (72.14.205.0)  20.217 ms  9.951 ms  9.926 ms
 7  209.85.245.91 (209.85.245.91)  10.207 ms  14.119 ms  14.223 ms
 8  108.170.233.191 (108.170.233.191)  14.464 ms  15.709 ms  15.569 ms
 9  dns.google (8.8.8.8)  15.500 ms  15.551 ms  17.308 ms
ganyariyaganyariya

何も該当するものがないときは、 172.19.0.1 に送信する(デフォルトゲートウェイ)。
dev eth0、つまり eth0 から送信することを表す。

root@10a73e451191:/# ip route show
default via 172.19.0.1 dev eth0 
172.19.0.0/16 dev eth0 proto kernel scope link src 172.19.0.2
ganyariyaganyariya

docker-network

https://qiita.com/Arturias/items/b538e6bbf05dd3364397

  • veth
    • 仮想的なネットワークインターフェース (NIC)
    • docker 内の各コンテナのNIC には veth が利用されている。つまり、各コンテナに対して1つの veth が作成されている。
  • docker0
    • Docker によって作成された仮想ブリッジ(Layer2)
    • Docker Container をつなぐもの
    • Docker for Mac からは見れない

Mac からだとよくわからないので、 linux vpn 借りて docker 入れるのが早そう


ganyariyaganyariya

第 3 章

router1 に以下を設定する必要がある。

  • 198.51.100.1 に送信するために、 198.51.100.0/24 の通信が来たら、 gw1-veth0 (203.0.113.1) を利用して 203.0.113.2 に向けて送信する。
    • 上記を設定しないと、 router1 は 198.51.100.0 の情報を持っていない(192.0.2.0 と 203.0.113.0 しかわからないため)
  • ip netns exec router1 ip route add 198.51.100.0/24 via 203.0.113.2

以下は以下のように読み解く。

  • 192.0.2.0/24 向けの送信では、gw1-veth0 nic (192.0.2.254) から送信する。
  • 198.51.100.0/24 向けに送信したい場合は、 gw-veth1 から送信し、 203.0.113.2 にとりあえず送信する。
  • 203.0.113.0/24 向けの送信では、gw1-veth1 nic (203.0.113.1) から送信する。
root@8990a3e50db6:/# ip netns exec router1 ip route
192.0.2.0/24 dev gw1-veth0 proto kernel scope link src 192.0.2.254 
198.51.100.0/24 via 203.0.113.2 dev gw1-veth1 
203.0.113.0/24 dev gw1-veth1 proto kernel scope link src 203.0.113.1 
ganyariyaganyariya

第 4 章

ns1 から ns2 への通信, -e をつけることでイーサネットの情報も送信できる。

00:00:5e:00:53:01 > 00:00:5e:00:53:02 は データリンク層イーサネットの通信を表している。
また、そのときに 192.0.2.1 > 192.0.2.2 の IP 層のパケットを送っている。

root@8990a3e50db6:/# ip netns exec ns1 tcpdump -tnel -i ns1-veth0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns1-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
00:00:5e:00:53:01 > 00:00:5e:00:53:02, ethertype IPv4 (0x0800), length 98: 192.0.2.1 > 192.0.2.2: ICMP echo request, id 259, seq 1, length 64
00:00:5e:00:53:02 > 00:00:5e:00:53:01, ethertype IPv4 (0x0800), length 98: 192.0.2.2 > 192.0.2.1: ICMP echo reply, id 259, seq 1, length 64
00:00:5e:00:53:01 > 00:00:5e:00:53:02, ethertype IPv4 (0x0800), length 98: 192.0.2.1 > 192.0.2.2: ICMP echo request, id 259, seq 2, length 64
00:00:5e:00:53:02 > 00:00:5e:00:53:01, ethertype IPv4 (0x0800), length 98: 192.0.2.2 > 192.0.2.1: ICMP echo reply, id 259, seq 2, length 64
ganyariyaganyariya

ARP を利用して送信先コンピュータの mac アドレスを取得する。

00:00:5e:00:53:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.2 tell 192.0.2.1, length 28 で、 ローカルネットワーク全体に 192.0.2.2 を持つ機器の mac address を問い合わせている。

すると、返答として 00:00:5e:00:53:02 > 00:00:5e:00:53:01, ethertype ARP (0x0806), length 42: Reply 192.0.2.2 is-at 00:00:5e:00:53:02, length 28 192.0.2.2 が持っているよ、という mac address の返事が来る。
あとは、この mac address の機器に対して、 データリンク層 としてデータフレームを送信する。
データリンク層で、同じネットワーク内のフレーム送信を行っている。異なるネットワークは ip address &ネットワーク層

root@8990a3e50db6:/# ip netns exec ns1 ip neigh flush all
root@8990a3e50db6:/# ip netns exec ns1 tcpdump -tnel -i ns1-veth0 icmp or arp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ns1-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
00:00:5e:00:53:01 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.2 tell 192.0.2.1, length 28
00:00:5e:00:53:02 > 00:00:5e:00:53:01, ethertype ARP (0x0806), length 42: Reply 192.0.2.2 is-at 00:00:5e:00:53:02, length 28
00:00:5e:00:53:01 > 00:00:5e:00:53:02, ethertype IPv4 (0x0800), length 98: 192.0.2.1 > 192.0.2.2: ICMP echo request, id 263, seq 1, length 64
00:00:5e:00:53:02 > 00:00:5e:00:53:01, ethertype IPv4 (0x0800), length 98: 192.0.2.2 > 192.0.2.1: ICMP echo reply, id 263, seq 1, length 64
ganyariyaganyariya

複数の LAN をまたぐ場合の ARP の解決について

https://github.com/momijiame/linux-tcpip-book/blob/master/commands/4.ethernet/multi-domain.txt

router の gw-veth0 の NIC

192.0.2.1 は 198.51.100.1 に送信しようとしている。
このとき、 ns1 はルーティングテーブルを見て、デフォルトルートを見て 192.0.2.254 にひとまず送信する必要がある、と判断する。
(198.51.100.0/24 系の設定がないため、デフォルトルート)
よって、 192.0.2.254 に送信するために ARP を利用して、 00:00:5e:00:53:12 に送信する。

root@8990a3e50db6:/# ip netns exec router tcpdump -tnel -i gw-veth0 icmp or arp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on gw-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
00:00:5e:00:53:11 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 192.0.2.254 tell 192.0.2.1, length 28
00:00:5e:00:53:12 > 00:00:5e:00:53:11, ethertype ARP (0x0806), length 42: Reply 192.0.2.254 is-at 00:00:5e:00:53:12, length 28
00:00:5e:00:53:11 > 00:00:5e:00:53:12, ethertype IPv4 (0x0800), length 98: 192.0.2.1 > 198.51.100.1: ICMP echo request, id 303, seq 1, length 64
00:00:5e:00:53:12 > 00:00:5e:00:53:11, ethertype IPv4 (0x0800), length 98: 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 303, seq 1, length 64

router の gw-veth1 の NIC

その後、 router は同様に 198.51.100.1 を探そうとする。
すると、 router のルーティングテーブルには 198.51.100.0 系が設定されている。(つまり、同じ LAN 内に存在するとわかる)
よって、そのまま ARP で 198.51.100.1 の mac address を調べて送信する。

root@8990a3e50db6:/# ip netns exec router tcpdump -tnel -i gw-veth1 icmp or arp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on gw-veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
00:00:5e:00:53:21 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 198.51.100.1 tell 198.51.100.254, length 28
00:00:5e:00:53:22 > 00:00:5e:00:53:21, ethertype ARP (0x0806), length 42: Reply 198.51.100.1 is-at 00:00:5e:00:53:22, length 28
00:00:5e:00:53:21 > 00:00:5e:00:53:22, ethertype IPv4 (0x0800), length 98: 192.0.2.1 > 198.51.100.1: ICMP echo request, id 303, seq 1, length 64
00:00:5e:00:53:22 > 00:00:5e:00:53:21, ethertype IPv4 (0x0800), length 98: 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 303, seq 1, length 64

よって、 インターネット層・イーサネット層は以下の通信を永遠と繰り返す。

  • 送信先アドレス(例 8:8:8:8)がテーブルにあるか、今いる自分自身のルータのルーティングテーブルに問い合わせる。(ルータが自分自身1台のみで送信先のIPアドレスを判断する。IPアドレスのパケット送受信については、別のルータに問い合わせるわけではない。 あらかじめルーティングテーブルを動的・静的に設定しておく。
  • ないのであればデフォルトルートの IP アドレス、あるのであればその IP アドレスに送信することを決定する。
  • 決定された次のIPアドレスをもつ機器の MAC Address を ARP で問い合わせて、フレームとして送信する。

このとき、ルータとルータの1送信ごとに新しいフレームが作成される。
これはフレームには送信元MACアドレス、送信先MACアドレスが必要となるため。

ganyariyaganyariya

データリンク層の機器として、スイッチングハブ・ブリッジがある。(違いについてはこちら
ブリッジは同じ LAN 内の機器を相互接続するハブであり、各ポートに対応している機器の mac アドレスを記憶する。
そのため、同じ LAN 内の特定の IP アドレスの機器のみに通信を行える。(グローバルキャストを毎回行わなくて良くなる。)
ブリッジはこの管理情報を MAC アドレステーブルとして記憶している。

ns1 から ns2 に送信するときに、 ns3-veth0 にはデータフレームの送信が一切こない。
これは、 ns2 の MAC アドレスを bridge が記憶しているので、 ns3-veth0 NIC 先の機器に mac address を問い合わせなくて良いため。

bridge の mac アドレステーブルは、 bridge fdb show で確認できる。

root@8990a3e50db6:/# ip netns exec bridge bridge fdb show br br0 | grep -i 00:00:5e
00:00:5e:00:53:01 dev ns1-br0 master br0 
00:00:5e:00:53:02 dev ns2-br0 master br0 
00:00:5e:00:53:03 dev ns3-br0 master br0 
ganyariyaganyariya

トランスポート

IP パケットが正常に送信されることを保証するレイヤ。
ポート番号を利用して、どのアプリケーションに通信を届けるかを判別している。

Client から Server に UDP で通信を行う。
Client は 44493 ポートが実行時に動的に割り当てられている。
サーバは 54321 で待ち構えている。

Server

root@8990a3e50db6:/# nc -ulnv 127.0.0.1 54321
Bound on 127.0.0.1 54321
Connection received on 127.0.0.1 44493
Hello, World!
Neko daisuki

Client

root@8990a3e50db6:/# nc -u 127.0.0.1 54321
Hello, World!
Neko daisuki

tcp dump を見ると 44493 から 54321 に UDP 通信が行われている事がわかる。

tcp dump (udp)

root@8990a3e50db6:/# tcpdump -i lo -tnlA "udp and port 54321"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 127.0.0.1.44493 > 127.0.0.1.54321: UDP, length 14
E..*..@.@.q............1...)Hello, World!

IP 127.0.0.1.44493 > 127.0.0.1.54321: UDP, length 13
E..)..@.@.q............1...(Neko daisuki
ganyariyaganyariya

TCP 通信の場合

tcpdump で server と client の通信を覗き見る。

client から server に接続したときに Three Way Hand Shake が発生する。
Flags [S] が Sync, Flags[S.]. が Ack を表す。

Sync のときはシーケンス番号を互いに同期する。
Ack はデータが届いたよ、ということを相手に伝えるもの。

スリーウェイハンドシェイクが終わったら Hello, World! をクライアントからサーバに送信している。
P というコントロールフラグは、 PSH (Push Function) を表している。これは、TCP ソフトウェアはバッファにデータを貯めるが、強引にすぐに今のプールにあるデータをサーバに送信したいときに PSH が利用される。

返答では、 ack 15 として送信しており、これはシーケンス番号 15 まで過不足なく受け取りましたよ、ということを表す。
(この 15 は実際のシーケンス番号ではなく相対的な値になっている。本当の値はランダムに生成された初期値 3104755305 などになっている。)

root@8990a3e50db6:/# tcpdump -i lo -tnlA "tcp and port 54321"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes

# スリーウェイハンドシェイク

IP 127.0.0.1.53384 > 127.0.0.1.54321: Flags [S], seq 3104755305, win 65495, options [mss 65495,sackOK,TS val 1190260117 ecr 0,nop,wscale 7], length 0
E..<<.@.@.. ...........1...i.........0.........
F...........
IP 127.0.0.1.54321 > 127.0.0.1.53384: Flags [S.], seq 3801177887, ack 3104755306, win 65483, options [mss 65495,sackOK,TS val 1190260118 ecr 1190260117,nop,wscale 7], length 0
E..<..@.@.<..........1...._....j.....0.........
F...F.......
IP 127.0.0.1.53384 > 127.0.0.1.54321: Flags [.], ack 1, win 512, options [nop,nop,TS val 1190260118 ecr 1190260118], length 0
E..4<.@.@..'...........1...j.._ .....(.....
F...F...

# クライアントから Hello, World! がサーバに送信される

IP 127.0.0.1.53384 > 127.0.0.1.54321: Flags [P.], seq 1:15, ack 1, win 512, options [nop,nop,TS val 1190466023 ecr 1190260118], length 14
E..B<.@.@..............1...j.._ .....6.....
F...F...Hello, World!

IP 127.0.0.1.54321 > 127.0.0.1.53384: Flags [.], ack 15, win 512, options [nop,nop,TS val 1190466023 ecr 1190466023], length 0
E..4CE@.@..|.........1...._ ...x.....(.....
F...F...

ganyariyaganyariya

HTTP

python で立てたサーバに、 nc クライアントで TCP 通信を行う。
TCP 通信のペイロードとして、 GET / HTTP/1.0\r\n\r\n でヘッダを設定している。
当然だが、この通信は curl でも行える。

root@8990a3e50db6:/var/tmp/http-home# python3 -m http.server -b 127.0.0.1 80 
Serving HTTP on 127.0.0.1 port 80 (http://127.0.0.1:80/) ...
127.0.0.1 - - [07/May/2023 00:50:02] "GET / HTTP/1.0" 200 -
root@8990a3e50db6:/# echo -en "GET / HTTP/1.0\r\n\r\n" | nc 127.0.0.1 80
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.8.10
Date: Sun, 07 May 2023 00:50:02 GMT
Content-type: text/html
Content-Length: 116
Last-Modified: Sun, 07 May 2023 00:48:43 GMT

<!doctype html>
<html>
<head>
<title>Hello, World</title>
</head>
<body>
<h1>Hello, Test World</h1>
</body>
</body>

curl -X GET -D - http://127.0.0.1/
ganyariyaganyariya

DNS

リゾルバは domain から ip address を取得するために、外部の DNS サーバにリクエストを送信する。

dig コマンドを利用して、 Google の公開 DNS サーバ @8.8.8.8zenn.dev のドメイン名を問い合わせてみる。

8.8.8.8 (port:53) に対して、 DNS に関するパケットが送信されている。
そして、Google ネームサーバから 35.190.77.180 だよ、という返答が帰ってきている。

root@8990a3e50db6:/# dig +short @8.8.8.8 zenn.dev A
35.190.77.180
root@8990a3e50db6:/var/tmp/http-home# tcpdump -tnl -i any "udp and port 53"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
IP 172.19.0.2.40000 > 8.8.8.8.53: 33088+ [1au] A? zenn.dev. (49)
IP 8.8.8.8.53 > 172.19.0.2.40000: 33088 1/0/1 A 35.190.77.180 (53)

https://qiita.com/hypermkt/items/610b5042d290348a9dfa

ganyariyaganyariya

DHCP

UDP 67

サーバ側に DHCP サーバを立てて、クライアントの IP を払い出す。
192.0.2.100 ~ 192.0.2.200 のアドレスを、 s-veth0 NIC にアクセスが来たら、クライアントにランダムな IP アドレエスを降る。

root@8990a3e50db6:/var/tmp/http-home# ip netns exec server dnsmasq --dhcp-range=192.0.2.100,192.0.2.200,255.255.255.0 --interface=s-veth0 --port 0 --no-resolv --no-daemon
dnsmasq: started, version 2.80 DNS disabled
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth nettlehash DNSSEC loop-detect inotify dumpfile
dnsmasq-dhcp: DHCP, IP range 192.0.2.100 -- 192.0.2.200, lease time 1h
dnsmasq-dhcp: DHCPDISCOVER(s-veth0) a6:33:54:cf:5d:c1 
dnsmasq-dhcp: DHCPOFFER(s-veth0) 192.0.2.129 a6:33:54:cf:5d:c1 
dnsmasq-dhcp: DHCPDISCOVER(s-veth0) a6:33:54:cf:5d:c1 
dnsmasq-dhcp: DHCPOFFER(s-veth0) 192.0.2.129 a6:33:54:cf:5d:c1 
dnsmasq-dhcp: DHCPREQUEST(s-veth0) 192.0.2.129 a6:33:54:cf:5d:c1 
dnsmasq-dhcp: DHCPACK(s-veth0) 192.0.2.129 a6:33:54:cf:5d:c1 8990a3e50db6

client 側では DHCP client を建てる。
すると、サーバ側から 192.0.2.129 が付与される。

root@8990a3e50db6:/# ip netns exec client dhclient -d c-veth0
Internet Systems Consortium DHCP Client 4.4.1
Copyright 2004-2018 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/

Listening on LPF/c-veth0/a6:33:54:cf:5d:c1
Sending on   LPF/c-veth0/a6:33:54:cf:5d:c1
Sending on   Socket/fallback
DHCPDISCOVER on c-veth0 to 255.255.255.255 port 67 interval 3 (xid=0xcc09c96f)
DHCPDISCOVER on c-veth0 to 255.255.255.255 port 67 interval 4 (xid=0xcc09c96f)
DHCPOFFER of 192.0.2.129 from 192.0.2.254
DHCPREQUEST for 192.0.2.129 on c-veth0 to 255.255.255.255 port 67 (xid=0x6fc909cc)
DHCPACK of 192.0.2.129 from 192.0.2.254 (xid=0xcc09c96f)
bound to 192.0.2.129 -- renewal in 1435 seconds.

DHCP Server & Client 実行によって、private ip が自動で割り振られただけでなく、ルーティングテーブルも自動で設定される。

root@8990a3e50db6:/# ip netns exec client ip address show | grep inet
    inet 192.0.2.129/24 brd 192.0.2.255 scope global dynamic c-veth0
    inet6 fe80::a433:54ff:fecf:5dc1/64 scope link 
root@8990a3e50db6:/# ip netns exec client ip route                   
default via 192.0.2.254 dev c-veth0 
192.0.2.0/24 dev c-veth0 proto kernel scope link src 192.0.2.129 
ganyariyaganyariya

Source NAPT

ルータが private ip と global ip を、ポート番号とともに変換する技術(nat, napt はプロトコルではない。特定の技術・実装を指す)。

NAPT がされないと以下の問題が発生する。

  • 途中の WAN ルータで破棄されることがある
  • 送信先(zenn.dev)のサーバについた後、それをもとの送信元 (192.xx.xx.xx) に返却できない。

router に NAPT の設定を行う。

  • -A で処理を行うタイミング(チェイン)を指定する。 POSTROUTING はルーティングが完了して、インターフェースから出ていく直前。
  • -s 処理の対象とする IP アドレス
  • -o 処理の対象とする出力先 NIC (つまり、 gw-veth1 から出ていく通信に関しては NAPT 変換を行う)
  • -j 適応する処理ルール。 MASQUERADE は Source NAT (IP マスカレード)

設定後の iptables の nat テーブルでは、 POSTROUTING として MASQUERADE が適応されている。

root@8990a3e50db6:/# ip netns exec router iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o gw-veth1 -j MASQUERADE

root@8990a3e50db6:/# ip netns exec router iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  192.0.2.0/24         anywhere  

WAN 側の wan-veth0 の通信を覗いてみると、 送信元が 203.0.113.254 からのアクセスになっている。
これは router が送信元 IP アドレスを変更しているため。

root@8990a3e50db6:/# ip netns exec wan tcpdump -tnl -i wan-veth0 icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wan-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 203.0.113.254 > 203.0.113.1: ICMP echo request, id 746, seq 43, length 64
IP 203.0.113.1 > 203.0.113.254: ICMP echo reply, id 746, seq 43, length 64
ganyariyaganyariya

Destination NAT

インターネットから LAN への通信を許すためのもの。
インターネットに LAN のサーバを公開したり、 P2P 通信を行うために利用する。

GKE において、global ip を一つ用意し、LB を利用して内部の private ip に振り分けるときの技術。

router に Destination NAT を設定し、 wan 側から lan の private ip にアクセスできるようにする。

  • PREROURTING はインターフェースにパケットが入ってきた直後
  • -p は処理の対象となるトランスポート層のプロトコル。 tcp だけにすることで、 ssh など別のポートを悪用されないようにする。
  • -dport は処理の対象となるポート番号
  • -d 書き換える前の送信先 IP アドレス。 203.0.113.254(公開グローバルIP) に向けてやってきた通信を内部のIPアドレスに書き換えるときの、公開グローバルIPのこと
  • to-destination は送信先のIPアドレス。グローバルIP から内部の 192.0.2.1 に変換する。
root@8990a3e50db6:/var/tmp/http-home# ip netns exec router iptables -t nat -A PREROUTING -p tcp --dport 54321 -d 203.0.113.254 -j DNAT --to-destination 192.0.2.1

root@8990a3e50db6:/var/tmp/http-home# ip netns exec router iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DNAT       tcp  --  anywhere             203.0.113.254        tcp dpt:54321 to:192.0.2.1

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  192.0.2.0/24         anywhere 

lan 側で 54321 で TCP を待ち受ける。

root@8990a3e50db6:/# ip netns exec lan nc -lnv 54321
Listening on 0.0.0.0 54321
Connection received on 203.0.113.1 47556

wan 側から公開IPである 203.0.113.254 に接続する。

root@8990a3e50db6:/# ip netns exec wan nc 203.0.113.254 54321

WAN 側の wan-veth0 をキャプチャしてみる。

root@8990a3e50db6:/var/tmp/http-home# ip netns exec wan tcpdump -tnl -i wan-veth0 "tcp and port 54321"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wan-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 203.0.113.1.47556 > 203.0.113.254.54321: Flags [P.], seq 2745375848:2745375862, ack 1370880252, win 502, options [nop,nop,TS val 1924661001 ecr 1511212552], length 14
IP 203.0.113.254.54321 > 203.0.113.1.47556: Flags [.], ack 14, win 509, options [nop,nop,TS val 1511313294 ecr 1924661001], length 0

LAN 側の lan-veth0 をキャプチャしてみる。
送信元が 203.0.113.1 (47556) であり、途中で router で変換されて、 private である 192.0.2.1 に通信できている。

root@8990a3e50db6:/var/tmp/http-home# ip netns exec lan tcpdump -tnl -i lan-veth0 "tcp a
nd port 54321"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lan-veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^LIP 203.0.113.1.47556 > 192.0.2.1.54321: Flags [P.], seq 2745375862:2745375871, ack 1370880252, win 502, options [nop,nop,TS val 1924783710 ecr 1511313294], length 9
IP 192.0.2.1.54321 > 203.0.113.1.47556: Flags [.], ack 9, win 509, options [nop,nop,TS val 1511436002 ecr 1924783710], length 0
ganyariyaganyariya

最後に、 Go で複数のクライアントから接続を受け付けるサーバを実装する

ganyariyaganyariya

Socket APIは 同一または異なるマシンシステムにおける「プロセス間」同士が通信をやりとりするときの 共通 API。(異なっても同じでもよい)
Socket API を利用することによって、異なるマシンのプロセスが「TCP/UDP」レイヤを意識することなく通信できる。
同じマシンであればパイプなどを用いて、 2 つのプロセスが簡単に通信できる。

https://www.ibm.com/docs/ja/i/7.2?topic=programming-how-sockets-work

Socket の Address Family によって、ソケット API で利用するアドレスの形式が決まる(相手をどう記述するか)。
AF_INET であれば IP アドレスと Port 番号で、自分ならびに相手を指定する。
AN_INET6 であれば IPv6 と Port 番号。
AF_UNIX では、同じマシン限定のアドレス指定。

http://www.osssme.com/doc/funto45.html

ganyariyaganyariya

https://milestone-of-se.nesuke.com/sv-basic/linux-basic/fd-stdinout-pipe-redirect/

各プロセスは「プロセスごとに」ファイルディスクリプタを持つ。

[root@localhost ~]# ls -l | /proc/15223/fd/
合計 0
lrwx------. 1 root root 64  2月 28 17:48 0 -> /dev/pts/0
lrwx------. 1 root root 64  2月 28 17:48 1 -> /dev/pts/0
lrwx------. 1 root root 64  2月 28 17:48 2 -> /dev/pts/0
lrwx------. 1 root root 64  2月 28 17:48 255 -> /dev/pts/0

プロセスA(ls) と プロセスB (grep "hello") をパイプする ls | grep "hello" の場合は

  • 「プロセスA の fd:1 」のシンボリックリンク先が、 「プロセスB の fd:0」になっている

このように、ファイルディスクリプタはプロセスごとに 0 ~ から番号を振られて存在している。

ganyariyaganyariya

sockaddr は TCP, UDP, Unix network endpoint のアドレスを表す interface

https://github.com/golang/go/blob/master/src/net/sockaddr_posix.go#L15

TCPAddr は sockaddr interface を実装しており、 TCP 通信向けの socket のアドレスを表す。
(自分ならびに通信先)
https://github.com/golang/go/blob/aa4d5e739f32397969fd5c33cbc95d316686039f/src/net/tcpsock.go#L21

TCP 通信においては、相手の「IPアドレス」と「ポート番号」さえ「アドレス」としてわかれば良い。
このアドレスがわかれば、あとはそこに目指してクライアントはサーバにアクセスする。

通信を受け取ったサーバは「クライアントの IP アドレスとクライアントのポート番号」、そして「サーバ自身の laddr (local address)」を持った新しい socket (A とよぶことにする) を作成する。
このクライアント通信の socket A には、クライアントのIPアドレスとポート番号が入っているので、そこに向けて通信をすればよい。
この socket A はファイルディスクリプタなので、ディスクリプタ番号に向かって write, read 等をすれば、クライアントの「IPアドレス」と「ポート」に向かって通信できる。

クライアント側のプロセスは、IPアドレスとポートを受信しているので、判別できる。

ganyariyaganyariya

net.ListenTCP は *net.TCPAddr をもとに *net.TCPListener を生成する。

https://github.com/golang/go/blob/aa4d5e739f32397969fd5c33cbc95d316686039f/src/net/tcpsock.go#L279

内部で internalSocket が呼ばれる。
ここで、 socket 関数が呼ばれて Go では *net.netFD が生成される。
https://github.com/golang/go/blob/2ca4104f0519027c55266d48b47ea16ee4da6915/src/net/ipsock_posix.go#L137

はじめに、 sysSocket で通常のファイルディスクリプタが作成される。
https://github.com/golang/go/blob/2ca4104f0519027c55266d48b47ea16ee4da6915/src/net/sock_posix.go#L19

その後、 s (sysSocket で作成されたファイルディスクリプタ)に対して、 *net.netFD を生成する。
https://github.com/golang/go/blob/2ca4104f0519027c55266d48b47ea16ee4da6915/src/net/sock_posix.go#L27

*net.netFD は以下からなる。

  • family: INET, UNIX などの種類
  • net: "TCP", "UDP"
  • laddr (local address)
  • raddr (remote address)

https://github.com/golang/go/blob/2ca4104f0519027c55266d48b47ea16ee4da6915/src/net/fd_posix.go#L17

よって、 Go で listen をすると、以下の

  • 内部的にファイルディスクリプタが生成される
  • その後、 *net.netFD としてローカルとリモートのアドレスを管理するネットファイルディスクリプタが生成される
  • ネットファイルディスクリプタのローカルアドレスが解決される(ipaddr, port が入る)

go の socket 関数は、ファイルディスクリプタを生成し、リモート・ローカルのアドレスを解決した、 *net.netFD を返す関数である。

c の場合、 socket 関数を呼ぶことで、同様のことを内部的に行い、そのネットファイルディスクリプタの番号を int で返している。

よって、 Socket とは「同一もしくは異なるマシン同士のプロセスがトランスポート層を通して透過的に通信するための API」 であるといえる。
そして、 Socket API を呼ぶと、ネットファイルディスクリプタ(ファイルディスクリプタ+リモート・ローカルのアドレス)が用意される。

Socket で通信していると表現するが、内部的には Linux のファイルディスクリプタをうまくネット用に抽象化したもの。

このスクラップは2023/05/18にクローズされました