精华内容
下载资源
问答
  • Linux下C语言操作静态ARP表,包括增加、查询和删除,完整源代码,直接gcc可以编译,可运行测试。
  • Linux ARP缓存

    千次阅读 2008-02-24 16:39:00
    协议栈通过ARP协议获取到的网络上邻居主机的IP地址与MAC地址的对应关 系都会保存在这个中,以备下次与邻居通讯时使用,同时,ARP模块自身也会提供一套相应的机制来更新和维护这个邻居。下面逐个分析arp_tbl中的 ...
      arp_tbl是一个类型为struct neigh_table的全局变量,它是一个ARP的缓存表,也称为邻居表。协议栈通过ARP协议获取到的网络上邻居主机的IP地址与MAC地址的对应关 系都会保存在这个表中,以备下次与邻居通讯时使用,同时,ARP模块自身也会提供一套相应的机制来更新和维护这个邻居表。下面逐个分析arp_tbl中的 重要成员数据与函数。
        entry_size,key_len,kmem_cachep。
        entry_size是一个入口的大小,也就是arp_tbl中一个邻居的大小,邻居用struct neighbour结构体表示,该结构体的最后一个成员是u8 primary_key[0],用于存放IP地址,作为这个邻居的哈希主键。所以entry_size的大小就是sizeof(struct neighbour) + 4,因为是用IP地址作主键,所以key_len就是4。kmem_cachep是一个后备高速缓存,创建一个邻居需要的内存从这个后备高速缓存中去取。
        hash_buckets,hash_mask,entries,hash。
        hash_buckets是一个哈希数组,里面存放了arp_tbl当前维护的所有的邻居,hash_mask是哈希数组大小的掩码,其初始值为1,所以 hash_buckets的初始大小为2(0到hash_mask的空间范围)。entries是整个arp_tbl中邻居的数量,当entries大于 hash_mask+1的时候,hash_buckets增长为原来的两部。成员hash是一个哈希函数指针,用于计算哈希值。
        phash_buckets,PNEIGH_HASHMASK。
        这是用于代理ARP的邻居哈希表,PNEIGH_HASHMASK固定为0xF,所以phash_buckets固定有16项,其它与hash_buckets相同。
        id。
        id作为这个邻居表的一个名称,是一个字符串信息,内核协议栈的arp_tbl的id是arp_cache。
        gc_interval,gc_thresh1,gc_thresh2,gc_thresh3。
        gc_thresh3是arp_tbl中允许拥有的邻居数量的上限,一旦超过这个上限,并且表中没有可以清理掉的垃圾邻居,那么就无法创建新的邻居,这个 值缺省被置为1024。gc_thresh2是第二个阀值,如果表中的邻居数量超过这个阀值,并且在需要创建新的邻居时,发现已经超过5秒时间表没有被刷 新过,则必须立即刷新arp_tbl表,进行强制垃圾回收,这个值缺省被置为512。gc_thresh1的用途暂时还没有发现,它缺省被置为128。 gc_interval应该是常规的垃圾回收间隔时间,被缺省置为30秒,但目前在源代码中似乎没有看到它的应用。强制垃圾收集的工作即是把引用计数为 1,且状态中没有NUD_PERMANENT的邻居全部从arp_tbl表中删除。
        gc_timer。
        这是一个常规垃圾回收的定时器,其定时处理函数是neigh_periodic_timer。该定时器超时后,处理函数处理hash_buckets表中 的一项,下次超时后,再处理下一项,这里的垃圾回收比强制垃圾回收条件要宽松得多,如果邻居的状态为NUD_PERMANENT或 NUD_IN_TIMER(该邻居正在解析中),则不能回收。当邻居的引用计数为1时,并且邻居状态为NUD_FAILED(解析失败)或者该邻居距最近 一次被使用时间已超过参数表中gc_staletime的值(缺省为60秒),则可以作为垃圾回收。回收完毕后,要设置下一次进行回收的时间 (gc_timer的超时时间),下次回收时间为参数表中base_reachable_time的值(缺省设为30秒)的一半,再除以 hash_buckets哈希表中的项数。也就是,基本上15秒左右会把整个arp_tbl缓存表进行一次垃圾回收。
        proxy_timer,proxy_queue,proxy_redo。
        proxy_timer是一个关于代理ARP的定时器,proxy_queue是一个待处理的代理ARP数据包的队列,每次定时器超时,处理函数 neigh_proxy_process依次检查队列中每一个代理ARP数据包(struct sk_buff),对于超时,且满足相关条件的,调用proxy_redo进行处理。有关代理ARP,将专门分析讲述,这里暂时略过。
        constructor。
        这是一个邻居的初始化函数指针,每次创建出一个邻居后,需要马上调用这个函数对新创建的邻居进行一些初始化操作。邻居创建完,已经被赋于一个IP地址(邻 居结构体的primary_key成员),该函数首先根据这个IP地址来确定其地址类型,然后为邻居选择相应的操作函数集(初始化邻居结构体的一些成员, 在讲到邻居结构体内容时再进行分析)。
        pconstructor,pdestructor。
        这是代理ARP的邻居的构建和析构函数指针,在IPv4模块中,未提供这两个函数,所以它们的指针值为空。
        parms。
        这是一个结构体struct neigh_parms的链表,系统中每个网络设备接口对应链表中一个节点,表示该设备接口上的邻居的一些传输参数。同时,链表中还有一个缺省的项。
        last_rand,hash_rand
        这两个成员其实没有联系,hash_rand是用于邻居哈希表hash_buckets的一个随机数,last_rand用于记录一个时间,即上次为 parms链表中每个节点生成reachable_time的时间,reachable_time是需要被定时刷新的。
        stats。
        记录arp_tbl被操作次数的一些统计数据。 

        结构体struct neigh_table是一个哈希表,用于描述物理上互相连接的机器的信息。ARP缓存myarp_tbl就是这样的一个结构。在分析ARP相关的初始化之前,我们先来看一下这个结构体:
        truct neigh_table
        {
            struct neigh_table  *next;
            int         family;
            int         entry_size;
            int         key_len;
            __u32       (*hash)(const void *pkey, const struct net_device *);
            int         (*constructor)(struct neighbour *);
            int         (*pconstructor)(struct pneigh_entry *);
            void        (*pdestructor)(struct pneigh_entry *);
            void        (*proxy_redo)(struct sk_buff *skb);
            char        *id;
            struct neigh_parms  parms;
            /* HACK. gc_* shoul follow parms without a gap! */
            int         gc_interval;
            int         gc_thresh1;
            int         gc_thresh2;
            int         gc_thresh3;
            unsigned long       last_flush;
            struct timer_list   gc_timer;
            struct timer_list   proxy_timer;
            struct sk_buff_head proxy_queue;
            atomic_t            entries;
            rwlock_t            lock;
            unsigned long       last_rand;
            kmem_cache_t        *kmem_cachep;         struct neigh_statistics *stats;         struct neighbour    **hash_buckets;         unsigned int        hash_mask;
            __u32               hash_rnd;
            unsigned int        hash_chain_gc;
            struct pneigh_entry **phash_buckets;
    #ifdef CONFIG_PROC_FS
            struct proc_dir_entry   *pde;
    #endif
        };
        entry_size是一个入口的长度,一个入口代表一个neighbour的信息,hash_buckets即为存放所有邻居的一个哈希数组,每一项对 应一条neighbour链表。struct neighbour用于代表一个neighbour,包含了其信息,下面是其重要的一些成员:
        dev代表与这个邻居相连的网络设备;nud_state代表邻居的状态(未完成,无法访问,过时,失败);ha表示邻居的mac地址;hh是以太网包的 头部缓存;arp_queue是等待这个邻居的硬件地址的IP包队列;ops是对该neighbour节点操作的一套函数;primary_key是哈希 表的主键,一般为IP地址。
        key_len是哈希表主键的长度,一般IP地址长度为4。
        几个函数分别为哈希函数,构造和析构函数。
        parms是ARP缓存的一些参数,包括ARP包传输时间,重发时间,队列长度和代理队列长度等等。
        ARP缓存有一个回收机制(garbage collection),上面以gc_开头的参数用来设置回收的频率和阀值等等。
        stats是一些关于邻居的统计信息。
        ARP初始化的第一个步是初始化ARP缓存myarp_tbl,并把它加到全局链表neigh_tables的表头,其实,系统中所有的neigh_table都放在这个表中。
        ptype_base是一个有16项的哈希数组,每种协议包类型都注册在这个数组中。arp包,其类型是ETH_P_ARP,其接收函数是 myarp_rcv。有了这个注册信息,当设备上收到一个网络包(packet)的时候,会分配一个sk_buff(skb),将数据拷贝进这个缓冲区, 然后调用netif_rx把skb放入等待队列(input_pkt_queue)中,并且产生一个软中断。当系统处理这个软中断的时候,会调用 net_rx_action,它根据网络包的类型,调用相应的接收函数来处理。如果是ARP包,则调用myarp_rcv。


        ARP缓存myarp_tbl是用于描述物理上相互连接的机器的信息的一个哈希表,关于这个缓存,我们前面作过分析。现在我们来看看,当主机收到一个需要本地接收的ARP请求时,如何向ARP缓存中更新一个ARP信息。
        当网络设备收到一个ARP数据包后,最终会调用到协议栈中的myarp_process处理函数,这个函数的处理会涉及到路由表的查询和更新。但我们现在的my_inet模块还没有真正完成路由表的初始化,所以略过其中很多细节,只关注ARP缓存的更新与查询。
        myarp_process通过调用__neigh_lookup函数更新ARP缓存,下面是该函数的定义:
        static inline struct neighbour * __neigh_lookup(struct neigh_table *tbl,
                                                        const void *pkey,
                                                        struct net_device *dev,
                                                        int creat)
        参数tbl是ARP缓存哈希表,传入全局变量myarp_tbl。pkey是哈希主键,传入发送端ip地址,在我们的实验环境中,是通过 172.16.48.1向172.16.48.11发送icmp回显请求包来触发myarp_process的执行,所以,pkey就是ip地址 172.16.48.1。dev是接收到该数据包的网络接口。create表示在缓存中不存在该机器的信息时,是否需要创建一个,我们现在的目的是更新, 所以选择是,传入1。
        该函数首先调用neigh_lookup在ARP缓存中查找。neigh_lookup首先通过pkey,dev计算得到一个哈希值hash_val,再 找到myarp_tbl中的一个链表myarp_tbl->hash_buckets[hash_val],遍历该链表,如果能找到dev, pkey都相等的项,就是我们所要找的struct neighbour。在这里,还要更新myarp_tbl的成员stats中的一统计数据。
        显然,第一次收到ARP请求包,我们是找不到ARP缓存信息的,所以neigh_lookup返回NULL,__neigh_lookup判断 create值,如果不需要创建,就直接返回,如果需要,则调用neigh_create进行缓存信息的创建。
        neigh_create首先在内存中分配一个struct neighbour结构体。myarp_tbl的成员entries记录了该ARP缓存中已经存在了多少条缓存信息,如果数量超过了gc_thresh3 (1024),或者数量超过了gc_thresh2(512),并且距离上次缓存刷新时间还不到5秒,则需要先强制进行缓存垃圾回收,对于一些未使用的缓 存信息进行清理。如果清理后,缓存数量还是超过gc_thresh3,则无法再进行创建,出错返回。对于新创建的neighbour,先给赋一些缺省值。
        然后调用myarp_tbl的构造函数,对新创建的 neighbour进一步初始化。具体的初始化步骤不再详述,我们可以从最后创建出来的neighbour的内容看到一些东西。
        接下来,由于新增加了缓存项,需要对myarp_tbl的大小进行调整,如果有需要,需要扩大其容量。
        最后,把新创建的neighbour添加到链表示,
        新创建的neighbour的内容应该是这样子的:
        struct neighbour
        {
            struct neighbour    *next       =原来的链表头;
            struct neigh_table  *tbl        =myarp_tbl;
            struct neigh_parms  *parms      =in_dev->arp_parms;
            struct net_device       *dev    =dev;
            unsigned long       used        =now;
            unsigned long       confirmed   =now - 60秒;
            unsigned long       updated     =now;
            __u8            flags;
            __u8            nud_state       =NUD_NONE;
            __u8            type            =RTN_LOCAL;
            __u8            dead            =1;
            atomic_t        probes;
            rwlock_t        lock;
            unsigned char       ha[(MAX_ADDR_LEN+sizeof(unsigned long)-1)&~(sizeof(unsigned long)-1)];
                                            //ha是mac地址,在后续的操作会给它赋上值。
            struct hh_cache     *hh         =NULL;
            atomic_t        refcnt          =1;
            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)后被定期回收的定时器处理函 数回收掉。

     
    展开全文
  • Linux-ARP请求C程序

    千次阅读 2017-05-23 17:23:36
    今天终于把在Linux上的ARP请求程序完成了。中间经历了好多坎坷。弄不出来搞的我十分紧张。终于在16:30分解决掉遇到的所有问题了。 下面简述下我的经历和存留的疑问。直接上代码,分析代码的问题。 **2017.06.04...
    2017.05.23学习ARP协议好几天了。今天终于把在Linux上的ARP请求程序完成了。中间经历了好多坎坷。弄不出来搞的我十分紧张。终于在16:30分解决掉遇到的所有问题了。
    下面简述下我的经历和存留的疑问。直接上代码,分析代码的问题。
    
    **2017.06.04这下又无法淡定了。同样的程序我就加了一行
    printf("mac=%02x:%02x:%02x:%02x:%02x:%02x\n", local_mac[0],local_mac[1], local_mac[2], local_mac[3],local_mac[4],local_mac[5]);
    然后sendto又出现了invalid arguements的报错信息。再次删除这句printf语句,程序又可以正常执行了。
    还请大神帮忙看看。小弟感激不尽。。**。
    
    **2017.06.21补充。sendto()报错的问题终于解决了。彻底解决了。现在加上我的笔记。
    arp发送和接受的socket有两种方法。不同的方法对应着不同的流程。
    /* 先填充arp包头, padding使用bzero或者memset清空一下 */
    1、sockfd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ARP) );
    根据man 2 socket中的内容,这种方法已经不推荐使用了,但是继续使用也是可以的。
    流程如下:
    
        struct sockaddr sa;
        bzero(&sa, sizeof(sa));
        sa.sa_family = AF_INET;         //指定地址族,要和socket()函数中使用的相同。
        strcpy(sa.sa_data, “eth0”);     //指定发送的接口。
        sendto(sockfd, &arp_pck, sizeof(arp_pck), 0,  &sa, sizeof(sa));
    2、或者
    
        sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
        struct sockaddr_ll sa_ll;
        bzero(&sa_ll, sizeof(sa_ll));   //之前忘记使用bzero清空,也会导致sendto报“invalid argument”错误。
        sa_ll.sll_family = PF_PACKET,
        sa_ll.sll_ifindex = if_nametoindex(“eth0”);
        sendto(sockfd, &arp_pck, sizeof(arp_pck), 0, (struct sockaddr*)&sa_ll, sizeof(sa_ll) );
    总结:sendto()报“invalid argument”带来的错误引出的思考和注意点。
    1、  socket()函数的使用要注意,尤其是原始套接字。
    2、  对于不同的socket()方法,使用的结构体不同。struct sockaddr和struct sockadd_ll。其数据成员的填充方式不同。
    3、  上面提到的结构体,定义之后一定要进行清内存操作。否则也会导致sendto传入的参数错误。
    **2017.06.21    
    链接:[http://pan.baidu.com/s/1pLFMjmN](http://pan.baidu.com/s/1pLFMjmN) 密码:yplq
    

    MAC.h获取指定网卡的IP地址和MAC地址

    #ifndef _MAC_H
    #define _MAC_H
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <linux/if.h>
    #include <sys/ioctl.h>
    #include <linux/if_packet.h>
    #include <linux/if_ether.h>
    
    
    extern int get_eth_MAC(char *eth_name, unsigned char *MAC);
    extern int get_eth_IP(char *eth_name, unsigned char *IP);
    extern int get_eth_broadaddr(char *eth_name, unsigned char *broadaddr);
    
    
    #endif
    

    MAC.c暂时能用,还有简写。(可以加入一个选项参数,选择获取ip或者MAC)

    #include "MAC.h"
    
    
    int get_eth_MAC(char *eth_name, unsigned char *MAC)
    {
        struct ifreq ifr;
        int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock_fd < 0)
        {
            perror("socket");
            return sock_fd;
        }
        strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );
    
        int ret_ioctl = ioctl(sock_fd, SIOCGIFHWADDR, &ifr);
        if(ret_ioctl < 0)
        {
            perror("ioctl");
            return ret_ioctl;
        }
    
        int i = 0;
            for(i = 0 ; i < 14; i++)
        {
            printf("%02x\t",(unsigned char)ifr.ifr_hwaddr.sa_data[i]);
        }
        printf("\n");
        memcpy(MAC, ifr.ifr_hwaddr.sa_data, 6);
        close(sock_fd);
        return 0;
    }
    
    int get_eth_IP(char *eth_name, unsigned char *IP)
    {
        struct ifreq ifr;
        int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock_fd < 0)
        {
            perror("socket");
            return sock_fd;
        }
        strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );
    
        int ret_ioctl = ioctl(sock_fd, SIOCGIFADDR, &ifr);
        if(ret_ioctl < 0)
        {
            perror("ioctl");
            return ret_ioctl;
        }
        int i = 0;
        for(i = 0; i < 14; i++)
        {
            printf("%d\t", (unsigned char)ifr.ifr_addr.sa_data[i]);
        }
        printf("\n");
        memcpy(IP, ifr.ifr_addr.sa_data+2, 4);
        close(sock_fd);
        return 0;
    }
    
    
    int get_eth_broadaddr(char *eth_name, unsigned char *broadaddr)
    {
    
        struct ifreq ifr;
        int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if(sock_fd < 0)
        {
            perror("socket");
            return sock_fd;
        }
        strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );
    
        int ret_ioctl = ioctl(sock_fd, SIOCGIFBRDADDR, &ifr);
        int i = 0;
        for(i = 0; i < 14; i++)
        {
            printf("%d\t", (unsigned char)ifr.ifr_broadaddr.sa_data[i]);
        }
        printf("\n");
        memcpy(broadaddr, ifr.ifr_broadaddr.sa_data+2, 4);
    
        close(sock_fd);
        return 0;
    }
    

    main.c实现ARP请求的代码

    #include "MAC.h"
    #include <errno.h>
    
    #pragma pack(1)
    typedef struct
    {
        //DLC HEADER
        unsigned char dlc_dst_mac[6];
        unsigned char dlc_src_mac[6];
        unsigned short dlc_frame;
    
        //ARP PACKET
        unsigned short arp_hwtype;
        unsigned short arp_protype;
        unsigned char   arp_hwlen;
        unsigned char   arp_prolen;
        unsigned short arp_op;
        unsigned char arp_src_mac[6];
        unsigned char arp_src_ip[4];
        unsigned char arp_dst_mac[6];
        unsigned char arp_dst_ip[4];
        unsigne char arp_padding[18];
    }arp_packet;
    
    
    int main()
    {
    
        printf("sizeof(arp_packet)=%d\n", sizeof(arp_packet));
        //fill ARP packet
        arp_packet arp_pck;
        unsigned char brd_mac[6] = {0xff,0xff,0xff,0xff,0xff,0xff};  //广播地址
        unsigned char dst_ip[4] = {192,168,1,110};                   //目标ip
        unsigned char local_mac[6] = {0};
        unsigned char local_ip[4] = {0};
        get_eth_MAC("eth0", local_mac);
        get_eth_IP("eth0", local_ip);
    
        //DLC HEADER填充
        memcpy(arp_pck.dlc_src_mac, local_mac, 6);
        memcpy(arp_pck.dlc_dst_mac, brd_mac, 6);
        arp_pck.dlc_frame = htons(ETH_P_ARP);
    
        //arp 包填充
        arp_pck.arp_hwtype = htons(0x0001);
        arp_pck.arp_protype = htons(ETH_P_IP);
        arp_pck.arp_hwlen  = 6;
        arp_pck.arp_prolen = 4;
        arp_pck.arp_opr = htons(0x0001);
        memcpy(arp_pck.arp_src_mac, local_mac, 6);
        memcpy(arp_pck.arp_src_ip, local_ip, 4);
        memcpy(arp_pck.arp_dst_mac, brd_mac, 6);
        memcpy(arp_pck.arp_dst_ip, dst_ip, 4);
    
        //struct sockaddr_ll
        struct sockaddr_ll eth_in;
        bzero(&eth_in, sizeof(eth_in);
        eth_in.sll_family = PF_PACKET;
        printf("index=%d\n", eth_in.sll_ifindex = if_nametoindex("eth0") );
    
        //raw_socket
        int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
        if(sockfd < 0)
            {
                perror("socket");
                return 0;
            }
        printf("sockfd=%d\n", sockfd);
        int ret = sendto(sockfd, &arp_pck, sizeof(arp_pck), 0, (struct sockaddr *)&eth_in, sizeof(eth_in) );
        if(ret < 0)
            {
                perror("sendto");
                printf("errno=%d\n", errno);
        }
    
        return 0;
    }

    接下来就说说为了让程序能够正确运行经历的心酸历程:
    1、关于sockaddr的困惑。
    通过ioctl()函数可以获取指定网卡的IP和MAC值。参见博客。Linux底层网络编程–ARP,PING等
    观察struct ifreq结构体代码片段:

    union
          {
            struct sockaddr ifru_addr;
            struct sockaddr ifru_dstaddr;
            struct sockaddr ifru_broadaddr;
            struct sockaddr ifru_netmask;
            struct sockaddr ifru_hwaddr;
            short int ifru_flags;
            int ifru_ivalue;
            int ifru_mtu;
            struct ifmap ifru_map;
            char ifru_slave[IFNAMSIZ];  /* Just fits the size */
            char ifru_newname[IFNAMSIZ];
            __caddr_t ifru_data;
          } ifr_ifru;
    # define ifr_name   ifr_ifrn.ifrn_name          /* interface name   */
    # define ifr_hwaddr ifr_ifru.ifru_hwaddr        /* MAC address      */
    # define ifr_addr   ifr_ifru.ifru_addr          /* address      */
    # define ifr_dstaddr    ifr_ifru.ifru_dstaddr   /* other end of p-p lnk */
    # define ifr_broadaddr  ifr_ifru.ifru_broadaddr /* broadcast address    */
    # define ifr_netmask    ifr_ifru.ifru_netmask   /* interface net mask   */
    # define ifr_flags  ifr_ifru.ifru_flags         /* flags        */
    # define ifr_metric ifr_ifru.ifru_ivalue        /* metric       */
    # define ifr_mtu    ifr_ifru.ifru_mtu           /* mtu          */
    # define ifr_map    ifr_ifru.ifru_map           /* device map       */
    # define ifr_slave  ifr_ifru.ifru_slave         /* slave device     */
    # define ifr_data   ifr_ifru.ifru_data          /* for use by interface */
    # define ifr_ifindex    ifr_ifru.ifru_ivalue    /* interface index      */
    # define ifr_bandwidth  ifr_ifru.ifru_ivalue    /* link bandwidth   */
    # define ifr_qlen   ifr_ifru.ifru_ivalue        /* queue length     */
    # define ifr_newname    ifr_ifru.ifru_newname   /* New name     */

    发现ifr_name,ifr_addr,ifr_hwaddr等都是sockaddr结构体类型的数据:

    #include <netinet/in.h>
    
    struct sockaddr
    {
        unsigned short    sa_family;    // 2 bytes address family, AF_xxx
        char              sa_data[14];  // 14 bytes of protocol address
    };
    
    // IPv4 AF_INET sockets:
    
    struct sockaddr_in
    {
        short            sin_family;     // 2 bytes e.g. AF_INET, AF_INET6
        unsigned short   sin_port;       // 2 bytes e.g. htons(3490)
        struct in_addr   sin_addr;       // 4 bytes see struct in_addr, below
        char             sin_zero[8];    // 8 bytes zero this if you want to
    };
    
    struct in_addr
    {
        unsigned long s_addr;            // 4 bytes load with inet_pton()
    };

    想IP地址,MAC,子网掩码等信息全都保存在sockaddr结构体的sa_data[14]成员中。这里我产生了疑问,这个sa_data[14]中ip、MAC、NETMASK是怎样的存储结构呢?所以在MAC.c代码中使用printf打印了sa_data[14]的信息。(要注意的是sa_data是char型数据,打印时类型强转成了unsigned char)。打印结果如下:
    这里写图片描述可以看到sa_data[0~5]存放的是MAC。sa_data[2~5]存放的是IP地址、广播地址。ioctl还有一些其他的REQUEST我没试。

    2、蛋疼的问题(还没搞清楚)
    编译时报错结构体成员中没有*成员。

    3、struct sockaddr_ll头文件未包含报错。头文件为”linux/if_packet.h”
    使用命令查找头文件grep -R “^struct sockaddr_ll” find / -name include -print
    4、原始套接字出错
    使用AF_INET时,错误。

    int sockfd = socket(AF_INET, SOCK_RAW, htons(ETH_P_ARP) );

    使用PF_PACKET或AF_PACKET都可以。实际上PF_PACKET和AF_INET数值相同,但是意义不同。PF_PACKET表示协议族,AF_PACKET表示地址族。

    int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
    //也可以使用int sockfd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ARP) );

    通过man socket可知要使用AF_PACKET:
    这里写图片描述

    5、sendto报错:invalid arguments,errno=22.
    这个问题真是找了我老长时间。使用sendto在操作原始套接字和UDP套接字时,要用指导对端的地址。
    这里写图片描述于是定位到struct sockaddr_ll结构体赋值语句。发现问题其实不在此处。参见博客(本人真的是感谢这位博主,如果不是您的播客,我遇到的问题不知道什么时候才能查出来)linux原始套接字(1)-arp请求与接收
    读过分析这篇博客的“ARP请求代码”发现博主直接使用数组来构造ARP包。42这个数字引起了我的注意。
    因为在博客【linux环境编程】 ARP编程这里写图片描述
    这里明确写了还要18个填充字节。

    char buf[42];

    我已开始觉得会不会是结构体arp_packet长度过长。用了sizeof()一看结果是60。我又以为会不会是编译器默认4字节对齐,还特意加了一句#pragma pack(1)。发现sizeof()结果还是60。我就不淡定了。然后自己一计算6+6+2 +2+2+1+1+2+6+4+6+4+6+4=60。我尝试者把arp_padding[18]注释掉。结果sendto不再报错,程序运行无误。wireshark抓包结果如下。虚拟机给主机发送ARP包。
    这里写图片描述

    作为一个网络编程菜鸟,还要太多主要积累。特写出此经历,大家共勉。谢谢阅读。
    2017.06.21添加的笔记内容解答了大部分我遇到的问题。

    展开全文
  • Linux日常——ARP

    2017-06-18 21:03:32
    地址解析协议,即ARP(Address Resolution Protocol)是根据IP地址获取物理地址的一个TCP/IP协议。 主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;...

    地址解析协议,即ARP(Address Resolution Protocol)

    是根据IP地址获取物理地址的一个TCP/IP协议。
    主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间,下次请求时直接查询ARP缓存以节约资源。
    通俗的说:
    在⽹络通讯时,源主机的应⽤程序知道⽬的主机的IP地址和端⼜号,却不知道⽬的主机的硬 件地址,⽽数据包⾸先是被⽹卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址 与本机不符,则直接丢弃。因此在通讯前必须获得⽬的主机的硬件地址。ARP协议就起到这 个作⽤。

    地址解析协议是建立在网络中各个主机互相信任的基础上的,网络上的主机可以自主发送ARP应答消息,其他主机收到应答报文时不会检测该报文的真实性就会将其记入本机ARP缓存;由此攻击者就可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。
    ARP命令可用于查询本机ARP缓存中IP地址和MAC地址的对应关系、添加或删除静态对应关系等。相关协议有RARP、代理ARP。NDP用于在IPv6中代替地址解析协议。

    源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多少”,并将这个请求 ⼴播到本地⽹段(以太⽹帧⾸部的硬件地址填FF:FF:FF:FF:FF:FF表⽰⼴播),⽬的主机接收到 ⼴播的ARP请求,发现其中的IP地址与本机相符,则发送⼀个ARP应答数据包给源主机,将⾃ ⼰的硬件地址填写在应答包中。 每台主机都维护⼀个ARP缓存表,可以⽤arp -a命令查看。缓存表中的表项有过期时间(⼀ 般为20分钟),如果20分钟内没有再次使⽤某个表项,则该表项失效,下次还要发ARP请求来获得 ⽬的主机的硬件地址。
    ARP数据报的格式如下所:
    这里写图片描述
    下面是 ARP脚本抓取主机MAC地址:

    #!/bin/bash
    index=1
    num=0
    while [ $index -le 254 ]
    do
       if [ $sum -eq 20 ]
       then
          sleep 1
          hum=0
          countinue
        fi
        ping -c 1 "196.168.**.$index" &
        let index++
        let num++
    done  
    
    展开全文
  • 如需调整其他ARP参数,只需对程序适当位置修改即可。 本程序着重在于功能实现的学习,故没有考虑像制作成工具一样的编写,尽量保证代码简洁。 程序基本分为三部分 构造以太网帧头部 构造ARP包内容 构造...

    #前言

    手动输入的参数有网卡名源IP目标IP,它们定义在程序开头。如需调整其他ARP参数,只需对程序适当位置修改即可。

    本程序着重在于功能实现的学习,故没有考虑像制作成工具一样的编写,尽量保证代码简洁。

    需要先熟悉ARP包中的各个字段再来编写程序。


    进一步改写为组包工具,可以自由设定源IP、目标IP以及源MAC:Linux ARP请求组包工具 C语言socket


    程序基本分为三部分

    构造以太网帧头部

    构造ARP包内容

    构造sockaddr_ll地址结构

    其中以太网帧头部和ARP请求内容(也就是网络中传输的一个ARP包完整内容)共同存储在一个buffer中,构造好buffer之后通过socket发送出去即可。

    #代码实现

    #include <stdio.h>      
    #include <stdlib.h>  
    #include <string.h>
    #include <unistd.h>
    
    #include <sys/socket.h>     
    #include <sys/ioctl.h>      
    #include <arpa/inet.h>      
    
    #include <linux/if.h>
    #include <linux/if_packet.h> 
    #include <linux/if_ether.h>     
    #include <linux/if_arp.h>       
    
    #define IPV4_LENGTH 4
    #define Dev "wlan0"     //网卡名
    #define buffer_len 60   //ARP请求包大小为60B,,抓包时会抓到一些42B的包,这是抓包软件没有显示18B的Padding字段,Padding全0填充在包的末尾
    unsigned char sender_ip[4] = {192,168,1,33};    //ARP请求的源IP
    unsigned char target_ip[4] = {192,168,1,1};     //ARP请求的目标IP
    /*ARP包结构*/
    /*字段顺序不可更改,发包时是直接将buffer发出*/
    struct arp_head
    {
        unsigned short hardware_type;   //硬件类型#1:Ethernet
    	unsigned short protocol_type;   //协议类型#0x0800:IPv4
        unsigned char hardware_size;    //MAC地址长度#6
        unsigned char protocol_size;    //IP地址长度#4
        unsigned short opcode;          //ARP类型#1:request;2:reply
        unsigned char sender_mac[ETH_ALEN];    //源MAC地址
        unsigned char sender_ip[IPV4_LENGTH];  //源IP地址
        unsigned char target_mac[ETH_ALEN];    //目标MAC地址
        unsigned char target_ip[IPV4_LENGTH];  //目标IP地址
    };
    
    int main()
    {
        //创建buffer
        unsigned char buffer[buffer_len];  
        memset(buffer, 0, buffer_len);
        //创建以太网头部指针,指向buffer
        struct ethhdr *eth_req = (struct ethhdr*)buffer;
        //创建ARP包指针,指向buffer的后46字节,因为以太网头包含:2*6B(MAC地址)+2B(协议地址)=14B
        struct arp_head *arp_req = (struct arp_head*)(buffer+14);
        //创建sockaddr_ll结构地址
        struct sockaddr_ll sock_addr;
        //创建socket
        /***int socket(int __domain, int __type, int __protocol)
         *** __domain
         * PF_PACKET指示为二层协议簇
         * 使用AF_PACKET也可,socket.h中有#define AF_PACKET PF_PACKET
         *** __type
         * 使用PF_PACKET的后,__type只能选择SOCK_RAW或者SOCK_DGRAM
         * 其中SOCK_RAW可以自己构造帧头,SOCK_DGRAM不行
         * 帧头使用sockaddr_ll结构体构建,这个结构体在if_packet.h中
         * 有一些资料这里选择的是SOCK_PACKET,这个类型目前已经被建议弃用
         *** __protocol
         * ETH_P_ARP意味着我们仅仅接受ARP类型
         * 如果是ETH_P_ALL就意味着我们接受所有类型帧
         * 更多选项参看if_ether.h中定义
         */
        int sock_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
        if(sock_fd == -1){
            perror("socket()");
            exit(-1);
        }
        /**获取网卡等需要的信息
         * ifreq结构体可以用于设置或者获取网卡等相关信息,定义在if.h中
         * 配合ioctl()一起使用
         * ioctl()的具体参数用法和系统实现相关,不是通用的,具体参见ioctls.h
         * 以下获取的信息都会保存在ifreq不同字段之中
         */ 
        struct ifreq ifr;
    
        /*根据网卡设备名获取Index*/
        strcpy(ifr.ifr_name, Dev);
        if(ioctl(sock_fd, SIOCGIFINDEX, &ifr) == -1)
        {
            perror("SIOCGIFINDEX");
            exit(-1);
        }
        int ifindex = ifr.ifr_ifindex;
        printf("网卡索引为:%d\n",ifindex);
    
        /*获取网卡设备MAC地址*/
        if(ioctl(sock_fd, SIOCGIFHWADDR, &ifr) == -1)
        {
            perror("SIOCGIFHWADDR");
            exit(-1);
        }
    
        /*将MAC地址写入所需结构*/
        for(int i=0;i<6;i++)
        {
            //以太网帧的目标MAC,即广播MAC,全1
            eth_req->h_dest[i] = (unsigned char)0xff;
            //ARP请求包目标MAC,全0
            arp_req->target_mac[i] = (unsigned char)0x00;
            //以太网帧源MAC,即本机MAC
            //ifr_hwaddr是sockaddr结构体格式
            eth_req->h_source[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
            //ARP请求包源MAC,即本机MAC
            arp_req->sender_mac[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
            //sockaddr中的MAC,也是本地MAC
            sock_addr.sll_addr[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
        }
        
        /*打印MAC地址*/
        printf("网卡MAC地址: %02X:%02X:%02X:%02X:%02X:%02X\n",
                eth_req->h_source[0],
                eth_req->h_source[1],
                eth_req->h_source[2],
                eth_req->h_source[3],
                eth_req->h_source[4],
                eth_req->h_source[5]);
    
        /*完善sockaddr_ll结构体*/
        sock_addr.sll_family = PF_PACKET;  
        sock_addr.sll_protocol = htons(ETH_P_ARP);
        sock_addr.sll_ifindex = ifindex;
        sock_addr.sll_hatype = htons(ARPHRD_ETHER);
        sock_addr.sll_halen = ETH_ALEN;
    
        /*完善以太网帧头*/
        eth_req->h_proto = htons(ETH_P_ARP);
    
        /*完善ARP包头*/
        arp_req->hardware_type = htons(0x01);
        arp_req->protocol_type = htons(ETH_P_IP);
        arp_req->hardware_size = ETH_ALEN;
        arp_req->protocol_size = IPV4_LENGTH;
        arp_req->opcode = htons(ARPOP_REQUEST);
        memcpy(arp_req->sender_ip,sender_ip,IPV4_LENGTH);
        memcpy(arp_req->target_ip,target_ip,IPV4_LENGTH);
    
        /*发送ARP请求*/
        if(sendto(sock_fd, buffer, 60, 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) == -1)
        {
            perror("sendto()");
            exit(-1);
        }
        printf("发送ARP请求包:");
        for(int i=0;i<60;i++)
        {
            if(i%16==0)
                printf("\n\t");
            printf("%02X ",buffer[i]);
        }
        close(sock_fd);
        return 0;
    }

    #运行结果

    #结果抓包

    192.168.1.1是网段路由器地址,192.168.1.33是虚构的一个地址。

    虽然192.168.1.33是虚构的主机地址,但MAC地址是本机真实的,所以192.168.1.1还是给我们回复了一个ARP。 

    展开全文
  • linux环境实现ARP欺骗

    千次阅读 2019-01-05 01:03:14
    根据ARP协议的工作原理,我们知道ARP大多时候都会发起广播请求,而处于同一局域网内的所有主机都可以收到某主机发出的ARP广播请求,利用这个工作原理我们可以接收到网络上与自己无关的ARP请求包,然后回复一个带有假...
  • arp_ignore定义了对目标地址为本机IP的ARP询问的不同应答模式。 arp_announce对网络接口(网卡)上发出的ARP请求包中的源IP地址作出相应的限制;主机会根据这个参数值的不同选择使用IP数据包的源IP或当前网络接口卡...
  • kali Linux 渗透测试 | ARP 欺骗

    千次阅读 2020-01-30 18:39:53
    目录 ARP 欺骗及其原理 ARP 欺骗实施步骤 必备工具安装 ...ARP 欺骗测试 ...ARP 断网攻击 ...ARP 欺骗(不断网) ...获取账号与密码 ...arp 缓存对照 ARP协议: 地址解析协议,即ARP(Address Resoluti...
  • 后来发现linux使用arping命令可以判断,如使用arping -D -f -w 1 x.x.x.x 但是对于经过裁剪的嵌入式linux,busybox中不一定还保留arping命令,而且C代码中调用shell命令需要临时创建一个子进程来执行,频繁操作会...
  • 最近朋友服务器每天经常遭遇arp病毒攻击导致网站无法正常运营,问我有没有好的解决策略,经过google了一番尝试了一下终于...话不多说具体操作方法如下:1./*找出ARP攻击的机器*/获取同一网段下所有机器MAC地址的办法 
  • LinuxARP——种种

    2019-06-20 11:04:00
    Linux ARP缓存配置和状态查看命令 查看Linux ARP缓存老化时间 cat /proc/sys/net/ipv4/neigh/eth0/base_reachable_time同目录下还有一个文件gc_stale_time,官方解释如下:Determines how often to check ...
  • linux 下C++实现 ARP发布,和ARP监听

    千次阅读 2017-08-15 21:19:54
    linux 下C++实现 ARP发布,和ARP监听
  • 路由表(RIB表、FIB表)、ARP表、MAC表整理

    千次阅读 热门讨论 2021-05-23 21:52:20
    ARP表 本文对这网络中的最关键的四个表项做一个详细介绍。 目录 1. 路由表(Routing Table)、转发表(Forwarding Table) 2.Mac表(Media Access Control Table) 2.1 地址学习线程: 2.2 报文转发线程: 3....
  • LINUX获取IP地址和MAC地址.程序相关结构体在程序后面。 打印网卡的ip地址 子网掩码 广播地址 mac地址 环境: [root@bogon temp]# uname -a Linux bogon 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 ...
  • ARP协议: 地址解析协议 对于以太网,数据链路层上是根据48bit的以太网地址来确定目的接口,设备驱动程序从不检查IP数据报中的目的IP地址。ARP协议为IP地址到对应的硬件地址之间提供动态映射。 在网络通讯时,...
  • Linux ARP协议源码解析

    千次阅读 2012-02-08 21:25:01
    协议栈通过ARP协议获取到的网络上邻居主机的IP地址与MAC地址的对应关 系都会保存在这个中,以备下次与邻居通讯时使用,同时,ARP模块自身也会提供一套相应的机制来更新和维护这个邻居。下面逐个分析arp_tbl中的...
  • linux环境编程】 ARP编程

    千次阅读 2014-11-04 10:28:33
    根据图一,我们可以看出arp,rarp和ip虽然同属于网络层(又名IP层),但是他们的数据包装是独立的。虽然icmp和igmp也处在IP层,但是它们又需要ip数据报的包装。所以我们在为arp和rarp建立socket的时候,就不能利用ip...
  • Linux编程获取网络信息总结

    千次阅读 2016-03-19 22:13:32
    Linux下C获取所有可用网卡信息 在Linux下开发网络程序时,经常会遇到需要取本地网络接口名、IP、广播地址 、子网掩码或者MAC地址等信息的需求,最常见的办法是配合宏SIOCGIFHWADDR、 SIOCGIFADDR、...
  • 保护Linux计算机再次遭受arpspoof攻击的实用程序 特征: 从arp扫描仪实用程序隐藏您的计算机(ip / MAC)。 列出您局域网中的所有活动主机。 切断任何活动主机与网关之间的连接。 使用wondershaper来控制您的...
  • [Linux网络编程]ARP简单实例

    千次阅读 2010-12-22 15:54:00
    一个简单的ARP实例。结构图示及代码讲解。
  • ARP获取硬件地址

    2020-08-19 19:50:46
      在进行网络通信时,源主机中的发送程序只知道目的主机的ip地址和端口号,却不知道目的主机的硬件地址,而数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址与本机不符,则直接丢弃。...
  • 转发表、ARP表与路由表的在网络数据包转发功能中发挥的作用,以及它们协同工作的原理,顺便也会接着之前的文章继续谈谈交换机和路由器的一些事儿。 网络分层协议 计算机网络是将地理上隔离的计算节点从物理上相连(双...
  • /*所获取的硬件地址复制到结构server_config的数组arp[6]参数中*/    memcpy(arp, ifr.ifr_hwaddr.sa_data, 6);   printf( "adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x \n " ,   ...
  • linux原始套接字-发送ARP报文

    千次阅读 2016-09-27 11:58:30
    下面的例子是向192.168.1.60的电脑,发送伪造的ARP报文,使其更新ARP表,导致无法PING通192.168.1.71。 使用命令arp -d 删除arp缓存即可恢复。 本示例仅供学习交流,请勿用于非法用途。   #include &lt;...
  • Ifreq结构用来配置ip地址、获取ip、获取MTU等等关于网卡的信息,Linux下可以使用ioctl()函数以及结构体struct ifreq来获取网卡的各种信息。  在Linux系统中获取IP地址通常都是通过ifconfig命令来实现的,然而...
  • Linux ARP缓存配置和状态查看命令

    千次阅读 2017-04-27 16:44:03
    转载自:... 查看Linux ARP缓存老化时间 cat /proc/sys/net/ipv4/neigh/eth0/base_reachable_time 同目录下还有一个文件gc_stale_time,官方解释如下: Determines how often to check for stale
  • linux获取MAC地址,IP地址

    千次阅读 2014-02-18 12:36:52
    另外,在已知MAC,IP地址之一,去获取另外一个的方法,可以参考ARP, RARP协议 。   获取以太网卡的MAC地址 转自:http://www.cnblogs.com/guoyilin/archive/2009/04/01/1426846.htm
  • 原理其实很简单,那就是广播一个arp包,然后recv,如果没有数据(这里要设置延时),那么说明...这里可以看下busybox的dhcp中的检测程序。  networking/udhcp/arpping.c  C代码  /* vi: set sw=4 t

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,658
精华内容 6,663
关键字:

linux程序获取arp表

linux 订阅