结论
RAW套接字收IPv6报文无法获取IPv6头部信息,可能没有想象中的那么严重。
您甚至可以完美地伪造头部信息出来!
澄清常见使用误解
网络协议 | IP_HDRINCL or IPV6_HDRINCL | recvfrom携带IPv*头部字段 |
---|---|---|
IPv4 | 是 | 是 |
IPv4 | 否 | 是 |
IPv6 | 是 | 否 |
IPv6 | 否 | 否 |
无论您是否设定IP_HDRINCL,IPv4 RAW Socket固定可以在收包时,携带IPv4的头部信息给用户;但,IPv6的RAW Socekt,无论设定,还是不设定IPV6_HDRINCL套接字选项,都无法携带完整的头部字段出来!
虽然,在Linux 4.5的内核以后,内核就支持了IPV6_HDRINCL的宏定义和套接字选项操作,但是,它也并不是为了支持收包时携带完整的IPv6头部信息出来。
在Ubuntu 20.04版本上试验,IPv6设定选项后,依然无法收取IPv6头出来
无论是IP_HDRINCL,还是IPV6_HDRINCL套接字选项,更侧重于发包时的使用,对于收包影响没有那么大。
IPPROTO_RAW
生成IPPROTO_RAW
协议号的RAW套接字,暗含着IP_HDRINCL的操作语义;但是,IPPROTO_RAW
类型的RAW套接字仅是sendOnly
发送套接字,并不能指望它能够收取任意协议的IP报文
设定IP_HDRINCL无法分片的BUG
设定IP_HDRINCL
套接字选项,或生成IPPROTO_RAW
类型的RAW套接字,都会导致报文消息无法正确分片的bug。即使在IPv6的RAW Socket实现中,IPV6_HDRINCL
依然保持了这种一致性,以至于都可以认为它要成为一个众说周知的特性!
RAW Socket在收包时,无论是否设定IP_HDRINCL or IPV6_HDRINCL套接字选项,支持分片的重组操作
如何支持发送时分片
- 尽量不设定
IP_HDRINCL
套接字选项,或使用IPPROTO_RAW
类型的RAW套接字 - 做用户层面的自主分片;但如果需要非常高的正确性、可靠性,就要考虑路径MTU探测问题,而不单是考虑网卡上设定的MTU
RAW套接字,几乎可以支撑起微型化核心网发送平面的所有工作
IPv6收报无法携带头部字段的解决方法
伪造头部信息
伪造的头部在很大程度上,可以完美复原,并不需要担心太多!
理论分析
IPv6头部格式
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| Traffic Class | Flow Label |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length | Next Header | Hop Limit |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Source Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ Destination Address +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
typedef struct _IPv6_HEADER_t
{
union
{
uint8_t abVTF[4]; //版本信息最高4位 traffic class、flow label
uint32_t dwVTF; //低20 bits Flow Label信息
};
uint16_t u16PayloadLen; //数据包长度
uint8_t bNextHeader; //数据包标识
uint8_t bHopLimit; // max hop limit
union
{
uint8_t abSrcAddr[16];
u_int32_t au32SrcAddr[4];
u_int64_t au64SrcAddr[2];
};
union
{
uint8_t abDstAddr[16];
u_int32_t au32DstAddr[4];
u_int64_t au64DstAddr[2];
};
} __attribute__((packed)) IPv6_HEADER_t, *IPv6_HEADER_t_Ptr;
分析
通过阅读IPv6的rfc规范,在IPv6头部信息中比较重要的是flow label
和traffic class
。对于要求不高的网络层处理,和其上层应用来说,其他头域信息可以完美推导出来,例如,下个头的协议号、源地址、目的地址;或无关紧要,例如,报文限制的最大跳数。
在IPv6 rfc规范中flow label
认为可以在源和目的之间,结合源地址、目的地址、流标签形成某种三元组管理;但,traffic class
头域就没有那么重要了,被界定为网络层使用,且规范认为,即使在收包时能够获取到此字段信息,也不能认为此字段和源头一模一样,所以,重要的信息就只剩下flow label
了。
The 8-bit Traffic Class field in the IPv6 header is used by the network for traffic management. The value of the Traffic Class bits in a
received
packet or fragment might be different from the value sent by the packet’s source.
好的福音是,对于flow label
的信息,我们可以在socket send/recv
套接字接口的struct sockaddr_in6
地址参数中进行设定或获取,所以,flow label
信息也可以完美恢复出来!
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
以我个人的猜测,IPv6收报时不携带头部字段,可能是觉得无需携带,因为IPv6头部信息比较标准和简单,可以比较无损地恢复出来;或被遗忘了,忘记与IPv4保持一致性了。。。
特别的隧道应用
如果您是终点应用,IPv6头部信息,除了flow label
信息外,都可以不用关心;但对于中间节点应用,特别是隧道应用,就会担心如果不是原始的头部信息,擦除了flow label
等信息,可能会存在一些风险。
分析来看,IPv6 RAW Socket收取报文,无法携带完整头部确实存在那么一点风险,但通过对于rfc规范的研读,以及完美地恢复出和源紧密相关的头部flow label
信息,即使再次伪造出头部,一样可以完成任务!