6. TCP/IP协议栈及以太网帧
- 目标
- 了解TCP/IP协议栈的组织结构
- 掌握以太网帧的数据格式定义
- 能应用编码实现以太网帧的解析方法
6.1. TCP/IP 协议栈
TCP/IP网络协议栈分为应用层(Application)、传输层(Transport)、网络层(Network)和链路层(Link)四层。如下图所示(该图出自[TCPIP])。
两台计算机通过TCP/IP协议通讯的过程如下所示(该图出自[TCPIP])。
传输层及其以下的机制由内核提供,应用层由用户进程提供(后面将介绍如何使用socket API编写应用程序),应用程序对通讯数据的含义进行解释,而传输层及其以下处理通讯的细节,将数据从一台计算机通过一定的路径发送到另一台计算机。应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装(Encapsulation),如下图所示(该图出自[TCPIP])。
不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。我们在pcap文件中,所得到的package data数据就是一个以太网帧, 因此对“package data”的解析就是解析一个以太网帧
6.2. 以太网(RFC 894)帧格式
以太网的帧格式如下所示(该图出自[TCPIP]):
- 源地址是指网卡的硬件地址(也叫MAC地址),长度是48位,是在网卡出厂时固化的。用ifconfig命令看一下,“HWaddr 00:15:F2:14:9E:3F”部分就是硬件地址。
- 目的地址, 意义同源地址
-
协议字段有三种值,分别对应IP、ARP、RARP。帧末尾是CRC校验码。
以太网帧中的数据长度规定最小46字节,最大1500字节。
ARP和RARP数据包的长度不够46字节,要在后面补填充位。
最大值1500称为以太网的最大传输单元(MTU),不同的网络类型有不同的MTU,如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路的MTU了,则需要对数据包进行分片(fragmentation)。
ifconfig命令的输出中也有“MTU:1500”。
注意,MTU这个概念指数据帧中有效载荷的最大长度,不包括帧首部的长度。
6.3. 以太网栈头部
// /usr/include/linux/if_ether.h
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* 目标mac地址 */
unsigned char h_source[ETH_ALEN]; /* 源mac地址 */
__be16 h_proto; /* 以太网类型 */
} __attribute__((packed));
6.4. 以太网帧解析代码
一下为以太网帧的核心解析代码
MAC_FRM_HDR *mac_hdr; //define a Ethernet frame header
IP_HDR *ip_hdr; //define a IP header
char *tmp1, *tmp2;
int AND_LOGIC = 0xFF;
mac_hdr = buf; //buf is what we got from the socket program
ip_hdr = buf + sizeof(MAC_FRM_HDR);
//udp_hdr = buf + sizeof(MAC_FRM_HDR) + sizeof(IP_HDR); //if we want to analyses the UDP/TCP
tmp1 = mac_hdr->src_addr;
tmp2 = mac_hdr->dest_addr;
/* print the MAC addresses of source and receiving host */
printf("MAC: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X==>" "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X",
tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
tmp1[4]&AND_LOGIC, tmp1[5]&AND_LOGIC,
tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC,
tmp2[4]&AND_LOGIC, tmp2[5]&AND_LOGIC);
tmp1 = (char*)&ip_hdr->ip_src;
tmp2 = (char*)&ip_hdr->ip_dest;
/* print the IP addresses of source and receiving host */
printf("IP: %d.%d.%d.%d => %d.%d.%d.%d",
tmp1[0]&AND_LOGIC, tmp1[1]&AND_LOGIC, tmp1[2]&AND_LOGIC,tmp1[3]&AND_LOGIC,
tmp2[0]&AND_LOGIC, tmp2[1]&AND_LOGIC, tmp2[2]&AND_LOGIC,tmp2[3]&AND_LOGIC);
/* print the IP protocol which was used by the socket communication */
switch(ip_hdr->ip_protocol) {
case IPPROTO_ICMP: LOGI("ICMP"); break;
case IPPROTO_IGMP: LOGI("IGMP"); break;
case IPPROTO_IPIP: LOGI("IPIP"); break;
case IPPROTO_TCP:
case IPPROTO_UDP:
LOGI("Protocol: %s", ip_hdr->ip_protocol == IPPROTO_TCP ? "TCP" : "UDP");
LOGI("Source port: %u, destination port: %u", udp_hdr->s_port, udp_hdr->d_port);
break;
case IPPROTO_RAW: LOGI("RAW"); break;
default: printf("Unknown, please query in inclued/linux/in.h\n"); break;
}
7. IP 报文
- 目标
- 了解IP报文的组织结构
- 掌握IP报文的分析方法。
7.1. IP数据报格式
IP数据报的格式如下(这里只讨论IPv4)(该图出自[TCPIP]):
-
版本:IP报文版本号 IPV4:4,IPV6:6
-
首部长度:IP header 长度,是4字节的整数倍, 没有选项,则一般为5(5x32bit=20B)
- 8位服务类型:一般没有使用,详细参考RFC
- 总长度:header+数据 总长度
- 16位标识:IP 报文的唯一id,分片报文的id 相同,便于进行重组。
- 3位标志:标明是否分片。是一个3位的控制字段,包含:
- 保留位:1位
- 不分段位:1位,取值:0(允许数据报分段)、1(数据报不能分段)
- 更多段位:1位,取值:0(数据包后面没有包,该包为最后的包)、1(数据包后面有更多的包)
Bit 0: reserved, must be zero
Bit 1: (DF) 0 = May Fragment, 1 = Don't Fragment.
Bit 2: (MF) 0 = Last Fragment, 1 = More Fragments.
0 1 2
+---+---+---+
| | D | M |
| 0 | F | F |
+---+---+---+
- 13位片偏移:参考下图。如果是第一片取值为0,第二片取值175,以此类推。
- TTL:生存时间,即路由器的跳数,每经过一个路由器,该TTL 减一,因此路由器需要重新计算IP报文的校验和。
- 8位协议:ICMP:1,TCP:6,UDP:17,其他的自行百度
- 首部校验和:IP header校验和,接收端收到报文进行计算如果校验和错误,直接丢弃。
- 源IP地址:无须解释
- 目的IP地址:无须解释
- 选项:这个一般也没有使用。详细参考RFC
- 数据:上层的报文,如TCP 报文、UDP报文等。
7.2. IP报文头部
IP头部,总长度20字节
// /usr/include/linux/ip.h
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; //源IP地址
__be32 daddr; //目的IP地址
/*The options start here. */
};
关于协议类型定义
// /usr/include/linux/netinet/in.h
/* Standard well-defined IP protocols. */
enum
{
IPPROTO_IP = 0, /* Dummy protocol for TCP. */
#define IPPROTO_IP IPPROTO_IP
IPPROTO_ICMP = 1, /* Internet Control Message Protocol. */
#define IPPROTO_ICMP IPPROTO_ICMP
IPPROTO_IGMP = 2, /* Internet Group Management Protocol. */
#define IPPROTO_IGMP IPPROTO_IGMP
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94). */
#define IPPROTO_IPIP IPPROTO_IPIP
IPPROTO_TCP = 6, /* Transmission Control Protocol. */
#define IPPROTO_TCP IPPROTO_TCP
IPPROTO_EGP = 8, /* Exterior Gateway Protocol. */
#define IPPROTO_EGP IPPROTO_EGP
IPPROTO_PUP = 12, /* PUP protocol. */
#define IPPROTO_PUP IPPROTO_PUP
IPPROTO_UDP = 17, /* User Datagram Protocol. */
.....