IP协议数据包格式:
IP协议最终目的是把数据从源传送到目的地,它不保证数据传送的靠性性!
主要作用:
- 数据传送:将数据从一个主机传到另一个主机
- 寻址:根据子网划分IP地址,发现正确的目的主机地址
- 路由选择:选择数据在互联网上的传送路径
- 数据报文的分段:当传送的数据大于MTU时,将数据进行分段发送和接收并组装
协议字段解释:
- 版本:IP协议版本号,长度为4位。对于IPV4,此处值为4;对于IPV6为6。
- 首部长度:4位,代表IP头部长度,该值 x 4 = 首部占用字节数。
- 服务类型:8位,该字段现在基本没什么作用,通常设为0x00。
- 总长度:16位(IP报文最大数据长度为65535字节),表示以字节为单位的数据报文长度,长度包含IP的头部和数据部分。
标识和片偏移:IP每发送一份数据报文后都会将此值+1。当一份报文数据大于MTU(最大传输单元,一般为1500)时就会进行分片,所分的片用片偏移记录,以便重组,由于这些分片属于同一份IP数据,故标识相同,片偏移不同。
现在TCP协议传输数据时会将数据分段,分的段足够小(一般<=1460),以至于数据到达IP层封装的时候不足以让IP分片,知道为什么会这样吗?
因为IP协议不保证数据传送的靠性性!当一份IP数据的某个小分片丢失时(事实上数据包丢失的情况非常常见),整个IP报文都得重传!!!TCP协议事先在三次握手的过程中协商了MSS值(最大TCP分段大小),将设置MSS值小于等于1460,这样大大降低了数据重传所付出的代价。生存时间TTL:8位,该字段的值表示数据报文组多可以经过的路由器的数量。该字段很有意思,每经过一个路由器,TTL的值会减1,当TTL的值为0时,路由器会将该数据包丢弃,以防止该包在网络中不断循环,当然路由器丢弃该数据包时会发送一个ICMP报文通知源主机。
- 协议类型:8位,表示IP上承载的是什么协议,见下表:
值 | 协议类型 | 值 | 协议类型 |
---|---|---|---|
1 | ICMP | 6 | TCP |
2 | IGMP | 17 | UDP |
- 头部校验和:16位,只对IP头部进行校验。接收方接收到数据后也会对数据进行校验,然后和该字段进行比对,如果不匹配则表示此帧数据发生错误。由于数据每经过一个路由器都会更改TTL值,所以路由器要重新对IP包头进行校验。
- 源地址和目的地址:分别表示发送数据的主机和接收数据的主机的IP地址。
- IP选项:该字段一般为空,很少用到,不解释。
相关源代码
再linux/ip.h文件中,ip包头结构体定义如下:
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
/*The options start here. */
};
获取ip包头结构体函数:
#ifdef __KERNEL__
#include <linux/skbuff.h>
static inline struct iphdr *ip_hdr(const struct sk_buff *skb)//常用
{
return (struct iphdr *)skb_network_header(skb);
}
static inline struct iphdr *ipip_hdr(const struct sk_buff *skb)
//TCP头,在数据包未拆封之前,sk_buff结构体成员TCP头指针可能和IP头指针指向同一个位置
{
return (struct iphdr *)skb_transport_header(skb);
}
#endif