book: Understanding Linux Network Internals
socket读写错误返回值:errno
TCP: Robert Elliot Kahn
IP: Robert Elliot Kahn, Vint Cerf
1 RFC规范
RFC793:TCP
RFC768:UDP
RFC791:IP
RFC826:ARP
RFC792:ICMP
RFC5681:TCP拥塞
RFC6298:TCP重传
RFC2131:DHCP
RFC1112和RFC2365:IP组播
RFC4862:IPv6无状态地址自动配置
RFC2460:IPv6规范
2 TCP/IP二层协议
2.1 ARP超时时间设置
/proc/sys/net/ipv4/neigh/ethX/base_reachable_time_ms
[timeout/2, 3*timeout/2] (default from 15s to 45s)
LwIP etharp_tmr(void)
Linux static ARP
arp -s 10.0.0.2 00:0c:29:c0:94:bf
The above commands tells local ARP table that the host with IP address 10.0.0.2 has MAC address 00:0c:29:c0:94:bf. Once you have configured a static ARP entry, you can verify that.
arp -a -n
2.2 TUN and TAP
TUN/TAP:TUNnel / Network Terminal Access Point
TAP运行在链路层,主要用于虚拟机虚拟ethernet网络。
TUN运行在L3 IP层,TUN与IPSec配合,实现IP加密。对/dev/net/tun节点ioctl创建tun0网络,需要给tun0配置IP地址,如下所示。
ifconfig tun0 192.168.1.2/24 up
应用程序通过/dev/net/tun读取tun0发送的报文,经过处理后,通过物理网卡发送出去;应用程序从物理网卡接收到数据,经过处理后,再写入/dev/net/tun,使用tun0接口的应用程序就接收到了数据。
2.3 Bridge
struct net_device {
[...]
// 属于哪个网桥端口
struct net_bridge_port *br_port;
[...]
};
网桥设备端口(eth0、eth1,etc)的MAC被静态配置到FDB(Forwarding Data Base)中,is_local和is_static同时置1。
- QNX hypervisor showcase
1) 连接USB转以太网的RJ45网线到Windows,并且配置其静态IP地址,需要与Android虚拟eth0保持在同一个网段
2) Android一端建立网桥
3) attach Android虚拟eth0到网桥
4) attach Android eth1 (USB转以太网) 到网桥
5) 将Android eth0的IP地址配置给网桥br0,并配置br0的路由
6) 在Windows上ping QNX端虚拟vp0
1) Create network bridge
# create bridge
ip link add name br0 type bridge
ip link set dev eth0 master br0
ip link set dev eth1 master br0
# ip link show master br0
# ip link set eth1 promisc on
# release virtual eth0 IP address
ip link set dev eth0 down
ip addr del 192.168.5.100/24 dev eth0
ip link set dev eth0 up
# bring up USBEthernet
ip link set dev eth1 down
ip addr add 0.0.0.0 dev eth1
ip link set dev eth1 up
# configure br0 ip address and route
ip addr add 192.168.5.100 dev br0
ip link set dev br0 up
ip route add 192.168.5.0/24 via 192.168.5.100
# Windows: ping 192.168.5.X -l 1024 -w 20000
2) Delete network bridge
# tear down bridge
ip route del 192.168.5.0/24 via 192.168.5.100 dev br0
ip link set dev eth0 down
ip link set dev eth1 down
ip link set dev br0 down
# delete bridge
ip link set dev eth0 nomaster
ip link set dev eth1 nomaster
ip link del br0
# bring up virtual eth0 with IP address
ip addr add 192.168.5.100/24 dev eth0
ip link set dev eth0 up
2.4 Switch VLAN Mode
Tag-based VLANs are the industry standard 802.1Q VLANs (dot1q), while the port-based VLANs are more akin to private VLANs.
trunk:VLAN ID多于一个的port口,基于Tag-based,使用IEEE 802.1Q VID
access:untagged port,基于Tag-based,使用IEEE 802.1Q PVID,一个port最多有一个untagged的VLAN
2.5 raw socket CONFIG_PACKET_MMAP
https://www.kernel.org/doc/html/latest/networking/packet_mmap.html
3 TCP协议细节
3.1 TCP的6种标识
SYN:SYNchronous,建立联机
ACK:ACKnowledgement,确认
PSH:PuSH,传送
FIN:FINish,结束
RST:ReSeT,重置
URG:URGent,紧急
SEQ:SEQuence number,顺序号码
3.2 TCP接收数据流程
net/ipv4/tcp_ipv4.c
tcp_v4_do_rcv()
net/ipv4/tcp_input.c
tcp_rcv_established()
tcp_validate_incoming()
TCP fast path只能处理ACK和PSH;如果TCP flag中有其它标志的必须走slow path,譬如有RST标志的一定走slow path
3.3 TCP ACK机制
TCP延时确认时间通常为40毫秒(#define TCP_DELACK_MIN ((unsigned)(HZ/25)))
icsk->icsk_ack.pingpong == 0,表示使用快速确认。
icsk->icsk_ack.pingpong == 1,表示使用延迟确认。
3.4 tcp_mmap
TCP: A subsequent read from the socket will block until SO_RCVLOWAT (Receive Low Water Mark) bytes are available, the default value is 1 byte.
https://lwn.net/Articles/752207/
UDP: recvfrom() receives one datagram at a time. If you want to receive more than one at a time, use recvmsg().
UDP isn't a stream protocol, once you do the initial recvfrom, the remainder of the packet is discarded. The second recvfrom is awaiting the next packet.
4 TCP 3次握手
4.1 Wireshark抓包分析
Wireshark抓包时,3次握手显示的是相对序列号/确认号。如果想要关闭相对序列号/确认号,可以选择Wireshark菜单栏中的 Edit -> Preferences ->protocols ->TCP,去掉Relative sequence number后面勾选框中的√即可。
原始的Seq和ACK的值都很大,为便于理解,使用相对值;有点类似于真值表。
Seq, ACK
0, 0
0, 1
1, 1
3次握手Wireshark过滤规则:tcp.flags.syn==1 or tcp.flags.ack==0
4.2 Linux内核3次握手和接受数据状态机
Figure 4-1 SYN Queue and Accept Queue in Server side
net/ipv4/tcp_input.c
服务端 - tcp_rcv_state_process()
客户端 - tcp_rcv_synsent_state_process()
TCP server端完成最终的3次握手 - 一直处于TCP_LISTEN状态监听连接
in net/ipv4/tcp_minisocks.c
三次握手成功后,tcp_check_req()调用syn_recv_sock() -> tcp_v4_syn_recv_sock(),创建一个新的socket给accept()函数返回。
int tcp_child_process(struct sock *parent, struct sock *child, struct sk_buff *skb)
4.3 tcp_timestamps and tcp_tw_recycle
man tcp
TCP时间戳位于TCP选项中,总共10个字节,kind=8,lenth=10,info字段由timestamp和timestamp echo两个值组成,各4个字节的长度。
tcp_tw_recycle只用在服务端,而tcp_timestamps同时用在服务和客户端,当打开了tcp_timestamps,那么对应机器的TCP报文中就包含时间戳。
服务器tcp_v4_conn_request()
tm->tcpm_ts_stamp记录的是之前收到FIN包时服务器的时间戳
tm->tcpm_ts记录的是FIN包中的时间戳
req->ts_recent表示当前SYN包中的时间戳
因此 (u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL (60s) 表示缓存还未过期,(s32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW (1s) 表示当前SYN包中的时间戳比之前FIN包中的时间戳还小1s(换句话说,时光倒流了),因此认为当前SYN包是之前重传的,直接丢弃。
例子:Carplay server端打开tcp_tw_recycle(socket方式可以设置SO_LINGER = 0),有可能会导致server端因为时间戳异常直接丢弃client发过来的SYN报文 - tcp_peer_is_proven(req, dst, true),导致client连接超时,可以使用命令netstat -s确认。特别是有多个client位于路由器NAT之后,虽然有多个client连接服务器,因为路由器要做SNAT转换,所以server只看到一个源IP地址,由于不同client机器的时间不同步,一个client的断开,可能会导致其它client无法连接,实际是不同client发送过来的SYN报文,因此server端一般不要使能tcp_tw_recycle选项。
Linux系统的默认设置
tcp_timestamps默认开启。tcp_timestamps是RFC1323定义的优化选项,主要用于TCP连接中 RTT(Round Trip Time)的计算,开启tcp_timestamps有利于系统计算更加准确的RTT,也就有利于TCP性能的提升。tcp_tw_recycle默认关闭。
如果客户端的tcp_timestamps没有开启,那么TCP报文中(当然也包括SYN报文)不会包含时间戳,根据上述分析,服务端即使打开tcp_tw_recycle也不起作用。
4.4 TCP 3次握手SYN重试次数设置
2分7秒:
第1次发送SYN报文后等待1s(2的0次幂),如果超时,则重试;
第2次发送后等待2s(2的1次幂),如果超时,则重试;
第3次发送后等待4s(2的2次幂),如果超时,则重试;
第4次发送后等待8s(2的3次幂),如果超时,则重试;
第5次发送后等待16s(2的4次幂),如果超时,则重试;
第6次发送后等待32s(2的5次幂),如果超时,则重试;
第7次发送后等待64s(2的6次幂),如果超时,则超时失败;
上面的结果刚好是127秒。也就是说Linux内核在尝试建立TCP连接时,最多会尝试7次,超时则返回错误Connection timed out。
echo 3 > /proc/sys/net/ipv4/tcp_syn_retries
4.5 TCP滑动窗口
由于TCP的头部窗口字段只有16 bit,最多表示64k,为了表示更大的窗口,使用了可选的放大倍数。
1)在TCP三次握手的时候在SYN或SYN-ACK包中,通知options可选信息,告知对方将使用放大倍数
2)SYN本身不放大
3)Window size value表示报文的值,Calculated window size表示放大后的值,也就是实际可用的值;Window size scaling factor表示放大倍数
cat /proc/sys/net/ipv4/tcp_window_scaling
4)在途字节数:on the line,是站在发送者的角度,表示的概念是,我已经发了多少,减去对方最近的一次确认,确认了多少,也就是Seq + Len - Ack (最近的一次Ack)。
如果在途字节数等于对方的接收窗口,这个时候Wireshark打上TCP window Full标记,表示我不能再发送数据了。
5)showcase
#include <linux/tcp.h>
struct tcp_info ti;
socklen_t tisize = sizeof(ti);
getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &tisize);
// ti.tcpi_rcv_space contains the advertised tcp receive
// Since Linux 4.8, read or set the send and receive window directly.
struct tcp_repair_window trw;
socklen_t trwsize = sizeof(trw);
getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &trwsize);
4.6 TCP SYN fail
[9th-Apr-2022]
4.6.1 tcp_timestamps and tcp_tw_recycle
tcp_tw_reuse is only for tcp client.
tcp_timestamps is for both client and server, enabled by default in Linux.
tcp_tw_recycle is only for tcp server, never enable it, otherwise CarPlay fails three-way tcp handshake in latest iOS.
Refer to 4.3 for more info.
4.6.2 iptables
iptables -A OUTPUT -o <NODE> -p tcp -m tcp --syn -j ACCEPT
This configuration is for HiCar.
5 TCP 4次挥手
5.1 工作原理
@ net/ipv4/tcp.c
tcp_close() - 如果接收缓冲区中还有数据,协议栈就会发送RST而不是FIN
4次握手Wireshark过滤规则:tcp.flags.fin == 1
5.2 MSL
MSL就是maximum segment lifetime(最大分段生存时间),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间,IP数据包将在网络中消失(IP报文头中的TTL减到0),MSL在RFC 1122上建议是2分钟,而源自berkeley的TCP实现传统上使用30秒。
遇到特殊问题时,也可以直接修改内核的宏TCP_TIMEWAIT_LEN。
Figure 5-1 4-way handshake
5.3 showcase
# FIN_WAIT1
# if this value is equal to 0, kernel chooses 8
echo 2 > /proc/sys/net/ipv4/tcp_orphan_retries
# TCP_FIN_TIMEOUT
echo 3 > /proc/sys/net/ipv4/tcp_fin_timeout
# TCP_TIMEWAIT_LEN,
# this may cause TCP SYN issue
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
6 FIB
6.1 路由表
1)Android支持255个路由表
Linux内核支持策略表的数目是2的15次方,范围是[0, 32767],而内核支持的路由表范围是[0, 255]。
ip route list table [ID]
0 - 显示全部表
253 - default
254 - main
255 - local
2)Android默认gateway
Android Framework激活一个网络后,会用网络的名字(wlan0或者eth0)创建一个ip rule(kernel函数fib_nl_newrule)作为default gateway,优先级等于22000(RULE_PRIORITY_DEFAULT_NETWORK,参考netd),当目的IP不是局域网地址,并且对下面的三个表做LPM后,仍旧不匹配,会使用这个表(kernel函数fib_select_default)。
ip route list table default
ip route list table main
ip route list table local
可以用如下的方式查看优先级是22000的那个ip rule。
ip route list table eth0
ip route list table wlan0
3)Route Flags
route -n:
U: Route is up
H: Target is a host
G: Use gateway
R: Reinstate route for dynamic routing
D: Dynamically installed by daemon or redirect
M: Modified from routing daemon or redirect
A: Installed by addrconf
!: Reject route
6.2 fib_lookup
1)路由查找使用的算法是LPM(最长匹配,Longest Prefix Match)。匹配的网络掩码越长,说明路由越准确。
比如,192.168.0.0/16和192.168.0.0/24这两个网络掩码,根据LPM算法,192.168.0.0/24将首先进行匹配,匹配不符,才匹配192.168.0.0/16。
2)fib_select_default,多网卡负载均衡文件节点/proc/net/bonding/bond0。
route:基于ioctl
ip route:基于netlink
6.3 ip rule解读
CONFIG_IP_MULTIPLE_TABLES
busybox route -n
ip route list table main或者ip route
ip route list table 1
ip rule
6.4 默认网关的作用
syntax:
ip route add {NETWORK/MASK} via {GATEWAYIP}
ip route add {NETWORK/MASK} dev {DEVICE}
ip route add default {NETWORK/MASK} dev {DEVICE}
ip route add default {NETWORK/MASK} via {GATEWAYIP}
ip route add default via 192.168.0.1 dev eth0 table 1
# ip rule add from all lookup main pref 9999
# 上一条命令等价于在netd中添加modifyIpRule(RTM_NEWRULE, 9999, 254, 0, 0);
ip rule add from 192.168.0.0/24 table 1
ip rule add to 192.168.0.0/24 table 1
table 1中包含了eth0默认网关,而table main中包含了正常的eth0路由。
6.5 获取网络接口的IP地址
ret = ioctl(fd, SIOCGIFADDR, &ifr);
1) /sys/class/net/<ifname> not exists, errno = ENODEV
2) /sys/class/net/<ifname> exists and ip address not configured, errno = EADDRNOTAVAIL
3) <ifname> ip address configured, ioctl return 0
7 Linux sk_buff重要字段
h:指向TCP/UDP头
nh:指向IP头
mac:指向MAC头,neigh_hh_output(),hh means hardware header
head:指向缓冲区的头,head <= data
data:指向缓冲区MAC头
tail:指向payload尾部
end:指向缓冲区的尾部,end >= tail
TX: 邻居子系统,添加12字节MAC地址头
RX: __netif_receive_skb()
8 Security
TLS
IPsec: strongSwan
MACsec: 2021 28nm 88Q5151, 88Q5152
9 常用调试命令
9.1 dropwatch
CONFIG_NET_DROP_MONITOR=m
echo 0 > /proc/sys/kernel/kptr_restrict
libncurses.a (new curses) for Android
https://github.com/CyanogenMod/android_external_libncurses
libhistory.a and libreadline.a for Android
https://github.com/LineageOS/android_external_bash
libnl.a
external/libnl
libpcap.a
external/libpcap
dropwatch
https://github.com/nhorman/dropwatch
9.2 ethtool
rxwen/ethtool
https://github.com/rxwen/ethtool
ethtool eth0
netstat -apnt
netstat -apnu
9.3 nmap
Network Mapper,Linux下的网络扫描和嗅探工具。
9.4 ss
ss -4tu0
ss -tan state time-wait | wc -l
ss:Socket Statistics,代替net-tools中的netstat。ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。ss快的秘诀在于,它利用到了TCP协议栈中tcp_diag。tcp_diag是一个用于分析统计的模块,可以获得Linux内核中第一手的信息,这就确保了ss的快捷高效。
9.5 tcpdump
tcpdump -X -i eth0 -s 0 -C 20 -W 3 -w /data/eth_sniff.pcap
9.6 traceroute
Windows 等效命令是tracert
9.7 TCP/UDP测试工具
Linux TCP Server: busybox nc -lvz -s 192.168.0.10 -p 8001
10 Abbreviations
3Com: Computer, Communication, Compatibility, by Robert Metcalfe
ndos: network device operations
PFE: S32G Packet Forward Engine, route table offload
trie: pronounced tree or try