AF_PACKET是socket的一种,用于在链路层(也就是OSI的二层)接收和发送数据包,可以让用户在用户态实现自定义的协议模型。由于该socket可以接收任何类型的链路层数据包,在这里利用其能力在用户态接收特定的arp协议包并发送响应。PF_PACKET可以看作等同于AF_PACKET,甚至在语义上更符合该场景,但linux文档中使用AF_PACKET。
BPF是一个数据包过滤器,可以关联到socket上用于过滤特定的数据包,用于在内核态将不符合需要的数据包过滤掉,减少通过socket传输到用户态的数据量,可以提升性能降低负载,其工作模式类似于状态机或称之为虚拟机。由于更新的eBPF的出现,原有的BPF现在可以称为cBPF,本文使用cBPF选择符合要求的arp数据包。
本文运行环境:
- 操作系统 CentOS Linux release 7.5.1804
- 内核版本 Linux centos1804-2 3.10.0-862.el7.x86_64
- 依赖包 libev-devel
背景
在host服务器上通过bridge连接了一组虚拟机,host上用户态程序需要伪造TCP和UDP数据包发送给虚拟机,并接收从虚拟机发出的相应连接的TCP和UDP数据包。为了不影响host网络的正常运行,选择使用与虚拟机同网段但保留出来的一段IP地址作为host伪造地址池,该伪造地址池对虚拟机内部完全透明。为了让虚拟机协议栈发送的目的地址为伪造地址池的数据包可以正常发送出来,host上需要读取到虚拟机发送的arp请求包,并做出正确的响应,让虚拟机协议栈看到的伪造地址池的IP地址对应的MAC地址是dridge上的MAC地址。
比如:
- bridge作为网关,名字为s-engine-br0,IP地址设定为172.16.0.1,MAC地址52:54:00:69:eb:0f
- 虚拟机DHCP地址池,[172.16.100.100, 172.16.100.255]
- 伪造地址池,[172.16.128.0, 172.16.255.254]
目标就是让虚拟机协议栈看到的伪造地址池中的IP地址对应的MAC地址均为bridge的MAC地址,也就是52:54:00:69:eb:0f。
背景
+--------------------------------+
| host |
| |
| app |
| \ |
| \ |
| \ |
| +--------------+ |
| | bridge | |
| +-------+------+ |
| / | \ |
| / | \ |
| / | \ |
| / | \ |
| +--+ +-++ +--+ |
| |vm| |vm| |vm| |
| +--+ +--+ +--+ |
| |
+--------------------------------+
AF_PACKET
首先需要说明的是,AF_PACKET收到的数据包可以理解为旁路监听到的,并不能阻止内核将数据包由协议栈继续向上传递。如果需要在内核hook点控制数据包是否放行需要使用内核netfilter,比如过滤arp数据包使用NFPROTO_ARP,netfilter参考前文Netfilter 内核数据包过滤框架。
参考man 2 socket,socket函数原型为int socket(int domain, int type, int protocol);,这里简单介绍参数:
- domain
指明该socket用于通信的协议族,比如:AF_INET
用于IPv4协议通信AF_PACKET
用于链路层通信,本文使用该协议族,详细信息参考man 7 packet - type
指明该socket在domain指定的协议族下使用的具体通信语义。具体哪种类型是可用的取决于domain具体使用何种协议族。,比如:SOCK_STREAM
顺序、可靠、双向、有连接的字节流。SOCK_DGRAM
无连接、不可靠的数据报。SOCK_RAW
原始协议数据包。 - protocol
指明该socket具体使用的协议,如果在特定domain特定type下只有一种protocol可用则可以设置为0。其取值取决于domain使用的具体协议族。
本文使用AF_PACKET协议族。参考man 7 packet,type仅支持SOCK_RAW和SOCK_DGRAM,其中SOCK_RAW收发的数据包均包含链路层协议头,SOCK_DGRAM收发的数据包均不包含链路层协议头。protocol为链路层上承载的协议号,字节序需要使用网络序,用于过滤接收的数据包协议类型,如果设置为htons(ETH_P_ALL)则接收所有数据包。socket相关的地址信息使用结构体struct sockaddr_ll。
struct sockaddr_ll {
unsigned short sll_family; /* Always AF_PACKET */
unsigned short sll_protocol; /* Physical layer protocol */
int sll_ifindex; /* Interface number */
unsigned short sll_hatype; /* ARP hardware type */
unsigned char sll_pkttype; /* Packet type */
unsigned char sll_halen; /* Length of address */
unsigned char sll_addr[8]; /* Physical layer address */
};
本文分别演示了SOCK_RAW和SOCK_DGRAM的使用,因为目标是接收arp请求包,因此protocol设置为htons(ETH_P_ARP)。因为只关注host中bridge上收到的arp请求,因此调用bind将socket绑定到该特定设备。为了进一步提高效率,使用了cBPF进行过滤,介绍见后文。
cBPF
参考linux BPF 文档。这里仅使用原始的cBPF(Berkeley Packet Filter)过滤socket接收的数据包。
BPF使用最多的地方应该是libpcap,tcpdump工具使用libpcap完成数据包的监听抓取,tcpdump提供了一个简单的编译器,可以将符合tcpdump语法规则的人类可读规则编译成BPF规则并输出,本文使用的BPF规则也是使用tcpdump编译的。这里需要说明的是使用tcpdump导出BPF规则需要指定网络设备,因为不同的网络设备可能使用不同的二层协议,这样在二层协议头具体信息以及上层协议偏移量上会产生影响。tcpdump使用SOCK_RAW类型的socket,因此编译生成的BPF规则都假定包含了链路层协议头,如果使用SOCK_DGRAM类型的socket需要对编译生成的BPF规则做一些调整,演示代码中有体现。
这里简单说明一下所使用的BPF规则的含义。
汇编样式
输出人类可读的汇编样式代码使用命令:
sudo tcpdump -d 'arp[24:4] >= 0xAC100001 && arp[24:4] <=0xAC100005 && arp[6:2] = 1' -i lo
整体判断流程为:
- 检查是否为ARP协议。
- 检查ARP协议目标IP地址是否在[172.16.0.1, 172.16.0.5]之间。(这里仅仅是预填充便于编译,在代码中会对这个范围做修改)
- 检查ARP协议op字段是否为1,既是否为ARP请求。
BPF 汇编样式
(000) ldh [12] 从偏移12位置取2字节写入寄存器A。
该位置是以太网协议头中标明的上层协议号。
(001) jeq #0x806 jt 2 jf 8 判断寄存器A值是否等于0x806。
该值是ARP协议号ETH_P_ARP
(002) ld [38] 从偏移38位置取4字节写入寄存器A。
该位置是ARP协议中的目标IP地址。
(003) jge #0xac100001 jt 4 jf 8 判断寄存器A值是否大于等于十六进制数ac100001,
如果为真跳到偏移为4的指令,如果为假跳到8。
ac100001是172.16.0.1的数值。
bpf会处理网络字节序和本地字节序的问题。
(004) jgt #0xac100005 jt 8 jf 5 判断寄存器A值是否大于十六进制数ac100005,
如果为真跳到偏移为8的指令,如果为假跳到5。
ac100005是172.16.0.5的数值。
(005) ldh [20] 从偏移20的位置取2字节写入寄存器A。
该位置是ARP协议中指令op
(006) jeq #0x1 jt 7 jf 8 判断寄存器A值是否等于1,
如果为真跳到偏移为7的指令,如果为假跳到8.
值1代表ARP协议中op为ARPOP_REQUEST,既ARP请求。
(007) ret #262144 符合要求,放行。
(008) ret #0 不符合要求,过滤掉。
C语言片段
输出C语言片段使用命令:
sudo tcpdump -dd 'arp[24:4] >= 0xAC100001 && arp[24:4] <=0xAC100005 && arp[6:2] = 1' -i lo
这里的输出可以与汇编样式部分完全对应,不做解释了。
BPF c语言片段
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 6, 0x00000806 },
{ 0x20, 0, 0, 0x00000026 },
{ 0x35, 0, 4, 0xac100001 },
{ 0x25, 3, 0, 0xac100005 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x15, 0, 1, 0x00000001 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
演示代码
libev使用参考官网文档
通过网卡名查询唯一索引号和mac地址参考获取网卡列表的几种方式
编译
gcc test.c -Wall -g -lev -lpthread
使用SOCK_RAW运行
sudo ./a.out s-engine-br0 172.16.128.0 172.16.255.254 0
使用SOCK_DGRAM运行
sudo ./a.out s-engine-br0 172.16.128.0 172.16.255.254 1
虚拟机可以使用ping命令测试,协议栈会发送相应地址的arp请求。如果需要清空本地的arp表,可以使用命令sudo ip ne flush all
一个输出例子
|
c语言代码:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <ev.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if_arp.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))
#define LOG_ERROR(fmt, ...) _log("ERROR", __FILE__, __LINE__, \
__FUNCTION__, fmt, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) _log("INFO ", __FILE__, __LINE__, \
__FUNCTION__, fmt, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) _log("DEBUG", __FILE__, __LINE__, \
__FUNCTION__, fmt, ##__VA_ARGS__)
/* from net/if_arp.h */
struct my_arphdr {
unsigned short int ar_hrd; /* Format of hardware address. */
unsigned short int ar_pro; /* Format of protocol address. */
unsigned char ar_hln; /* Length of hardware address. */
unsigned char ar_pln; /* Length of protocol address. */
unsigned short int ar_op; /* ARP opcode (command). */
/* Ethernet looks like this : This bit is variable sized
* * however... */
/* 考虑结构体成员中以太网地址6字节,以及成员对齐的影响
* IP地址需要使用单字节数组形式定义,而不是平常使用的unsigned int
* */
unsigned char ar_sha[ETH_ALEN]; /* Sender hardware address. */
unsigned char ar_sip[4]; /* Sender IP address. */
unsigned char ar_tha[ETH_ALEN]; /* Target hardware address. */
unsigned char ar_tip[4]; /* Target IP address. */
};
struct my_arp_response_struct {
int fd;
struct ev_io io_watcher;
unsigned char hwaddr[ETH_ALEN];
};
static volatile int _stop = 0;
static void _log(const char *level, const char *file, int line,
const char *func, const char *fmt, ...) {
char buf[2048];
va_list list;
time_t t;
struct tm t_tm = {};
if (time(&t) != (time_t)-1) {
localtime_r(&t, &t_tm);
}
time(&t);
va_start(list, fmt);
vsnprintf(buf, sizeof(buf), fmt, list);
printf("%4d-%02d-%02d,%02d:%02d:%02d tid:%5lu [%s %s:%d] %s",
t_tm.tm_year + 1900, t_tm.tm_mon + 1, t_tm.tm_mday, t_tm.tm_hour,
t_tm.tm_min, t_tm.tm_sec, pthread_self() % 100000, level, file,
line, buf);
}
static void *thread_loop(void *arg) {
struct ev_loop *loop = arg;
LOG_INFO("loop start\n");
ev_run(loop, 0);
LOG_INFO("loop end\n");
return NULL;
}
static void async_callback(struct ev_loop *loop, struct ev_async *w, int revents) {
LOG_INFO("in async callback\n");
ev_break(loop, EVBREAK_ONE);
}
static void term_handler(int sig) {
LOG_INFO("in term handler\n");
_stop = 1;
}
static void arp_raw_callback(struct ev_loop *loop, ev_io *w, int revents) {
struct my_arp_response_struct *ar;
int fd = w->fd;
struct sockaddr_ll addr;
socklen_t addr_len;
char buf[2048];
int r;
struct my_arphdr *arph;
struct ethhdr *ethh;
ar = w->data;
while (1) {
addr_len = sizeof(addr);
memset(&addr, '\0', addr_len);
/* nonblock模式接收 */
r = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&addr, &addr_len);
if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* 接收没了 */
break;
} else {
LOG_ERROR("recv arp packet failed, %s\n", strerror(errno));
break;
}
} else {
LOG_DEBUG("recv arp packet %d bytes\n", r);
if (r == sizeof(*arph) + sizeof(*ethh)) {
ethh = (struct ethhdr *)buf;
arph = (struct my_arphdr *)(buf + sizeof(*ethh));
if (arph->ar_op == htons(ARPOP_REQUEST)) {
/* 响应 */
char src_ip[32];
char dst_ip[32];
unsigned int target_ip = *(unsigned int *)arph->ar_tip;
inet_ntop(AF_INET, arph->ar_sip, src_ip, sizeof(src_ip));
inet_ntop(AF_INET, arph->ar_tip, dst_ip, sizeof(dst_ip));
LOG_DEBUG("arp request %s from %s\n", dst_ip, src_ip);
memcpy(ethh->h_dest, ethh->h_source, ETH_ALEN);
memcpy(ethh->h_source, ar->hwaddr, ETH_ALEN);
arph->ar_op = htons(ARPOP_REPLY);
memcpy(arph->ar_tip, arph->ar_sip, sizeof(arph->ar_sip));
memcpy(arph->ar_tha, arph->ar_sha, sizeof(arph->ar_sha));
memcpy(arph->ar_sip, &target_ip, sizeof(target_ip));
memcpy(arph->ar_sha, ar->hwaddr, sizeof(ar->hwaddr));
/* block模式发送响应 */
r = sendto(fd, ethh, r, 0, (struct sockaddr *)&addr, sizeof(addr));
if (r == -1) {
LOG_ERROR("send arp reply failed, %s\n", strerror(errno));
} else {
LOG_DEBUG("send arp reply %d bytes\n", r);
}
}
}
}
}
}
static void arp_dgram_callback(struct ev_loop *loop, ev_io *w, int revents) {
struct my_arp_response_struct *ar;
int fd = w->fd;
struct sockaddr_ll addr;
socklen_t addr_len;
char buf[2048];
int r;
struct my_arphdr *arph;
ar = w->data;
while (1) {
addr_len = sizeof(addr);
memset(&addr, '\0', addr_len);
/* nonblock模式接收 */
r = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&addr, &addr_len);
if (r == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* 接收没了 */
break;
} else {
LOG_ERROR("recv arp packet failed, %s\n", strerror(errno));
break;
}
} else {
LOG_DEBUG("recv arp packet %d bytes\n", r);
if (r == sizeof(*arph)) {
arph = (struct my_arphdr *)buf;
if (arph->ar_op == htons(ARPOP_REQUEST)) {
/* 响应 */
char src_ip[32];
char dst_ip[32];
unsigned int target_ip = *(unsigned int *)arph->ar_tip;
inet_ntop(AF_INET, arph->ar_sip, src_ip, sizeof(src_ip));
inet_ntop(AF_INET, arph->ar_tip, dst_ip, sizeof(dst_ip));
LOG_DEBUG("arp request %s from %s\n", dst_ip, src_ip);
arph->ar_op = htons(ARPOP_REPLY);
memcpy(arph->ar_tip, arph->ar_sip, sizeof(arph->ar_sip));
memcpy(arph->ar_tha, arph->ar_sha, sizeof(arph->ar_sha));
memcpy(arph->ar_sip, &target_ip, sizeof(target_ip));
memcpy(arph->ar_sha, ar->hwaddr, sizeof(ar->hwaddr));
/* block模式发送响应 */
r = sendto(fd, buf, sizeof(*arph), 0, (struct sockaddr *)&addr, sizeof(addr));
if (r == -1) {
LOG_ERROR("send arp reply failed, %s\n", strerror(errno));
} else {
LOG_DEBUG("send arp reply %d bytes\n", r);
}
}
}
}
}
}
/*
* 成功返回fd
* 失败返回-1
* */
static struct my_arp_response_struct *get_arp_response_struct(
int ifindex, struct ev_loop *loop, const char *start, const char *end,
const unsigned char hwaddr[ETH_ALEN], int is_dgram) {
struct sockaddr_ll addr;
struct my_arp_response_struct *ar;
struct sock_fprog bpf;
int socket_type;
void (*cb)(struct ev_loop *, struct ev_io *, int);
/*
* 由于socket使用SOCK_DGRAM,数据包被剥掉了链路层头,因此对bpf数据定位有影响
* 参考以下命令的输出调整而来
* sudo tcpdump -dd 'arp[24:4] >= 0xAC100001 && arp[24:4] <=0xAC100005 && arp[6:2] = 1' -i lo
* */
struct sock_filter dgram_filter[] = {
{ 0x20, 0, 0, 0x00000018 },
{ 0x35, 0, 4, 0xac100001 },
{ 0x25, 3, 0, 0xac100005 },
{ 0x28, 0, 0, 0x00000006 },
{ 0x15, 0, 1, 0x00000001 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
};
/*
* sudo tcpdump -dd 'arp[24:4] >= 0xAC100001 && arp[24:4] <=0xAC100005 && arp[6:2] = 1' -i lo
* */
struct sock_filter raw_filter[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 6, 0x00000806 },
{ 0x20, 0, 0, 0x00000026 },
{ 0x35, 0, 4, 0xac100001 },
{ 0x25, 3, 0, 0xac100005 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x15, 0, 1, 0x00000001 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000000 },
};
if (is_dgram) {
/*
* 这里使用 SOCK_DGRAM 表示只接收和发送不包含二层以太网协议头的数据
* 接收数据包的以太网协议头由内核剥离后发送到用户态
* 发送数据包的以太网协议头由内核填充后向外发送
* */
socket_type = SOCK_DGRAM;
cb = arp_dgram_callback;
/* 填充伪造IP的范围 */
dgram_filter[1].k = inet_network(start);
dgram_filter[2].k = inet_network(end);
bpf.len = ARRAY_SIZE(dgram_filter);
bpf.filter = dgram_filter;
} else {
/*
* 这里使用 SOCK_RAW 表示接收和发送的数据包均包含以太网协议头
* */
socket_type = SOCK_RAW;
cb = arp_raw_callback;
/* 填充伪造IP的范围 */
raw_filter[3].k = inet_network(start);
raw_filter[4].k = inet_network(end);
bpf.len = ARRAY_SIZE(raw_filter);
bpf.filter = raw_filter;
}
ar = (typeof(ar))malloc(sizeof(*ar));
if (ar == NULL) {
LOG_ERROR("out of memory\n");
return NULL;
}
memcpy(ar->hwaddr, hwaddr, ETH_ALEN);
/*
* 只有ETH_P_ALL才能接收本机向外发出的数据包,因此该socket只能接收本机收到的数据包
* */
ar->fd = socket(AF_PACKET, socket_type, htons(ETH_P_ARP));
if (ar->fd == -1) {
LOG_ERROR("socket AF_PACKET failed, %s\n", strerror(errno));
free(ar);
return NULL;
}
memset(&addr, '\0', sizeof(addr));
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(ETH_P_ARP);
addr.sll_ifindex = ifindex;
if (bind(ar->fd, (struct sockaddr *)&addr, sizeof(addr))) {
LOG_ERROR("bind failed, %s\n", strerror(errno));
close(ar->fd);
free(ar);
return NULL;
}
/* bpf filter */
if (setsockopt(ar->fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)) != 0) {
LOG_ERROR("set bpf filter failed, %s\n", strerror(errno));
close(ar->fd);
free(ar);
return NULL;
}
ev_io_init(&ar->io_watcher, cb, ar->fd, EV_READ);
ev_io_start(loop, &ar->io_watcher);
ar->io_watcher.data = ar;
return ar;
}
/*
* 成功返回设备index
* 失败返回-1
* */
static int get_ifindex(const char *if_name) {
struct ifreq ifr;
int r;
int index;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
LOG_ERROR("socket failed, %s\n", strerror(errno));
return -1;
}
strncpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
r = ioctl(fd, SIOCGIFINDEX, &ifr);
if (r == -1) {
LOG_ERROR("get %s ifindex failed, %s\n", if_name, strerror(errno));
close(fd);
return -1;
}
close(fd);
index = ifr.ifr_ifindex;
return index;
}
/*
* 将网络设备if_name的以太网地址填充入buf中
*
* 成功返回0
* 失败返回-1
* */
static int get_hwaddr(const char *if_name, unsigned char buf[6]) {
struct ifreq ifr;
int r;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
LOG_ERROR("socket failed, %s\n", strerror(errno));
return -1;
}
strncpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
r = ioctl(fd, SIOCGIFHWADDR, &ifr);
if (r == -1) {
LOG_ERROR("get %s hwaddr failed, %s\n", if_name, strerror(errno));
close(fd);
return -1;
}
close(fd);
if (ifr.ifr_hwaddr.sa_family != ARPHRD_ETHER) {
LOG_ERROR("invalide hwaddr sa_family %u\n", ifr.ifr_hwaddr.sa_family);
return -1;
}
memcpy(buf, ifr.ifr_hwaddr.sa_data, 6);
return 0;
}
int main(int argc, const char *argv[]) {
struct ev_loop *loop = NULL;
struct ev_async async_watcher;
pthread_t thread;
struct sigaction term_action;
struct my_arp_response_struct *arp_response = NULL;
int ifindex;
unsigned char hwaddr[6];
int is_dgram;
if (argc < 4) {
printf("usage: %s ifname pseudo_ip_start pseudo_ip_end [dgram]\n"
" ifname: 监听arp请求包的网络设备名\n"
" pseudo_ip_start: 监听arp请求的ip地址范围的起始地址\n"
" pseudo_ip_end: 监听arp请求的ip地址范围的结束地址\n"
" dgram: 大于0的数字表示使用SOCK_DGRAM模式,"
"默认使用SOCK_RAW模式\n" , argv[0]);
goto cleanup;
}
/* 信号处理 */
bzero(&term_action, sizeof(term_action));
term_action.sa_handler = term_handler;
if (sigaction(SIGINT, &term_action, NULL) ||
sigaction(SIGTERM, &term_action, NULL)) {
LOG_ERROR("sigaction failed, %s\n", strerror(errno));
goto cleanup;
}
/* ev */
loop = ev_loop_new(EVFLAG_AUTO);
if (loop == NULL) {
LOG_ERROR("ev_loop_new failed\n");
goto cleanup;
}
ev_async_init(&async_watcher, async_callback);
ev_async_start(loop, &async_watcher);
/* ifindex */
ifindex = get_ifindex(argv[1]);
if (ifindex == -1) {
LOG_ERROR("get_ifindex failed\n");
goto cleanup;
}
LOG_INFO("interface %s index %d\n", argv[1], ifindex);
/* hwaddr */
if (get_hwaddr(argv[1], hwaddr)) {
LOG_ERROR("get_hwaddr failed\n");
goto cleanup;
}
LOG_INFO("interface %s hwaddr %02x:%02x:%02x:%02x:%02x:%02x\n", argv[1],
hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
/* is_dgram */
if (argc > 4 && atoi(argv[4]) > 0) {
is_dgram = 1;
} else {
is_dgram = 0;
}
LOG_INFO("socket type: %s\n", is_dgram ? "SOCK_DGRAM" : "SOCK_RAW");
/* PF_PACKET & BPF */
arp_response = get_arp_response_struct(ifindex, loop,
argv[2], argv[3], hwaddr, is_dgram);
if (arp_response == NULL) {
LOG_ERROR("get_arp_response_struct failed\n");
goto cleanup;
}
/* 启动线程 */
if (pthread_create(&thread, NULL, thread_loop, loop)) {
LOG_ERROR("pthread_create failed\n");
goto cleanup;
}
while (!_stop) {
sleep(1);
}
/* 通知并等待线程退出 */
LOG_INFO("before async send\n");
ev_async_send(loop, &async_watcher);
LOG_INFO("after async send\n");
pthread_join(thread, NULL);
/* 释放资源 */
close(arp_response->fd);
free(arp_response);
ev_loop_destroy(loop);
LOG_INFO("bye\n");
return 0;
cleanup:
if (arp_response) {
close(arp_response->fd);
free(arp_response);
}
if (loop)
ev_loop_destroy(loop);
return -1;
}