第二十八章:邻居子系统:地址解析协议(ARP)

上一章介绍了邻居基础结构对所有邻居协议提供的通用服务,本章介绍ipv4使用的arp协议如何适应邻居基础结构的模块化设计。

本章主要介绍以下几点:

    如何初始化neigh_table结构arp_tbl,使其调整通用基础结构的行为来适应arp。

    如何初始化neigh_parms,使其调整通用基础结构的行为来适应arp。

    arp封包的接收如何与邻居子系统配合,以及solicit方法如何运作。

    如何根据设备类型和L3地址类型(单播,多播,广播)初始化neigh_ops结构。

    arp代理如何使用通用基础结构。

    如何根据编译选项和某些特性的明确配置来进一步调节arp的行为。

    为了处理特别繁重的负荷,内核如何将一些工作交给用户空间守护进程arpd。

    arp和逆向arp(RARP)之间的关系。

    arp会将哪些事件通知其他的内核子系统,反之亦然。

    另外,会简要介绍ipv6的NDi协议对arp做了哪些改进。

ARP封包格式:

在Linux中用arphdr结构来表示arp封包:

    硬件类型:硬件类型标识符,如Ethernet。

    协议类型:L3协议标识符,如ipv4

    硬件地址长度:L2地址长度,如Ethernet为6字节。

    协议地址长度:L3地址长度,如ipv4为4字节。

    操作码:如ARPOP_QEQUEST(发送一个广播请求L3地址到L2地址的解析,或者发给单独的主机确认一个存在的邻居项),ARPOP_REPLY(ARPOP_QEQUEST的应答)。

    发送方硬件地址和协议地址:

    目的硬件地址和目的协议地址:

从上图中可以看到,发送方硬件地址可能出现两次,一次在头部,一次在封包中,两个地址通常都是一样的,除非是代理。

无端ARP:

通常发出一个ARPOP_QEQUEST是由于发送方想和一个ip地址通信,需要找到对应的L2地址,但有时,只是为了通知接收方一些信息,这种封包称为无端ARP,通常用于这些情况:L2地址改变,重复地址探测,虚拟IP。

    重复地址探测:同一个局域网中不应该出现两台主机有相同的ip地址。主机可以使用无端arp发出一个目的地是你自己地址的arp请求,如果不存在重复地址,则不会收到任何应答。

    虚拟IP:通常一个站点配有多个备用服务器,当主服务器故障后,会启动备用服务器,这个新服务器会发出一个无端arp来更新网络中其他主机上的arp缓存,新服务器继承了旧服务器的地址。

多个网络接口的应答:

Linux设计认为一个ip地址属于一个主机,而不是某个接口。因此,假设在同一个LAN中,一台主机有两块NIC,并且另一台主机向其中一个地址发出一个ARP请求,在两个接口卡上都会收到该请求,并都会做出应答。因此,发出请求的主机会收到两个应答,一个来自正确的L2地址的NIC,一个属于另一个NIC。至于哪个弟子会进入该主机的arp缓存是不确定的。ARP flux 问题可以通过调整arp选项来解决。

调整ARP选项:

内核运行用户通过/proc和编译时选项来调整arp的行为。次啊吗介绍其中主要的几个特性:

编译选项:

    ARPD:允许用户空间的守护进程处理arp包。

/proc选项:

    ARP_ANNOUNCE:当产生solicitation请求的主机有多个ip地址时,这个选项控制哪个地址应被放到solicitation请求的arp头中。当它为0时,任何本地ip都行,当它为1时,尽量选择和目的地址位于同一个子网的地址,当它为2时,优先使用主地址。

    ARP_IGNORE:这些选项控制是否处理ARPOP_QEQUEST封包的条件。其值如下:

                            0:对任何本地地址的arp请求都应答。

                            1:如果目的ip配置在收到arp请求的接口上,才应答。    

                            2:和值为1类似,但是源ip必须和目的ip属于同一子网。

                            3:如果目的ip的scope(关于scope第三十章会介绍)不是本地主机,才应答。

                            4~7:保留。

                            8:不应答。

                            >8:未知的值,接受请求。

    ARP_FILTER:当一台主机有多个NIC时,且配置在同一个子网,这时,所有NIC都会收到同一个arp请求,通过这个选项,你可以只选择一个接口来应答。这个选项只能开启或关闭。

    Medium ID:介质id的取值:-1表示arp代理关闭,0表示介质id特性关闭,>0表示合法的介质id。具体介绍略。对于图28-8,主机B不会应答,让主机C自己应答。对于图28-9,主机B会代替C做应答。

初始化ARP协议:

初始化函数首先是注册一个虚函数表(arp_tbl)和ARP协议使用的其他常用参数,这个工作由neigh_table_init函数完成。arp_tbl的类型是neigh_table,其中含有arp协议涉及的关键变量。

