首页 > 系统相关 >Linux 网络编程——原始套接字实例:MAC 地址扫描器

Linux 网络编程——原始套接字实例:MAC 地址扫描器

时间:2022-09-28 21:41:55浏览次数:46  
标签:ARP 0x00 地址 MAC Linux msg 接字 recv


如果 A (192.168.1.1 )向 B (192.168.1.2 )发送一个数据包,那么需要的条件有 ip、port、使用的协议(TCP/UDP)之外还需要 MAC 地址,因为在以太网数据包中 MAC 地址是必须要有的。那么怎样才能知道对方的 MAC 地址?答案是:它通过 ARP 协议来获取对方的 MAC 地址。


ARP(Address Resolution Protocol,地址解析协议),是 TCP/IP 协议族中的一个,主要用于查询指定 ip 所对应的的 MAC(通过 ip 找 MAC)。

请求方使用广播来发送请求,应答方使用单播来回送数据。收到返回消息后将该 IP 地址和物理地址存入本机 ARP 缓存中并保留一定时间,下次请求时直接查询 ARP 缓存以节约资源。


以机器 A 获取机器 B 的 MAC 为例,A 广播发送一个 ARP 请求包,和 A 同在一个局域网的主机都会收到这个请求包,每个机器都会比较自己的 ip 和请求包的目的 ip 是不是一样的,如果不一样,就丢弃这个请求包,结果,只有 B 机器符合条件,B 机器单独给 A 发送 ARP 应答包,应答包带上了 B 的 ip 所对应的 MAC 地址,当 A 收到这个应答包后,就把 B 的 ip 以及其对应的 MAC 地址存入本机 ARP 缓存中。

Linux 网络编程——原始套接字实例:MAC 地址扫描器_Linux

在 Linux 查看 ARP 缓存表:arp

Linux 网络编程——原始套接字实例:MAC 地址扫描器_ip地址_02


在 Windows 查看 ARP 缓存表:arp -a

Linux 网络编程——原始套接字实例:MAC 地址扫描器_Linux_03


ARP头部

Linux 网络编程——原始套接字实例:MAC 地址扫描器_网络编程_04


Linux 网络编程——原始套接字实例:MAC 地址扫描器_Linux_05

1、Dest MAC:目的 MAC 地址
2、Src MAC:源 MAC 地址
3、帧类型:0x0806
4、硬件类型:1(以太网)
5、协议类型:0x0800(IP地址)
6、硬件地址长度:6
7、协议地址长度:4
8、OP:1(ARP请求),2(ARP应答),3(RARP请求),4(RARP应答)


接下来这个例子为,虚拟机(ubuntu)获取 PC 机的 MAC 地址:

先查看 ubuntu 的 ip 和 MAC 地址:

Linux 网络编程——原始套接字实例:MAC 地址扫描器_ip地址_06


完整代码如下:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h> //struct ifreq
#include <sys/ioctl.h> //ioctl、SIOCGIFADDR
#include <sys/socket.h>
#include <netinet/ether.h> //ETH_P_ALL
#include <netpacket/packet.h> //struct sockaddr_ll
#include <netinet/in.h>

int main(int argc,char *argv[])
{
//1.创建通信用的原始套接字
int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

//2. 根据各种协议首部格式构建发送数据报
unsigned char send_msg[1024] = {
//--------------组MAC--------14------
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //dst_mac: FF:FF:FF:FF:FF:FF
0x00, 0x0c, 0x29, 0x97, 0xc7,0xc1, //src_mac: 00:0c:29:97:c7:c1
0x08, 0x06, //类型:0x0806 ARP协议

//--------------组ARP--------28-----
0x00, 0x01, 0x08, 0x00, //硬件类型1(以太网地址),协议类型0x0800(IP)
0x06, 0x04, 0x00, 0x01, //硬件、协议地址分别是6、4,op:(1:arp请求,2:arp应答)
0x00, 0x0c, 0x29, 0x97, 0xc7,0xc1, //发送端的MAC地址
10, 221, 0, 11, //发送端的IP地址
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //目的MAC地址(由于要获取对方的MAC,所以目的MAC置零)
10, 221, 20, 10 //目的IP地址
};

//3.数据初始化
struct sockaddr_ll sll; //原始套接字地址结构
struct ifreq req; //网络接口地址
strncpy(req.ifr_name, "eth0", IFNAMSIZ); //指定网卡名称

//4.将网络接口赋值给原始套接字地址结构
ioctl(sock_raw_fd, SIOCGIFINDEX, &req);
bzero(&sll, sizeof(sll));
sll.sll_ifindex = req.ifr_ifindex;

//5. 发送 ARP 请求包
int len = sendto(sock_raw_fd, send_msg, 42, 0 , (struct sockaddr *)&sll, sizeof(sll));
if(len == -1)
{
perror("sendto");
}

//6.接收对方的ARP应答
unsigned char recv_msg[1024] = {0};
recvfrom(sock_raw_fd, recv_msg, sizeof(recv_msg), 0, NULL, NULL);
if(recv_msg[21] == 2) //ARP应答
{
char resp_mac[18] = ""; //arp响应的MAC
char resp_ip[16] = ""; //arp响应的IP

sprintf(resp_mac, "%02x:%02x:%02x:%02x:%02x:%02x", \
recv_msg[22],recv_msg[23],recv_msg[24],recv_msg[25],recv_msg[26],recv_msg[27]);
sprintf(resp_ip, "%d.%d.%d.%d", recv_msg[28], recv_msg[29], recv_msg[30], recv_msg[31]);
printf("IP:%s - MAC:%s\n",resp_ip, resp_mac);
}

return 0;
}

