当前位置:网站首页>邻居子系统之ARP协议数据处理过程
邻居子系统之ARP协议数据处理过程
2022-06-12 11:37:00 【fanxiaoyu321】
文章目录
无端ARP
大多数情况下,主机发送ARP请求都是为了解析L3地址对应的L2地址,但是特殊情况下,也可以用ARP请求来通知接收方自己发生了一些事情,这种ARP请求也称为无端ARP。常见有下面三种情况。
L2地址发生了变化
默认情况下,如果一个主机L2地址发生了变化,同一个网络中的其它主句并不能立刻感知到,它们只能通过邻居子系统的过期机制探测,这会导致有一小段时间数据包是不可达的。如果愿意,发生这种情况是,本地主机主动发送一个ARP请求,触发接收方刷新缓存。
要注意的是内核并不会在L2地址变化时执行这个操作,如果需要,应该由用户态触发。
L3地址重复检测
当主机通过静态配置一个L3地址后,可以通过发送一个目的地址为配置的L3地址的ARP请求来识别同网段中是否有其它主机使用该地址,如果收到了应答,说明有人再使用,否则没有。
这只是提供了一种L3地址重复检测的方式,但是不太介意使用这种方式,DHCP方式获取IP地址才是正途。
虚拟IP
在服务器备份设计中,如果服务器A出现了问题,启动备份服务器B后,让B继承A的IP地址,然后B向网络中发送一个ARP请求,触发网络中其它主机更新对该IP地址的缓存,可以实现快速的切换。
这种场景的思想和L2地址发生变化的场景是一致的,都是为了让其它主机能够及时刷新自己的邻居项缓存。
ARP选项
在IPv4配置块中,有几个选项会影响ARP协议的行为,和IPv4配置块中的其它配置一样,这些选项可以是网络设备级别的配置,也可以是全局配置。
arp_announce
网络设备可以配置多个IP地址,当发送ARP请求时,到底应该选择使用哪个IP地址作为源地址封装到ARP请求报文中,该选项用于控制这种选择行为,可取三个值:
- 0:任何本地IP地址都可以;
- 1:如果可能,选择和目的地址位于同一个子网内的地址,否则按照配置2选择;
- 2:优先使用主地址;
arp_ignore
由于IP地址属于主机,但是是配置到网络设备上的,所以主机有可能在网络设备A上收到一个对网络设备B上的一个地址的查询(如这两个设备都有属于同一个子网的IP地址配置)。主机是否应该对这种ARP请求做出应答是可配置的,这种行为就是通过该选项控制。该选项可取的值以及它们的含义见下面的arp_ingore()函数。
arp_filter
当主机有两个网络设备,它们连接到同一个局域网,并且配置了同一个子网的IP地址时,对于子网内其它主机的的ARP请求,这两个网络设备都可以收到,该选项用于确定由哪个网络设备来对该请求做出响应。
arp_accept
如果本机没有发送ARP请求,但是收到了一个ARP响应,ARP协议使用该选项来决定是否使用该ARP响应中的源IP和源L2地址映射关系。
和其它选项不同,ARP使用的是IP配置块中all中的配置值,并非网络设备上的配置值。
ARP报文格式
struct arphdr
{
__be16 ar_hrd; // L2层帧类型,取值ARPHRD_ETHER等
__be16 ar_pro; // L3层地址类型,取值如ETH_P_IP
unsigned char ar_hln; // L2层地址长度
unsigned char ar_pln; // L3层地址长度
__be16 ar_op; // ARP操作码,如ARPOP_REQUEST等
// 下面的内容根据L2和L3地址长度不同有所不同,但是实现上,L3地址实际上就是IP地址(很多地方代码写死了)
#if 0
/* * Ethernet looks like this : This bit is variable sized however... */
unsigned char ar_sha[ETH_ALEN]; /* sender hardware address */
unsigned char ar_sip[4]; /* sender IP address */
unsigned char ar_tha[ETH_ALEN]; /* target hardware address */
unsigned char ar_tip[4]; /* target IP address */
#endif
};
ARP协议报文的前8字节是固定的,后面部分根据协议地址长度以及L2层地址长度不同有所变化,可见在设计的时候,ARP不仅仅是为IPv4服务的,只不过实际中只有IPv4使用它而已。
发送ARP请求: arp_solicit()
在邻居子系统数据发送流程中有看到,ARP协议的邻居项操作集实现中。其中非常重要的一个接口就是Solicitation请求的发送,邻居子系统框架会在需要的时候回调该函数(在NUD_INCOMPLETE和NUD_PROBE状态)。
static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)
{
__be32 saddr = 0;
u8 *dst_ha = NULL;
struct net_device *dev = neigh->dev;
__be32 target = *(__be32*)neigh->primary_key; // 数据包的下一跳地址
int probes = atomic_read(&neigh->probes);
struct in_device *in_dev = in_dev_get(dev); // 找到该网络设备的IPv4配置块
if (!in_dev)
return;
// 根据该网络设备的IPv4配置项arp_announce选择一个出口IP地址
switch (IN_DEV_ARP_ANNOUNCE(in_dev)) {
default:
case 0:
// 0配置为默认值,此时优先选择数据包的源地址
if (skb && inet_addr_type(dev_net(dev), ip_hdr(skb)->saddr) == RTN_LOCAL)
saddr = ip_hdr(skb)->saddr;
break;
case 1:
// 1配置表示必须选择一个和出口数据包在同一个子网的源IP地址
if (!skb)
break;
saddr = ip_hdr(skb)->saddr;
if (inet_addr_type(dev_net(dev), saddr) == RTN_LOCAL) {
// target、saddr和本地网络设备上的某个IP地址均属于同一个子网
if (inet_addr_onlink(in_dev, target, saddr))
break;
}
saddr = 0;
break;
case 2:
/* Avoid secondary IPs, get a primary/preferred one */
break;
}
// 可见,arp_announce配置仅仅是一种建议,如果都无法选出,则用inet_select_addr()选择一个
if (in_dev)
in_dev_put(in_dev);
if (!saddr)
// 优先选择一个和target属于同一个子网的主IP地址
saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);
if ((probes -= neigh->parms->ucast_probes) < 0) {
// 单播场景
if (!(neigh->nud_state&NUD_VALID))
printk(KERN_DEBUG "trying to ucast probe in NUD_INVALID\n");
dst_ha = neigh->ha;
read_lock_bh(&neigh->lock);
} else if ((probes -= neigh->parms->app_probes) < 0) {
#ifdef CONFIG_ARPD
// 用户态的arp实现
neigh_app_ns(neigh);
#endif
return;
}
// 构造并发送ARP请求报文,如果dst_ha为NULL则发送ARP广播
arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
dst_ha, dev->dev_addr, NULL);
if (dst_ha)
read_unlock_bh(&neigh->lock);
}
arp_solicit()最重要的工作就是为ARP请求选择源IP地址、目的L2地址(单播或者广播)。
源IP地址的选择过程受ARP选项arp_nonounce的影响。选择策略如下:
- 如果arp_nonunce为0(默认值),若IP包头中的源IP地址就是本地地址,则选中它,否则按照步骤3选择;
- 如果arp_nonunce为1,若IP包头中的源IP地址就是本地地址,并且它和本地网络设备上的某个IP地址处于同一个子网,则选中它,否则按照步骤3选择;
- 使用inet_select_addr()选择一个IP地址,该函数会优先选择一个和输入地址(下一跳地址)位于同一个子网的地址,否则任意选择一个主地址;
构造&&发送ARP报文: arp_send()
/* * Create and send an arp packet. */
void arp_send(int type, int ptype, __be32 dest_ip, struct net_device *dev, __be32 src_ip,
const unsigned char *dest_hw, const unsigned char *src_hw, const unsigned char *target_hw)
{
struct sk_buff *skb;
/* * No arp on this interface. */
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报文
arp_xmit(skb);
}
构造ARP报文: arp_create()
/* * Create an arp packet. If (dest_hw == NULL), we create a broadcast * message. */
@type: ARP报文类型,如ARP_REQUEST;
@ptype: L2帧首部的协议字段;
@dest_ip: 下一跳IP地址;
@src_ip: 本机IP地址;
@dest_hw: 下一跳L2层地址,该地址会作为L2帧的目的地址,所以如果全0,会设置为广播
@src_hw: 本机L2层地址;
@target_hw: ARP报文中填充的目标主机L2层地址
struct sk_buff *arp_create(int type, int ptype, __be32 dest_ip, struct net_device *dev, __be32 src_ip,
const unsigned char *dest_hw, const unsigned char *src_hw, const unsigned char *target_hw)
{
struct sk_buff *skb;
struct arphdr *arp;
unsigned char *arp_ptr;
/* * Allocate a buffer */
// arp_hdr_len()为标准ARP首部+2个IP地址+2个L2层地址长度
skb = alloc_skb(arp_hdr_len(dev) + LL_ALLOCATED_SPACE(dev), GFP_ATOMIC);
if (skb == NULL)
return NULL;
skb_reserve(skb, LL_RESERVED_SPACE(dev));
skb_reset_network_header(skb);
arp = (struct arphdr *) skb_put(skb, arp_hdr_len(dev));
skb->dev = dev;
skb->protocol = htons(ETH_P_ARP);
if (src_hw == NULL) // 源地址
src_hw = dev->dev_addr;
if (dest_hw == NULL) // 如果不指定目的地址构造的就是广播报文
dest_hw = dev->broadcast;
// 调用网络设备提供的接口填充ARP报文的L2帧首部
if (dev_hard_header(skb, dev, ptype, dest_hw, src_hw, skb->len) < 0)
goto out;
/* * Fill out the arp protocol part. * * The arp hardware type should match the device type, except for FDDI, * which (according to RFC 1390) should always equal 1 (Ethernet). */
/* * Exceptions everywhere. AX.25 uses the AX.25 PID value not the * DIX code for the protocol. Make these device structure fields. */
// 根据不同的设备类型,填充标准ARP报文首部字段
switch (dev->type) {
default:
arp->ar_hrd = htons(dev->type);
arp->ar_pro = htons(ETH_P_IP);
break;
...
}
arp->ar_hln = dev->addr_len;
arp->ar_pln = 4;
arp->ar_op = htons(type);
arp_ptr=(unsigned char *)(arp+1);
memcpy(arp_ptr, src_hw, dev->addr_len);
arp_ptr += dev->addr_len;
memcpy(arp_ptr, &src_ip, 4);
arp_ptr += 4;
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报文: arp_xmit()
注意,经过过滤器后直接调用的是设备接口层的发送函数dev_queue_xmit(),不会再进入邻居子系统了。
/* * Send an arp packet. */
void arp_xmit(struct sk_buff *skb)
{
/* Send it off, maybe filter it using firewalling first. */
NF_HOOK(NFPROTO_ARP, NF_ARP_OUT, skb, NULL, skb->dev, dev_queue_xmit);
}
接收ARP报文: arp_rcv()
/* * Receive an arp request from the device layer. */
static int arp_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
struct arphdr *arp;
/* ARP header, plus 2 device addresses, plus 2 IP addresses. */
if (!pskb_may_pull(skb, arp_hdr_len(dev))) // 确保报文有足够的ARP报文首部
goto freeskb;
// 校验ARP报文首部,确保数据是发给本设备的
arp = arp_hdr(skb);
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;
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out_of_mem;
// 设置skb控制块为0
memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));
// 经过过滤器后调用arp_process()
return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
freeskb:
kfree_skb(skb);
out_of_mem:
return 0;
}
处理ARP报文: arp_process()
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;
__be32 sip, tip;
u16 dev_type = dev->type;
int addr_type;
struct neighbour *n;
struct net *net = dev_net(dev);
/* arp_rcv below verifies the ARP header and verifies the device * is ARP'able. */
if (in_dev == NULL)
goto out;
arp = arp_hdr(skb);
// 根据设备类型,再次校验ARP报文首部字段,我们只关注default分支,即以太网
switch (dev_type) {
default:
if (arp->ar_pro != htons(ETH_P_IP) || htons(dev_type) != arp->ar_hrd)
goto out;
break;
...
}
// 只处理ARP请求和ARP应答报文
if (arp->ar_op != htons(ARPOP_REPLY) && arp->ar_op != htons(ARPOP_REQUEST))
goto out;
/* * Extract fields */
arp_ptr= (unsigned char *)(arp+1); // 实际跳过的是sizeof(arphdr)字节
sha = arp_ptr; // 发送方L2层地址保存在sha中
arp_ptr += dev->addr_len;
memcpy(&sip, arp_ptr, 4); // 发送方IP地址保存在sip中
arp_ptr += 4;
arp_ptr += dev->addr_len;
memcpy(&tip, arp_ptr, 4); // 目的方IP地址保存在tip中
/* * Check for bad requests for 127.x.x.x and requests for multicast * addresses. If this is one such, delete it. */
// 异常的目的地址,多播地址是不需要ARP查询的
if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
goto out;
// 仔细阅读注释
/* * Process entry. The idea here is we want to send a reply if it is a * request for us or if it is a request for someone else that we hold * a proxy for. We want to add an entry to our cache if it is a reply * to us or if it is a request for our address. * (The assumption for this last is that if someone is requesting our * address, they are probably intending to talk to us, so it saves time * if we cache their address. Their address is also probably not in * our cache, since ours is not in their cache.) * * Putting this another way, we only care about replies if they are to * us, in which case we add them to the cache. For requests, we care * about those for us and those for our proxies. We reply to both, * and in the case of requests for us we add the requester to the arp * cache. */
/* Special case: IPv4 duplicate address detection packet (RFC2131) */
// 无端ARP中的IP地址重复检测场景,如果允许,回复ARP响应报文
if (sip == 0) {
if (arp->ar_op == htons(ARPOP_REQUEST) &&
inet_addr_type(net, tip) == RTN_LOCAL &&
!arp_ignore(in_dev, sip, tip))
arp_send(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha, dev->dev_addr, sha);
goto out;
}
// 对于ARP请求报文,路由查询可以过滤掉本地路由不应该接收的报文
if (arp->ar_op == htons(ARPOP_REQUEST) &&
ip_route_input(skb, tip, sip, 0, dev) == 0) {
rt = skb->rtable;
addr_type = rt->rt_type;
if (addr_type == RTN_LOCAL) {
// ARP请求来自本地子网
int dont_send = 0;
if (!dont_send)
dont_send |= arp_ignore(in_dev,sip,tip); // 检查是否需要忽略该报文
if (!dont_send && IN_DEV_ARPFILTER(in_dev)) // 如果开启ARP报文过滤,则进行过滤
dont_send |= arp_filter(sip,tip,dev);
if (!dont_send) {
// ARP请求报文可以接收,作为接收方,也可以从中获取到发送方的地址映射关系,
// 使用这个信息更新或者创建邻居项,相比于主动发送,这种方式也叫被动学习
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
// 实际上即使本地缓存失败,也应该向发送方回应ARP响应
if (n) {
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)) {
// ARP报文不是给本机的,并且设备开启了转发能力,根据是否启用代理功能对数据包进行转发,
// 这部分逻辑分析见“邻居子系统之代理功能”
if (addr_type == RTN_UNICAST && rt->u.dst.dev != dev &&
(arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, net, &tip, dev, 0))) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n)
neigh_release(n);
if (NEIGH_CB(skb)->flags & 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;
}
}
}
// ARP响应报文,或者是不该接收的ARP请求报文(路由查询失败),到这里更新本地邻居表
/* Update our ARP tables */
// 根据ARP报文的源IP地址查询邻居项,最后一个参数为0,查询失败不会创建邻居项
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
if (IPV4_DEVCONF_ALL(dev_net(dev), ARP_ACCEPT)) {
// 判断的是all配置中的设置
/* Unsolicited ARP is not accepted by default. It is possible, that this option should be enabled for some devices (strip is candidate) */
// 下面情况成立,说明本机没有对应的ARP请求,但是收到了一个ARP响应,
// 如果arp_accept选项打开,那么也用该信息更新邻居表
if (n == NULL &&
arp->ar_op == htons(ARPOP_REPLY) &&
inet_addr_type(net, sip) == RTN_UNICAST)
n = __neigh_lookup(&arp_tbl, &sip, dev, 1);
}
if (n) {
// 更新邻居项状态
int state = NUD_REACHABLE;
int override;
/* If several different ARP replies follows back-to-back, use the FIRST one. It is possible, if several proxy agents are active. Taking the first reply prevents arp trashing and chooses the fastest router. */
// 锁定期内只按照第一个更新
override = time_after(jiffies, n->updated + n->parms->locktime);
/* Broadcast replies and request packets do not assert neighbour reachability. */
// 默认是更新到NUD_REACHABLE,但是对于ARP请求和广播的ARP响应,仅仅更新到NUD_STALE
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请求忽略识别: arp_ignore()
该函数用于识别是否应该忽略收到的ARP请求报文。
// 调用者已经确保tip是本地地址
static int arp_ignore(struct in_device *in_dev, __be32 sip, __be32 tip)
{
int scope;
// 根据网络设备上的arp_ignore选项做不同处理
switch (IN_DEV_ARP_IGNORE(in_dev)) {
case 0:
// 任何本地地址的ARP请求都应该应答(默认值)
return 0;
case 1:
// 目的地址必须是配置在输入接口上的地址才应答
sip = 0;
scope = RT_SCOPE_HOST;
break;
case 2:
// 目的地址必须是配置在数据接口上,并且和sip必须是同一个子网才应答
scope = RT_SCOPE_HOST;
break;
case 3: /* Do not reply for scope host addresses */
sip = 0;
scope = RT_SCOPE_LINK;
break;
case 4: /* Reserved */
case 5:
case 6:
case 7:
return 0;
case 8:
// 不应答
return 1;
default:
return 0;
}
// 进行目的地址确认
return !inet_confirm_addr(in_dev, sip, tip, scope);
}
ARP请求过滤: arp_filter()
该函数是对前面提到的arp_filter的实现。该函数会使得只有本机可以到达发送方,并且是出口设备收到ARP请求时才会处理ARP请求报文。
static int arp_filter(__be32 sip, __be32 tip, struct net_device *dev)
{
struct flowi fl = {
.nl_u = {
.ip4_u = {
.daddr = sip,
.saddr = tip } } };
struct rtable *rt;
int flag = 0;
/*unsigned long now; */
struct net *net = dev_net(dev);
if (ip_route_output_key(net, &rt, &fl) < 0)
return 1;
if (rt->u.dst.dev != dev) {
NET_INC_STATS_BH(net, LINUX_MIB_ARPFILTER);
flag = 1;
}
ip_rt_put(rt);
return flag;
}
被动学习: neigh_event_ns()
struct neighbour *neigh_event_ns(struct neigh_table *tbl, u8 *lladdr, void *saddr,
struct net_device *dev)
{
// 先查询邻居表中是否有该邻居向,如果没有,该函数会新建一个(根据lladdr)
struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev, lladdr || !dev->addr_len);
if (neigh)
// 将邻居项状态更新未NUD_STALE,特别注意,如果原来是NUD_REACHABLE是不会将其变为NUD_STALE的
neigh_update(neigh, lladdr, NUD_STALE, NEIGH_UPDATE_F_OVERRIDE);
return neigh;
}
边栏推荐
- UML系列文章(31)体系结构建模---部署图
- Golang Foundation (7)
- 35. search insertion position
- Clickhouse column basic data type description
- 字节序 - 如何判断大端小端
- Basic principle of Doppler effect
- MySQL45讲 01 | 基础架构:一条SQL查询语句是如何执行的?
- tensorflow 2. X multi classification confusion matrix and evaluation index calculation method (accuracy rate, recall rate, F1 score)
- Golang基础(6)
- 视频分类的类间和类内关系——正则化
猜你喜欢

