Search code examples
csocketsnetwork-programmingudpwireshark

Raw socket not capturing DHCP packets on correct interface but Wireshark is


I'm following this C application example of a raw socket sniffer:

https://www.binarytides.com/packet-sniffer-code-c-linux/

Except I binded the socket to my interface after creating the socket:

const std::string& iff = "wlp3s0";

int r = setsockopt(sock_raw, SOL_SOCKET, SO_BINDTODEVICE, iff.c_str(), iff.length());
if (r == -1)
{
    std::abort();
}

I then leave the application running:

sudo ./the_app

I open Wireshark and listen to wlp3s0 wireless interface. I removed the wired Ethernet interface from the capture.

I then run sudo dhclient -r and sudo dhclient to stop/start DHCP and Wireshark detects 4 or 5 DHCP packets, as expected:

enter image description here

However, the C raw socket sniffer shows absolutely no UDP packets received.

If I then open a web browser, it starts to show TCP packets (Wireshark reported the DHCP packets were UDP).

What's happening? I'm using Ubuntu 22.04

Minimal example:

#include<stdio.h>   //For standard things
#include<stdlib.h>  //malloc
#include<string.h>  //memset
#include<netinet/ip_icmp.h> //Provides declarations for icmp header
#include<netinet/udp.h> //Provides declarations for udp header
#include<netinet/tcp.h> //Provides declarations for tcp header
#include<netinet/ip.h>  //Provides declarations for ip header
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>

void ProcessPacket(unsigned char* , int);

int tcp=0,udp=0,icmp=0,others=0,igmp=0,total=0,i,j;
struct sockaddr_in source,dest;

int main()
{    
    unsigned char *buffer = (unsigned char *)malloc(65536); //Its Big!
    
    printf("Starting...\n");
    int sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_TCP);

    if(sock_raw < 0)
    {
        std::abort();
    }

    int r = setsockopt(sock_raw, SOL_SOCKET, SO_BINDTODEVICE, "wlp3s0", strlen("wlp3s0"));
    
    if (r == -1)
    {
        std::abort();
    }
   
    while(1)
    {
        int data_size = recv(sock_raw , buffer , 65536 , 0);
        
        if(data_size <0 )
        {
            std::abort();
        }

        ProcessPacket(buffer , data_size);
    }
    
    ::close(sock_raw);
    return 0;
}

void ProcessPacket(unsigned char* buffer, int size)
{
    //Get the IP Header part of this packet
    struct iphdr *iph = (struct iphdr*)buffer;
    ++total;
    switch (iph->protocol) //Check the Protocol and do accordingly...
    {
        case 1:  //ICMP Protocol
            ++icmp;
            //print_icmp_packet(buffer, size);
            break;
        
        case 2:  //IGMP Protocol
            ++igmp;
            break;
        
        case 6:  //TCP Protocol
            ++tcp;
            //print_tcp_packet(buffer , size);
            break;
        
        case 17: //UDP Protocol
            ++udp;
            //print_udp_packet(buffer , size);
            break;
        
        default: //Some Other Protocol like ARP etc.
            ++others;
            break;
    }

    printf("TCP : %d   UDP : %d   ICMP : %d   IGMP : %d   Others : %d   Total : %d\r",tcp,udp,icmp,igmp,others,total);
}

Solution

  • Well, of course you are not receiving UDP packets. You are explicitly only asking for TCP packets with IPPROTO_TCP:

    int sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
    

    After all, the very guide you are following tells you this:

    1. The above sniffer picks up only TCP packets, because of the declaration:

      sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_TCP);
      

    Now you may think of using IPPROTO_RAW, but that is only possible for sending, as man 7 raw explains:

    A protocol of IPPROTO_RAW implies enabled IP_HDRINCL and is able to send any IP protocol that is specified in the passed header. Receiving of all IP protocols via IPPROTO_RAW is not possible using raw sockets.

    So you cannot receive multiple protocols with an AF_INET socket. You will need an AF_PACKET socket and slightly more complex code. See man 7 packet for more info.

    Note that in this case you will have to bind using bind(2) and struct sockaddr_ll, you cannot use setsockopt(SO_BINDTODEVICE, ...) with AF_PACKET sockets, as man 7 socket states:

    SO_BINDTODEVICE

    [...] Note that this works only for some socket types, particularly AF_INET sockets. It is not supported for packet sockets (use normal bind(2) there).