#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <netpacket/packet.h>
struct ethernet {
unsigned char dest[6];
unsigned char source[6];
uint16_t eth_type;
};
struct arp {
uint16_t htype;
uint16_t ptype;
unsigned char hlen;
unsigned char plen;
uint16_t oper;
/* addresses */
unsigned char sender_ha[6];
unsigned char sender_pa[4];
unsigned char target_ha[6];
unsigned char target_pa[4];
};
#define ETH_HDR_LEN 14
#define BUFF_SIZE 2048
#define ARP_PROTO 0x0806
static void dump_arp(struct arp *arp_hdr);
int main(void)
{
int sock, err;
void *buffer = NULL;
ssize_t recvd_size;
struct sockaddr_ll s_ll;
struct ethernet *eth_hdr = NULL;
struct arp *arp_hdr = NULL;
if( (sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) == -1)
// if( (sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1)
{
perror("socket(): ");
exit(-1);
}
s_ll.sll_family = AF_PACKET;
s_ll.sll_protocol = htons(ETH_P_ARP);
//s_ll.sll_protocol = htons(ETH_P_ARP);
s_ll.sll_ifindex = 0; // all ifaces
//s_ll.sll_ifindex = 2 // eth0
if( (err = bind(sock, (struct sockaddr *)&s_ll, sizeof(s_ll))) == -1)
{
perror("bind(): ");
exit(-1);
}
buffer = malloc(BUFF_SIZE);
while(1)
{
if( (recvd_size = recv(sock, buffer, BUFF_SIZE, 0)) == -1)
{
perror("recv(): ");
free(buffer);
close(sock);
exit(-1);
}
if(recvd_size <= (sizeof(struct ethernet) + sizeof(struct arp)))
{
printf("Short packet. Packet len: %d\n", recvd_size);
continue;
}
eth_hdr = (struct ethernet *)buffer;
if(ntohs(eth_hdr->eth_type) != ARP_PROTO)
continue;
arp_hdr = (struct arp *)(buffer+ETH_HDR_LEN);
dump_arp(arp_hdr);
}
free(buffer);
close(sock);
}
static void
dump_arp(struct arp *arp_hdr)
{
uint16_t htype = ntohs(arp_hdr->htype);
uint16_t ptype = ntohs(arp_hdr->ptype);
uint16_t oper = ntohs(arp_hdr->oper);
switch(htype)
{
case 0x0001:
printf("ARP HTYPE: Ethernet(0x%04X)\n", htype);
break;
default:
printf("ARP HYPE: 0x%04X\n", htype);
break;
}
switch(ptype)
{
case 0x0800:
printf("ARP PTYPE: IPv4(0x%04X)\n", ptype);
break;
default:
printf("ARP PTYPE: 0x%04X\n", ptype);
break;
}
printf("ARP HLEN: %d\n", arp_hdr->hlen);
printf("ARP PLEN: %d\n", arp_hdr->plen);
switch(oper)
{
case 0x0001:
printf("ARP OPER: Request(0x%04X)\n", oper);
break;
case 0x0002:
printf("ARP OPER: Response(0x%04X)\n", oper);
break;
default:
printf("ARP OPER: 0x%04X\n", oper);
break;
}
printf("ARP Sender HA: %02X:%02X:%02X:%02X:%02X:%02X\n",
arp_hdr->sender_ha[0],arp_hdr->sender_ha[1],arp_hdr->sender_ha[2],
arp_hdr->sender_ha[3], arp_hdr->sender_ha[4], arp_hdr->sender_ha[5]);
printf("ARP Sender PA: %d.%d.%d.%d\n", arp_hdr->sender_pa[0],
arp_hdr->sender_pa[1], arp_hdr->sender_pa[2], arp_hdr->sender_pa[3]);
printf("ARP Target HA: %02X:%02X:%02X:%02X:%02X:%02X\n",
arp_hdr->target_ha[0],arp_hdr->target_ha[1],arp_hdr->target_ha[2],
arp_hdr->target_ha[3], arp_hdr->target_ha[4], arp_hdr->target_ha[5]);
printf("ARP Target PA: %d.%d.%d.%d\n", arp_hdr->target_pa[0],
arp_hdr->target_pa[1], arp_hdr->target_pa[2], arp_hdr->target_pa[3]);
printf("ARP DONE =====================\n");
}
我使用AF_PACKET,SOCK_RAW,ETH_P_ARP args创建socket()。并使用args(AF_PACKET,ETH_P_ARP)在所有接口上绑定()它(无关紧要)。因此,所有ARP数据包都必须通过此套接字。
我的主机:192.168.1.2远程主机:192.168.1.7,主机不包含192.167.1.7的ARP记录。
程序输出,当我ping 192.168.1.7时:
... ARP OPER: Response(0x0002) ARP Sender HA: 50:67:F0:94:70:F5 ARP Sender PA: 192.168.1.7 ARP Target HA: 00:22:15:A2:D0:C5 ARP Target PA: 192.168.1.2 ARP DONE ===================== ... ARP OPER: Request(0x0001) ARP Sender HA: 50:67:F0:94:70:F5 ARP Sender PA: 192.168.1.7 ARP Target HA: 00:00:00:00:00:00 ARP Target PA: 192.168.1.2 ARP DONE =====================
我的套接字只有2个数据包4(my_host请求和错过了my_host响应)。 tcpdump -n -p -i eth0 arp显示所有4个数据包。
如果我在socket()和bind()中将ETH_P_ARP更改为ETH_P_ALL,则所有4个数据包都将转到socket(使用IP和其他数据包)。
为什么呢?如何解决?
PS。请告诉我一些邮件列表的地址,我可以询问这个行为。
答案 0 :(得分:3)
有点迟到,但我试着用这个来玩。也许一些googler / duckduckgoer可以受益。
我的建议是使用ETH_P_ALL接收所有数据包,然后使用Linux套接字过滤器过滤它们,以便应用程序只接收请求的ARP数据包。
这是我的代码。大变化标记为CHANGE注释
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <linux/filter.h> // CHANGE: include lsf
struct ethernet {
unsigned char dest[6];
unsigned char source[6];
uint16_t eth_type;
};
struct arp {
uint16_t htype;
uint16_t ptype;
unsigned char hlen;
unsigned char plen;
uint16_t oper;
/* addresses */
unsigned char sender_ha[6];
unsigned char sender_pa[4];
unsigned char target_ha[6];
unsigned char target_pa[4];
};
#define ETH_HDR_LEN 14
#define BUFF_SIZE 2048
/* CHANGE
Linux socket filters use the Berkeley packet filter syntax.
This was adapted from BSDs "man 4 bpf" example for RARP.
*/
struct sock_filter arpfilter[] = {
BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12), /* Skip 12 bytes */
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETH_P_ARP, 0, 1), /* if eth type != ARP
skip next instr. */
BPF_STMT(BPF_RET+BPF_K, sizeof(struct arp) +
sizeof(struct ethernet)),
BPF_STMT(BPF_RET+BPF_K, 0), /* Return, either the ARP packet or nil */
};
static void dump_arp(struct arp *arp_hdr);
int main(void)
{
int sock;
void *buffer = NULL;
ssize_t recvd_size;
struct ethernet *eth_hdr = NULL;
struct arp *arp_hdr = NULL;
struct sock_filter *filter;
struct sock_fprog fprog;
if( (sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1)
{
perror("socket(): ");
exit(-1);
}
/* CHANGE prepare linux packet filter */
if ((filter = malloc(sizeof(arpfilter))) == NULL) {
perror("malloc");
close(sock);
exit(1);
}
memcpy(filter, &arpfilter, sizeof(arpfilter));
fprog.filter = filter;
fprog.len = sizeof(arpfilter)/sizeof(struct sock_filter);
/* CHANGE add filter */
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) == -1) {
perror("setsockopt");
close(sock);
exit(1);
}
buffer = malloc(BUFF_SIZE);
while(1)
{
if( (recvd_size = recv(sock, buffer, BUFF_SIZE, 0)) < 0)
{
perror("recv(): ");
free(buffer);
close(sock);
exit(-1);
}
if((size_t)recvd_size < (sizeof(struct ethernet) + sizeof(struct arp)))
{
printf("Short packet. Packet len: %ld\n", recvd_size);
continue;
}
eth_hdr = (struct ethernet *)buffer;
if(ntohs(eth_hdr->eth_type) != ETH_P_ARP) {
printf("Received wrong ethernet type: %X\n", eth_hdr->eth_type);
exit(1);
}
arp_hdr = (struct arp *)(buffer+ETH_HDR_LEN);
dump_arp(arp_hdr);
}
free(buffer);
close(sock);
}
static void
dump_arp(struct arp *arp_hdr)
{
uint16_t htype = ntohs(arp_hdr->htype);
uint16_t ptype = ntohs(arp_hdr->ptype);
uint16_t oper = ntohs(arp_hdr->oper);
switch(htype)
{
case 0x0001:
printf("ARP HTYPE: Ethernet(0x%04X)\n", htype);
break;
default:
printf("ARP HYPE: 0x%04X\n", htype);
break;
}
switch(ptype)
{
case 0x0800:
printf("ARP PTYPE: IPv4(0x%04X)\n", ptype);
break;
default:
printf("ARP PTYPE: 0x%04X\n", ptype);
break;
}
printf("ARP HLEN: %d\n", arp_hdr->hlen);
printf("ARP PLEN: %d\n", arp_hdr->plen);
switch(oper)
{
case 0x0001:
printf("ARP OPER: Request(0x%04X)\n", oper);
break;
case 0x0002:
printf("ARP OPER: Response(0x%04X)\n", oper);
break;
default:
printf("ARP OPER: 0x%04X\n", oper);
break;
}
printf("ARP Sender HA: %02X:%02X:%02X:%02X:%02X:%02X\n",
arp_hdr->sender_ha[0],arp_hdr->sender_ha[1],arp_hdr->sender_ha[2],
arp_hdr->sender_ha[3], arp_hdr->sender_ha[4], arp_hdr->sender_ha[5]);
printf("ARP Sender PA: %d.%d.%d.%d\n", arp_hdr->sender_pa[0],
arp_hdr->sender_pa[1], arp_hdr->sender_pa[2], arp_hdr->sender_pa[3]);
printf("ARP Target HA: %02X:%02X:%02X:%02X:%02X:%02X\n",
arp_hdr->target_ha[0],arp_hdr->target_ha[1],arp_hdr->target_ha[2],
arp_hdr->target_ha[3], arp_hdr->target_ha[4], arp_hdr->target_ha[5]);
printf("ARP Target PA: %d.%d.%d.%d\n", arp_hdr->target_pa[0],
arp_hdr->target_pa[1], arp_hdr->target_pa[2], arp_hdr->target_pa[3]);
printf("ARP DONE =====================\n");
}
我还删除了bind()
作为不必要的内容,并在比较捕获的数据包大小时将其关闭了一个。比较为<= sizeof(struct ethernet) + sizeof(struct arp)
,应为<
没有读取数据包套接字内核源代码,我现在还不知道,我没有找到一个很好的解释为什么示例代码只接收发往主机IP的数据包。由于OP和Internet上的许多示例确认,当级别为ETH_P_ALL时,套接字也会接收传出数据包。我想这只是一个实施选择。它可能是大多数应用程序的首选行为,例如那些实施协议的人,而不是窥探现有协议。
注意说到内核源代码,我完全不确定为什么lsf / bpf过滤器在我给它两个字节的ETH_P_ARP而没有字节顺序修改的情况下工作。我想可能是因为内核中的这些行:
case BPF_S_ANC_PROTOCOL:
A = ntohs(skb->protocol);