Address conversions #
See how to convert textual and binary IPv4 addresses back and forth:
make -B addrconv
./addrconv 192.168.1.100
./addrconv 127.0.0.1
./addrconv 256.0.0.1
./addrconv abrakadabra
Binding addresses #
make -B addrbind
Bind localhost address:
./addrbind 127.0.0.1 8080
Bind INADDR_ANY:
./addrbind 0.0.0.0 8080
Explicitly bind ephemeral port:
./addrbind 127.0.0.1 0
Network setup #
Set up server independently connected with two clients over two distinct networks:
sudo ./two_clients.sh up
graph TD
subgraph ns_client1 ["Namespace: ns_client1 (LAN Simulation)"]
direction TB
lo1((lo
127.0.0.1/8))
veth_c1[veth_c1
IP: 10.0.1.2/24
Def GW: 10.0.1.1]
end
subgraph ns_server ["Namespace: ns_server (Application Server & Router)"]
direction TB
lo_s((lo
127.0.0.1/8))
veth_srv_c1[veth_srv_c1
IP: 10.0.1.1/24]
veth_srv_c2[veth_srv_c2
IP: 10.0.2.1/24]
forward[IPv4 Forwarding = ENABLED]
end
subgraph ns_client2 ["Namespace: ns_client2 (WAN / Chaos Simulation)"]
direction TB
lo2((lo
127.0.0.1/8))
veth_c2[veth_c2
IP: 10.0.2.2/24
Def GW: 10.0.2.1]
end
veth_c1 <-->|VETH Pair 1| veth_srv_c1
veth_c2 <-->|VETH Pair 2| veth_srv_c2
classDef namespace fill:#f9f9f9,stroke:#333,stroke-width:2px,stroke-dasharray: 5 5;
class ns_client1,ns_server,ns_client2 namespace;
classDef interface fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
class veth_c1,veth_c2,veth_srv_c1,veth_srv_c2 interface;
classDef loopback fill:#f3e5f5,stroke:#8e24aa,stroke-width:2px,shape:circle;
class lo1,lo2,lo_s loopback;
classDef forwarder fill:#e8f5e9,stroke:#4caf50,stroke-width:1px,stroke-dasharray: 2 2;
class forward forwarder;Namespace ns_server will act here both, as a routing device between networks 10.0.1.0/24 and10.0.2. 0/24
and as a node running the server application and receiving traffic.
Note clients have a default gateway configured to 10.0.1.1 and 10.0.2.1 respectively.
Verify connectivity:
sudo ip netns exec ns_client1 ping -c 2 10.0.1.1 # Client 1 <-> Server
sudo ip netns exec ns_client2 ping -c 2 10.0.2.1 # Client 2 <-> Server
sudo ip netns exec ns_client1 ping -c 2 10.0.2.2 # Client 1 <-> Client 2
sudo ip netns exec ns_client2 ping -c 2 10.0.1.2 # Client 2 <-> Client 1
User Datagram Protocol #
Basic Usage #
make time_server time_client
time_protocol.h time_server.c time_client.c
Run server bound on INADDR_ANY:8080:
sudo ip netns exec ns_server ./time_server 0.0.0.0 8080
And try to have it serve some requests:
sudo ip netns exec ns_client1 ./time_client 10.0.1.1 8080
sudo ip netns exec ns_client2 ./time_client 10.0.2.1 8080
Capture an incoming packet and inspect them:
sudo ip netns exec ns_server tcpdump -i veth_srv_c1 -n -c 1 -v -XX -w udp.pcap --print udp port 8080
tshark -r udp.pcap -V -x
Binding specific interface #
sudo ip netns exec ns_server ./time_server 10.0.1.1 8080
Observe that client 1 is handled as previously, client 2 requests are not processed.
Note packets from client 2 are reaching the server. The OS net stack drops them and generates back ICMP response:
sudo ip netns exec ns_server tcpdump -i veth_srv_c2 -n
Experiment with binding non-local adresses:
sudo ip netns exec ns_server ./time_server 10.0.3.1 8080
Localhost communication #
Try binding lo address:
sudo ip netns exec ns_server ./time_server 127.0.0.1 8080
sudo ip netns exec ns_server ./time_client 127.0.0.1 8080
Note ns_server used in client invocation. External communication won’t in such a setup.
Port choice #
Try to run multiple servers in parallel and note Address already in use error:
sudo ip netns exec ns_server ./time_server 0.0.0.0 8080
You can easily check with ss (socket statistics) tool system-wide port usage:
sudo ip netns exec ns_server ss -aun
Try running server bound on 10.0.1.1 and 10.0.2.1 in parallel.
Note that in the host namespace, typically binding low ports (< 1024) is not allowed.
./time_server 127.0.0.1 999
In explicitly created namespaces there are no such restrictions.
Packet loss #
Inject random packet drops between server and client 2 simulating weak connection:
sudo ip netns exec ns_client2 tc qdisc add dev veth_c2 root netem loss 40%
Run the server as normal:
sudo ip netns exec ns_server ./time_server 0.0.0.0 8080
Observe client 1 running normally:
sudo ip netns exec ns_client1 ./time_client 10.0.1.1 8080
Check how client 1 behaves:
sudo ip netns exec ns_client2 ./time_client 10.0.2.1 8080
Never assume any single UDP packet gets delivered. Always expect it to be lost!
Rollback to the normal state:
sudo ip netns exec ns_client2 tc qdisc del dev veth_c2 root
Reorderings #
Build and try out the client sending a burst of requests:
make time_burst_client
sudo ip netns exec ns_client2 ./time_burst_client 10.0.2.1 8080
Now let’s simulate another possible network behavior - packet reordering:
sudo ip netns exec ns_client2 tc qdisc add dev veth_c2 root netem delay 500ms 400ms distribution normal reorder 50%
Try running client 2 now and observe server and client logs.
Never assume UDP datagram delivery order!
Rollback to the normal state:
sudo ip netns exec ns_client2 tc qdisc del dev veth_c2 root
Datagram truncation #
Run UDP echo server:
make -B echo_server
sudo ip netns exec ns_server ./echo_server
Utilize nc as a client which sends a datagram line by line:
sudo ip netns exec ns_client1 nc -u 10.0.1.1 5678
Observe that messages longer than the server’s read buffer size get truncated.
Transmission Control Protocol #
TCP Sink Server #
make byte_sink
Setup ss watch before running the server:
sudo ip netns exec ns_server watch -n0.1 ss -tan
Setup tcpdump too:
sudo ip netns exec ns_server tcpdump -i veth_srv_c1 -n tcp port 8080
sudo ip netns exec ns_server ./byte_sink
Try connecting to the server before listen() and observe packet dump:
sudo ip netns exec ns_client1 nc -nv 10.0.1.1 8080
Note the remote server responds with RST segment refusing the connection.
Now let the server call listen(). Check ss output. Re-try to connect:
sudo ip netns exec ns_client1 nc -nv 10.0.1.1 8080
Note:
- client socket emerged in
ssinESTABLISHEDstate, it has a peer address set. - the whole handshake is completed without any server process intervention.
- client may even send some data and get it acknowledged
Now, let the server call accept(). Observe nothing changes in ss or tcpdump.
Receive bytes, observe decreasing Rx buffer length in ss.
Close the connection by terminating nc and observe server reading EOF.
Follow-ups:
- Send some data before server calls
accept() - Close the connection before server calls
accept() - Try to connect more clients, while the first one is still connected.
Client application #
Now build and use small client application which reads from stdin and writes everything to an established TCP conneciton.
make simple_nc
sudo ip netns exec ns_client1 ./simple_nc 10.0.1.1 8080
Experiment as previously.
Try to populate server Rx buffer and C-d to send FIN fin. Observe server reading EOF.
Follow-ups:
- Kill the server when it’s Rx buffer is empty. Close the client without sending anything more!
- Observe server socket going into
TIME_WAITstate. - Try re-running the server immediately after.
- Observe server socket going into
- Kill the server when it’s Rx buffer is empty, then close the client!
- Observe that the client cannot distinguish between half-closed and fully-closed stream just by writing.
- Note the first attempt to send works and the next one kills the client application!
- Kill the server when it’s Rx buffer is not empty
- Again see client gets killed with signal on second write attempt.
KV Store #
Now let’s build a simple key-value store server which interatively serves many clients, processing their requests and sending reponses.
make kv_store kv_client
kv_protocol.h kv_store.c kv_client.c
Sniff traffic on port 8085:
sudo ip netns exec ns_server tcpdump -i veth_srv_c1 -n tcp port 8085
sudo ip netns exec ns_server ./kv_store
Run the first client and play with the commands:
sudo ip netns exec ns_client1 ./kv_client 10.0.1.1 8085
Without closing the first client run the second one and attempt to do something:
sudo ip netns exec ns_client2 ./kv_client 10.0.2.1 8085
Observe requests are served only after the first client disconnects.
Follow-ups:
- Try to execute special
firecommand- Observe server gets killed with
SIGPIPE.
- Observe server gets killed with
Broken networks #
TBD