欢迎光临散文网 会员登陆 & 注册

Linux内核邻接子系统(arp协议) 的工作原理(一)

2022-09-19 20:34 作者:补给站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协议还提供了前缀发现,邻居不可达检测,重复地址检测,地址自动配置等功能 NDP 加强了地址解析协议与底层链路的独立性、增强了安全性、减小了报文传播范围

**在第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能够到达。

Linux邻接系统的基本数据结构是邻居,表示与当前链路相连的网络结点,用结构neighbour来表示。

struct neighbour 如果一台主机和你的计算机连接在同一个LAN上(也就是说,你和这台主机通过一个共享介质相连或点对点直接相连),那么它就是你的邻居(neighbor),而且它有相同的L3网络配置。


【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)   

具体内核源码分析如下: include\net\neighbour.h

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回调函数的辅助方法。

void neigh_connect(struct neighbour *neigh):将指定邻居的output回调函数设置为neigh->ops->connected_output。 void neigh_suspect(struct neighbour *neigh):将指定邻居的output回调函数设置为neigh->ops->output。 **nud_state: 邻居的NUD状态。**在邻居的生命周期内,可动态地修改nud_state的值。在7.5节中,表7-1描述了基本的NUD状态及其Linux符号。NUD状态机非常复杂。

下面这张状态机图描述的很清楚(状态变化)。

NUD_INCOMPLETE:该状态是请求报文已发送,但尚未收到应答的状态。该状态下还没解析到硬件地址,因此尚无可用硬件地址,如果有报文要输出到该邻居,会将其缓存起来。 这个状态会启动一个定时器,如果在定时器到期时还没有接收到邻居的回应,则会重复发送请求报文,否则发送请求报文的次数打到上限,便会进入NUD_FAILED。 NUD_REACHABLE :该状态以及得到并缓存了邻居的硬件地址。进入该状态首先设置邻居项相关的output函数(该状态使用neighbors_ops结构的connectd_outpt),然后查看是否存在要发送给该邻居的报文。如果在该状态下闲置时间达到上限,便会进入NUD_STATLE。 NUD_STALE :该状态一旦有报文要输出到该邻居,则会进入NUD_DELAY并将该报文输出。如果在该状态下闲置时间达到上限,且此时的引用计数为1,则通过垃圾回收机制将其删除,在该状态下,报文的输出不收限制,使用慢速发送过程 NUD_DELAY :该状态下表示NUD_STATE状态下发送的报文已经发出,需得到邻居的可达性确认的状态。在为接收到邻居的应答或确认时也会定时地重发请求,如果发送请求报文的次数到上限,如果收到邻居的应答,进入NUD_REACHABLE,否则进入NUD_FAILED,在该状态下,报文的输出不收限制,使用慢速发送过程。 NUD_PROBE :过渡状态,和NUD_INCOMPLETE 状态类似,在未收到邻居状态的应答或者确认时,也会定时的重发请求,直到收到邻居的应答、确认、或者尝试发送请求报文的次数达到上限,如果收到应答或者确认就会进入NUD_REACHABLE,如果尝试发送请求到达上限,则进入NUD_FAILD状态,在该状态,报文的输出也不受限制,使用慢速发送过程。 NUD_FAILED:由于没有收到应答报文而无法访问状态, NUD_NOARP:标识邻居无需将三层地址协议映射到二层地址协议。如一些三层overlay的虚拟接口,loopback等。 NUD_PERMANENT: 设置邻居表项的硬件地址为静态。 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。

**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

struct neigh_ops 每个邻居对象结构neigh_ops中定义一组方法,它包含一个协议簇成员和4个函数指针,具体内核源码如下

邻居创建是由_neigh_create()处理

方法_neigh_create()首先调用方法neigh_alloc(),以分配一个邻居对象并执行各种初始化。在某些情况下,方法neigh_alloc()还将调用同步垃圾收集器(方法neigh_forced_gc() ). 接下来,方法__neigh_create()将调用指定邻接表的constructor方法(对于ARP来说,该方法为arp_constructor();对于NDISC来说,该方法为ndisc_constructor() ),以执行因协议而异的设置工作。 在constructor方法中,将处理组播地址和环回地址等特殊情况。例如,在方法arp_constructor()中,需调用方法arp_mc_map(),来根据邻居的IPv4 primary_key地址设置邻居的硬件地址( ha ),再将nud_state设置为NUD_NOARP,因为组播地址不需要ARP。 对于广播地址也需特殊对待。例如,在方法arp_constructor()中,如果邻居类型为RTN_BROADCAST,就将其硬件地址( ha)设置为网络设备的广播地址( net_device对象的broadcast字段)、并将nud_state设置为NUD_NOARP。 在方法_neigh_create()的最后,将dead标志初始化为0,并将邻居对象添加到邻居散列表中。 邻居删除是由neigh_release()处理

方法neigh_release()将邻居的引用计数器减1。如果它变成了0,就调用方法neigh_destroy()将邻居对象释放。方法neigh_destroy()会检查邻居的dead标志。如果该标志为0,就不会将邻居删除。 用户空间和邻接子系统之间的交互 管理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。

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

**要添加邻居条目,可使用命令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 。静态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事件。



Linux内核邻接子系统(arp协议) 的工作原理(一)的评论 (共 条)

分享到微博请遵守国家法律