linuxで動かしながら学ぶtcp/ipネットワーク入門(https://www.amazon.co.jp/dp/B085BG8CH5) のコードを macOS Big Sur(Intel) + Docker で動かすためのメモ
※ privileged がないとあとで netns の追加ができない
docker run -it -d --privileged --name tcpip ubuntu:20.04
apt update && \
apt install sudo && \
sudo apt-get update && \
sudo apt-get -y install bash coreutils grep iproute2 iputils-ping traceroute tcpdump bind9-dnsutils dnsmasq-base netcat-openbsd python3 curl wget iptables procps isc-dhcp-client
sudo ip netns add ns1
sudo ip netns add ns2
veth(Virtual Ethernet Device)
sudo ip link add ns1-veth0 type veth peer name ns2-veth0
ip link show | grep veth
sudo ip link set ns1-veth0 netns ns1
sudo ip link set ns2-veth0 netns ns2
sudo ip netns exec ns1 ip link show | grep veth
sudo ip netns exec ns2 ip link show | grep veth
※ ドキュメンテーションアドレスを使用 ※ 同じセグメント(192.0.2) CIDR(/24)
sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0
sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0
sudo ip netns exec ns1 ip link set ns1-veth0 up
sudo ip netns exec ns2 ip link set ns2-veth0 up
# ping
sudo ip netns exec ns1 ping -c 3 192.0.2.2
sudo ip --all netns delete
sudo ip netns add ns1
sudo ip netns add router
sudo ip netns add ns2
sudo ip link add ns1-veth0 type veth peer name gw-veth0
sudo ip link add ns2-veth0 type veth peer name gw-veth1
sudo ip link set ns1-veth0 netns ns1
sudo ip link set gw-veth0 netns router
sudo ip link set gw-veth1 netns router
sudo ip link set ns2-veth0 netns ns2
※ セグメント 192.0.2.0/24
sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0
sudo ip netns exec router ip address add 192.0.2.254/24 dev gw-veth0
※ セグメント 198.51.100.0/24 ※ ns1 とは異なるホストアドレスということ
sudo ip netns exec router ip address add 198.51.100.254/24 dev gw-veth1
sudo ip netns exec ns2 ip address add 198.51.100.1/24 dev ns2-veth0
# ping
sudo ip netns exec ns1 ping -c 3 192.0.2.254 -I 192.0.2.1
sudo ip netns exec ns2 ping -c 3 198.51.100.254 -I 198.51.100.1
sudo ip netns exec ns1 ip link set ns1-veth0 up
sudo ip netns exec router ip link set gw-veth1 up
sudo ip netns exec router ip link set gw-veth2 up
sudo ip netns exec ns2 ip link set ns2-veth0 up
sudo ip netns exec ns1 ping -c 3 192.0.2.254 -I 192.0.2.1
sudo ip netns exec ns2 ping -c 3 198.51.100.254 -I 198.51.100.1
繋がらないので、ルーティングエントリを追加する必要がある
# ping: sendmsg: Network is unreachable が出る
sudo ip netns exec ns1 ping -c 3 198.51.100.1 -I 192.0.2.1
# ルーティングテーブルの確認
sudo ip netns exec ns1 ip route show
ns1 でルーティングエントリに一致しない場合(default)、ネクストホップ 192.0.2.254 に渡すようにする(つまりさっきルーター gw-veth0 に設定した IP アドレス) ns2 も同じやり方で、gw-veth1 に設定した IP アドレスに渡す
sudo ip netns exec ns1 ip route add default via 192.0.2.254
sudo ip netns exec ns2 ip route add default via 198.51.100.254
sudo ip netns exec ns1 ping -c 3 198.51.100.1 -I 192.0.2.1
ping の結果でもしパケットロスが出ている場合は、カーネル側で IPv4 のルータとして動作しないようになっている
sysctl
で設定を変える
sudo ip netns exec router sysctl net.ipv4.ip_forward=1
sudo ip --all netns delete
ルーター1 と ルーター2 を設置する
前と同じなので詳しくは省略
sudo ip netns add ns1 && sudo ip netns add router1 && sudo ip netns add router2 && sudo ip netns add ns2
前と同じなので詳しくは省略
sudo ip link add ns1-veth0 type veth peer name gw1-veth0 && sudo ip link add gw1-veth1 type veth peer name gw2-veth0 && sudo ip link add gw2-veth1 type veth peer name ns2-veth0
前と同じなので詳しくは省略
sudo ip link set ns1-veth0 netns ns1 && sudo ip link set gw
1-veth0 netns router1 && sudo ip link set gw1-veth1 netns router1 && sudo ip link set gw2-veth0 netns router2 && sudo ip link set gw2-veth1 netns router2 && sudo ip link set ns2-veth0 netns ns2
前と同じなので詳しくは省略
sudo ip netns exec ns1 ip link set ns1-veth0 up && \
sudo ip netns exec router1 ip link set gw1-veth0 up && \
sudo ip netns exec router1 ip link set gw1-veth1 up && \
sudo ip netns exec router2 ip link set gw2-veth0 up && \
sudo ip netns exec router2 ip link set gw2-veth1 up && \
sudo ip netns exec ns2 ip link set ns2-veth0 up
※ ルーターが 1 つ増えたのでセグメントも 1 つ増える 203.0.113.0
sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 && \
sudo ip netns exec router1 ip address add 192.0.2.254/24 dev gw1-veth0 && \
sudo ip netns exec router1 ip address add 203.0.113.1/24 dev gw1-veth1 && \
sudo ip netns exec router2 ip address add 203.0.113.2/24 dev gw2-veth0 && \
sudo ip netns exec router2 ip address add 198.51.100.254/24 dev gw2-veth1 && \
sudo ip netns exec ns2 ip address add 198.51.100.1/24 dev ns2-veth0
前と同じなので詳しくは省略
sudo ip netns exec ns1 ip route add default via 192.0.2.254 && \
sudo ip netns exec ns2 ip route add default via 198.51.100.254
ping すると Destination Net Unreachable
になる。
From の 192.0.2.0 は先ほど router1 に設定した セグメント。
router1 が 198.51.100.1 をどこに渡すかわからない。
sudo ip netns exec ns1 ping -c 3 198.51.100.1 -I 192.0.2.1
# PING 198.51.100.1 (198.51.100.1) from 192.0.2.1 : 56(84) bytes of data.
# From 192.0.2.254 icmp_seq=1 Destination Net Unreachable
# From 192.0.2.254 icmp_seq=2 Destination Net Unreachable
# From 192.0.2.254 icmp_seq=3 Destination Net Unreachable
ルーター1 のセグメント: 192.0.2.0.254/24 ルーター2 のセグメント: 198.51.100.0/24
ルーター1 がルーター2 を、ルーター2 がルーター1 の IP アドレスを指定すればいい
sudo ip netns exec router1 ip route add 198.51.100.0/24 via 203.0.113.2 && \
sudo ip netns exec router2 ip route add 192.0.2.0/24 via 203.0.113.1
手動でルーティングエントリを追加する方式をスタティックルーティングという。
一方で BGP や OSPF などのルーティングプロトコルを用いて、ルータ同士が自分自身のルーティング情報を交換する方式もあり、そちらはダイナミックルーティングという。
sudo ip --all netns delete
今までと同じなので省略
sudo ip netns add ns1 && \
sudo ip netns add ns2 && \
sudo ip link add ns1-veth0 type veth peer name ns2-veth0 && \
sudo ip link set ns1-veth0 netns ns1 && \
sudo ip link set ns2-veth0 netns ns2 && \
sudo ip netns exec ns1 ip link set ns1-veth0 up && \
sudo ip netns exec ns2 ip link set ns2-veth0 up && \
sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 && \
sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0
sudo ip netns exec ns1 ip link set dev ns1-veth0 address 00:00:5E:00:53:01 && \
sudo ip netns exec ns2 ip link set dev ns2-veth0 address 00:00:5E:00:53:02
sudo ip netns exec ns1 tcpdump -tnel -i ns1-veth0 icmp
もう一個ターミナルを立ち上げ
sudo ip netns exec ns1 ping -c 3 192.0.2.2 -I 192.0.2.1
# 観察に影響が出るので、一回 MAC アドレスのキャッシュを消す
sudo ip netns exec ns1 ip neigh flush all
さっき tcpdump を実行したターミナルで
sudo ip netns exec ns1 tcpdump -tnel -i ns1-veth0 icmp or arp
ARP で MAC アドレスを解決するのは IPv4 のみ。 IPv6 は ICMPv6 というプロトコルで Neighbor Discoevry という仕組みを使う。
sudo ip --all netns delete
今までと同じなので省略
sudo ip netns add ns1 && \
sudo ip netns add router && \
sudo ip netns add ns2 && \
sudo ip link add ns1-veth0 type veth peer name gw-veth0 && \
sudo ip link add ns2-veth0 type veth peer name gw-veth1 && \
sudo ip link set ns1-veth0 netns ns1 && \
sudo ip link set gw-veth0 netns router && \
sudo ip link set gw-veth1 netns router && \
sudo ip link set ns2-veth0 netns ns2 && \
sudo ip netns exec ns1 ip link set ns1-veth0 up && \
sudo ip netns exec router ip link set gw-veth0 up && \
sudo ip netns exec router ip link set gw-veth1 up && \
sudo ip netns exec ns2 ip link set ns2-veth0 up && \
sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 && \
sudo ip netns exec router ip address add 192.0.2.254/24 dev gw-veth0 && \
sudo ip netns exec router ip address add 198.51.100.254/24 dev gw-veth1 && \
sudo ip netns exec ns2 ip address add 198.51.100.1/24 dev ns2-veth0 && \
sudo ip netns exec ns1 ip route add default via 192.0.2.254 && \
sudo ip netns exec ns2 ip route add default via 198.51.100.254
sudo ip netns exec ns1 ip link set dev ns1-veth0 address 00:00:5E:00:53:11 && \
sudo ip netns exec router ip link set dev gw-veth0 address 00:00:5E:00:53:12 && \
sudo ip netns exec router ip link set dev gw-veth1 address 00:00:5E:00:53:21 && \
sudo ip netns exec ns2 ip link set dev ns2-veth0 address 00:00:5E:00:53:22
ターミナルを 3 つ立ち上げ
1 (gw-veth0)
sudo ip netns exec router tcpdump -tnel -i gw-veth0 icmp or arp
2 (gw-vwth1)
sudo ip netns exec router tcpdump -tnel -i gw-veth1 icmp or arp
3 (ping)
sudo ip netns exec ns1 ping -c 3 198.51.100.1 -I 192.0.2.1
sudo ip -all netns delete
sudo ip netns add ns1 && \
sudo ip netns add ns2 && \
sudo ip netns add ns3
sudo ip netns add bridge
省略
sudo ip link add ns1-veth0 type veth peer name ns1-br0 && \
sudo ip link add ns2-veth0 type veth peer name ns2-br0 && \
sudo ip link add ns3-veth0 type veth peer name ns3-br0 && \
sudo ip link set ns1-veth0 netns ns1 && \
sudo ip link set ns2-veth0 netns ns2 && \
sudo ip link set ns3-veth0 netns ns3 && \
sudo ip link set ns1-br0 netns bridge && \
sudo ip link set ns2-br0 netns bridge && \
sudo ip link set ns3-br0 netns bridge && \
sudo ip netns exec ns1 ip link set ns1-veth0 up && \
sudo ip netns exec ns2 ip link set ns2-veth0 up && \
sudo ip netns exec ns3 ip link set ns3-veth0 up && \
sudo ip netns exec bridge ip link set ns1-br0 up && \
sudo ip netns exec bridge ip link set ns2-br0 up && \
sudo ip netns exec bridge ip link set ns3-br0 up && \
sudo ip netns exec ns1 ip address add 192.0.2.1/24 dev ns1-veth0 && \
sudo ip netns exec ns2 ip address add 192.0.2.2/24 dev ns2-veth0 && \
sudo ip netns exec ns3 ip address add 192.0.2.3/24 dev ns3-veth0 && \
sudo ip netns exec ns1 ip link set dev ns1-veth0 address 00:00:5E:00:53:01 && \
sudo ip netns exec ns2 ip link set dev ns2-veth0 address 00:00:5E:00:53:02 && \
sudo ip netns exec ns3 ip link set dev ns3-veth0 address 00:00:5E:00:53:03
※ UP が必要なのでいっしょにやる
sudo ip netns exec bridge ip link add dev br0 type bridge && \
sudo ip netns exec bridge ip link set br0 up
sudo ip netns exec bridge ip link set ns1-br0 master br0 && \
sudo ip netns exec bridge ip link set ns2-br0 master br0 && \
sudo ip netns exec bridge ip link set ns3-br0 master br0
sudo ip netns exec ns1 ping -c 3 192.0.2.2 -I 192.0.2.1
sudo ip netns exec ns1 ping -c 3 192.0.2.3 -I 192.0.2.1
ターミナルを2 つ立ち上げ
1 (ping)
sudo ip netns exec ns1 ping 192.0.2.2 -I 192.0.2.1
2 (tcpdump)
sudo ip netns exec ns2 tcpdump -tnel -i ns2-veth0 icmp
ns3 のインターフェースを観測しても、フレームが転送されていないことがわかる。
ブリッジが、ポートの先にある MAC アドレスを把握していて、ns3 に通信相手がないことを知っているから。
sudo ip netns exec ns3 tcpdump -tnel -i ns3-veth0 icmp
ネットワークブリッジの MAC アドレスの対応関係(MAC アドレステーブル) を調べてみる
sudo ip netns exec bridge bridge fdb show br br0 | grep -i 00:00:5e
サーバー->クライアント
サーバとなるアプリケーションが通信しようとするプロトコル(トランスポート層より上位層のプロトコル)によって決まる。
例えば DNS では 53 を使う。
クライアント->サーバー
への通信で、アプリケーションから特に指定がない限り OS 側が自動的に選んで割り当てるポート番号(エフェメラルポート)
49152〜65535
それ未満は IANA が用途を管理しているので省略
※nc は netcat の略 ターミナルを3台立ち上げる
1: サーバーの立ち上げ
# -u UDPで
# -l さーばーとして
# -n IP アドレスを DNS で名前解決させない
# -v コマンドの詳細表示
nc -ulnv 127.0.0.1 54321
2: クライアントの立ち上げ
nc -u 127.0.0.1 54321
3: tcpdump
sudo tcpdump -i lo -tnlA "udp and port 54321"
この状態で 2(クライアント)または1(サーバー)で何か入力し、Enterを押すと経路が追える
ターミナルを3台立ち上げる
1: サーバーの立ち上げ
# -l さーばーとして
# -n IP アドレスを DNS で名前解決させない
# -v コマンドの詳細表示
nc -ulnv 127.0.0.1 54321
2: tcpdump(TCP の場合はクライアントの接続と同時に通信が始まるので、先にやる)
sudo tcpdump -i lo -tnlA "tcp and port 54321"
3: クライアントの立ち上げ
nc 127.0.0.1 54321
この時の tcpdump を見れば、3WAYハンドシェイクが行われる(クライアント->サーバー, サーバー->クライアント, クライアント->サーバー)ことがわかる。
※SYN, SYN/ACK の見方
tcpdump の Flags[] の部分。Sが SYN で . が ACK
さらにクライアントで文字列を入力して送ると、P フラグが立つ、これは PSH (受信側に、今あるデータをすぐに処理してくれと言うこと)
mkdir -p /var/tmp/http-home
cd /var/tmp/http-home
cat << 'EOF' > index.html
<!doctype html>
<html>
<head>
<title>Hello, World!</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
EOF
ターミナルを 2 台起動し
1: httpサーバーの起動
sudo python3 -m http.server -b 127.0.0.1 80
2: HTTPクライアントを使って接続
echo -en "GET / HTTP/1.0\r\n\r\n" | nc 127.0.0.1 80
# または
curl -X GET -D - http://127.0.0.1/
grep 127.0.0.1 /etc/hosts
ターミナルを 2 台起動
1: tcpdump ※ UDP のポート 53 は DNS に使われるもの
sudo tcpdump -tnl -i any "udp and port 53"
2: dig
# A は A レコード(ドメイン名に対応するIPv4 アドレス)
dig +short @8.8.8.8 example.org A
sudo ip netns add server && \
sudo ip netns add client && \
sudo ip link add s-veth0 type veth peer name c-veth0 && \
sudo ip link set s-veth0 netns server && \
sudo ip link set c-veth0 netns client && \
sudo ip netns exec server ip link set s-veth0 up && \
sudo ip netns exec client ip link set c-veth0 up
server のインターフェースのみに設定する。
client は DHCP プロトコルで設定するため。
sudo ip netns exec server ip address add 192.0.2.254/24 dev s-veth0
ターミナルを 2 台起動
1: DHCPサーバー立ち上げ
sudo 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
2: クライアントから接続
sudo ip netns exec client dhclient -d c-veth0
bound to
で IP アドレスが配布されているはず。
↓で client に IP アドレスやデフォルトルートが割り当てられていることが確認できる。
sudo ip netns exec client ip address show | grep "inet "
sudo ip netns exec client ip route show
sudo ip --all netns delete
lan の 192.0.2.0/24 のセグメントを、Source NAT で 203.0.113.254 に置き換えること
sudo ip netns add lan && \
sudo ip netns add router && \
sudo ip netns add wan && \
sudo ip link add lan-veth0 type veth peer name gw-veth0 && \
sudo ip link add wan-veth0 type veth peer name gw-veth1 && \
sudo ip link set lan-veth0 netns lan && \
sudo ip link set gw-veth0 netns router && \
sudo ip link set gw-veth1 netns router && \
sudo ip link set wan-veth0 netns wan && \
sudo ip netns exec lan ip link set lan-veth0 up && \
sudo ip netns exec router ip link set gw-veth0 up && \
sudo ip netns exec router ip link set gw-veth1 up && \
sudo ip netns exec wan ip link set wan-veth0 up && \
sudo ip netns exec lan ip address add 192.0.2.1/24 dev lan-veth0 && \
sudo ip netns exec lan ip route add default via 192.0.2.254 && \
sudo ip netns exec router ip address add 192.0.2.254/24 dev gw-veth0 && \
sudo ip netns exec router ip address add 203.0.113.254/24 dev gw-veth1 && \
sudo ip netns exec wan ip address add 203.0.113.1/24 dev wan-veth0 && \
sudo ip netns exec wan ip route add default via 203.0.113.254
テーブル形式で現在の設定を表示
sudo ip netns exec router iptables -t nat -L
"Chain PREROUTING" など、処理を適用するタイミング(Chain)に何もないことを確認
-A
でチェインを指定、今回はPOSTROUTING(ルーティングが終わり、パケットがインターフェースから出ていく直前)
-s
は対象となる IP アドレスの範囲
-o
処理の対象とする出力先のネットワークインターフェイス
-j
条件に一致したパケットをどのように処理するか(target)ここでは IP マスカレード(Linux の Souce NAT の実装)
sudo ip netns exec router iptables -t nat \
-A POSTROUTING \
-s 192.0.2.0/24 \
-o gw-veth1 \
-j MASQUERADE
もう一回確認
sudo ip netns exec router iptables -t nat -L
ターミナルを 2 台起動
1: ping
sudo ip netns exec lan ping 203.0.113.1
2: tcpdump
LAN 側は今まで通り
sudo ip netns exec lan tcpdump -tnl -i lan-veth0 icmp
WAN 側は router の WAN 側に付与されている IP アドレスでやりとりされている
sudo ip netns exec wan tcpdump -tnl -i wan-veth0 icmp
※ Distination NAT に設定を再利用するので NS は消さない
-A
PREROUTING はインターフェースからパケットが入ってきた直後
-p
処理の対象となるトランスポート層のプロトコル
--dport
処理の対象となるポート番号
-d
書き換える前の IP アドレス
-j
条件に一致したパケットをどのように処理するか(target) ここでは Distination NAR であることを示す
--to-destination
書き換えた後のアドレス
sudo 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
ターミナルを 2 台起動
1: 54321 ポートを待ち受けるサーバの起動
sudo ip netns exec lan nc -lnv 54321
- WAN 側の NS からサーバーにアクセス
sudo ip netns exec wan nc 203.0.113.254 54321
ターミナルを 3 台起動
1: tcpdump
sudo ip netns exec wan tcpdump -tnl -i wan-veth0 "tcp and port 54321"
- サーバー
sudo ip netns exec lan nc -lnv 54321
- クライアント
sudo ip netns exec wan nc 203.0.113.254 54321
Hello, World!
ルーターのグローバルアドレスに相当する IP アドレスが送信元になっている
ターミナルを 3 台起動
1: tcpdump
sudo ip netns exec lan tcpdump -tnl -i lan-veth0 "tcp and port 54321"
- サーバー
sudo ip netns exec lan nc -lnv 54321
- クライアント
sudo ip netns exec wan nc 203.0.113.254 54321
Hello, World!
送信先の IP アドレスが LAN のセグメントに付与されたものになっている
- HTTPクライアント
- エコーサーバ
- 独自のバイナリベースのプロトコルを実装したサーバとクライアント
mkdir -p /var/tmp/http-home && \
cd /var/tmp/http-home/
cat << 'EOF' > index.html
<!doctype html>
<html>
<head>
<title>Hello, World</title>
</head>
<body>
<h1>Hello, World</h1>
</body>
</html>
EOF
sudo python3 -m http.server -b 127.0.0.1 80
touch httpclient.py
#!/usr/bin/env python3
import socket
# HTTP Client
# ソケットに指定したバイト数を書き込む
def send_msg(sock, msg):
total_sent_len = 0
total_msg_len = len(msg)
while total_sent_len < total_msg_len:
sent_len = sock.send(msg[total_sent_len:])
if sent_len == 0:
raise RuntimeError("socket connection broken")
total_sent_len += sent_len
# ソケットから接続が終わるまでバイト列を読み込む
def recv_msg(sock, chunk_len=1024):
while True:
received_chunk = sock.recv(chunk_len)
if len(received_chunk) == 0:
break
yield received_chunk
def main():
# socket.AF_INET はネットワーク層に IPv4 を使うことの指定、SOCK_STREAM はトランスポート層に TCP を使うことの指定
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 80))
request_text = 'GET / HTTP/1.0\r\n\r\n'
request_bytes = request_text.encode('ASCII')
send_msg(client_socket, request_bytes)
received_bytes = b''.join(recv_msg(client_socket))
received_text = received_bytes.decode('ASCII')
print(received_text)
client_socket.close()
if __name__ == '__main__':
main()
touch echoserver.py
#!/usr/bin/env python3
import socket
# Echo Server
def send_msg(sock, msg):
total_sent_len = 0
total_msg_len = len(msg)
while total_sent_len < total_msg_len:
sent_len = sock.send(msg[total_sent_len:])
if sent_len == 0:
raise RuntimeError('socket connection broken')
total_sent_len += sent_len
def recv_msg(sock, chunk_len=1024):
while True:
received_chunk = sock.recv(chunk_len)
if len(received_chunk) == 0:
break
yield received_chunk
def main():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# bind でどの IP アドレスを使ってクライアントからの接続を待つか指定している
server_socket.bind(('127.0.0.1', 54321))
server_socket.listen()
print('starting server...')
client_socket, (client_address, client_port) = server_socket.accept()
print(f'accepted from {client_address}{client_port}')
# ソケットからバイト列を読み込む処理
for received_msg in recv_msg(client_socket):
# 読みこんだ内容をそのまま書き込む(エコーバック)
send_msg(client_socket, received_msg)
print(f'echo: {received_msg}')
client_socket.close()
server_socket.close()
if __name__ == '__main__':
main()
ターミナルを 2 台用意
1: エコーサーバを起動
python3 echoserver.py
2: nc でクライアントの作成
nc 127.0.0.1 54321
Hello, World!
ソケットとプロセス一覧の確認
sudo ss -tlnp
TCP/IP ではバイトオーダーをビッグエンディアンに統一している。 ネットワークにおけるビッグエンディアンを、ネットワークバイトオーダーと呼ぶ。 CPU のアーキテクチャに依存したコンピューター内部の表現方法はホストバイトオーダーと呼ぶ。
ホストバイトオーダー <-> ネットワークバイトオーダーに変換する必要がある。
足し算をする ADD プロトコルを実装する。
touch addserver.py
#!/usr/bin/env python3
import socket
import struct
# ADD Protocol
def send_msg(sock, msg):
total_sent_len = 0
total_msg_len = len(msg)
while total_sent_len < total_msg_len:
sent_len = sock.send(msg[total_sent_len:])
if sent_len == 0:
raise RuntimeError('socket connection broken')
total_sent_len += sent_len
def recv_msg(sock, total_msg_size):
total_recv_size = 0
while total_recv_size < total_msg_size:
received_chunk = sock.recv(total_msg_size - total_recv_size)
if len(received_chunk) == 0:
raise RuntimeError('socket connection broken')
yield received_chunk
total_recv_size += len(received_chunk)
def main():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
server_socket.bind(('127.0.0.1', 54321))
server_socket.listen()
print('starting server...')
client_socket, (client_address, client_port) = server_socket.accept()
print(f'accepted from {client_address}{client_port}')
received_msg = b''.join(recv_msg(client_socket, total_msg_size=8))
print(f'received: {received_msg}')
# ネットワークバイトオーダーからホストバイトオーダーへ変換している。struct.unpack() で '!ii' を指定し、受信したバイト列を 2 つの 4 バイト整数で解釈している。
(operand1, operand2) = struct.unpack('!ii', received_msg)
print(f'operand1: {operand1}', f'operand2: {operand2}')
result = operand1 + operand2
print(f'result: {result}')
# ホストバイトオーダーからネットワークバイトオーダーへ変換している。struct.pack() で '!q' を指定し、結果を 64 ビットの整数として解釈できるように変換している。
result_msg = struct.pack('!q', result)
send_msg(client_socket, result_msg)
print(f'sent: {result_msg}')
client_socket.close()
server_socket.close()
if __name__ == '__main__':
main()
touch addclient.py
#!/usr/bin/env python3
import socket
import struct
def send_msg(sock, msg):
total_sent_len = 0
total_msg_len = len(msg)
while total_sent_len < total_msg_len:
sent_len = sock.send(msg[total_sent_len:])
if sent_len == 0:
raise RuntimeError('socket connection broken')
total_sent_len += sent_len
def recv_msg(sock, total_msg_size):
total_recv_size = 0
while total_recv_size < total_msg_size:
received_chunk = sock.recv(total_msg_size - total_recv_size)
if len(received_chunk) == 0:
raise RuntimeError('socket connection broken')
yield received_chunk
total_recv_size += len(received_chunk)
def main():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(('127.0.0.1', 54321))
operand1, operand2 = 1000, 2000
print(f'operand1: {operand1}, operand2: {operand2}')
request_msg = struct.pack('!ii', operand1, operand2)
send_msg(client_socket, request_msg)
print(f'sent: {request_msg}')
received_msg = b''.join(recv_msg(client_socket, 8))
print(f'received: {received_msg}')
(added_value, ) = struct.unpack('!q', received_msg)
print(f'result: {added_value}')
client_socket.close()
if __name__ == '__main__':
main()
ターミナルを 2 台起動する
1: サーバー
python3 addserver.py
2: クライアント
python3 addclient.py
> operand1: 1000, operand2: 2000
> sent: b'\x00\x00\x03\xe8\x00\x00\x07\xd0'
> received: b'\x00\x00\x00\x00\x00\x00\x0b\xb8'
> result: 3000