阅读830 返回首页    go 阿里云 go 技术社区[云栖]


网络子系统16_arp传输接收接口

//	邻居子系统的配置属性:
//	1.ARP_ANNOUNCE用于生成arp请求时,源ip的选择 
//		1.1 内核中产生的请求,使用arp_send传输arp_solicitation请求
//  	1.2 用户空间中产生的请求,arp_solicition调用neigh_app_ns来通知相应的用户空间程序,需要生成一个solicitation

//	2.ARP_IGNORE 当ARP请求的目的地址为本机地址时,根据目的地地址与接收接口的关系,来决定是否应答
//	3.ARP_FILTER 当ARP请求的目的地址为本机地址时,处理一台主机有多个NIC连接到同一LAN,并且配置同一子网时,控制一个接口是对入口ARP应答。常见于虚拟服务器
//	4.medium-id  当ARP请求的目的地址非本机地址时,处理ip子网横跨不同的lan,且该子网中提供arp请求的主机有多快nic,设置这些nic是否在同一lan

//	
//	发送arp消息
//		arp协议是ip->l2地址的转换协议,可以运行在多种共享介质的l2链路类型
//	参数:
//		type 	: 	arp头中的操作码
//		ptype 	:	l2帧头中填写的l3协议类型

//	函数主要任务:
//		1.检查是否需要进行地址解析
//		2.创建arp封包
//		3.发送数据包
1.1 void arp_send(int type, int ptype, u32 dest_ip, 
	      struct net_device *dev, u32 src_ip, 
	      unsigned char *dest_hw, unsigned char *src_hw,
	      unsigned char *target_hw)
{
	struct sk_buff *skb;

	//表明该接口不能执行arp	
	if (dev->flags&IFF_NOARP)
		return;
	//创建arp封包
	skb = arp_create(type, ptype, dest_ip, dev, src_ip,
			 dest_hw, src_hw, target_hw);
	if (skb == NULL) {
		return;
	}
	//传输出去
	arp_xmit(skb);
}

//	创建arp封包
//	调用路径:
//		arp_send->arp_create
//	参数:
//		type  :		arp头中的操作码
//		ptype :		l2帧头中填写的l3协议类型

//		dest_ip, src_ip, target_hw, src_hw, arp协议中填写的内容
//		dest_hw, l2帧头填写的目标地址

//		dev   :		出口设备

//	函数主要任务:
//		1.填充l2帧头
//		2.填写arp协议字段
1.2 struct sk_buff *arp_create(int type, int ptype, u32 dest_ip,
			   struct net_device *dev, u32 src_ip,
			   unsigned char *dest_hw, unsigned char *src_hw,
			   unsigned char *target_hw)
{
	struct sk_buff *skb;
	struct arphdr *arp;
	unsigned char *arp_ptr;

	//封包大小 = arp头 + (arp源ip地址,源mac地址,目的ip地址,目的mac地址) + l2头
	skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)
				+ LL_RESERVED_SPACE(dev), GFP_ATOMIC);
	if (skb == NULL)
		return NULL;
	//移动skb->data,腾出mac的长度
	skb_reserve(skb, LL_RESERVED_SPACE(dev));//处理设备相关的l2链路类型
	//初始化l3头指针
	skb->nh.raw = skb->data;
	//移动skb->tail到skb->data + arp报文长度
	arp = (struct arphdr *) skb_put(skb,sizeof(struct arphdr) + 2*(dev->addr_len+4));
	skb->dev = dev;//设置出口设备
	skb->protocol = htons(ETH_P_ARP);//skb的协议类型
	if (src_hw == NULL)
		src_hw = dev->dev_addr;//如果没有提供源地址,则设置为出口设备的l2地址
	if (dest_hw == NULL)
		dest_hw = dev->broadcast;//如果没有目的地址,则设置为出口设备的广播地址
	//填充mac头
	if (dev->hard_header &&
	    dev->hard_header(skb,dev,ptype,dest_hw,src_hw,skb->len) < 0)
		goto out;
	//填充协议字段
	switch (dev->type) {
	default:
		arp->ar_hrd = htons(dev->type);//arp头的硬件类型
		arp->ar_pro = htons(ETH_P_IP);//arp头的协议类型,ip协议
		break;
		....

	}

	arp->ar_hln = dev->addr_len;//硬件地址长度
	arp->ar_pln = 4;//协议地址长度
	arp->ar_op = htons(type);//操作码

	arp_ptr=(unsigned char *)(arp+1);//arp_ptr指向协议头后的第一个字节

	memcpy(arp_ptr, src_hw, dev->addr_len);//拷贝出口设备地址到源地址
	arp_ptr+=dev->addr_len;//移动指针
	memcpy(arp_ptr, &src_ip,4);//拷贝源ip到源ip
	arp_ptr+=4;
	//拷贝目地l2地址
	if (target_hw != NULL)
		memcpy(arp_ptr, target_hw, dev->addr_len);
	else
		memset(arp_ptr, 0, dev->addr_len);
	arp_ptr+=dev->addr_len;
	memcpy(arp_ptr, &dest_ip, 4);

	return skb;

