网络子系统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
上一篇:
2013-09-28
下一篇:
objective-c下的消息机制
WCF技术剖析之二:再谈IIS与ASP.NET管道
9月26日云栖精选夜读:阿里Java代码规约插件即将全球首发,邀您来发布仪式现场
深入浅出了解 JavaScript 中的 this
上云,让业务尽情拥抱互联网:阿里云在企业专有云与混合云最佳实践
《Spring Data官方文档》翻译邀请
Android开发问题 - Some projects cannot be imported because they already exist in the workspace
listView下拉刷新(仿sina微博Android客户端效果)
分布式环境下的性能追踪介绍
开源大数据周刊-第63期
英特尔击败AMD输给高通