程序运行结果如下:

Linux 网络编程——原始套接字实例:MAC 地址扫描器_网络编程_07


查看 PC 的网卡信息:

Linux 网络编程——原始套接字实例:MAC 地址扫描器_ip地址_08


下面的例子能够获取指定网段所有机器的 MAC 地址:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h> //struct ifreq
#include <sys/ioctl.h> //ioctl、SIOCGIFADDR
#include <sys/socket.h>
#include <netinet/ether.h> //ETH_P_ALL
#include <netpacket/packet.h> //struct sockaddr_ll
#include <pthread.h>
#include <netinet/in.h>
void *send_arp_ask(void *arg);
int main(int argc,char *argv[])
{
//1.创建通信用的原始套接字
int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

//2.创建发送线程
pthread_t tid;
pthread_create(&tid, NULL, (void *)send_arp_ask, (void *)sock_raw_fd);

while(1)
{
//3.接收对方的ARP应答
unsigned char recv_msg[1024] = "";
recvfrom(sock_raw_fd, recv_msg, sizeof(recv_msg), 0, NULL, NULL);
if(recv_msg[21] == 2) //ARP应答
{
char resp_mac[18] = ""; //arp响应的MAC
char resp_ip[16] = ""; //arp响应的IP

sprintf(resp_mac, "%02x:%02x:%02x:%02x:%02x:%02x", \
recv_msg[22],recv_msg[23],recv_msg[24],recv_msg[25],recv_msg[26],recv_msg[27]);
sprintf(resp_ip, "%d.%d.%d.%d", recv_msg[28], recv_msg[29], recv_msg[30], recv_msg[31]);
printf("IP:%s - MAC:%s\n",resp_ip, resp_mac);
}
}

return 0;
}

