Search code examples
c++network-programmingrecvraw-socketsospf

why my raw socket recv() seemed to get nothing?


Main Situation

I'm using raw socket trying to recv OSPF packet. Although I've successfully grabbed the packet through Wireshark, I could not receive anything through function recv(), where the thread just stuck.

the related thread function is as follows:

#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 */

    }
}

the result is as follows (thread SendHelloPacket is using sendto() and is successful) result of the program

what's wrong with my usage? (I'm new hand of socket programming)

Other Details

  • environment: ubuntu 20.04 in VMWare
  • using ensp to simulate routers equipped with OSPF and send OSPF packets to my ubuntu in my virtual machine enter image description here
  • the packets catched by Wireshark in my ubuntu 20.04 is as follows: wireshark packets
  • the NIC information is as follows ("promisc" mode is of no use) enter image description here

Solution

  • Oh, I think it's just because of my ignorance.

    Reason May lie in: socket(AF_INET, SOCK_RAW, IPPROTO_OSPF). handling method in socket function like recv() isn't implemented. Only IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP, etc would work.

    So I changed to use AF_PACKET(the same as PF_PACKET) with htons(ETH_P_IP), then without any other configuration I could receive all the Frames on data link layer. The rest things would be manually filter the receiving packets.

    Here's the new function:

    #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
            /* ... */
        }
    }
    

    But I'm also wondering if there's more proper way to do this. (filter the unwanted packet in somewhere of linux kernel not here)