c++ 为什么我的原始套接字recv()似乎什么也没有得到?

cfh9epnr  于 2023-07-01  发布在  其他
关注(0)|答案(1)|浏览(126)

主要情况

我正在使用原始套接字尝试接收OSPF数据包。虽然我已经通过Wireshark成功地抓取了数据包,但我无法通过函数recv()接收任何内容,线程只是卡住了。
相关线程函数如下:

#define IPPROTO_OSPF (89)
// myconfigs::nic_name is "ens33", I'm sure it's correct (using it)

void* threadRecvPackets(void *intf) {
    Interface *interface = (Interface*) intf;
    int socket_fd;
    if ((socket_fd = socket(AF_INET, SOCK_RAW, IPPROTO_OSPF)) < 0) {
        perror("[Thread]RecvPacket: socket_fd init");
    }

    /* Bind sockets to certain Network Interface */
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strcpy(ifr.ifr_name, myconfigs::nic_name);
    if (setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) < 0) {
        perror("[Thread]RecvPacket: setsockopt - bind to device");
    }

    /* Add to OSPF multicast */
    struct ip_mreq mreq;
    memset(&mreq, 0, sizeof(mreq));
    mreq.imr_interface.s_addr = htonl(interface->ip);
    mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.5");
    if (setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
        perror("[Thread]RecvPacket: setsockopt - multicast ip add membership");
    }

    /* Set Source Address - useless */
    // struct sockaddr_in src_addr;
    // memset(&src_addr, 0, sizeof(src_addr));
    // src_addr.sin_family = AF_INET;
    // src_addr.sin_addr.s_addr = htonl(INADDR_ANY);

#ifdef DEBUG
    printf("[Thread]RecvPacket init\n");
#endif

#define RECV_LEN 1024
    char* packet_rcv = (char*)malloc(RECV_LEN);
    while (true) {
        memset(packet_rcv, 0, RECV_LEN);

        #ifdef DEBUG
            printf("[Thread]RecvPacket: start to recv\n");
        #endif

        recv(socket_fd, packet_rcv, RECV_LEN, 0);  // stuck here!!
        // socklen_t src_addr_len = sizeof(src_addr);
        // recvfrom(socket_fd, packet_rcv, RECV_LEN, 0, (struct sockaddr*)&src_addr, &src_addr_len);
        
        in_addr_t src_ip = ntohl(*(uint32_t*)(packet_rcv + IPHDR_SRCIP));

        #ifdef DEBUG
            printf("[Thread]RecvPacket: recv one\n");
        #endif

        if (src_ip == interface->ip) {
            continue; // from self, finish packet process
        }

        /* ... dealing with the packet recvd, treated as 5 type of OSPF packet */

    }
}

结果如下(线程SendHelloPacket正在使用sendto()并且成功)

我的用法有什么问题吗?(我是socket编程新手)

其他详情

  • 环境:ubuntu 20.04在VMWare中

  • 使用ensp模拟配备OSPF的路由器,并将OSPF数据包发送到我的虚拟机

    中的ubuntu

  • 在我的ubuntu 20.04中,Wireshark捕获的数据包如下:

  • NIC信息如下(“promisc”模式没有用)

qjp7pelc

qjp7pelc1#

我想是因为我的无知。

原因可能在于socket(AF_INET, SOCK_RAW, IPPROTO_OSPF) .未实现类似recv()的套接字函数中的处理方法。只有IPPROTO_TCPIPPROTO_UDPIPPROTO_ICMP等可以工作。

所以我改为使用AF_PACKET(与PF_PACKET相同)和htons(ETH_P_IP),然后无需任何其他配置,我就可以接收数据链路层上的所有帧。其余的事情将是手动过滤接收的数据包。
下面是新函数:

#include <netinet/if_ether.h> // should define more

void* threadRecvPackets(void *intf) {
    Interface *interface = (Interface*) intf;
    int socket_fd;
    if ((socket_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0) {
        perror("[Thread]RecvPacket: socket_fd init");
    }

#ifdef DEBUG
    printf("[Thread]RecvPacket init\n");
#endif

    struct iphdr *ip_header;
    struct in_addr src, dst;
#define RECV_LEN 1514
    char* frame_rcv = (char*)malloc(RECV_LEN);
    char* packet_rcv = frame_rcv + sizeof(struct ethhdr);
    while (true) {
        memset(frame_rcv, 0, RECV_LEN);
        int recv_size = recv(socket_fd, frame_rcv, RECV_LEN, 0);
        
        /* check IP Header : filter  */
        ip_header = (struct iphdr*)packet_rcv;
        // 1. not OSPF packet
        if (ip_header->protocol != 89) {
            continue;
        }
        // 2. src_ip or dst_ip don't fit
        in_addr_t src_ip = ntohl(*(uint32_t*)(packet_rcv + IPHDR_SRCIP));
        in_addr_t dst_ip = ntohl(*(uint32_t*)(packet_rcv + IPHDR_DSTIP));
        if ((dst_ip != interface->ip && dst_ip != ntohl(inet_addr("224.0.0.5"))) ||
            src_ip == interface->ip) {
            continue;
        }

        #ifdef DEBUG
            printf("[Thread]RecvPacket: recv one");
            src.s_addr = src_ip;
            dst.s_addr = dst_ip;
            printf(" src:%s, dst:%s\n", inet_ntoa(src), inet_ntoa(dst));
        #endif
        /* ... */
    }
}

但我也想知道是否有更合适的方式来做这件事。(过滤不需要的数据包在某处的linux内核不是这里)

相关问题