信号继电器RXSF1-RK271018DC110V

CLJ3-100ALH30剩余电流继电器

6.6 Convolution de séparation

21 reasons why you need social media QR code

无限生长,我们都将奔赴未来 | InfoQ中国成立15周年

K58. Chapter 1 installing kubernetes V1.23 based on kubeadm -- cluster deployment

UI自动化测试中比较少见的异常记录

ReentrantLock源码分析

conda环境下pip install 无法安装到指定conda环境中(conda环境的默认pip安装位置)

UML series articles (30) architecture modeling -- product diagram
随机推荐
Selenium uses proxy IP
ARM指令集之杂类指令
rosbridge使用案例心得总结之_第26篇在同一个服务器上打开多个rosbridge服务监听端口
Unit test case framework --unittest
mysql中的索引show index from XXX每个参数的意义
ARM指令集之伪指令
35. 搜索插入位置
LVS health state detection based on application layer
信号继电器RXSF1-RK271018DC110V
LLD monitored by ZABBIX
35. search insertion position
Byte order (network / host) conversion
ioremap
Go sends SMS based on alicloud
B.刷墙(C语言)
字节序 - 如何判断大端小端
arm各种交叉编译工具的区别
Construction and construction of meta Universe System
你需要社交媒体二维码的21个理由
Windows10 install mysql-8.0.28-winx64