out:
	kfree_skb(skb);
	return NULL;
}

//	发送arp封包
//		衔接netfilter处理点NF_ARP_OUT
//	调用路径: arp_send->arp_xmit
1.3 void arp_xmit(struct sk_buff *skb)
{
	//通过NETFILTER HOOK,最后由dev_queue_xmit传输出去
	NF_HOOK(NF_ARP, NF_ARP_OUT, skb, NULL, skb->dev, dev_queue_xmit);
}

//	arp协议接收例程
//		保存在ptype_base的hash表中,在netif_receive_skb中,根据l2帧头中指定的l3协议号,调用此函数
//	调用路径:netif_receive_skb->arp_rcv

//	函数主要任务:
//		1.报文合理性检查:
//			1.1 在不支持arp的设备上,收到了arp报文,丢掉
//			1.2 非本机l2地址,如果开启网桥功能,则由桥接处理
//			1.3 l2地址为回环地址
//		2.由于arp协议可能会修改sk_buff,如果skb被其他子系统引用,则拷贝一份
//		3.衔接netfilter处理点NF_ARP_IN

//	注:arp报文的l2地址,可以为广播地址或l2多播地址,用于解析发送者自己的l2地址			
2.1 int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
	struct arphdr *arp;

	//使skb->data-tail足够一个完整的arp报文
	if (!pskb_may_pull(skb, (sizeof(struct arphdr) +
				 (2 * dev->addr_len) +
				 (2 * sizeof(u32)))))
		goto freeskb;

	arp = skb->nh.arph;
	//报文有效性检查:
	//	1.在不支持arp的设备上,收到了arp报文,丢掉
	//	2.非本机l2地址,如果开启网桥功能,则由桥接处理
	//	3.l2地址为回环地址,l2地址可以为广播地址或l2多播地址
	if (arp->ar_hln != dev->addr_len ||
	    dev->flags & IFF_NOARP |
	    skb->pkt_type == PACKET_OTHERHOST ||
	    skb->pkt_type == PACKET_LOOPBACK ||//回环
	    arp->ar_pln != 4)
		goto freeskb;
	//如果报文引用计数!=1,拷贝一份
	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
		goto out_of_mem;
	//NETFILTER HOOK,在执行实际的arp处理之前,执行HOOK调用
	return NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);

freeskb:
	kfree_skb(skb);
out_of_mem:
	return 0;
}

