1,之前在解bug的时候碰到了arping命令,就简单记录一下。它就是在同一个网段下扫描某个ip,通过广播的方式发送arp请求包,然后收到绑定了该ip的网卡的响应,从而得到该ip的mac地址。但是反过来,该命令不能通过mac地址知道它绑定的ip,那我们可以通过向本网段下所有主机发起arp请求,然后看收到响应的消息的mac地址是否是我们需要的mac地址,如果是,那么这个ip就是该网卡绑定的ip了。linux上的arping命令是系统自带的,centos7的arping是iputils工具包下的一个命令,但centos和ubuntu系列自带的arping命令是不同的,centos7的arping没有ubuntu的arping好用,因为功能比较少,比如centos7不能通过某个mac地址获取它的ip地址。
2,在linux网络体系里,arp在内核协议层的网络层,它是内核支持的协议,所以我们自己创建一个arp包,发一个arp请求包,遇到合适的机器它就会给我们返回arp响应包。我们可以回顾一下linux网络的一些重要流程,linux启动的时候会加载和初始化网络设备netdev,当接收队列的设备接收到有数据帧过来了,它就分配一个Socket Buffer,并建立响应的sk_buff结构体指针来指向这个数据帧,设备发出硬件中断信号irq,cpu就根据设备注册的中断处理例程去处理,于是cpu暂停工作去处理这些数据帧,此时cpu关闭硬件中断,这些数据帧就会从设备的数据链路层拷贝到内核空间,期间cpu会不断地使用poll来轮询是否有其他的数据,并等到数据到齐再一起处理,这些都是backlog积压,这个过程和网卡驱动这些有关,做完这些后cpu开启了硬件中断,现在数据帧就到内核空间,此时内核开始调度软件中断,进入到cpu队列的poll_list输入队列,使用net_rx_action()开始处理,它一次只能取出64帧数据帧处理,如果它在指定的时间片内处理不完,就再次标记软中断,下次继续处理,net_rx_action()的process_backlog()可以探测这个帧的协议,因为二层网络也有一些协议,但我们一般只使用以太网协议,它初始化sk_buff的mac,网络层,传输层的指针指向,丢弃或把sk_buff可以推给上层处理。硬件中断的时间很短,因为它只做很关键的操作。
这时候内核协议层开始处理这些数据,协议每处理一层,数据包就丢弃一个协议头,最后数据从内核拷贝到用户空间,到达应用程序。在网络数据帧的接收处理过程中,硬件中断服务程序只完成最紧要的任务,即将数据从硬件缓冲区复制到内核地址空间,以防止数据丢失。而对数据帧的处理,如发送至上层协议实例,就由网络子系统的软件中断处理程序在CPU不忙时来完成。我们构建的arp请求数据包就在内核协议层的网络层被arp协议所处理,为什么会被arp协议所处理?数据链路层的处理程序解析数据链路层的协议头(MAC头)信息,据此判断该数据帧(去掉MAC协议头后)应传给网络层的哪个协议处理程序。
一个数据包可以同时被多个协议所处理,arp_init()方法在网络协议栈初始化时被注册到内核,linux就可以处理arp请求,因为在这个数据包的协议头和协议选项就这样说明了。可以使用伯克利包过滤器bpf这些工具检测到这个数据包,netmap等工具可以拦截这个数据包。到了网络层和传输层,我们就比较熟悉了。
3,在以太网(Ethernet)协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的 MAC 地址。而在 TCP/IP 协议中,网络层和传输层只关心目标主机的IP地址。这就导致在以太网中使用 IP 协议时,数据链路层的以太网协议接到上层IP协议提供的数据中,只包含目的主机的IP地址。于是需要一种方法,根据目的主机的IP地址,获得其MAC地址。这就是 ARP 协议要做的事情。所谓地址解析(address resolution)就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。另外,当发送主机和目的主机不在同一个局域网中时,即便知道对方的MAC地址,两者也不能直接通信,必须经过路由转发才可以。所以此时,发送主机通过ARP协议获得的将不是目的主机的真实MAC地址,而是一台可以通往局域网外的路由器的MAC地址。于是此后发送主机发往目的主机的所有帧,都将发往该路由器,通过它向外发送。这种情况称为委托ARP或ARP代理ARP Proxy。
arp原理:用udp实现的arp协议。arp是使用udp工作的,所以不需要建立连接,即没有三次握手,不需要客户端执行connect(),服务端不需要执行listen()和accept(),arping的实现代码也遵循了这样的顺序,socket(),bind(),sendto(),recvfrom(),close()这些方法都是linux系统自带的。与之相反的rarp协议工作在数据链路层 。arping算是一个常用的命令了,更常用的一个命令是ping命令,它是udp+icmp,它实现了icmp协议,也是工作在ip层,它可以跨路由寻找指定ip是否存在。
一个简单的arping命令: arping -c 1 -D -I eth0 100.66.66.66。意思是检查本地网络里是否有100.66.66.66这个ip地址,如果收到一个响应信息,这个响应信息里包含了绑定了100.66.66.66这个ip的网卡mac地址,那就说明有这个ip。arping的选项和参数如下:
3,arping命令的代码在 iputils/arping.c at master · iputils/iputils · GitHubhttps://github.com/iputils/iputils/blob/master/arping.c ,我们可以使用arping -h 看到它的用法,这个帮助手册在该代码中的usage()方法中,只有几百行代码,我也看不太懂,只能讲讲大概的流程。-D部分代码暂时不管。
在该文件的main方法中,首先根据getopt()获取我们传入的选项和参数进行初始化赋值,选项就是上面那张图的选项,然后校验参数变量有没有问题,我们要检测的ip是必传的,它会通过inet_aton()转换为一个无符号整形的数字。
然后guess_device()方法会检查我们是否指定了发送arping命令的网卡名称,然后使用netlink的方式和内核通信,查询路由表的信息来设置设备的名称,netlink是一种特殊的socket,socket作为传输层和应用层的接口使用,它可以通过socket在用户地址空间和内核空间传递消息,通过sendmsg()和recvmsg()发收消息,struct msghdr就是那个消息,在这个消息内部再填充一个struct nlmsghdr的消息,设置struct nlmsghdr的消息类型为RTM_GETROUTE即可。
之后的check_device()方法遍历本机的所有网卡并检查它们是否符合发包要求,比如它用位运算检查网卡不能是down的,网卡的标志位不能是IFF_NOARP或IFF_LOOPBACK的,然后就使用socket()方法创建一个套接字,用setsockopt()方法设置一些socket的参数配置,用bind()方法把套接字绑定到本机ip和端口。
做好了这些准备工作后就进入event_loop()方法,先设置一些进程可响应的中断,设置定时器,超时事件监听,并使用poll()轮询他们,
poll的作用就是:轮询自己的设备,在数据链路层将网络输入数据帧复制到内核地址空间的Socket Buffer,再放入CPU输入队列。
最后它进入一个for循环,在这个for循环里,它可以读取信号文件响应中断退出,可以读取定时器文件超时退出,但主要功能是不停地用send_pack()方法创建和发送arp请求数据包,它按照apr协议的报文格式填充了struct arphdr,然后广播发包,未知的广播地址是00.00.00.00.00.00,也可以是ff:ff:ff:ff:ff:ff,放在以太网帧里。send_pack()这个方法里面的sendto()方法是实际发送数据包的。linux系统里的所有实体都是用struct数据结构来表示,struct和struct之间是有层次依赖关系的,这个实体的行为和功能就用函数指针来表示,一个协议报文对应一个大的结构体,我们按照这个已定义的结构体的属性名称自上而下地填充我们要发送的数据,就算实现了这个协议。下面是arp协议的报文格式。
//定义整个arp报文包,总长度42字节 typedef struct arpPacket { EHHDR ehhdr; ARPHDR arphdr; } ARPPACKET, *PARPPACKET;
//定义以太网arp字段 typedef struct arphdr { //arp首部 unsigned short arp_hrd; /* format of hardware address */ unsigned short arp_pro; /* format of protocol address */ unsigned char arp_hln; /* length of hardware address */ unsigned char arp_pln; /* length of protocol address */ unsigned short arp_op; /* ARP/RARP operation */ //这是以太网的,不同的网络介质可能不同 unsigned char arp_sha[6]; /* sender hardware address */ unsigned long arp_spa; /* sender protocol address */ unsigned char arp_tha[6]; /* target hardware address */ unsigned long arp_tpa; /* target protocol address */ }ARPHDR, *PARPHDR;
- 硬件类型:指明了发送方想知道的硬件接口类型,以太网的值为 1。
- 协议类型:表示要映射的协议地址类型。它的值为 0x0800,表示 IP 地址。
- 硬件地址长度和协议长度:分别指出硬件地址和协议的长度,以字节为单位。对于以太网上 IP 地址的ARP请求或应答来说,它们的值分别为 6 和 4。
- 操作类型:用来表示这个报文的类型,ARP 请求为 1,ARP 响应为 2,RARP 请求为 3,RARP 响应为 4。
- 发送方 MAC 地址:发送方设备的硬件地址。
- 发送方 IP 地址:发送方设备的 IP 地址。
- 目标 MAC 地址:接收方设备的硬件地址。
- 目标 IP 地址:接收方设备的IP地址。
它使用recvfrom()从套接字文件里读取数据包,并使用recv_pack()方法解析这个数据包,在这个方法里,它会根据一些标志来过滤包,然后按照协议的格式来解析我们需要参数,
比如下面这样就获取到了源ip和目的ip,在finish()方法中打印出来,这样我们就能看到了我们希望的输出结果了。
然后就退出循环。后面就是一些释放资源的操作。
4,我们也可以在本地抓包验证arp协议
输入arp && eth.src eq fa:c6:af:3d:45:00做请求包过滤
5,linux上有个工具arp-scan,它是用于渗透测试的arp扫描工具,它需要知道自己从哪个网卡发起请求,否则就默认是从eth0开始发起请求,它需要知道自己的ip和子网掩码是多少,知道这些后就知道了网络段,它就可以像网络段内的所有主机发起请求,获取这些主机的ip地址,mac地址,生产厂商信息。
ps:
netlink : linux用户空间与内核空间通信——Netlink通信机制【转】_mob604756f1200e的技术博客_51CTO博客
linux netlink通信机制 - zhangwju - 博客园
arp初始化: