c - SOCK_RAW 套接字中缺少 ARP 数据包

标签 c linux sockets arp raw-sockets

#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 参数创建 socket()。并使用args(AF_PACKET,ETH_P_ARP)在所有接口(interface)上绑定(bind)()它(没关系)。因此,所有 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 =====================

我的套接字仅收到 4 个数据包中的 2 个(my_host 请求和 my_host 响应丢失)。 tcpdump -n -p -i eth0 arp 显示所有 4 个数据包。

如果我在socket()和bind()中将ETH_P_ARP更改为ETH_P_ALL,那么所有4个数据包都会发送到套接字(带有IP和其他)。

为什么?如何解决这个问题?

PS。请告诉我一些邮件列表的地址,我可以在其中询问此行为。

最佳答案

有点晚了,但我尝试这个是为了好玩。也许一些 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()认为不必要,并在比较捕获的数据包大小时将其修正为 1。比较是 <= sizeof(struct ethernet) + sizeof(struct arp)什么时候应该是<

由于我暂时没有阅读数据包套接字内核源代码,所以我没有找到很好的解释为什么示例代码只接收寻址到主机 IP 的数据包。正如OP和互联网上的许多示例所证实的那样,当级别为ETH_P_ALL时,套接字也会接收传出数据包。我想这只是一个实现选择。对于大多数应用程序来说,这可能是更好的行为,例如那些实现协议(protocol),而不是窥探现有协议(protocol)的人。

注意说到内核源代码,我完全不确定为什么当我给 lsf/bpf 过滤器提供两个字节的 ETH_P_ARP 而不修改字节顺序时它会起作用。我认为这可能是因为内核中的这些行:

case BPF_S_ANC_PROTOCOL:
A = ntohs(skb->protocol);

来自http://lxr.linux.no/#linux+v3.11/net/core/filter.c#L317

关于c - SOCK_RAW 套接字中缺少 ARP 数据包,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16543191/

相关文章:

c - 在 C 文件中重新声明不透明结构

linux - 在 IP 末尾检测 0,最后一个八进制/bash

iOS : Keep socket persistent after background

ruby-on-rails - 将多个 Rails 应用程序部署到 DigitalOcean

c - 在我的 TCP 连接中,客户端发送 Hello,但服务器收到 Hellob

c - 在 C 中验证整数

linux - 检查脚本命令中的错误

c++ - Netbeans 7.2 显示 "Unable to resolve identifier",尽管构建成功

angularjs - Node.js 可写流创建错误的文件(更大且不可读)

mysql - 如何释放: MYSQL_ROW row;?