//	处理arp报文
//	调用路径:arp_rcv->netfilter->arp_process
//	函数主要任务:
//		1.入口设备需要配置有ipv4信息
//		2.arp合理性检查:
//			2.1 arp只处理l3协议为ip的地址解析
//			2.2 arp只处理l2协议为以太网,IEEE802的地址解析
//			2.3 arp报文请求解析的目标ip地址不能为多播地址,或回环地址地址
//		3.入口设备的l2类型应该与arp报文中指定的l2类型相同
//		4.arp操作合法性检查:
//			4.1 只处理arp_request
//			4.2 只处理arp_reply
//		5.处理重复地址检查:
//			5.1 如果arp报文中没有指定源ip地址,则此arp报文用于重复地址检查
//			5.2 应答重复地址检查的条件:
//				5.2.1 目的ip地址为本机
//				5.2.2 arp ignore配置为可可应答
//			5.2 使用源ip,目的ip均为arp报文中指定的目的ip进行应答,然后退出
//		6.目标ip为本机的arp request
//			6.1 判断目标ip为本机的办法:
//					通过路由子系统路由该目的地址,可达,且路由项指出路由类型为本机
//			6.2 被动学习,接收到arp request触发创建一个新邻居项
//			6.3 根据arp ignore,arp filter 决定是否进行应答
//			6.4 应答此arp, 然后退出
//		7.目的ip非本机,处理arp代理
//		8.更新邻居项:
//			8.1 存在对应的邻居项
//			8.2 如果为arp应答,且目的ip为本机,则说明邻居项可达,更新邻居项状态为REACHABLE
//			8.3 否则更新邻居状态为STALE, 表明邻居项有可用的l2地址,但是可可到达性需要确认。

//		注:arp ignore为全局配置,arp filter为接口配置,对代理的arp请求可以被延迟处理
2.2 static int arp_process(struct sk_buff *skb)
{
	struct net_device *dev = skb->dev;
	struct in_device *in_dev = in_dev_get(dev);
	struct arphdr *arp;
	unsigned char *arp_ptr;
	struct rtable *rt;
	unsigned char *sha, *tha;
	u32 sip, tip;
	u16 dev_type = dev->type;
	int addr_type;
	struct neighbour *n;
	//获取输入设备的ipv4配置信息
	if (in_dev == NULL)
		goto out;
	//skb的以太网头,此指针由驱动程序移动跨过l2地址时,完成更新
	arp = skb->nh.arph;
	//设备类型
	switch (dev_type) {
	default:	
		if (arp->ar_pro != htons(ETH_P_IP) ||//arp只处理ip协议
		    htons(dev_type) != arp->ar_hrd)//接收此skb的设备的设备类型,不是arp报文中指定的设备类型
			goto out;
		break;
		...

		if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&//arp报文中指定的硬件地址类型不是以太网,或者IEEE802
		     arp->ar_hrd != htons(ARPHRD_IEEE802)) ||
		    arp->ar_pro != htons(ETH_P_IP))//非ip协议
			goto out;
		break;
		...
	}

	//处理的操作码只限定在ARP应答与请求
	if (arp->ar_op != htons(ARPOP_REPLY) &&
	    arp->ar_op != htons(ARPOP_REQUEST))
		goto out;
	//获取源mac,源ip,目的mac,目的ip
	arp_ptr= (unsigned char *)(arp+1);
	sha	= arp_ptr;
	arp_ptr += dev->addr_len;
	memcpy(&sip, arp_ptr, 4);
	arp_ptr += 4;
	tha	= arp_ptr;
	arp_ptr += dev->addr_len;
	memcpy(&tip, arp_ptr, 4);
	//arp不处理回环地址与多播地址
	if (LOOPBACK(tip) || MULTICAST(tip))
		goto out;

	//如果设备类型为DLCI
	//源地址为设备的广播地址ff:ff:ff:ff:ff:ff
	if (dev_type == ARPHRD_DLCI)
		sha = dev->broadcast;

	//源ip没有指定,说明此arp请求用于重复地址检查
	if (sip == 0) {
		//arp请求 && 目的ip为
		if (arp->ar_op == htons(ARPOP_REQUEST) &&
		    inet_addr_type(tip) == RTN_LOCAL &&//本机ip
		    !arp_ignore(in_dev,dev,sip,tip))//判断是否忽略此arp
			arp_send(ARPOP_REPLY,ETH_P_ARP,tip,dev,tip,sha,dev->dev_addr,dev->dev_addr);//arp报文的源ip,目的ip均为arp请求中的目的ip
		goto out;
	}
	//ARP请求,处理无端arp,更新邻居项
	if (arp->ar_op == htons(ARPOP_REQUEST) &&
	    ip_route_input(skb, tip, sip, 0, dev) == 0) {//路由目的地址与源地址

		rt = (struct rtable*)skb->dst;//获取查询的路由信息
		addr_type = rt->rt_type;

		if (addr_type == RTN_LOCAL) {//本机ip
			n = neigh_event_ns(&arp_tbl, sha, &sip, dev);//被动学习
			if (n) {//
				int dont_send = 0;

				if (!dont_send)
					dont_send |= arp_ignore(in_dev,dev,sip,tip);
				if (!dont_send && IN_DEV_ARPFILTER(in_dev))//ARP_FILTER为接口选项,只能开启或者关闭,设置该选项后,只有在内核知道如何到达发送方的ip地址
					//并且只有到达发送方ip地址的设备为接收此ARP_REQUEST的设备,才进行应答
					//该选项主要用于当一台主机有多个NIC连接到同一LAN,且配置在同一个IP子网上时,控制一个接口是否对arp请求进行应答
					dont_send |= arp_filter(sip,tip,dev); 
				if (!dont_send)//没有过滤到此arp,则进行应答
					arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);

				neigh_release(n);
			}
			goto out;
		} else if (IN_DEV_FORWARD(in_dev)) {//非本机ip,但本地接口具有转发功能
			if ((rt->rt_flags&RTCF_DNAT) ||//进一步检查是否为代理
			    (addr_type == RTN_UNICAST  && rt->u.dst.dev != dev &&
			     (arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, &tip, dev, 0)))) {
				n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
				if (n)
					neigh_release(n);

				if (skb->stamp.tv_sec == LOCALLY_ENQUEUED || 
				    skb->pkt_type == PACKET_HOST ||
				    in_dev->arp_parms->proxy_delay == 0) {
					arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
				} else {
					pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
					in_dev_put(in_dev);
					return 0;
				}
				goto out;
			}
		}
	}
	//存在邻居项
	n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
	...
	//存在对应的邻居项,此skb为邻居可达性的证据,更新邻居状态
	if (n) {
		int state = NUD_REACHABLE;//更新邻居项的可达性
		int override;
		//判断距离上一次修改邻居项的属性是否已经经历了n->parms->locktime这么久
		override = time_after(jiffies, n->updated + n->parms->locktime);

		//非arp应答,或者封包目的地非本机
		if (arp->ar_op != htons(ARPOP_REPLY) ||
		    skb->pkt_type != PACKET_HOST)
			state = NUD_STALE;
		//更新邻居项
		neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
		neigh_release(n);
	}

