在该C语言代码“#define _GNU_SOURCE //用于启用GNU C 库,需要在任何头文件之前定义
#include <linux/if_arp.h> //arp 地址解析协议
//#include <linux/if_packet.h> //原始数据包的数据结构定义
//#include <net/ethernet.h> //包括几个以太网的数据结构
//#include <net/if.h> //socket头文件,定义了网卡的接口信息的宏,和linux/if.h可能存在重复定义问题,先加载linux/if.h
#include <netinet/ether.h> //以太帧的网络字节和ascii字节的转换
#include <arpa/inet.h>
#include <errno.h>
//#include <pthread.h> //多线程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h> //IO控制操作相关的函数声明
#include <sys/socket.h>
#include <sys/types.h> //类型重定义
#include <unistd.h> //read/write/close函数
/**************************************************************************************************/
/* DEFINES */
/**************************************************************************************************/
/*定义/宏*/
// 对外可见的最小接口(由 arpsd.c 调用)
// 接口信息 包含系统索引、mac地址和IP地址
typedef struct {
int ifindex; //网络接口的本地索引
uint8_t src_mac[6]; //mac地址
uint32_t src_ip; //主机字节序
} arpsd_ctx_t;
// 描述ARP报文帧的紧凑内存布局
// __attribute__((packed)) 无填充字节,确保字段紧密排列
typedef struct __attribute__((packed)) {
struct ether_header eth; //以太网帧头部
struct {
uint16_t htype; // 硬件类型
uint16_t ptype; // 协议类型
uint8_t hlen; // 硬件地址长度
uint8_t plen; // 协议地址长度
uint16_t oper; // 操作码
uint8_t sha[6]; // 发送方MAC
uint32_t spa; // 发送方IP
uint8_t tha[6]; // 目标MAC
uint32_t tpa; // 目标IP
} arp;
} arpsd_frame_t;
/**************************************************************************************************/
/* TYPES */
/**************************************************************************************************/
/*类型*/
/**************************************************************************************************/
/* EXTERN_PROTOTYPES */
/**************************************************************************************************/
/*外部原型*/
/**************************************************************************************************/
/* LOCAL_PROTOTYPERS */
/**************************************************************************************************/
int arpsd_ctx_init(arpsd_ctx_t *ctx, uint32_t probe_ip_host);
static int get_iface_for_ip(uint32_t ip_host, char *ifname, size_t len);
int arpsd_send_who_has(const arpsd_ctx_t *ctx, uint32_t target_ip_host);
int arpsd_recv_is_at(uint8_t *out_mac6, uint32_t *out_ip_host, int timeout_ms);
/**************************************************************************************************/
/* VARIABLES */
/**************************************************************************************************/
/*本文件中的变量声明*/
/**************************************************************************************************/
/* PUBLIC_FUNCTIONS */
/**************************************************************************************************/
/*全局函数*/
/*
* fn arpsd_ctx_init
* brief 创建原始套接字并绑定到用于发包的接口;自动选路由接口
* detailes 根据目标IP动态获取绑定的网络接口,获取接口的系统索引、MAC地址和IP地址。
*
* param[in] uint32_t probe_ip_host 主机字节序的IP地址
* param[out] arpsd_ctx_t *ctx 网络接口的系统索引、MAC地址和IP地址
*
* return 返回函数运行状态
* retval 0表示成功,-1表示失败
*/
int arpsd_ctx_init(arpsd_ctx_t *ctx, uint32_t probe_ip_host)
{
char ifname[IFNAMSIZ] = {0}; //IFNAMSIZ网络接口名称的最大长度 16
//根据目标IP地址动态获取对应的网络接口
if (get_iface_for_ip(probe_ip_host, ifname, sizeof(ifname)) != 0)
{
// 兜底:默认 eth0
snprintf(ifname, sizeof(ifname), "eth0");
}
int fd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET-IPv4通信,SOCK_DGRAM-UDP连接
if (fd < 0) return -1;
struct ifreq ifr; //网络接口名称
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1);
// index
int ifindex = if_nametoindex(ifname); //将网络接口的名称转换为本地索引
if (!ifindex) { close(fd); return -1; }
// MAC
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { close(fd); return -1; } //获取MAC地址
memcpy(ctx->src_mac, ifr.ifr_hwaddr.sa_data, 6);
// IP
if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) { close(fd); return -1; }
struct sockaddr_in *sa = (struct sockaddr_in *)&ifr.ifr_addr;
ctx->src_ip = ntohl(sa->sin_addr.s_addr); //网络字节序->主机字节序
ctx->ifindex = ifindex;
close(fd);
return 0;
}
/*
* fn arpsd_send_who_has
* brief 发送ARP请求报文
* detailes 通过原始套接字构造并向局域网广播一个ARP请求帧,以查询目标地址对应的MAC地址
*
* param[in] const arpsd_ctx_t *ctx 发包网络接口
* param[in] uint32_t target_ip_host 目标IP地址
*
* return 返回函数运行状态
* retval 0-成功,-1失败
*/
int arpsd_send_who_has(const arpsd_ctx_t *ctx, uint32_t target_ip_host)
{
// 创建链路层原始套接字,协议为ARP协议
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (fd < 0) return -1;
// 设置目标地址结构
struct sockaddr_ll addr = {0};
addr.sll_family = AF_PACKET; //链路层地址族
addr.sll_ifindex = ctx->ifindex; //发包网络接口
addr.sll_halen = ETH_ALEN; //MAC地址长度
memset(addr.sll_addr, 0xff, ETH_ALEN); // 广播地址 FF:FF:FF:FF:FF:FF,确保ARP请求广播到局域网
// 构造ARP请求帧
arpsd_frame_t frm;
memset(&frm, 0, sizeof(frm));
// eth以太网帧头部
memset(frm.eth.ether_dhost, 0xff, 6); // 广播地址全ff
memcpy(frm.eth.ether_shost, ctx->src_mac, 6); //发包MAC地址
frm.eth.ether_type = htons(ETH_P_ARP); //ARP协议
// arp
frm.arp.htype = htons(ARPHRD_ETHER); //硬件类型为以太网(1)
frm.arp.ptype = htons(ETH_P_IP); //协议类型为IPv4(0x0800)
frm.arp.hlen = 6; //MAC地址长度
frm.arp.plen = 4; //IP地址长度
frm.arp.oper = htons(ARPOP_REQUEST); //操作码为ARP请求(1)
memcpy(frm.arp.sha, ctx->src_mac, 6);
frm.arp.spa = htonl(ctx->src_ip); // 发送方MAC和IP地址
memset(frm.arp.tha, 0x00, 6); //目标MAC未知
frm.arp.tpa = htonl(target_ip_host); //目标IP
// 发送ARP请求
int n = sendto(fd, &frm, sizeof(frm), 0, (struct sockaddr *)&addr, sizeof(addr));
int ret = (n == sizeof(frm)) ? 0 : -1;
close(fd);
return ret;
}
/*
* fn arpsd_recv_is_at
* brief ARP响应接受器
* detailes 用于监听并解析局域网内的ARP回复报文,从中提取发送方的MAC地址和IP地址
*
* param[in] int timeout_ms 超时时间,避免程序长时间挂起
* param[out] uint8_t *out_mac6 发送方MAC地址
* param[out] uint32_t *out_ip_host 发送方IP地址
*
* return 返回函数运行状态
* retval 0 - 成功并填充输出
* -1 - 套接字创建失败
* -2 - 接收数据超时或出错
* -3 - 接收的数据包长度小于 arpsd_frame_t 结构体大小
* -4 - 以太网帧类型非 ARP
* -5 - ARP 操作码非回复
*
* note 简易实现,并未实现tha=本机MAC或广播
*/
int arpsd_recv_is_at(uint8_t *out_mac6, uint32_t *out_ip_host, int timeout_ms)
{
//链路层原始套接字,ARP协议
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (fd < 0) return -1;
// 设置超时时间
struct timeval tv = { .tv_sec = timeout_ms/1000, .tv_usec = (timeout_ms%1000)*1000 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
uint8_t buf[1500];
ssize_t n = recv(fd, buf, sizeof(buf), 0);
if (n < 0) { close(fd); return -2; }
// 长度需满足arp报文要求
if ((size_t)n < sizeof(arpsd_frame_t)) { close(fd); return -3; }
arpsd_frame_t *f = (arpsd_frame_t*)buf;
//检验报文有效性
if (ntohs(f->eth.ether_type) != ETH_P_ARP) { close(fd); return -4; } //以太网帧类型为ARP协议
if (ntohs(f->arp.oper) != ARPOP_REPLY) { close(fd); return -5; } //ARP操作码为2
if (out_mac6) memcpy(out_mac6, f->arp.sha, 6); //发送方MAC地址
if (out_ip_host) *out_ip_host = ntohl(f->arp.spa); //发送方IP地址,主机字节序
close(fd);
return 0;
}
/**************************************************************************************************/
/* LOCAL_FUNCTIONS */
/**************************************************************************************************/
/*
* fn get_iface_for_ip
* brief 根据目标IP地址查找匹配的网络接口名称
* detailes 解析内核的路由表文件/proc/net/route
*
* param[in] uint32_t ip_host 目标IP
* param[in] size_t len 复制长度
* param[out] char *ifname 最佳网络接口
*
* return 返回函数运行状态
* retval 0表示成功,-1表示失败
*/
static int get_iface_for_ip(uint32_t ip_host, char *ifname, size_t len)
{
// 读取 /proc/net/route,取第一条 UP 的默认或最匹配路由(简单实现)
// /proc/net/route-linux内核提供的路由表信息
// 每条路由包含接口名iface, 目标网络地址dest,子网掩码mask,标志位flags
FILE *f = fopen("/proc/net/route", "r");
if (!f) return -1;
char line[512];
// skip header
fgets(line, sizeof(line), f);
uint32_t best_mask = 0, best_dest = 0;
char best_if[IFNAMSIZ] = {0};
while (fgets(line, sizeof(line), f)) {
char iface[IFNAMSIZ];
unsigned int dest, gateway, flags, mask;
int refcnt, use, metric, mtu, win, irtt;
if (sscanf(line, "%s\t%X\t%X\t%X\t%d\t%d\t%d\t%X\t%d\t%d\t%d",
iface, &dest, &gateway, &flags, &refcnt, &use, &metric,
&mask, &mtu, &win, &irtt) != 11) continue;
// ip_host/ mask 与目标匹配度
// 将目标ip与路由条目中的mask进行按位与运算,判断是否属于该路由的网络范围
uint32_t d = dest;
uint32_t m = mask;
if ((htonl(ip_host) & m) == (d & m))
{
//选择最长前缀匹配作为最佳匹配接口,若存在多条匹配路由,优先选择掩码最大的条目
if (m >= best_mask) {
best_mask = m;
best_dest = d;
strncpy(best_if, iface, IFNAMSIZ-1);
}
}
// 记录默认路由兜底
if (best_if[0] == '\0' && d==0) {
strncpy(best_if, iface, IFNAMSIZ-1);
}
}
fclose(f);
if (best_if[0]=='\0') return -1;
snprintf(ifname, len, "%s", best_if);
return 0;
}”中,编译时会报错“In file included from /usr/include/netinet/if_ether.h:61,
from arpsd_sock.c:26:
/usr/include/net/if_arp.h:53:8: error: redefinition of ‘struct arphdr’
53 | struct arphdr
| ^~~~~~
In file included from arpsd_sock.c:21:
/usr/include/linux/if_arp.h:144:8: note: originally defined here
144 | struct arphdr {
| ^~~~~~
In file included from /usr/include/netinet/if_ether.h:61,
from arpsd_sock.c:26:
/usr/include/net/if_arp.h:138:8: error: redefinition of ‘struct arpreq’
138 | struct arpreq
| ^~~~~~
In file included from arpsd_sock.c:21:
/usr/include/linux/if_arp.h:116:8: note: originally defined here
116 | struct arpreq {
| ^~~~~~
In file included from /usr/include/netinet/if_ether.h:61,
from arpsd_sock.c:26:
/usr/include/net/if_arp.h:147:8: error: redefinition of ‘struct arpreq_old’
147 | struct arpreq_old
| ^~~~~~~~~~
In file included from arpsd_sock.c:21:
/usr/include/linux/if_arp.h:124:8: note: originally defined here
124 | struct arpreq_old {
| ^~~~~~~~~~
arpsd_sock.c: In function ‘arpsd_ctx_init’:
arpsd_sock.c:130:19: warning: implicit declaration of function ‘if_nametoindex’ [-Wimplicit-function-declaration]
130 | int ifindex = if_nametoindex(ifname); //将网络接口的名称转换为本地索引
| ^~~~~~~~~~~~~~
”请问如何修改?