每个协议在它的neigh_table->constructor虚函数中指定一个函数来初始化一个neighbour结构。arp的相关函数是arp_constructor。创建邻居的原因不同,邻居的初始态也会不同。下面这些字段的初始化很重要:

    nud_state:neighbour结构的初始态,根据L3地址的类型和创建该邻居项的原因取值。

    output:根据指定给的nud_state的值来初始化output。

    ha:表示L2地址

    ops:这个是虚函数的集合,它决定了ip子系统要调用的动作。

根据邻居的状态,可以被指定给neigh->ops字段的四组方法如下图:

在上图四个实例中,有三个字段在初始化ARP时被设成相同的值:

    family:AF_INET表示和ipv4一起工作。

    solicit:调用arp_solicit来生成一个请求。

    error_report:当一个ARP事物中发生错误时,arp_error_report函数就会通知上面的网络层。

arp_constructor函数的启动:

arp_constructor函数的首要任务是从与邻居相关的网络设备中取出一个in_dev结构。这个结构中保存着该网络设备的ip配置信息,其中也包括arp信息,如果没有配置信息,则函数终止并返回错误,如果有,将里面的arp配置信息保存到neigh->parms中。

当设备驱动没有提供填充L2帧头的函数,也就是说设备不需要L2帧头,因此邻居项的状态应该被设为NUD_NOARP,此外,neigh_ops应该被初始化为arp_direct_ops,该结构中的所有函数都被初始化为dev_queue_xmit。此时,设备不需要邻居协议。

当设备需要邻居协议时,内核根据设备驱动的性能初始化neigh->ops,如果驱动提供了管理L2帧头缓存的函数(dev->hard_header_cache),就使用arp_hh_ops,否则,使用arp_generic_ops。

传输和接收ARP封包:

用于发送和接收ARP封包的函数分别是:

    arp_send:邻居子系统调用neigh_ops->solicit发出solicitation请求。在arp中,solicit函数是对arp_send的简单包裹。

    arp_rcv:由于arp是一个完整功能的协议,因而他要在arp_init中注册一个处理函数。

上图中arp_rcv和arp_send之间的虚线表明,某些情况下收到一个arp封包会导致发出至少一个其他arp封包:

    配置了网桥,收到arp封包的网桥可能不对其做任何处理,只是转发到网桥的其他接口。

    入口的arp封包是ARPOP_REQUEST类型,且邻居子系统根据配置决定作出应答。

arp_send函数介绍:

如图28-13b所示,arp_send函数分为两个部分,arp_create初始化arp封包,arp_xmit在Netfilter中设置钩子,然后调用dev_queue_xmit函数。这样分为两部分,可以让需要操作封包的程序在arp_create和arp_xmit之间插入用户定制的任务,比如bonding程序在封包中添加802.1Q标记。

arp中,用于生成solicitation请求的函数是arp_solicit,该函数的主要任务如下:

    选择要放到arp头中的源ip地址。

    更新生成的solicitation数目。

    使用arp_send传输请求。

处理入口ARP封包:

ARP封包可以从skb缓存区中得到,arp包头是skb->nh.arph。arp_rcv函数的首要任务是确保收到的arp封包不是成碎片的(这种情况与ip包碎片无关,第二章有介绍)。当收到封包的设备没有使用arp协议或者封包的目的地址不是收到封包的接口,封包就会被丢弃。一旦arp封包以及准备进行处理,arp_proccess函数就会负责处理。apr_process只处理ARPOP_REQUEST,ARPOP_REPLY类型的封包,其他类型的会被丢弃。arp_process函数结构如下图:

被动学习和优化arp:

在一个ARP事物结束时,请求方主动学到了其请求的L3地址到L2地址的映射,应答方被动学到了发送方的L3地址到L2地址的映射。被动学习由neigh_event_ns负责。该函数会检测是否有一个邻居项和请求方关联,有则跟新,无则创建。当无法创建新邻居项时(比如内存不够了),就不会发送应答给发送者。

零地址请求:

当一个ARP请求的源ip地址被设为0.0.0.0时,它就可能是一个被破坏的封包,但是也可能是dhcp用于检测重复地址的特殊封包。

ARP代理:

前面主要讲了arp_process如何处理对本地地址的请求,接下来会介绍arp_process如何处理对远端地址的请求(代理)。

ARP中增加了一个执行代理的条件:目的网络地址转换(DNAT)。

代理服务器能够处理的ARPOP_REQUEST请求必须满足下列条件:

    在接收封包的设备或代理服务器上的所有设备启动了转发功能。

    目标IP地址是单播地址(因为其他类型的地址不需要被解析)。

    收到这个请求的设备不是到达目的IP地址所在的设备。(不然的话,目的主机自己会应答,不需要代理)。

如果上述条件都满足,代理程序就要检查基于设备代理和基于目的地址代理的配置信息。

下面的条件决定了代理服务器是否对请求作出响应:

    在某个设备上或所有设备上都启动了ARP代理。

    Medium ID一节提到的,入口接口和出口接口不是同一个介质。

    代理过的地址数据库中有目的地址。

目的NAT(DNAT):

