TCP数据包是由"以太网首部,IP头部,TCP头部和用户数据"四部分组成。
1、以太网首部
以太网首部有14字节:接收方MAC地址(占6个字节),发送方MAC地址(占6个字节),以太网帧的类型(占2个字节)。
struct uip_eth_addr {
u8_t addr[6];
};
struct uip_eth_hdr {
struct uip_eth_addr dest; //uip_eth_addr结构成员是字节型数组addr[6],存放目的MAC地址
struct uip_eth_addr src; //uip_eth_addr结构成员是字节型数组addr[6],存放源MAC地址
u16_t type;
//报文类型
//0x0800表示后面跟着的是IPV4数据包;
//0x0806表示后面跟着的是ARP数据包;
//0x86dd表示后面跟着的是IPV6数据包;
};
2、TCP和IP头部
协议:
#define UIP_PROTO_ICMP 1 //ICMP协议编号定义为1
#define UIP_PROTO_TCP 6 //TCP协议编号定义为6
#define UIP_PROTO_UDP 17 //TCP协议编号定义为17
#define UIP_PROTO_ICMP6 58 //ICMP6协议编号定义为58
/* The TCP and IP headers. */
typedef u16_t uip_ip6addr_t[8];
//uip_ip6addr_t[]数组有8个元素,每个元素都是u16_t型。
//使用typedef修饰后,uip_ip6addr_t就变成了数据类型,可以用来声明u16型数组,且数组元素数量为8个
struct uip_tcpip_hdr {
#if UIP_CONF_IPV6
/* IPV6头部,IPv6 header. */
u8_t vtc;
u8 tcflow;
u16_t flow;
u8_t len[2];
u8_t proto;
u8_t ttl;
uip_ip6addr_t srcipaddr; //发送方IP地址占8个字节
uip_ip6addr_t destipaddr; //接收方IP地址占8个字节
#else
//IP头部/IPV4头部(IPv4 header),占20个字节
u8_t vhl; //IP版本和头部长度
u8_t tos; //服务类型
u8_t len[2]; //IP报文总长度2个字节
u8_t ipid[2]; //标识,2字节,用于分片重组
u8_t ipoffset[2]; //片偏移,2字节,包含DF/MF标志和偏移量
u8_t ttl; //生存时间
u8_t proto; //协议:1字节,6表示TCP
u16_t ipchksum; //检验和
u16_t srcipaddr[2];//发送方IP地址,4字节
u16_t destipaddr[2];//接收方IP地址,4字节
#endif
//TCP头部(TCP header)总共为20字节。
//但是,在某些场合,特别是在处理选项(Options)时,TCP头部的长度为24字节。
u16_t srcport; //发送方端口
u16_t destport; //接收方端口
u8_t seqno[4]; //序列号
u8_t ackno[4]; //确认号
u8_t tcpoffset;//数据偏移
u8_t flags; //标志位
u8_t wnd[2]; //窗口大小
u16_t tcpchksum; //校验和
u8_t urgp[2]; //紧急指针
u8_t optdata[4];
//最大段字节数1个字节,MSS选项的长度占1个字节,最大段高8数值,最大段低8数值
};
3、解析TCP发送的数据
下面是ENC28J60发送给计算机的TCP数据
0xB4 0x2E 0x99 0x59 0xEC 0x1E 0x00 0x08 0xDC 0x11 0x11 0x02 0x08 0x00 0x45 0x00 0x00 0x49 0x00 0x06 0x00 0x00 0x40 0x06 0xF6 0x66 0xC0 0xA8 0x01 0x11 0xC0 0xA8 0x01 0xBE 0x00 0x50 0x15 0xE1 0x00 0x00 0x00 0x77 0x4C 0xFC 0x96 0x03 0x50 0x18 0x05 0xA6 0xBB 0x3D 0x00 0x00 <html><body><div 0x20 align=center><br><h2>Hello</h2></div></body></html>
以太网首部的接收方MAC地址:0xB4 0x2E 0x99 0x59 0xEC 0x1E
以太网首部的发送方MAC地址:0x00 0x08 0xDC 0x11 0x11 0x02
以太网首部的IPV4数据包类型:0x08 0x00
IPV4头部的IP版本和头部长度vhl:0x45
IPV4头部的服务类型tos:0x00
IPV4头部的IP报文总长度len[2]:0x00 0x49
IPV4头部的标识ipid[2]:0x00 0x06
IPV4头部的标志与片偏移ipoffset[2]:0x00 0x00
IPV4头部的生存时间ttl:0x40
IPV4头部的TCP协议:0x06
IPV4头部的检验和ipchksum:0xF6 0x66
IPV4头部的发送方IP地址:0xC0 0xA8 0x01 0x11
IPV4头部的接收方IP地址:0xC0 0xA8 0x01 0xBE
TCP头部的源端口:0x00 0x50
TCP头部的目的端口:0x15 0xE1
TCP头部的序列号:0x00 0x00 0x00 0x77
TCP头部的确认号:0x4C 0xFC 0x96 0x03
TCP头部的数据偏移:0x50
TCP头部的标志位:0x18
TCP头部的窗口大小:0x05 0xA6
TCP头部的校验和tcpchksum:0xBB 0x3D
TCP头部的紧急指针:0x00 0x00
用户数据:<html><body><div 0x20 align=center><br><h2>Hello</h2></div></body></html>
4、检验和
//函数功能:将"大端存储方式"的data[]中的前len个字节按照"双字节进行累加和校验"
//data为"待校验数据缓存"的起始地址
//len为待校验数据的字节总数
static u16_t chksum(u16_t sum, const u8_t *data, u16_t len)
{
u16_t t;
const u8_t *dataptr;
const u8_t *last_byte;
dataptr = data; //指向"待校验数据缓存"的起始地址
last_byte = data + len - 1; //指向"待校验数据缓存"的结束地址
while(dataptr < last_byte)//至少有两个字节才能执行此while循环
{
t = (dataptr[0] << 8) + dataptr[1];//生成"大端存储方式"的16位整型数据
sum += t;//计算"累加和"
if(sum < t)//sum越过0x0000,发生进位
{
sum++;//若有进位,则加1
}
dataptr += 2;//修改指针,为下次计算"累加和"做准备
}
if(dataptr == last_byte)//剩余一个字节
{
t = (dataptr[0] << 8) + 0;//生成"大端存储方式"的16位整型数据
sum += t;//计算"累加和"
if(sum < t)//sum越过0x0000,发生进位
{
sum++;//若有进位,则加1
}
}
return sum;//按照"大端存储方式"返回sum
}
//函数功能:将data[]中的前len个字节按照双字节进行累加和校验
u16_t uip_chksum(u16_t *data, u16_t len)
{
u16_t sum;
sum=chksum(0, (u8_t *)data, len);
//将"大端存储方式"的data[]中的前len个字节按照"双字节进行累加和校验"
return htons(sum);
//将sum转换为"小端端存储方式"返回,因为KEIL使用的是小端存储方式
}
//函数功能:将"大端存储方式"的首地址为&uip_buf[14]的缓存中的前20个字节按照"双字节进行累加和校验"
//计算IP头部校验和
u16_t uip_ipchksum(void)
{
u16_t sum;
u8_t *tmpIPDataPointer;
tmpIPDataPointer=&uip_buf[UIP_LLH_LEN];
sum = chksum(0, tmpIPDataPointer, UIP_IPH_LEN);
//将"大端存储方式"的tmpIPDataPointer[]中的前20个字节按照"双字节进行累加和校验"
//定义UIP_LLH_LEN=14,IP头的大小为UIP_IPH_LEN=20
DEBUG_PRINTF("uip_ipchksum: sum 0x%04x\n", sum);
return (sum == 0) ? 0xffff : htons(sum);
//htons()将累加和sum按照小端存储方式返回
}
//计算"上层长度,IP协议,发送方IP和接收方IP,TCP头和数据"的校验和
//proto=UIP_PROTO_TCP 6 //TCP协议
//proto=UIP_PROTO_UDP 17 //UDP协议编
//proto=UIP_PROTO_ICMP6 58 //ICMP6协议
static u16_t upper_layer_chksum(u8_t proto)
{
u16_t upper_layer_len;
u16_t sum;
#if UIP_CONF_IPV6
upper_layer_len = (((u16_t)(BUF->len[0]) << 8) + BUF->len[1]);
#else
upper_layer_len = (((u16_t)(BUF->len[0]) << 8) + BUF->len[1]) - UIP_IPH_LEN;
//上层长度upper_layer_len = IP报文总长度 - IP头的大小
#endif
sum = upper_layer_len + proto;
/*累加"上层长度和IP协议"时,无需考虑进位。
IP protocol and length fields. This addition cannot carry. */
sum = chksum(sum, (u8_t *)&BUF->srcipaddr[0], 2 * sizeof(uip_ipaddr_t));
/* 发送方IP和接收方IP需要考虑累加时考虑进位。Sum IP source and destination addresses. */
sum = chksum(sum, &uip_buf[UIP_IPH_LEN + UIP_LLH_LEN],upper_layer_len);
/*累加"TCP头和数据"时需要考虑加法进位。 Sum TCP header and data. */
return (sum == 0) ? 0xffff : htons(sum);
}
u16_t uip_tcpchksum(void)
{
return upper_layer_chksum(UIP_PROTO_TCP);
}
5、跟踪TCP数据包
#define ARPPointer ( (struct arp_hdr *)&uip_buf[0] )
#define TCPPointer ( (struct ethip_hdr *)&uip_buf[0] )
//读取一包数据,保存到uip_buf[],并返回数据长度;
//MAX_FRAMELEN定义为1518
uint16_t tapdev_read(void)
{
u8 ch;
u32 len;
len=ENC28J60_Packet_Receive(MAX_FRAMELEN,uip_buf);
ch=0;
if( (u8)ARPPointer->ethhdr.type==(u8)(UIP_ETHTYPE_ARP>>8) )ch=1;
if( (u8)(ARPPointer->ethhdr.type>>8)==(u8)UIP_ETHTYPE_ARP ) ch=(u8)(ch<<1);
if( (u8)ARPPointer->sipaddr[0]==192 ) ch=(u8)(ch<<1);
if( (u8)(ARPPointer->sipaddr[0]>>8)==168) ch=(u8)(ch<<1);
if( (u8)ARPPointer->sipaddr[1]==1) ch=(u8)(ch<<1);
if( (u8)(ARPPointer->sipaddr[1]>>8)==190 ) ch=(u8)(ch<<1);
if(ch==0x20) Print_Receive_Package(uip_buf,len);//打印ARP数据
else ch=0;
if(ch==0 && (u8)TCPPointer->ethhdr.type==(u8)(UIP_ETHTYPE_IP>>8) )ch=1;
if((u8)(TCPPointer->ethhdr.type>>8)==(u8)UIP_ETHTYPE_IP )ch=(u8)(ch<<1);
if( (u8)TCPPointer->srcipaddr[0]==192 ) ch=(u8)(ch<<1);
if( (u8)(TCPPointer->srcipaddr[0]>>8)==168) ch=(u8)(ch<<1);
if( (u8)TCPPointer->srcipaddr[1]==1) ch=(u8)(ch<<1);
if( (u8)(TCPPointer->srcipaddr[1]>>8)==190 ) ch=(u8)(ch<<1);
if(ch==0x20) Print_Receive_Package(uip_buf,len); //打印TCP数据
return len;
}
//发送一包数据
void tapdev_send(void)
{
u8 ch;
ch=0;
if( (u8)ARPPointer->ethhdr.type==(u8)(UIP_ETHTYPE_ARP>>8) )ch=1;
if( (u8)(ARPPointer->ethhdr.type>>8)==(u8)UIP_ETHTYPE_ARP ) ch=(u8)(ch<<1);
if( (u8)ARPPointer->dipaddr[0]==192 ) ch=(u8)(ch<<1);
if( (u8)(ARPPointer->dipaddr[0]>>8)==168) ch=(u8)(ch<<1);
if( (u8)ARPPointer->dipaddr[1]==1) ch=(u8)(ch<<1);
if( (u8)(ARPPointer->dipaddr[1]>>8)==190 ) ch=(u8)(ch<<1);
if(ch==0x20) Print_Send_Package(uip_buf,uip_len); //打印ARP数据
else ch=0;
if(ch==0 && (u8)TCPPointer->ethhdr.type==(u8)(UIP_ETHTYPE_IP>>8) )ch=1;
if((u8)(TCPPointer->ethhdr.type>>8)==(u8)UIP_ETHTYPE_IP )ch=(u8)(ch<<1);
if( (u8)TCPPointer->destipaddr[0]==192 ) ch=(u8)(ch<<1);
if( (u8)(TCPPointer->destipaddr[0]>>8)==168) ch=(u8)(ch<<1);
if( (u8)TCPPointer->destipaddr[1]==1) ch=(u8)(ch<<1);
if( (u8)(TCPPointer->destipaddr[1]>>8)==190 ) ch=(u8)(ch<<1);
if(ch==0x20) Print_Send_Package(uip_buf,uip_len); //打印TCP数据
ENC28J60_Packet_Send(uip_len,uip_buf);
delay_us(uip_len);
uip_len=0;
}