void *send_arp_ask(void *arg)
{
int i = 0;
int sock_raw_fd = (int)arg;
//1.根据各种协议首部格式构建发送数据报
unsigned char send_msg[1024] = {
//--------------组MAC--------14------
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //dst_mac: FF:FF:FF:FF:FF:FF
0x00, 0x0c, 0x29, 0x75, 0xa6, 0x51, //src_mac: 00:0c:29:75:a6:51
0x08, 0x06, //类型:0x0806 ARP协议

//--------------组ARP--------28-----
0x00, 0x01, 0x08, 0x00, //硬件类型1(以太网地址),协议类型0x0800(IP)
0x06, 0x04, 0x00, 0x01, //硬件、协议地址分别是6、4,op:(1:arp请求,2:arp应答)
0x00, 0x0c, 0x29, 0x75, 0xa6, 0x51, //发送端的MAC地址
172, 20, 226, 12, //发送端的IP地址
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //目的MAC地址(由于要获取对方的MAC,所以目的MAC置零)
172, 20, 226, 11 //目的IP地址
};

//2.数据初始化
struct sockaddr_ll sll; //原始套接字地址结构
struct ifreq req; //网络接口地址
strncpy(req.ifr_name, "eth0", IFNAMSIZ); //指定网卡名称

//3.将网络接口赋值给原始套接字地址结构
ioctl(sock_raw_fd, SIOCGIFINDEX, &req);
bzero(&sll, sizeof(sll));
sll.sll_ifindex = req.ifr_ifindex;

//4.本地机的IP
if(!(ioctl(sock_raw_fd, SIOCGIFADDR, &req)))
{
int num = ntohl(((struct sockaddr_in*) (&req.ifr_addr))->sin_addr.s_addr);
for(i=0; i<4; i++)
{
send_msg[31-i] = num>>8*i & 0xff; //将发送端的IP地址组包
}
}

//5.获取本地机(eth0)的MAC
if (!(ioctl(sock_raw_fd, SIOCGIFHWADDR, (char *) &req)))
{
for(i=0; i<6; i++)
{
//将src_mac、发送端的MAC地址组包
send_msg[22+i] = send_msg[6+i] = (unsigned char) req.ifr_hwaddr.sa_data[i];
}
}

while(1)
{
int i = 0;
int num[4] = {0};
unsigned char input_buf[1024] = "";

//6.获取所要扫描的网段(172.20.226.0)
printf("input_dst_Network:172.20.226.0\n");
fgets(input_buf, sizeof(input_buf), stdin);
sscanf(input_buf, "%d.%d.%d.", &num[0], &num[1], &num[2]//目的IP地址
);

//7.将键盘输入的信息组包
for(i=0;i<4;i++)
send_msg[38+i] = num[i];//将目的IP地址组包

//8.给1~254的IP发送ARP请求
for(i=1; i<255; i++)
{
send_msg[41] = i;
int len = sendto(sock_raw_fd, send_msg, 42, 0 , (struct sockaddr *)&sll, sizeof(sll));
if(len == -1)
{
perror("sendto");
}
}
sleep(1);
}
return;
}


程序运行结果如下:

Linux 网络编程——原始套接字实例:MAC 地址扫描器_网络编程_09




标签:ARP,0x00,地址,MAC,Linux,msg,接字,recv
From: https://blog.51cto.com/u_3002289/5720947

相关文章

  • Linux 网络编程——IP 数据报格式详解
    IP数据报首部TCP/IP协议定义了一个在因特网上传输的包,称为IP数据报(IPDatagram)。这是一个与硬件无关的虚拟包,由首部和数据两部分组成。首部的前一部分是固定长度,共2......
  • Linux 网络编程—— libpcap 详解
    概述libpcap是一个网络数据包捕获函数库,功能非常强大,Linux下著名的tcpdump就是以它为基础的。libpcap主要的作用1)捕获各种数据包,列如:网络流量统计。2)过滤网络数据包,列如......
  • Linux 网络编程——TCP 和 UDP 数据报格式详解
    TCP报文格式TCP(TransmissionControlProtocol传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP报文段的报头有10个必需的字段和1个可选字段......
  • Linux网络编程——原始套接字实例:MAC 头部报文分析
    ​​通过《Linux网络编程——原始套接字编程》得知​​,我们可以通过原始套接字以及 recvfrom()可以获取链路层的数据包,那我们接收的链路层数据包到底长什么样的呢?MAC头......
  • Linux系统编程——线程同步与互斥:互斥锁
    为什么需要互斥锁?在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在......
  • Linux系统编程——进程间通信:消息队列
    概述消息队列提供了一种在两个不相关的进程之间传递数据的简单高效的方法,其特点如下:1)消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息......
  • Linux系统编程——进程间通信:管道(pipe)
    管道的概述管道也叫无名管道,它是是UNIX系统IPC(进程间通信)的最古老形式,所有的UNIX系统都支持这种通信机制。无名管道有如下特点:1、半双工,数据在同一时刻只能在一个方向......
  • Mac M1 安装 Nacos 操作及问题解决
    nacos依赖mysql先安装mysql,这里使用的是8+版本,原因在于原本的5.7版本中并没有对m1的良好支持,如果启动会有报错说查询不到对应版本信息(虽然可以通过自定义mirror......
  • Linux高级网络开发奇妙之旅
    一、基础理论篇​​01、网络协议入门​​​​02、LAN、WAN、WLAN、VLAN和VPN的区别​​​​03、IP地址介绍​​​​04、广播地址介绍​​​​05、无连接和面向连接协议......
  • 一步步学习Linux开发环境搭建与使用
    ​​00、Linux开发环境搭建与使用1——Linux简史​​​​01、Linux开发环境搭建与使用2——Linux系统(ubuntu)安装方案​​​​02、Linux开发环境搭建与使用3——通过虚拟机......