目的NAT也称为路由NAT,允许主机定义伪地址,主机检查到目的地址是这些伪地址的入口封包后,将其转发到另一些地址。主要是路由器使用DNAT。

上图中,路由器配置了一个伪NAT地址10.0.0.5,当路由器收到目的地址是10.0.0.5的流量时,它都将封包的目的地址改为10.0.1.10,然后转发到该地址对应的主机。这一切都是由arp代理实现的。

ARP代理服务器作为路由器:

IPv4下路由器通常就是处理ARP代理的主机,IPv6下只允许使用路由器。但是,代理和路由器还是有区别的:ARP代理服务器对其服务对象来说通常是透明的,而路由器不是这样,每个主机需要明确配置来使用路由器,大多数情况下,代理服务器担当着透明路由器的角色,其服务的主机位于不同的LAN,但属于相同的子网。

图a,两个子网掩码都是/25的子网通过路由器进行通信,图b给出了同样的拓扑,通过代理连接两个子网,但是子网掩码改为/24。图c给出的是两个子网中的主机用图b配置后的等价拓扑图。

路由器将子网分为多个LAN,ARP代理服务器将不同的LAN合为单一子网。

一台ARP代理服务器可以配置成一台透明的默认网关,也就是说管理员可以让LAN中的主机使用ARP代理服务器待访问默认路由,而不必在每台主机上配置一条默认路由。虽然将ARP代理服务器作为路由器使用可以简化主机和子网配置,而且这些主机可以使用一个轻量级的tcp/ip栈,因为没有路由功能,但是,会有大量的solicitation包道中网络负载和代理服务器的cpu占用非常高。

一个例子:

对于上图,先做如下假设:

    所有主机都使用Ethernet接口卡。

    LAN1和LAN2中所有主机配置为相同的子网掩码。在它们的路由表中没有任何路由,也没有配置默认网关,也即是说,在LAN1和LAN2中的主机只能和同一个逻辑子网内的主机通信。

    所有邻居缓存都是空的。

    网桥功能都关闭了。这样排除了图26-10中右上角所示情况。

注意,尽管LAN1和LAN2都属于同一个逻辑子网,但是它们在物理上被隔离了,需要路由器的帮助才能通信。路由器合并的LAN的真实掩码应该是/25,如果RT不是一台ARP代理服务器,那么RT或者LAN1,LAN2中的主机都属于错误配置(这里排除了一些特殊功能,如桥接,这种说法就是正确的)。

下面分析一些arp请求的常见情况:

    从LAN1到LAN1(从主机D到主机E):由于D和E位于同一个LAN,当D发出solicitation请求时,虽然LAN1中所有主机都会收到请求,但只有E会响应。

    从LAN1到LAN1中的非法IP(从D到网络地址10.0.0.128):从D的角度看,这是一个合法IP,从RT角度看,这是一个网络地址,没有人会应答,不管RT是否代理。

    从LAN1到LAN2(从D到A):由于A位于另一个LAN,因此它不会收到请求并应答,但是,RT在eth0接口上配置了代理,因此eth0会有应答。当D发数据到A时,实际上是发到了RT,然后RT再转发到A。

    从LAN1到LAN3(从D到F):由于D和F不在同一个子网内,且再D中没有定义访问LAN3(10.0.1.0/24)的路由,于是内核的ip层会用一个消息应答,表明没有到达F的可用路由,主机D因此不会产生solicitation请求。

收到的事件:

ARP为了能够得到设备事件的通知,要向内核注册一个函数arp_netdev_event,它负责处理这些事件。ARP只对NETDEV_CHANGEADDR类型的事件感兴趣。当一个设备的L2地址变化时,内核就会产生该事件。

产生的事件:

在arp中,由arp_error_report负责产生事件,当一个arp事务失败时,arp子系统调用该函数,用来从路由表删除与不可到达的邻居项相关的缓存,以及通知发送方邻居不可达。

网路唤醒事件(Wake-on_LAN):

一些高级的NIC能提供网路唤醒的功能(WOL)。WOL通过发送一个特殊的帧来唤醒处于待机模式下的系统,在各种帧类型中,有唤醒功能的只有ARP帧,该功能是硬件级实现的,因为系统待机时,cpu上没有运行能处理封包的设备驱动。带有wol功能的NIC需要有自己的电源输入,以便能收到这些特殊的帧。

ARPD守护进程:

arpd是一个守护进程,可用将ARP负载(大量的邻居项)从内核转移到用户空间,虽然这样会导致速度稍慢,但比起邻居项大量消耗内核内存所带来的性能问题是可以接受的。

RARP协议目前已经 被bootp和DHCP替代,RARP也是有arp包,并使用相同的传输函数。Linux内核默认不包含rarp协议。

ND对ARP的改进:

    ND是icmpv6提供的一个函数,icmpv6的协议覆盖了icmpv4,arp和其他功能。

    ND使用多播solicitation而不是广播。使用多播的地址来源于要请求的目的地址,表示只有注册到给定ip多播地址的主机才接收相应的solicitation请求。

    

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值