⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 linux arp协议源码解析.txt

📁 ARP协议源码解析,初略概述ARP攻击原理和防范原理
💻 TXT
📖 第 1 页 / 共 2 页
字号:
   int (*output)(struct sk_buff *skb) = this->ops->connected_output;
   struct sk_buff_head arp_queue;
   struct timer_list timer;
   timer.function = neigh_timer_handler;
   timer.data = this;
   struct neigh_ops *ops =&myarp_hh_ops;
   u8 primary_key[0] =172.16.48.1(分配内存时,本身就加了4的);
   };
  
  
   在发送一个IP数据报时,当在确定数据报的输出路由时,需要为本次通讯绑定一个邻居,TCP/IP协议栈用结构体struct neighbour表示一个邻居。绑定邻居首先调用的函数是arp_bind_neighbour,该函数调用_neigh_lookup_errno函 数从ARP缓存表中以目的IP地址(网关IP或者同一子网内的对端IP地址)为哈希主键,寻找相应的邻居,如果找不到,则创建。
   首先是调用neigh_lookup函数从arp_tbl的成员hash_buckets中寻找,如果找到的邻居的成员dev等于当前的网络设备接口,且primary_key等于当前的目的IP地址,则即为需要的邻居,返回即可。
   如果找不到,则需要通过调用neigh_create函数创建一个新的邻居。下面看一下表示邻居的结构体struct neighbour的成员,以及创建时为这些成员初始化了什么样的值。
   parms。
   parms指向ARP缓存表arp_tbl的parms成员,包含了该邻居上的一些传输参数。
   dev。
   dev成员指向该邻居所在子网内的本机网络设备接口。
   type。
   该邻居的IP地址类型,这个值由FIB表根据邻居的IP地址来确定,比如类型RTN_UNICAST,RTN_LOCAL等。
   ops, output。
   ops是该邻居的一组操作函数集,包括输出函数output,直接输出函数dev_queue_xmit,错误报告函数 arp_error_report,arp解析函数solicit等。根据type的不同,操作函数集略有不同,这在arp_tbl的 constructor函数中确定。output即为ops中的普通输出函数output,因为其比较常用,所以单独为其设置一个成员。
   timer。
   这是邻居的定时器,用于解析ARP,其超时函数是neigh_timer_handler。
   arp_queue
   这是一个struct sk_buff的队列,协议栈在发送一个IP数据包时,如果还未进行arp解析,则先把该IP数据包放入arp_queue,然后进行ARP解析。
  
   下面重点看一下struct neighbour的成员nud_state,它是邻居的当前状态,邻居在创建,解析的过程中,其状态经历了一系例的变迁过程。
   当一个邻居刚刚被创建,其状态是NUD_NONE,此时,邻居被创建出来,并根据其IP地址被加入到arp_tbl的哈希表hash_buckets中, 但它还没有被解析,其成员ha(邻居的硬件地址)为空。直到向这个邻居发送IP数据报,并且发送流程到达网络层的最后一个发送函数 ip_finish_output2时,调用邻居的output成员函数,一般这个函数是neigh_resolve_output(根据type的不同 会略有不同)。它会先进行ARP解析,然后把IP数据报发往正确的邻居。
   因为当前状态是NUD_NONE,neigh_resolve_output首先判断parms的成员mcast_probes加上app_probes 的值,这两个参数表示ARP解析时尝试的次数,mcast_probes缺省值置为3,app_probes是0,如果它们的和为零,则不作ARP解析尝 试,直接将状态置为NUD_FAILED。当前不为零,所以进行解析,首先置邻居的成员probes为parms的ucast_probes,缺省为3, 这样,该邻居等于是已经尝试了3次了,另外还只能多3次(mcast_probes),即该邻居的解析最多尝试3次,如果3次不成功,则失败,结束。然后 将状态置为NUD_INCOMPLETE,表示正在解析中,并把待解析的socket缓冲skb放入arp_queue队列中,然后启动邻居的定时器开始 ARP解析。
   定时器处理函数neigh_timer_handler首先确定下次的超时时间(如果这次解析没有成功)为当前时间加上parms的成员 retrans_time的值(缺省设置为1秒)。所以,ARP解析的发包超时时间是1秒,连续尝试3次。ARP解析是通过ops的成员函数 solicit去做的,该成员函数指针指向的arp_solicit函数,arp_solicit会实际发送ARP请求包。
   如果arp_solicit发送ARP请求包,连续三次没有得到正确的回应,则把nud_state置为NUD_FAILED,调用ops的成员函数 error_report报告错误。并把skb从arp_queue队列中取出。error_report错误报告置dst的超时时间为当前时间,并删除 skb,关于dst,跟FIB相关,涉及到FIB时再详细分析。
   置为NUD_FAILED的邻居在下次进行常规垃圾回收时,被删除回收掉。 
   协议栈在发送一个IP数据报之前,需要发送ARP请求是为了解析IP数据报的目的IP地址对应的硬件地址。ARP协议可以承载多种硬件类型和多种协议,这里我们只关注以太网上的IP协议,那ARP请求的目的就是为了解析邻居的以太网MAC地址。
   ARP请求/应答数据报总共有28字节,其具体格式如下所示(在以太网,IP协议的情况下):
   字段含义:硬件类型 协议类型 硬件地址长度 协议地址长度 op
   字段长度:2 2 1 1 2
   字段含义:发送端以太网地址 发送端IP地址 目的以太网地址 目的IP地址
   字段长度:6 4 6 4
   硬件类型,对于10Mbps以太网,其取值是ARPHRD_ETHER(值为1),协议类型,对于IP协议来说,就是ETH_P_IP(0x0800), 在上述条件限定下,硬件地址长度为6,协议地址长度为4,op的值为ARPOP_REQUEST(值为1,表示是ARP请求,或者ARPOP_REPLY (值为2,表示是ARP应答)。
   发送端以太网地址填发送接口的MAC地址,发送端IP地址填源IP地址(关于源IP地址的选取下文会有详细分析),目的以太网地址,因为当前是ARP请 求,所以填入以太网广播地址0xFF:0xFF:0xFF:0xFF:0xFF:0xFF,目的IP地址填入IP数据报的目的地址。
   这里,发送端IP地址的选取是一个可配置的选项,文件/proc/sys/net/ipv4/conf/设备名/arp_announce记录的是它的配 置值,这是一个整型数值,取值范围为0-2,0表示只要待发送IP数据报的源地址为本地地址,就取该地址为发送端IP地址,这也是缺省配置;1表示若待发 送IP数据报的源地址为本地地址,并且该IP地址与目的IP地址在同一子网内,则取该地址为发送端IP地址,否则从所有的本地地址中取一个与目的IP地址 在同一子网内的地址;2表示完全忽略IP数据报中的地址,直接从所有本地输出网络接口中选取一个最为合适的发送端IP地址。配置级别越高,我们能够获得 ARP回应的机率也就越大。
   构建好的ARP请求数据报通过函数dev_queue_xmit发送出去。
  
   在分析接收和处理ARP的回应数据之前,先复习一下协议栈接收网络数据的一个基本流程,网卡驱动程序会创建一个skb,并填入接收到的数据,并把这个 skb传给协议栈的第一个接收函数netif_rx。softnet_data是一组全局变量,每个CPU拥有一个,它是一个结构体struct softnet_data,其定义如下:
   struct softnet_data
   {
   struct net_device *output_queue;
   struct sk_buff_head input_pkt_queue;
   struct list_head poll_list;
   struct sk_buff *completion_queue;
  
   struct net_device backlog_dev;
   };
   input_pkt_queue是一个接收队列,从网卡接收上来的skb一般会放在这里队列中待处理,这个队列的长度是有限制的, netdev_max_backlog就是该队列长度的上限,值为1000,如果队列中的skb数量已经达到1000,对于新收到的skb会直接丢弃。当 新接收到一个skb,首先检查input_pkt_queue队列,如果队列长度未太到上限,且队列中已经存在skb,则直接把新收到的skb放在队列 尾,返回成功。如果队列为空,则先调用函数netif_rx_schedule把接收工作启动起来,再把skb放入队列。
   netif_rx_schedule把softnet_data的成员backlog_dev连入poll_list,产生一个软中断 NET_RX_SOFTIRQ。随后该中断处理函数net_rx_action被执行,net_rx_action检查softnet_data的 poll_list队列,如果不空,则开始进行处理接收数据。
   net_rx_action的处理时间有一个限制,当该函数处理时间超过1个时钟滴嗒后,必须退出,重新产生一个NET_RX_SOFTIRQ,在下一个 中断处理中继续处理。同时,net_rx_action的处理数据报的数量也有一个限制,netdev_budget是一个全局变量,值为300, net_rx_action每处理完300个数据报,也必须退出,在下一个中断处理中继续处理。
   softnet_data的成员backlog_dev在系统初始化时,被置了一些值,大体如下:
   set_bit( __LINK_STATE_START, &queue->backlog_dev.state );
   queue->backlog_dev.weight = weight_p;
   queue->backlog_dev.poll = process_backlog;
   atomic_set( &queue->backlog_dev.refcnt, 1 );
   weight_p是一个全局变量,值为64,process_backlog是net_rx_action调用,用于实际处理接收数据报的函数,变量 queue->backlog_dev.quota在每次调用netif_rx_schedule时置为64(如果quota当前值小于0,则加上 64)。process_backlog负责把数据报从input_pkt_queue中取出来,传给函数netif_receive_skb,函数处理 时间不得超过一个时钟滴嗒,并且处理数据报数量存在限制,超过限制,返回-1,处理完所有数据报,返回0。
   netif_receive_skb对于每一个收到的skb,到已注册的协议类型中去匹配,先是匹配列表ptype_all,ptype_all中注册的 struct packet_type表示要接收处理所有协议的数据报,struct packet_type是表示网络层协议类型(也即以太网首部中的帧类型字段)的一个数据结构,其定义如下:
   struct packet_type {
   __be16 type; //协议类型
   struct net_device *dev; //指定的设备,为NULL表示接收所有设备上的数据报。
   int (*func) (struct sk_buff *,
   struct net_device *,
   struct packet_type *,
   struct net_device *); //接收处理函数
   void *af_packet_priv;
   struct list_head list; //注册到ptype_all和ptype_base中用。
   };
   对于匹配到的struct packet_type结构(dev为NULL,或者dev等于skb的dev),调用其func成员,把skb传递给它处理。
   匹配完ptype_all后,netif_receive_skb再匹配ptype_base数组中注册的协议类型,skb有一个成员protocol, 其值即为以太网首部中的帧类型,在ptype_base中匹配到协议相同,并且dev符合要求的,调用其func成员即可。
   arp模块在初始化时,就往ptype_base中注册了一个协议类型ETH_P_ARP,其接收处理函数为arp_rcv,所以ARP应答最终到达函数arp_rcv。
   arp_rcv首先检查skb的长度是否到达一个ARP应答包的基本长度要求,再检查应答包中的硬件地址长度字段值是否跟设备的硬件地址长度一致,再检查数据报是否是本地接收,协议地址长度是否为4。检查无误后,克隆一个skb,交由arp_process处理。
   arp_process中处理所有收到的ARP数据报,这次,我们只关注收到的ARP回应包。ARP回应包中的发送端IP地址即为邻居的IP地址,我们以 这个IP地址为主键到ARP缓存arp_tbl中去寻找这个邻居节点(hash_buckets成员),因为在发送ARP请求之前,进行邻居绑定的时候, 我们已经建立了一个未被解析的邻居,所以这次寻找肯定是成功的,如果没有找到,则直接放弃,不作处理。
   找到了这个邻居,我们就要用新的ARP回应包的内容刷新这个邻居的内容,但为了防止邻居被过度频繁刷新,引起抖动,只有距邻居上次刷新时间超过parms ->locktime(缺省置为1秒)后才允许再次刷新,这是一个可配置的项,可通过修改文件/proc/sys/net/ipv4/neigh/ 设备名/locktime进行修改。
   实际的刷新工作在neigh_update函数中完成,新的邻居状态为NUD_REACHABLE,该函数中首先更新邻居的成员数据confirmed和 updated为当前时间,然后删除邻居的定时器(定时器正在等待下一次重发ARP请求),把定时器修改到距当前时间parms-> reachable_time(初始设置为30秒,实际值在15-45秒之间随机变动)。并把新的MAC地址复制到邻居的成员ha中。
   邻居的成员hh是一个硬件头缓存,它是一个结构体struct hh_cache,其定义如下:
   struct hh_cache
   {
   struct hh_cache *hh_next;
   atomic_t hh_refcnt;
   unsigned short hh_type;
   int hh_len;
   int (*hh_output)(struct sk_buff *skb);
   rwlock_t hh_lock;
  
  #define HH_DATA_MOD 16
  #define HH_DATA_OFF(__len) \
   (HH_DATA_MOD - (((__len - 1) & (HH_DATA_MOD - 1)) + 1))
  #define HH_DATA_ALIGN(__len) \
   (((__len)+(HH_DATA_MOD-1))&~(HH_DATA_MOD - 1))
   unsigned long hh_data[HH_DATA_ALIGN(LL_MAX_HEADER) / sizeof(long)];
   };
   邻居的成员hh是一个链表,每种协议对应一个节点,协议类型记录在hh_type中,我们现在只处理IP协议,所以这个链表中总是只有一项, hh_data是缓存的硬件头(对于以太网来说,就是以太网头),hh_output是输出函数,有了hh,下次再发送数据报,就不需要重新构建以太网头 了。当ARP解析完成后,需要更新hh缓冲。
   邻居的成员output用于输出IP数据报,目前它指向neigh_resolve_output,这里,需要把它改为 neigh_connected_output,即下次发送数据报,不再需要解析邻居的MAC地址了。最后把邻居的arp_queue队列上等待发送的 skb全部通过neigh_connect_output发送出去。到这里,ARP解析算是全部完成了。
   前面还留有一个问题,就是邻居的定时器被再次启动了,此时邻居是处于NUD_REACHABLE状态的,下面再来看邻居定时器的超时处理函数 neigh_timer_handler,处于NUD_REACHABLE状态的邻居,如果距上次被证实(收到邻居的ARP回应包,确认这个邻居还是有效 的)时间超过了parms->reachable_time,且距上次被使用时间不足parms->delay_probe_time(缺省 设置为5秒),则邻居进入NUD_DELAY(延迟)状态,如果距上次使用时间也超过了5秒,则进入NUD_STALE(过期)状态,进入延迟和过期状态 的邻居,其成员output重新指向neigh_resolve_output。
   进入延迟状态的邻居,在下一个邻居超时处理中,如果发现邻居最近又被证实过了,且距上次证实时间不足5秒,则恢复为NUD_REACHABLE状态,否则 进入NUD_PROBE(探测)状态,NUD_PROBE最多允许有3次尝试机会(ucast_probes)。具体流程同NUD_INCOMPLETE 状态。而进入NUD_STALE状态的邻居,只有下次在向它发送数据报时,才会恢复到NUD_DELAY状态。
   当主机收到一个来自邻居的ARP请求数据报时,也会去搜索arp_tbl缓存,找不到则创建一个新的邻居。然后把来自请求数据报的邻居的mac地址填入, 邻居被直接置成NUD_STALE(过期)状态。如果未被使用,则在60秒(parms->gc_staletime)后被定期回收的定时器处理函 数回收掉。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -