Linux 网络编程-结构体总结
资料来源:
<>
更新
1
2024.03.05 初始
导语
一些 Linux 网络编程中常见的结构体及一些用法总结.
Function and Algorithm
先是大小端 (网络序/主机序) 转换两个函数:
ntohl
ntohs
32bit 和 16bit | 网络序 -> 主机序htonl
htons
32bit 和 16bit | 主机序 -> 网络序
这俩函数基本是网络编程的基石了,先拜拜 永无 bug;
1’s Complement 加法
1's complement
翻译为 二进制数的 反码; 是一种 数字表示法
- 将 二进制 数每个数字反转得到的数: 若某一位为 0, 则使其变为 1,反之亦然.[1]
network 的 1's complement
加法
- 校验和 位置 置 0
- 待计算部分 划分为 16bit, 不足部分补 0.
- 逐个 16bit 相加 得到 sum
- sum 高于 16bit 部分 + 回低 16bit; 重复这个过程直到最后只剩下 16bit res;
- 取 res 的逐位取反 (即 res 的
1's complement
表示).
校验
- 同样 16bit 得到 sum;
- sum += 校验和
- sum 高 16bit 加回低 16bit; 得到 16bit res;
- 再取反 应该得到 0;
校验和算法参考: https://github.com/buckrudy/Blog/issues/12
L2
以太网头部: 默认长度 12 字节, 有标签情况下可能是 18 字节 (802.1q) 22 字节 (802.1ad)
1 |
|
h_proto 是以太网的协议类型; v4 v6 arp; 0x8100 VXLAN (ETH_P_8021Q ETH_P_8021AD); MPLS 的 单/多标签
0x8100 VXLAN (ETH_P_8021Q ETH_P_8021AD) 时候需要处理拓展的 标签长度.
L3
ip 头 和 ip 地址的存储转换
Ip 地址
v4 地址 struct in_addr
就是一个 网络序的 u32; v6 struct in6_addr
则是联合体 32*4
128bit 网络序;
1 | /* Internet address. */ |
地址转换: 人类可读的 str 类型 和 struct in_addr
struct in6_addr
互相转换
inet_pton
inet_ntop
1 | struct in_addr ipv4addr; |
Ipv4
1 | struct iphdr |
saddr daddr 代表了 v4 地址的数值表示.
校验和
ihl(Internet Header Length): 第一个字节的后 4bit 代表了头部长度 (x 4 字节 x32bit)
- 基本情况下是 20 字节, 也就是 5;
- 但是可选字段下最大可以拓展到 60 字节;
校验和计算范围: 包括可选字段的整个 header, 不足 16bit 补 0;
Ipv6
1 | struct ip6_hdr |
struct in6_addr
代表了 v6 地址表示,实际长度是 u32 x 4 = 128bit 对应 v6 的长度.
- 封装了 3 种形式的访问: 8bit 16bit 和 32bit
校验和
ipv6 没有校验和 第一军团没有秘密
l2 以及提供足够的校验了, tcp/udp 也有自己的校验, l3 其实没那么需要.
v6 设计时候就加快包转发, header 长度固定, 没有分片, 不计算校验和 更减轻了路由的计算压力.
L4
l4 这里主要是传输层协议 tcp udp icmp 和 icmpv6 了.
Tcp
struct tcphdr
tcp 头部
netinet/tcp.h
和netinet/tcp.h
, 不涉及内核, 网络编程就选netinet/tcp.h
struct tcphdr
这里使用了两个结构体访问同一个 tcp 头, 后一种更加细分, 访问到具体 各个 bit;
1 | /* |
- 源码太长了, 删减了 大小端判断, linux x64 基本是小端.
doff
tcp 头部长度, >5 有可选字段
窗口缩放因子
刷八股文时候, tcp 滑动窗口基本最大值是 65535; 但是实际写 conntack 时候遇到个 窗口缩放因子;
跨洋线路,高延迟 高容量 window_size 65535 完全不够用, 哪怕没有丢包也只会收敛到很小的速度.
实际窗口 = window_size(16bit) << 缩放因子;
- 变相将窗口拓展到了 32bit;
窗口缩放因子
- RFC 1072 中引入 RFC 1323 完善; 位于 tcp 头可选字段;
- 仅在握手时候确认固定值, 两侧都必须支持缩放才启用, 一侧不支持都禁用.
- 3 个字节, 第一个字节是选项种类 (3 固定值) 第二个 选项长度, 第三个 窗口缩放值 (0 至 14)
![[Pasted image 20240228185056.png]]
取值:
- 看 doff > 5,
- 遍历找到字段选项 3, 取值
- 还得确认对端也启用了窗口缩放.
校验和
需要个伪头部, 然后再进入 1's complement
加法流程
伪头部 对于 v4 v6 略有不同
- v4: 12 字节; 源地址 目的地址 padding(8bit) 协议值 (TCP) TCP 长度 (header+payload)
- v6: 源地址 目的地址 数据长度 (header+payload) padding(32bit) 下一个头部字段 (TCP 协议值)
Udp
struct udphdr 还是用 netinet/udp.h
的定义.
1 | /* UDP header as specified by RFC 768, August 1980. */ |
还是 udp 头部简单.
校验和
与 tcp 伪头部构造相同, 协议换成 udp;
Icmp
struct icmphdr: netinet/ip_icmp.h
1 | struct icmphdr |
conntrack 跟踪 icmp 则取的是 id type 和 code;
最常见的类型就是 ICMP_ECHO
ICMP_ECHOREPLY
一去一回.
校验和
不包括伪头部! 只计算 icmp 部分;
Icmp6
struct icmp6_hdr: 与 icmp 还是有些区别, 特别是具体类型.
1 | struct icmp6_hdr |
ICMP6_ECHO_REQUEST
和 ICMP6_ECHO_REPLY
一去一回.
校验和
icmpv6 是有伪头部的; 这一点和 icmp 完全不同
- 毕竟 icmpv6 几乎是重新设计的协议,虽然还是叫 icmp, 还承接了 igmp arp 等等 v4 协议的功能.
伪头部包含以下字段:源地址 目的地址 ICMPv6 消息长度 一个三个字节的填充字段 下一个头部字段 (icmpv6 协议值)
M Morris Mano; Michael D Ciletti. Digital design : with an introduction to the verilog hdl. 培生教育. 2013: 第 27 页. ISBN 9780273764526. ↩︎