QQ群852283276 微信arm80x86 微信公众号青儿创客基地 B站主页 https://space.bilibili.com/208826118
目前tapip协议栈只支持IPv4不支持IPv6另一个比较好的协议栈是uIP超轻量级lwIP广泛用于8位单片机系统也可以在linux用户态运行uIP现在已经支持IPv6
IPv4IPv4是L3层协议是传输层协议TCP和UDP的基础它是无连接的不像TCPIPv4数据包在网络协议栈中单独处理所以IP数据包可能会乱序。IP数据包同样不保证成功送达类似于UDP。如果你需要通信的可靠性则可以使用TCP协议它构建于IP之上高层协议来负责错误检测。 IP数据包头格式
struct iphdr {uint8_t version : 4;uint8_t ihl : 4;uint8_t tos;uint16_t len;uint16_t id;uint16_t flags : 3;uint16_t frag_offset : 13;uint8_t ttl;uint8_t proto;uint16_t csum;uint32_t saddr;uint32_t daddr;} __attribute__((packed));
IPv4包头长度一般为20字节IPv4头也可能会包含一些附加选项tapip不考虑这种情况version字段表示包头类型这里值为4表示IPv4ihlInternet Header Length字段表示IP包头的长度ihl * 4字节ihl字段长度为4bit所以表示的最大包头长度为60字节tostype of service字段在IP协议的发展过程中被分成了几个更小的字段表示IP的服务质量len字段表示整个IP数据报文长度2字节所以最大长度为65535字节大的IP数据报文会被分段为了满足通信接口的MTUid字段用来标志报文基本上是用于将分段的IP数据包重新拼接起来发送端将这个字段递增接收端根据这个来重排序IP分段报文flag字段表示IP报文的控制标志发送者通过设置这个字段来表示报文是否可以被分段传输是否是最后一个分段或者中间的分段flag_offset字段表示分段在报文中的位置所以第一个报文的值为0ttltime to live字段它告诉网络数据包在网络中的时间是否太长而应被丢弃。有很多原因使包在一定时间内不能被传递到目的地。解决方法就是在一段时间后丢弃这个包然后给发送者一个报文由发送者决定是否要重发。TTL的初值通常是系统缺省值是包头中的8位的域。TTL的最初设想是确定一个时间范围超过此时间就把包丢弃。由于每个路由器都至少要把TTL域减一TTL通常表示包在被丢弃前最多能经过的路由器个数。当记数到0时路由器决定丢弃该包并发送一个ICMP报文给最初的发送者。proto字段表示IP数据负载的协议类型通常这个字段值为16UDP或6TCP。csumheader checksum字段用来保证IP头的完整性。saddr和daddr分别表示源IP地址和目的IP地址尽管这个字段有32bit可以提供大概45亿的地址但即将被用尽IPv6协议扩展了地址长度到128bit保证了地址范围不被耗尽。 校验和的计算先设置csum为0然后按16bit字段计算IP包头的和
uint16_t checksum(void *addr, int count){/* Compute Internet Checksum for "count" bytes* beginning at location "addr".* Taken from https://tools.ietf.org/html/rfc1071*/register uint32_t sum 0;uint16_t * ptr addr;while( count > 1 ) {/* This is the inner loop */sum * ptr;count - 2;}/* Add left-over byte, if any */if( count > 0 )sum * (uint8_t *) ptr;/* Fold 32-bit sum to 16 bits */while (sum>>16)sum (sum (sum >> 16);return ~sum;}
当校验和计算出来后填入csum字段这时再用函数计算若结果为0则表示IP包头正确否则错误。
ICMPv4由于IP协议不保证可靠性所以需要一些方法来获知网络是否畅通ICMPv4Internet Control Message Protocol version 4被用来诊断测量网络状态比如如果一个网关无法连接网络协议栈发现之后会发送一个“Gateway Unreachable” 消息给对方。 ICMP包头格式ICMP是IP包的负载
struct icmp_v4 {uint8_t type;uint8_t code;uint16_t csum;uint8_t data[];} __attribute__((packed));
type字段表示消息的任务类型共有42种取值常用的有8种tapip实现了0Echo Reply3Destination Unreachable5Redirect关于redirect功能参考关于ICMP Redirect路由的一个不是bug的bug8Echo Request。code字段进一步描述消息的任务比如当type为3Destination Unreachable时code字段表示Destination Unreachable的原因例如当包无法路由到网络发送方一般会收到type为3 code为0的ICMP的消息。csum字段是ICMP包的校验和计算方法和IPv4包头一样但是这里把payload也计算进去。payload字段包含查询、通知和错误消息。 我们日常使用最多的ping就是响应请求Type8和应答Type0一台主机向一个节点发送一个Type8的ICMP报文如果途中没有异常例如被路由器丢弃、目标不回应ICMP或传输失败则目标返回Type0的ICMP报文说明这台主机存在更详细的tracert通过计算ICMP报文通过的节点来确定主机与目标之间的网络距离。type8和type0的payload格式一样
struct icmp_v4_echo {uint16_t id;uint16_t seq;uint8_t data[];} __attribute__((packed));
id由host设置决定哪一个进程处理echo reply比如设置进程id到这个字段seq字段是响应请求包的序列号简单的从0开始递增每当新的响应请求包建立可以用来判断包是否丢失或者发送时乱序。data字段是可选的通常包含了响应请求的时间戳可以用来估算两者之间来回程的时间。 最常见的ICMPv4错误消息Destination Unreachable type3的payload格式
struct icmp_v4_dst_unreachable {uint8_t unused;uint8_t len;uint16_t var;uint8_t data[];} __attribute__((packed));
第一个字节不用len代表数据报文长度以4字节为单位var字段取决图ICMP code字段data字段尽可能存放引起Destination Unreachable 状态的IP报文。 tapip在收到ICMP type0包处理在ip/icmp.c中
static void icmp_echo_request(struct icmp_desc *icmp_desc, struct pkbuf *pkb){struct ip *iphdr pkb2ip(pkb);struct icmp *icmphdr ip2icmp(iphdr);icmpdbg("echo request data %d bytes icmp_id %d icmp_seq %d",(int)(iphdr->ip_len - iphlen(iphdr) - ICMP_HRD_SZ),_ntohs(icmphdr->icmp_id),_ntohs(icmphdr->icmp_seq));if (icmphdr->icmp_code) {icmpdbg("echo request packet corrupted");free_pkb(pkb);return;}icmphdr->icmp_type ICMP_T_ECHORLY;/** adjacent the checksum:* If ~ >>> (cksum x 8) >>> 0* let ~ >>> (cksum x ) >>> 0* then cksum cksum 8*/if (icmphdr->icmp_cksum > 0xffff - ICMP_T_ECHOREQ)icmphdr->icmp_cksum ICMP_T_ECHOREQ 1;elseicmphdr->icmp_cksum ICMP_T_ECHOREQ;iphdr->ip_dst iphdr->ip_src;/* ip_src is set by ip_send_out() */ip_hton(iphdr);/* init reused input packet */pkb->pk_rtdst NULL;pkb->pk_indev NULL;pkb->pk_type PKT_NONE;ip_send_out(pkb);}