赞
踩
主要参考了《深入linux内核架构》和《精通Linux内核网络》相关章节
邻居子系统,提供三层地址到二层地址之间的映射,提供二层首部缓存加速二层头的封装,提供二层报文头的封装。
在IPv4当中,实现这种转换的协议为地址解析协议(Address ResolutionProtocol,ARP),而在IPv6则为邻居发现协议(Neighbour Discovery Protocol,NDISC或ND),邻接子系统为执行L3到L2映射提供了独立于协议的基础设施。
IPv6 NDP: 邻居发现协议(Neighbour Discovery Protocol, NDP),邻居子系统为了执行L3到L2的映射提供了独立于协议的基础设施。请求和应答分别为邻居请求和邻居应答。。
ND(Neighbor Discovery,邻居发现)协议是IPv6的一个关键协议,它综合了IPv4中的ARP,ICMP路由发现和ICMP重定向等协议,并对他们做了改进。作为IPv6的基础性协议,ND协议还提供了前缀发现,邻居不可达检测,重复地址检测,地址自动配置等功能
**在第2层发送数据包时,为创建L2报头,需要使用L2目标地址,使用邻接系统进行请示和应答,便可根据主机的L3地址获悉其L2地址(或获悉这样的L3地址不存在)。在最常用的数据链路层(L2)–以太网中,主机的L2地址为MAC地址。传输当前主机生成的外出数据包或转发当前主机收到的数据包。**有时不需要邻接子系统的帮助也能够获悉目标地址。比如发送广播时,在这种情况L2目标地址是固定的,例如,在以太网中为FF:FF:FF:FF:FF:FF,有时目标地址是组播地址,L3组播地址和L2组播地址的映射关系是固定的。
邻居子系统,提供三层地址到二层地址之间的映射,提供二层首部缓存加速二层头的封装,提供二层报文头的封装。
如下,邻居表信息,表达了IP地址是x.x.x.x的下一跳,它的mac地址是xx:xx:xx:xx:xx:xx,通过出接口ethx能够到达。
#ip neigh
172.16.10.34 dev eth1 lladdr 52:54:00:8f:77:cd STALE
172.16.100.2 dev eth1 lladdr 00:1e:08:0a:53:01 STALE
192.168.122.1 dev eth2 lladdr 52:54:00:7a:39:1c STALE
172.16.100.3 dev eth1 lladdr 00:1e:08:0a:b2:f7 STALE
172.16.0.2 dev eth1 lladdr 00:1e:08:15:18:65 STALE
172.16.0.1 dev eth1 lladdr 50:c5:8d:b4:3e:81 REACHABLE
1.1.1.1 dev eth0 lladdr 52:54:00:e4:f7:11 PERMANENT
192.168.121.1 dev eth0 lladdr 52:54:00:8a:20:74 STALE
20.1.1.10 dev eth2.100 lladdr 52:54:00:e4:f7:2a STALE
Linux邻接系统的基本数据结构是邻居,表示与当前链路相连的网络结点,用结构neighbour来表示。
如果一台主机和你的计算机连接在同一个LAN上(也就是说,你和这台主机通过一个共享介质相连或点对点直接相连),那么它就是你的邻居(neighbor),而且它有相同的L3网络配置。
具体内核源码分析如下: include\net\neighbour.h
struct neighbour { struct neighbour __rcu *next; // 指向散列表的同一个桶中的下一个邻居 struct neigh_table *tbl; // 与邻居相关联的邻接表(ARP表/ARP缓存) struct neigh_parms *parms; // 与邻居相关联的neigh_parms对象,由相关联的邻接表的构造函数对其进行初始化 unsigned long confirmed; // 确认时间戳 unsigned long updated; rwlock_t lock; atomic_t refcnt; // 引用计数器 struct sk_buff_head arp_queue; // arp_queue:一个未解析SKB队列 unsigned int arp_queue_len_bytes; struct timer_list timer; // 定时器 unsigned long used; atomic_t probes; __u8 flags; __u8 nud_state; __u8 type; __u8 dead; seqlock_t ha_lock; // 邻居对象的硬件地址。在以太网中,它为邻居的MAC地址。 unsigned char ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))]; struct hh_cache hh; // L2报头的硬件报头缓存(一个hh_cache对象) int (*output)(struct neighbour *, struct sk_buff *); // 一个指向传输方法的指针 const struct neigh_ops *ops; struct rcu_head rcu; struct net_device *dev; u8 primary_key[0]; // primary_key:邻居的IP地址(L3地址)邻接表查找是根据primary_key进行的 };
parms: 与邻居相关联的neigh_parms对象,由相关联的邻接表的构造函数对其进行初始化。例如,在IPv4中,方法arp_constructor()将parms初始化为相关联的网络设备的arp_parms。不要将其与邻接表的neigh_parms对象混为一谈。
refcnt: 引用计数器。neigh_hold()宏会将其加1,而neigh_release()宏则将其减1。仅当这个引用计数器的值被减为0时,方法neigh_release()才会调用方法neigh_destroy()来释放邻居对象。
timer: 每个neighbour对象都有一个定时器。定时器回调函数为方法neigh_timer_handler(),它可以修改邻居的网络不可达检测(NUD)状态。在发送请求时,邻居的状态为NUD_INCOMPLETE或NUD_PROBE。如果请求数达到或超过neigh_max_probes()的值,将把邻居的状态设置为NUD_FAILED,并调用方法neigh_invalidate()。
ha_lock: 对邻居硬件地址( ha)提供访问保护。
ha: 邻居对象的硬件地址。在以太网中,它为邻居的MAC地址。
hh: L2报头的硬件报头缓存(一个hh_cache对象)。
output: 一个指向传输方法(如方法neigh_resolve_output()或neigh_direct_output() )的指针。其值取决于NUD状态,因此在邻居的生命周期内可赋给其不同的值。在方法neigh_alloc()中初始化邻居对象时,会将其设置为方法neigh_blackhole()。这个方法将丢弃数据包并返回-ENETDOWN。下面是设置output回调函数的辅助方法。
**nud_state: 邻居的NUD状态。**在邻居的生命周期内,可动态地修改nud_state的值。在7.5节中,表7-1描述了基本的NUD状态及其Linux符号。NUD状态机非常复杂。
下面这张状态机图描述的很清楚(状态变化)。
dead: 一个标志**,在邻居对象处于活动状态时被设置。**创建邻居对象时,在方法_neigh_create()末尾将其设置为0。对于dead标志未被设置的邻居对象,调用方法neigh_destroy()将会失败。方法neigh_flush_dev()将dead标志设置为1,但不会删除邻居。被标记为失效( dead标志被设置)的邻居由垃圾收集器删除。
primary_key: 邻居的IP地址(L3地址),邻接表查找是根据primary_key进行的。primary_key的长度因协议而异。例如,对于IPv4来说,其长度为4字节;对于IPv6来说,其长度为sizeof(struct in6_addr),因为结构in6_addr表示IPv6地址。因此,primary_key被定义为0字节的数组,分配邻居时,必须考虑使用的协议。详情请参阅后面描述结构neigh_table的成员时,对entry_size和key_len的解释。
**为避免在每次传输数据包时都发送请求,内核将L3地址和L2地址之间的映射存储在了被称为邻接表的数据结构中。**在IPv4中,这个表就是ARP表,有时被称为ARP缓存,但它们指的是一回事。在IPv6中,邻接表就是NDISC表(也叫NDISC缓存)。ARP表( arp_tbl )和NDISC表( nd_tbl)都是结构neigh_table的实例。下面就来看看结构neigh_table。
struct neigh_table { int family; // ipv4\ipv6 ipv4= arp_tbl, ipv6=nd_tbl int entry_size; // 邻居表项结构的大小,包括邻居表项和其key的信息,对于ipv4,是根据ipv4地址查询neighbor表项的,所以=sizeof(neighbour)+4 int key_len; // 查找键长度,就是上面用到的neighbor表项key长度,三层地址,arp就是ipv4地址 __be16 protocol; // 三层协议类型,ETH_P_IP 或者 ETH_P_IPV6 __u32 (*hash)(const void *pkey, const struct net_device *dev, __u32 *hash_rnd); // 表项hash函数,eg arp_hash bool (*key_eq)(const struct neighbour *, const void *pkey); int (*constructor)(struct neighbour *); // 创建邻居对象 int (*pconstructor)(struct pneigh_entry *); // 创建邻居代理对象(arp不使用,NDSIC使用) void (*pdestructor)(struct pneigh_entry *); void (*proxy_redo)(struct sk_buff *skb); char *id; // 邻接表名称,、arp_tabl为arp_cache struct neigh_parms parms; // 存储与协议相关的可调节参数 struct list_head parms_list; int gc_interval; // 这四个是垃圾回收的时间参数 int gc_thresh1; int gc_thresh2; int gc_thresh3; unsigned long last_flush; // 最近一次运行方法neigh_forced_gc()的时间 struct delayed_work gc_work; // 垃圾回收的工作队列,异步垃圾回收层处理程序 struct timer_list proxy_timer; struct sk_buff_head proxy_queue; // 由SKB组成的代理ARP队列。SKB是使用方法pneigh_enqueue()添加的。 atomic_t entries; // 所有邻居项的数目 rwlock_t lock; unsigned long last_rand; struct neigh_statistics __percpu *stats; // 邻居统计信息( neigh_statistics )对象 struct neigh_hash_table __rcu *nht; // 邻居散列表(neigh_hash_table对象) struct pneigh_entry **phash_buckets; // 邻接代理散列表 };
**proxy_timer:主机被配置为ARP代理时,它可能不会立即处理请求,而是过一段时间再处理。这是因为,对于ARP代理主机来说,可能有大量的请求需要处理(这不同于不是ARP代理的主机,通常它们需要处理的ARP请求较少)。有时候,你可能希望延迟对这种广播做出应答,让拥有要解析的IP地址的主机先收到请求。**这种延迟是随机的,最长不超过参数proxy_delay的值。对于ARP来说,代理定时器处理程序为方法neigh proxy_process()。proxy_timer由方法neigh_table_init_no_netlink()进行初始化。
stats:邻居统计信息( neigh_statistics )对象,包含针对每个CPU的计数器,如allocs(方法neigh_alloc()分配的邻居对象数)、destroys(方法neigh_destroy()释放的邻居对象数)等。邻居统计信息计数器由NEIGH_CACHE_STAT_INC宏进行递增操作。请注意,由于这些统计信息是针对每个CPU的计数器的,因此NEIGH_CACHE_STAT_INC宏将调用this_cpu_inc()宏。要显示ARP统计信息和NDISC统计信息,可分别使用cat /proc/netstat/arp_cache和cat/proc/net/stat/ndisc_cache。在7.5节中,描述了结neigh_statistics,并指出了每个计数器的递增方法。
phash_buckets:邻接代理散列表,是在方法neigh_table_init_no_netlink()中分配的。邻接表的初始化工作是使用方neigh_table_init()完成的。
在IPv4中,ARP模块定义了ARP表(一个名为arp_tbl的neigh_table结构实例),并将其作为参数传递给方法neigh_table_init()(参见net/ipv4/arp.c中的方法arp_init() ).
在IPv6中,NDISC模块定义了NDSIC表(一个名为nd_tbl的neigh_table结构实例),并将其作为参数传递给方法neigh_table_init()(参见net/ipv6/ndisc.c中的方法ndisc_init() )。方法neigh_table_init()还可调用方法neigh_table_init_no_netlink(),后者将调用方法neigh_hash_alloc()创建邻接散列表(对象nht ),以便为8个散列条目分配空间。
使用邻接子系统的每种L3协议都还注册一个协议处理程序。对于IPv4来讲,ARP数据包处理程序方法为arp_rcv() arp.c
每个邻居对象结构neigh_ops中定义一组方法,它包含一个协议簇成员和4个函数指针,具体内核源码如下
struct neigh_ops { int family; // 发送请求报文函数。在发送一个报文时,需要更新邻居表项, // 发送报文会缓存到arp_queue中,然后调用solicit函数发送请求报文。 void (*solicit)(struct neighbour *, struct sk_buff *); // 在邻居状态为NUD_FAILED时,将在方法neigh_invalidate()调用该方法,例如在请求应答时间超时后就将出现这种情况。 // 邻居项缓存着未发送的报文,而该邻居项又不可达时, 被调用来向三层报告错误的函数。 void (*error_report)(struct neighbour *, struct sk_buff *); // 在下一跳的L3地址已知,但未能解析出L2地址时,应将output设置为neigh_resolve_output()。 // 通用输出报文函数,做邻居状态等校验,流程上会比connected_output 慢一些 int (*output)(struct neighbour *, struct sk_buff *); // 当邻居可达NUD_CONNECT的时候,肯定处于邻居可用状态,直接构造和封装二层头发送。 // 邻居状态为NUD_REACHABLE或NUD_CONNECTED时,应将output方法设置为connected_output指定的方法。 int (*connected_output)(struct neighbour *, struct sk_buff *); };
邻居创建是由_neigh_create()处理
邻居删除是由neigh_release()处理
管理ARP表,可使用iproute2包中的命令ip neigh,也可以使用net_tools包中的命令arp
arp: 由net/ipv4/arp.c中的万法arp_seq_show(处理)。
ip neigh show(或ip neighbour show ):由net/ core/neighbour.c中的方法neigh_dump_info()处理。
请注意,命令ip neigh show显示邻接表条目的NUD状态,如NUD_REACHABLE或NUD_STALE。另外,命令arp只显示IPv4邻接表(ARP表),而命令ip显示IPv4 ARP表和IPv6邻接表。如果只想显示IPv6邻接表,可使用命令ip -6 neigh show。
root@XYF:~# arp
Address HWtype HWaddress Flags Mask Iface
_gateway ether ee:ff:ff:ff:ff:ff C eth0
ARP和NDISC模块还可通过procfs导出数据。这意味着,要显示ARP表,还可执行命令cat/proc/net/arp(这个procfs条目由方法arp_seq_show()处理,该方法也用于处理前面提到的命令arp )。要显示ARP统计信息,可使用命令cat/proc/net/stat/ arp_cache;而要显示NDISC统计信息,可使用命令cat /proc/net/stat/ndisc_cache(这两个命令都由方法neigh_stat_seq_show()处理)。
net\ipv4\arp.c
static int arp_seq_show(struct seq_file *seq, void *v) { if (v == SEQ_START_TOKEN) { seq_puts(seq, "IP address HW type Flags " "HW address Mask Device\n"); } else { struct neigh_seq_state *state = seq->private; if (state->flags & NEIGH_SEQ_IS_PNEIGH) arp_format_pneigh_entry(seq, v); else arp_format_neigh_entry(seq, v); } return 0; }
**要添加邻居条目,可使用命令ip neigh add。**这个命令由方法neigh_add()处理。执行命令ipneigh add时,可指定要添加的邻居条目的状态(如NUD_PERMANENT、NUD_STALE、NUD_REACHABLE等),如下所示。
ip neigh add 192.168.0.121 dev etho lladdr 00:30:48:5b:cc:45 nud permanent
要删除邻居条目,可使用命令ip neigh del(这个命令由方法neigh_delete()处理),如下所示。
ip neigh del 192.168.0.121 dev etho
要在代理ARP表中添加条目,可使用命令ip neigh add proxy,如下所示。ip neigh add proxy 192.168.2.11 dew etho
这种添加工作也可由方法neigh_add()进行处理,但该方法将在从用户空间传递而来的数据中设置标志NTF_PROXY(参见对象ndm的ndm_flags字段),因此将调用方法pneigh_lookup()在代理邻接表( phash_buckets )中执行查找。如果没有找到,方法pneigh_lookup()将在代理邻接散列表中添加一个条目。
要从代理ARP表中删除条目,可使用命令ip neigh del proxy,如下所示。ip neigh del proxy 192.168.2.11 dev etho
这种删除工作由方法neigh_delete()处理。同样,在这种情况下,将在从用户空间传递而来的数据中设置NTF_PROXY标志(参见对象ndm的ndm_flags字段),因此将调用方法pneigh_delete().将条目从代理邻接表中删除。
使用命令ip ntable可显示和控制邻接表的参数,如下所示。ip ntable show:显示所有邻接表的参数。
ip ntable change:修改邻接表参数的值,由方法neightbl_set()处理,如ip ntable change name arp_cache queue 20 dev etho。
还可以使用命令arp add在ARP表中添加条目。另外,还可以像下面这样在ARP表中添加静态条目: arp -s <IPAddress> <MacAddress>。静态ARP条目不会被邻接子系统垃圾收集器删除,但会在重启后消失。
邻接核心不会使用方法register_netdevice_notifier()注册任何事件,而ARP和NDISC模块则会注册网络事件。在ARP中,方法arp_netdev_event()将被注册为netdev事件的回调函数,它调用通用方法neigh_changeaddr()以及方法rt_cache_flush()来处理MAC地址变更事件。从内核3.11起,在IFF_NOARP标志发生变化时,将调用方法neigh_changeaddr()来处理NETDEV_CHANGE事件。当设备使用方法_dev_notify_flags()修改其标志或使用方法netdev_state_change()修改其状态时,都将触发NETDEV_CHANGE事件。在NDISC中,方法ndisc_netdev_event()被注册为netdev事件的回调函数,它处理NETDEV_CHANGEADDR、NETDEV_DOwN和NETDEV_NOTIFY_PEERS事件。
地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。地址解析协议是建立在网络中各个主机互相信任的基础上的,局域网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。ARP命令可用于查询本机ARP缓存中IP地址和MAC地址的对应关系、添加或删除静态对应关系等。相关协议有RARP、代理ARP。NDP用于在IPv6中代替地址解析协议。
代理ARP(proxy ARP):对于没有配置缺省网关的计算机要和其他网络中的计算机实现通信,网关收到源计算机的 ARP 请求会使用自己的 MAC 地址与目标计算机的 IP地址对源计算机进行应答。**代理ARP就是将一个主机作为对另一个主机ARP进行应答。它能使得在不影响路由表的情况下添加一个新的Router,使得子网对该主机来说变得更透明化。**同时也会带来巨大的风险,除了ARP欺骗,和某个网段内的ARP增加,最重要的就是无法对网络拓扑进行网络概括。代理ARP的使用一般是使用在没有配置默认网关和路由策略的网络上的。
ARP协议是在RFC 826中定义的。在以太网中,硬件地址称为MAC地址,长48位。MAC地址必须是独一无二的,但必须考虑这样的情形,即可能会遇到并非独一无二的MAC地址。导致这种情形的一种常见原因是,在大多数网络接口上,系统管理员都可使用诸如ifconfig或ip等用户空间工具配置MAC地址。
发送IPv4数据包时,目标IPv4地址是已知的,但需要创建以太网报头,其中包含目标MAC地址。根据给定IPv4地址确定MAC地址的工作由ARP协议完成,稍后你讲看到这一点。如果MAC地址未知,就以广播方式发送ARP请求,其中包含已知的IPv4地址。如果有主机配置了这个IPv4地址,它将使用单播ARP响应进行应答。ARP表( arp_tbl)是一个neigh_table结构实例。ARP报头用结构arphdr表示。
include\linux\if_arp.h
struct arphdr { __be16 ar_hrd; /* format of hardware address */ __be16 ar_pro; /* format of protocol address */ unsigned char ar_hln; /* length of hardware address */ unsigned char ar_pln; /* length of protocol address */ __be16 ar_op; /* ARP opcode (command) */ #if 0 /* * 在以太网中,类似于下面这样,但这部分的长度并不是困定的 */ 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 };
紧跟在ar_op后面的是发送方的硬件(MAC)地址和IPv4地址,以及目标硬件(MAC)地址和IPv4地址。这些地址并非ARP报头(结构arphdr )的组成部分。在方法arp_process()中,通过读取ARP报头相应的偏移量来提取它们。在讨论方法arp_process()时,你将看到这一点。下图显示了ARP以太网数据包的ARP报头。
ARP包是前面是MAC首部 —— 目地MAC地址(8字节)源MAC地址(8字节)类型(2字节)
ARP 解析 MAC 地址
首先,每台主机都会在自己的ARP缓冲区(ARP Cache)中建立一个 ARP列表,以表示IP地址和MAC地址的对应关系。
当源主机需要将一个数据包要发送到目的主机时,会首先检查自己 ARP列表中是否存在该 IP地址对应的MAC地址,如果有﹐就直接将数据包发送到这个MAC地址;如果没有,就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。此ARP请求数据包里包括源主机的IP地址、硬件地址、以及目的主机的IP地址。
网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。如果不相同就忽略此数据包;如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个 ARP响应数据包,告诉对方自己是它需要查找的MAC地址;
源主机收到这个ARP响应数据包后,将得到的目的主机的IP地址和MAC地址添加到自己的ARP列表中,并利用此信息开始数据的传输。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
ARP 发送请求
static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct rtable *rt = (struct rtable *)dst;
struct net_device *dev = dst->dev;
unsigned int hh_len = LL_RESERVED_SPACE(dev);
struct neighbour *neigh;
u32 nexthop;
...
neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
if (unlikely(!neigh))
neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
...
}
ARP发送报文
arp_solicit函数,邻居子系统调用solicit函数指针 (neigh_ops) 发送solicitation请求,在arp指针中这个函数指针被初始化为arp_solicit函数。
static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb);
最终调用方法arp_send()来发送ARP请求。我们注意到最后一个参数(target_hw)为NULL,因为还不知道目标硬件(MAC)地址。在调用arp_send()时,如果参数target_hw为NULL,将以广播方式发送ARP请求。
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)
{
arp_send_dst(type, ptype, dest_ip, dev, src_ip, dest_hw, src_hw,
target_hw, NULL);
}
static int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { const struct arphdr *arp; /* do not tweak dropwatch on an ARP we will ignore */ // 如果收到ARP数据包的网络设备设置标志为IFF_NOARP, // 或者数据包不是发送给当前主机,或数据包是发送给环回设备的,就必须将数据包丢弃。 if (dev->flags & IFF_NOARP || skb->pkt_type == PACKET_OTHERHOST || skb->pkt_type == PACKET_LOOPBACK) goto consumeskb; // 如果SKB是共享的,就必须复制它,因为在方法arp_rcv()进行处理期间,它可能被其他人 // 修改。如果SKB是共享的,方法skb_share_check()就将创建其副本 skb = skb_share_check(skb, GFP_ATOMIC); if (!skb) goto out_of_mem; /* ARP报头,两个设备地址,两个IP地址 */ if (!pskb_may_pull(skb, arp_hdr_len(dev))) goto freeskb; arp = arp_hdr(skb); // ARP报头的ar_hln表示硬件地址的长度。对于以太网报头来说,其应为6字节, // 并与net_device对象的addr_len相等。ARP报头的ar_pln表示协议地址的长度,它应与IPv4地址的长度相等,即为4字节。 if (arp->ar_hln != dev->addr_len || arp->ar_pln != 4) goto freeskb; memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb)); // 如果一切正常,就接着调用方法arp_process(),由它执行处理ARP数据包的实际工作。 return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, dev_net(dev), NULL, skb, dev, NULL, arp_process); consumeskb: consume_skb(skb); return NET_RX_SUCCESS; freeskb: kfree_skb(skb); out_of_mem: return NET_RX_DROP; }
在方法arp_process()中,只处理ARP报文请求和响应(回复)
比如arp_process() 首先验证ARP报文头和设备是否使能ARP功能。
arp_process函数只处理ARPOP_REPLY和ARPOP_REQUEST报文类型。
... /* Understand only these message types */ if (arp->ar_op != htons(ARPOP_REPLY) && arp->ar_op != htons(ARPOP_REQUEST)) goto out_free_skb; ... if (n) { int state = NUD_REACHABLE; int override; /* 如果连续收到多个ARP应答,将使用第一个应答,如果有多个代理处于活动状态, 就可能出现这种情况,使用第一个应答可避免ARP受损,并确保选择的是最快的路由器。 */ override = time_after(jiffies, n->updated + NEIGH_VAR(n->parms, LOCKTIME)) || is_garp; /* Broadcast replies and request packets do not assert neighbour reachability. */ 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, 0); neigh_release(n); } out_consume_skb: consume_skb(skb); out_free_dst: dst_release(reply_dst); return NET_RX_SUCCESS; out_free_skb: kfree_skb(skb); return NET_RX_DROP; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。