用户态协议栈tapip代码分析-IPv4和ICMPv4

作者

QQ群:852283276
微信:arm80x86
微信公众号:青儿创客基地
B站:主页 https://space.bilibili.com/208826118

目前tapip协议栈只支持IPv4,不支持IPv6,另一个比较好的协议栈是uIP,超轻量级(lwIP),广泛用于8位单片机系统,也可以在linux用户态运行,uIP现在已经支持IPv6,

IPv4

IPv4是L3层协议,是传输层协议TCP和UDP的基础,它是无连接的,不像TCP,IPv4数据包在网络协议栈中单独处理,所以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,表示IPv4,ihl(Internet Header Length)字段表示IP包头的长度(ihl * 4字节),ihl字段长度为4bit,所以表示的最大包头长度为60字节,tos(type of service)字段在IP协议的发展过程中被分成了几个更小的字段,表示IP的服务质量,len字段表示整个IP数据报文长度,2字节,所以最大长度为65535字节,大的IP数据报文会被分段,为了满足通信接口的MTU,id字段用来标志报文,基本上是用于将分段的IP数据包重新拼接起来,发送端将这个字段递增,接收端根据这个来重排序IP分段报文,flag字段表示IP报文的控制标志,发送者通过设置这个字段来表示报文是否可以被分段传输,是否是最后一个分段或者中间的分段,flag_offset字段表示分段在报文中的位置,所以第一个报文的值为0,ttl(time to live)字段,它告诉网络,数据包在网络中的时间是否太长而应被丢弃。有很多原因使包在一定时间内不能被传递到目的地。解决方法就是在一段时间后丢弃这个包,然后给发送者一个报文,由发送者决定是否要重发。TTL的初值通常是系统缺省值,是包头中的8位的域。TTL的最初设想是确定一个时间范围,超过此时间就把包丢弃。由于每个路由器都至少要把TTL域减一,TTL通常表示包在被丢弃前最多能经过的路由器个数。当记数到0时,路由器决定丢弃该包,并发送一个ICMP报文给最初的发送者。proto字段表示IP数据负载的协议类型,通常这个字段值为16(UDP)或6(TCP)。csum(header 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 & 0xffff) + (sum >> 16);

    return ~sum;
}

当校验和计算出来后,填入csum字段,这时再用函数计算,若结果为0,则表示IP包头正确,否则错误。

ICMPv4

由于IP协议不保证可靠性,所以需要一些方法来获知网络是否畅通,ICMPv4(Internet 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实现了0(Echo Reply),3(Destination Unreachable),5(Redirect)关于redirect功能参考关于ICMP Redirect路由的一个不是bug的bug,8(Echo Request)。code字段进一步描述消息的任务,比如,当type为3(Destination Unreachable)时,code字段表示Destination Unreachable的原因,例如当包无法路由到网络:发送方一般会收到type为3 code为0的ICMP的消息。csum字段是ICMP包的校验和,计算方法和IPv4包头一样,但是这里把payload也计算进去。payload字段包含查询、通知和错误消息。
我们日常使用最多的ping,就是响应请求(Type=8)和应答(Type=0),一台主机向一个节点发送一个Type=8的ICMP报文,如果途中没有异常(例如被路由器丢弃、目标不回应ICMP或传输失败),则目标返回Type=0的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;
	else
		icmphdr->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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ICMPv4(Internet Control Message Protocol version 4,因特网控制报文协议第四版)定义了一系列用于控制和错误报告的消息类型和代码。在ICMPv4中,每个消息类型都有一个对应的类型码,每个类型码下面又有多个代码。以下是一些常见的ICMPv4类型和代码: - 0:Echo Reply(回显应答),代码为0 - 3:Destination Unreachable(目的地不可达),下面有多个代码 - 0:Network Unreachable(网络不可达) - 1:Host Unreachable(主机不可达) - 2:Protocol Unreachable(协议不可达) - 3:Port Unreachable(端口不可达) - 4:Fragmentation Needed and Don't Fragment was Set(需要进行分片但已设置不分片位) - 5:Source Route Failed(源站选路失败) - 6:Destination Network Unknown(目的网络未知) - 7:Destination Host Unknown(目的主机未知) - 8:Source Host Isolated(源主机被隔离) - 9:Communication with Destination Network is Administratively Prohibited(与目的网络的通信被管理人员禁止) - 10:Communication with Destination Host is Administratively Prohibited(与目的主机的通信被管理人员禁止) - 11:Destination Network Unreachable for Type of Service(目的网络由于服务类型TOS的原因不可达) - 12:Destination Host Unreachable for Type of Service(目的主机由于服务类型TOS的原因不可达) - 13:Communication Administratively Prohibited(由于管理原因通信被禁止) - 14:Host Precedence Violation(主机优先权低于要求的优先权) - 15:Precedence cutoff in effect(优先权截止) - 4:Source Quench(源端被关闭),代码为0 - 5:Redirect(重定向),下面有多个代码 - 0:Redirect Datagram for the Network(对网络重定向数据报) - 1:Redirect Datagram for the Host(对主机重定向数据报) - 2:Redirect Datagram for the ToS & network(对服务类型和网络重定向数据报) - 3:Redirect Datagram for the ToS & host(对服务类型和主机重定向数据报) - 8:Echo Request(回显请求),代码为0 - 11:Time Exceeded(超时),下面有多个代码 - 0:TTL expired in transit(在传输中过期的TTL) - 1:Fragment reassembly time exceeded(片段重组时间超时) - 12:Parameter Problem(参数问题),下面有多个代码 - 0:Pointer indicates the error(指针指示错误) - 1:Missing a Required Option(缺少所需选项) - 2:Bad Length(长度错误) 还有其他类型的消息和代码,详情可以参考RFC 792。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值