粉丝不过W
MAC 头部
CRC、 PAD 在组包时可以忽略
CRC,循环冗余校验码: 数据通信领域中最常用的一种查错校验码, 特征:信息字段和校验字段的长度可以任意选定
循环冗余检查:一种数据传输检错功能, 对数据进行h多项式计算, 并将得到的结果附在帧的后面, 接收设备也执行类似的算法 , 来保证数据传输的正确性和完整性
///usr/include/net/ethernet.h
/* 10Mb/s ethernet header */
struct ether_header
{
uint8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
uint8_t ether_shost[ETH_ALEN]; /* source ether addr */
uint16_t ether_type; /* packet type ID field */
} __attribute__ ((__packed__));
// /usr/include/linux/if_ether.h
/*
* This is an Ethernet frame header.
*/
/* allow libcs like musl to deactivate this, glibc does not implement this. */
#ifndef __UAPI_DEF_ETHHDR
#define __UAPI_DEF_ETHHDR 1
#endif
#if __UAPI_DEF_ETHHDR
struct ethhdr
{
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
__be16 h_proto; /* packet type ID field */
} __attribute__((packed));
#endif
ARP 头部
Dest MAC: 目的MAC地址
Src MAC: 源MAC地址
帧类型: 0x0806
硬件类型: 1( 以太网)
协议类型: 0x0800( IP地址)
硬件地址长度: 6
协议地址长度: 4
OP: 1( ARP请求) , 2( ARP应答) , 3( RARP请求) , 4( RARP应答)
// /usr/include/net/if_arp.h
/* See RFC 826 for protocol description. ARP packets are variable
in size; the arphdr structure defines the fixed-length portion.
Protocol type values are the same as those for 10 Mb/s Ethernet.
It is followed by the variable-sized fields ar_sha, arp_spa,
arp_tha and arp_tpa in that order, according to the lengths
specified. Field names used correspond to RFC 826. */
struct arphdr
{
unsigned short int ar_hrd; /* Format of hardware address. */
unsigned short int ar_pro; /* Format of protocol address. */
unsigned char ar_hln; /* Length of hardware address. */
unsigned char ar_pln; /* Length of protocol address. */
unsigned short int ar_op; /* ARP opcode (command). */
#if 0
/* Ethernet looks like this : This bit is variable sized
however... */
unsigned char __ar_sha[ETH_ALEN]; /* Sender hardware address. */
unsigned char __ar_sip[4]; /* Sender IP address. */
unsigned char __ar_tha[ETH_ALEN]; /* Target hardware address. */
unsigned char __ar_tip[4]; /* Target IP address. */
#endif
};
IP 头部
.版本: IP协议的版本。 通信双方使用过的IP协议的版本必须一致, 最广泛使用的IP协议版本号为4( 即IPv4 )
首部长度: 单位是32位( 4字节)
服务类型: 一般不适用, 取值为0。 前3位: 优先级, 第4-7位: 延时, 吞吐量, 可靠性, 花费。 第8位保留
总长度: 指首部加上数据的总长度, 单位为字节。 最大长度为65535字节
标识( identification) : 标识主机发送的每一份数据报。 IP软件在存储器中维持一个计数器, 每产生一个数据报, 计数器就加1, 并将此值赋给标识字段
标志( flag) : 目前只有两位有意义
标志字段中的最低位记为MF。 MF=1:后面“还有分片”的数据报。 MF=0:已是若干数据报片中的最后一个
标志字段中间的一位记为DF, :“不能分片”, DF=0:允许分片
片偏移: 指出较长的分组在分片后, 某片在源分组中的相对位置, 也就是说, 相对于用户数据段的起点, 该片从何处开始。 片偏移以8字节为偏移单位
生存时间: TTL,数据报在网络中的寿命, 也为“ 跳数限制 ”, 由发出数据报的源点设置这个字段。 路由器在转发数据之前就把TTL值减一, 当TTL值减为零时, 就丢弃这个数据报。 通常设置为32、64、 128
协议: 指出此数据报携带的数据时使用何种协议, 让目的主机的IP层知道应将数据部分上交给哪个处理过程, 常用 ICMP(1), IGMP(2), TCP(6), UDP(17), IPv6( 41)
首部校验和: 只校验数据报的首部, 不包括数据部分
源地址: 发送方IP地址
目的地址: 接收方IP地址
选项: 用来定义一些任选项; 如记录路径、 时间戳等。 这些选项很少被使用, 不是所有主机和路由器都支持这些选项
// /usr/include/netinet/ip.h
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4; /* 首部长度 */
unsigned int version:4; /* 版本 */
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4; /* 版本 */
unsigned int ihl:4; /* 首部长度 */
#else
# error "Please fix <bits/endian.h>"
#endif
uint8_t tos; /* 服务类型 */
uint16_t tot_len; /* 总长度 */
uint16_t id; /* 标识 */
uint16_t frag_off; /* 标志 偏移量 */
uint8_t ttl; /* 生存时间 */
uint8_t protocol; /* 协议 */
uint16_t check; /* 首部校验和 */
uint32_t saddr; /* 源地址 */
uint32_t daddr; /* 目标地址 */
/*The options start here. */
};
UDP 头部
源端口号: 发送方端口号
目的端口号: 接收方端口号
长度: UDP用户数据报的长度, 最小值是8( 仅有首部)
校验和: 检测UDP用户数据报在传输中是否有错, 有错就丢弃
// /usr/include/netinet/udp.h
/* UDP header as specified by RFC 768, August 1980. */
struct udphdr
{
__extension__ union
{
struct
{
uint16_t uh_sport; /* source port */
uint16_t uh_dport; /* destination port */
uint16_t uh_ulen; /* udp length */
uint16_t uh_sum; /* udp checksum */
};
struct
{
uint16_t source;
uint16_t dest;
uint16_t len;
uint16_t check;
};
};
};
TCP 头部
源端口号: 发送方端口号
目的端口号: 接收方端口号
序列号: 本报文段的数据的第一个字节的序号
确认序号: 期望收到对方下一个报文段的第一个数据字节的序号
首部长度( 数据偏移): TCP报文段的数据起始处与 TCP报文段的起始处距离, 即首部长度。 单位: 32位, 计算单位:4字节
保留: 占6位, 保留为今后使用, 目前应置为0
紧急URG: 1:紧急指针字段有效, 它告诉系统此报文段中有紧急数据, 应尽快传送
确认ACK: ACK = 1:确认号字段 有效, TCP规定, 在连接建立后所有传达的报文段都必须把ACK = 1
推送PSH: 当两个应用进程进行交互式的通信时, 有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。 在这种情况下, TCP就可以使用推送( push) 操作, 这时, 发送方TCP把PSH = 1, 并立即创建一个报文段发送出去, 接收方收到PSH=1的报文段, 就尽快地( 即“ 推送” 向前) 交付给接收应用进程, 而不再等到整个缓存都填满后再向上交付
复位RST: 复位相应的TCP连接
同步SYN: 在三次握手建立TCP连接时有效。 当SYN=1而ACK=0:一个连接请求报文段,对方若同意建立连接, 则应在相应的报文段中使用SYN=1和ACK=1。因此, SYN=1:一个连接请求或连接接受报文
终止FIN: 用来释放一个连接。 FIN=1:此报文段的发送方的数据已经发送完毕, 并要求释放运输连接
窗口: 指发送本报文段的一方的接收窗口( 而不是自己的发送窗口)
校验和: 校验和字段检验的范围包括 首部和 数据两部分, 在计算校验和时需要加上12字节的伪头部
紧急指针: URG=1:有用, 它指出本报文段中的紧急数据的字节数( 紧急数据结束后就是普通数据, 即指出了紧急数据的末尾在报文中的位置, 注意: 即使窗口 = 9,也可发送紧急数据
选项: 长度可变, 最长可达40字节, 当没有使用选项时, TCP首部长度是20字节
// /usr/include/netinet/tcp.h
/*
* TCP header.
* Per RFC 793, September, 1981.
*/
struct tcphdr
{
__extension__ union
{
struct
{
uint16_t th_sport; /* source port */
uint16_t th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
# if __BYTE_ORDER == __LITTLE_ENDIAN
uint8_t th_x2:4; /* (unused) */
uint8_t th_off:4; /* data offset */
# endif
# if __BYTE_ORDER == __BIG_ENDIAN
uint8_t th_off:4; /* data offset */
uint8_t th_x2:4; /* (unused) */
# endif
uint8_t th_flags;
# define TH_FIN 0x01
# define TH_SYN 0x02
# define TH_RST 0x04
# define TH_PUSH 0x08
# define TH_ACK 0x10
# define TH_URG 0x20
uint16_t th_win; /* window */
uint16_t th_sum; /* checksum */
uint16_t th_urp; /* urgent pointer */
};
struct
{
uint16_t source; /*源端口号*/
uint16_t dest;
uint32_t seq;
uint32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
uint16_t res1:4;
uint16_t doff:4;
uint16_t fin:1;
uint16_t syn:1;
uint16_t rst:1;
uint16_t psh:1;
uint16_t ack:1;
uint16_t urg:1;
uint16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
uint16_t doff:4;
uint16_t res1:4;
uint16_t res2:2;
uint16_t urg:1;
uint16_t ack:1;
uint16_t psh:1; /*推送PSH*/
uint16_t rst:1; /* 复位RST */
uint16_t syn:1; /* 同步SYN */
uint16_t fin:1; /* 终止FIN */
# else
# error "Adjust your <bits/endian.h> defines"
# endif
uint16_t window; /* 窗口 */
uint16_t check; /* 校验和 */
uint16_t urg_ptr; /* 紧急指针 */
};
};
};