out:
	if (in_dev)
		in_dev_put(in_dev);
	kfree_skb(skb);
	return 0;
}

//检查是否忽略此arp
2.3 static int arp_ignore(struct in_device *in_dev, struct net_device *dev,
		      u32 sip, u32 tip)
{
	int scope;
	//查看此ipv4上配置的属性
	switch (IN_DEV_ARP_IGNORE(in_dev)) {
	case 0:	//对任何本机的地址进行应答
		return 0;
	case 1://目的ip配置到接收报文的接口上,才应答
		sip = 0;
		scope = RT_SCOPE_HOST;//本地地址
		break;
	case 2:	
		scope = RT_SCOPE_HOST;//目的ip配置到接收报文的接口上,并且源ip与本机在同一子网
		break;
	case 3:	//不应答本子网地址
		sip = 0;
		scope = RT_SCOPE_LINK;
		dev = NULL;
		break;
	case 4:	//保留
	case 5:
	case 6:
	case 7:
		return 0;
	case 8://不应答
		return 1;
	default:
		return 0;
	}
	//确定tip是否属于scope指定的范围
	return !inet_confirm_addr(dev, sip, tip, scope);
}

最后更新:2017-04-03 15:22:03

  上一篇:go 2013-09-28
  下一篇:go objective-c下的消息机制