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。

背景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+--------------------------------+
| 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_RAWSOCK_DGRAM,其中SOCK_RAW收发的数据包均包含链路层协议头,SOCK_DGRAM收发的数据包均不包含链路层协议头。protocol为链路层上承载的协议号,字节序需要使用网络序,用于过滤接收的数据包协议类型,如果设置为htons(ETH_P_ALL)则接收所有数据包。socket相关的地址信息使用结构体struct sockaddr_ll

struct sockaddr_ll
1
2
3
4
5
6
7
8
9
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_RAWSOCK_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 汇编样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(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语言片段
1
2
3
4
5
6
7
8
9
{ 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

一个输出例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[huyu@centos1804-2 arp_response]$ sudo ./a.out s-engine-br0 172.16.128.0 172.16.255.254 1
2019-01-10,21:46:52 tid:48640 [INFO test.c:442] interface s-engine-br0 index 488
2019-01-10,21:46:52 tid:48640 [INFO test.c:450] interface s-engine-br0 hwaddr 52:54:00:69:eb:0f
2019-01-10,21:46:52 tid:48640 [INFO test.c:458] socket type: SOCK_DGRAM
2019-01-10,21:46:52 tid:67936 [INFO test.c:82] loop start
2019-01-10,21:47:07 tid:67936 [DEBUG test.c:186] recv arp packet 28 bytes
2019-01-10,21:47:07 tid:67936 [DEBUG test.c:197] arp request 172.16.200.1 from 172.16.100.100
2019-01-10,21:47:07 tid:67936 [DEBUG test.c:210] send arp reply 28 bytes
2019-01-10,21:47:13 tid:67936 [DEBUG test.c:186] recv arp packet 28 bytes
2019-01-10,21:47:13 tid:67936 [DEBUG test.c:197] arp request 172.16.200.2 from 172.16.100.100
2019-01-10,21:47:13 tid:67936 [DEBUG test.c:210] send arp reply 28 bytes
2019-01-10,21:47:16 tid:48640 [INFO test.c:94] in term handler
2019-01-10,21:47:16 tid:48640 [INFO test.c:479] before async send
2019-01-10,21:47:16 tid:48640 [INFO test.c:481] after async send
2019-01-10,21:47:16 tid:67936 [INFO test.c:89] in async callback
2019-01-10,21:47:16 tid:67936 [INFO test.c:84] loop end
2019-01-10,21:47:16 tid:48640 [INFO test.c:490] bye
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
#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;
}