背景
本文是基于对以太网帧(FRAME)的研究,其中又对TCP的报文进行了ACK应答,丢包重传机制的补充,本文将对这些技术做一个大概的阐述。另外本文将用到wireshark抓包工具,具体使用细节由读者自己研究。
系统:UBUNTU 16.04 64bit
1.以太网FRAME头
1.1.格式
| dest_mac(6 byte) | source_mac(6 byte) | type(2 byte ) |
- type:类型用于指出该包是IP,ARP,IPV6数据报的类型;
1.2.存储格式
dest_mac 50:5e:49:20:4f:ac
source_mac 00:1f:c6:9c:63:24
type 0x0800(IPv4)
存放格式:50 5e 49 20 4f ac 00 1f c6 9c 63 24 05 08 00
1.IP头
1.1.IPV4头
| 4bit version | 4bit headerlen | 8bit TOS |16bit totallen(byte)|
| 16bit id |3bit flags| 13bit frag_offset |
| 8bit TTL | 8bit protocol | 16bit checksum |
| 32bit source_ip |
| 32bit dest_ip |
| (option) |
version:版本号,一般为4
headerlen:IP头长度,右移2位,即若值为5,则长度为5<<2=20;
tos:服务类型,一般为0x00
totallen:总长度,包括IP头+IP数据
id,flags,frag_offset:这三项只有在IP包进行分片时,重组时会用到
TTL:生存时间。数据报每经过一个路由器,该值减1,直到为0,会将该数据报丢掉
protocol:用于标识传输协议类型,如TCP(6),UDP,ICMP,IGMP
checksum:校验和
source_ip:源IP地址
dest_ip:目的IP地址
1.2.校验和算法
- 把校验和字段置为0;
- 对IP头部中的每16bit进行二进制求和;
- 如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值;
- 将该16bit的值取反,存入校验和字段。
- 当接收IP包时,需要对报头进行确认,检查IP头是否有误,算法同上2、3步,然后判断取反的结果是否为0,是则正确,否则有错。
unsigned short msnet_api_checksum(unsigned short *addr,int bytes)
{
unsigned int sum=0;
while(bytes>1){
sum+=*addr++;
bytes-=2;
}
if(bytes==1){
sum+=*(unsigned char*)addr;
}
sum=((sum&0xffff0000)>>16)+(sum&0xffff);
sum+=((sum&0xffff0000)>>16);
sum=(~sum);
sum=((sum&0xff00)>>8|(sum&0xff)<<8);
return sum;
}
2.TCP报文知识
2.1.TCP头
| 16bit source_port | 16bit dest_port |
| 32bit seq_num |
| 32bit ack_num |
|4bit headerlen|6bit res |6bit flags | 16bit window_size |
| 16bit checksum | 16bit urgent_point |
| (option) |
| data |
2.2.校验和算法
- 把TCP报头中的校验和字段置为0
- 伪首部+TCP报头+TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。
- 用反码相加法累加所有的16位字(进位也要累加)。
- 对计算结果取反,作为TCP的校验和。
uint16 msframe_api_tcpchecksum(ETHERContext *pether_ctt,uint08 *tcpheader_buf)
{
FRAMEContext *pframe_ctt=&pether_ctt->frame_ctt;
IPHEADERContext *pipheader_ctt=&pether_ctt->ipheader_ctt;
TCPHEADERContext *ptcpheader_ctt=&pether_ctt->tcpheader_ctt;
unsigned char tcpcheck_buf[2048]={0};
/*----extern header----*/
//source_ip
ms_network_4b(&(tcpcheck_buf[0]), pipheader_ctt->source_ip);
//dest_ip
ms_network_4b(&(tcpcheck_buf[4]), pipheader_ctt->dest_ip);
//0
tcpcheck_buf[8]=0;
//protocol
tcpcheck_buf[9]=pipheader_ctt->protocol;
//len
ms_network_2b(&(tcpcheck_buf[10]), (ptcpheader_ctt->header_len*4+pether_ctt->size));
/*header*/
ms_memcpy(&tcpcheck_buf[12], tcpheader_buf, ptcpheader_ctt->header_len*4);
/*data*/
ms_memcpy(&tcpcheck_buf[12+ptcpheader_ctt->header_len*4], pether_ctt->data, pether_ctt->size);
return msnet_api_checksum((uint16 * )tcpcheck_buf, 12+ptcpheader_ctt->header_len*4+pether_ctt->size);
}
2.3.快速丢包重传
- 当对端检测到有数据包丢失时,对端返回的ACK的选项中将带有SACK标志
- 发送端接收到该SACK包后,根据ACK的ack_num以及选项SACK中的已接收到数据范围,确定那些数据包被丢失了;
- 发送端迅速将丢失包进行重传;
- 对端在接收到重传包前,将一直返回SACK包,其中ack_num不变,选项SACK中的已接收到数据范围将一直增加,直到将缓冲区填满;
- 对端在接收到重传包后,将返回ACK包,ack_num为选项SACK中的已接收到数据范围的最大值;
3.问题解决
3.1.wireshark和tcpdump抓TCP包时,出现tcp的校验和失败,禁用网卡的offload功能即可:
sudo ethtool --offload eth0 rx off tx off sg off tso off
3.2.使用iptables禁用端口
3.2.1.选项
-A 添加一条INPUT/OUTPUT的规则
-p 指定协议类型,如udp或者tcp
--dport 目标端口
--sport 源端口
-s <ip> 根据IP地址进行控制
-j 就是指定是 ACCEPT 接收 或者 DROP 不接收
-L -n 查看
3.2.2.例子
1.允许接收tcp上8090端口的数据
iptables -A INPUT -p tcp --dport 8090 -j ACCEPT
2.禁止接收tcp上8090端口的数据
iptables -A INPUT -p tcp --sport 8070 -j DROP
3.允许发送tcp上8090端口的数据
iptables -A OUTPUT -p tcp --sport 8090 -j ACCEPT
4.禁止发送tcp上8090端口的数据
iptables -A OUTPUT -p tcp --sport 8090 -j DROP
5.保存
service iptables save
3.3.当FRAME帧长度大于1514时,发送失败?
在1998年,Alteon Networks公司提出把Data Link Layer最大能传输的数据从1500 bytes 增加到9000 bytes,这个提议虽然没有得到IEEE 802.3 Working Group的同意,但是大多数设备厂商都已经支持
1500bytes 不包含18字节(14+4CRC),这种帧叫Jumbo frames,可以通过设置MTU来启动。