Search code examples
csocketsnetworkingpacket-capture

C Program to receive and send the same packets out over another interface


I have a linux system with two physical interfaces. I need to intercept (read) incoming packets over one interface, read (or process) the data and send it out over the other interface as it is - just like a middleman. I am able to extract all the header fields and payload data from the packets but I am not able to put it back on the wire again. How do I send the packet on its way through the other interface?

// All #includes 

struct sockaddr_in source,dest;
int i,j,k;
int main()
{
    int saddr_size , data_size;
    struct sockaddr_in saddr;
    unsigned char *buffer=malloc(65535);


    int sock_raw = socket( AF_PACKET , SOCK_RAW , htons(ETH_P_ALL)) ;

     if(sock_raw < 0)
        perror("setsockopt");

    setsockopt(sock_raw , SOL_SOCKET , SO_BINDTODEVICE , "eth0" , strlen("eth0")+ 1 );

    if(sock_raw < 0)
    {
        perror("Socket Error");
        return 1;
    }

    while(1)
    {
        saddr_size = sizeof (struct sockaddr);
        //Receive a packet
        data_size = recvfrom(sock_raw , buffer , 65536 , 0 ,(struct sockaddr *) &saddr , (socklen_t*)&saddr_size);

        if(data_size <0 )
        {
            printf("Recvfrom error , failed to get packets\n");
            return 1;
        }
        else{
        printf("Received %d bytes\n",data_size);

        //Huge code to process the packet

        //Send it out through "eth1" here 

        }
    }
    close(sock_raw);
    return 0;
}

Just assume only UDP or ICMP packets if it makes it easier to explain (using a simple "sendto" function maybe)- I can handle the sorting. Do not worry about the intended destination, I only want to put the packets back on the wire - delivery is not important.

Edit 1:

If I do this it gives me a runtime error saying "Invalid argument". It doesn't matter if I'm sending the buffer or even "Hello World".

bytes_sent=sendto(sock_raw, buffer, 65536, 0,(struct sockaddr *) &saddr ,saddr_size);
if (bytes_sent < 0) {
    perror("sendto");
    exit(1);
    }

Edit 2 : Let me make it simpler- I have two pipes A and B. Balls roll in from A and I receive them. I just want to put them in pipe B and send them on their way. Ethernet bridges work in a similar way - just sending all packets over all interfaces involved. I would have definitely used a bridge if I didn't have to get some basic information from the packet headers. And I'm not good at modifying the kernel bridge drivers.

Edit 3 : I'll try one last time with a different question. If I have received a complete raw packet with source/destination addresses included in the headers and all, how do I simply send it ANYWHERE (i don't care where) using sendto ? Should I add any information to the "struct sockaddr" in the sendto call , or can I simply use the same one I did in the recvfrom call ?


Solution

  • I finally got it to work ! I was able to send packets received on eth0 to eth1 unchanged.

    Corrections:

    1. "write" instead of "sendto" , although it isn't advisable.
    2. A fresh socket for sending only.
    3. It seems the SO_BINDTODEVICE using setsockopt doesn't work properly so I had to use a bind function to bind the socket to the interface.
    4. struct sockaddr_in to struct sockaddr_ll

    I put this together in a hurry so there may be some redundant lines in there. I don't know if daddr is even neccessry but it works so there it is. Here's the full code :

    // All #includes 
    
    int main()
    {
        int saddr_size , data_size, daddr_size, bytes_sent;
        struct sockaddr_ll saddr, daddr;
        unsigned char *buffer=malloc(65535);
    
        int sock_raw = socket( AF_PACKET , SOCK_RAW , htons(ETH_P_ALL)) ; //For receiving
        int sock = socket( PF_PACKET , SOCK_RAW , IPPROTO_RAW) ;            //For sending
    
        memset(&saddr, 0, sizeof(struct sockaddr_ll));
        saddr.sll_family = AF_PACKET;
        saddr.sll_protocol = htons(ETH_P_ALL);
        saddr.sll_ifindex = if_nametoindex("eth0");
        if (bind(sock_raw, (struct sockaddr*) &saddr, sizeof(saddr)) < 0) {
            perror("bind failed\n");
            close(sock_raw);
        }
    
        memset(&daddr, 0, sizeof(struct sockaddr_ll));
        daddr.sll_family = AF_PACKET;
        daddr.sll_protocol = htons(ETH_P_ALL);
        daddr.sll_ifindex = if_nametoindex("eth1");
        if (bind(sock, (struct sockaddr*) &daddr, sizeof(daddr)) < 0) {
          perror("bind failed\n");
          close(sock);
        }
        struct ifreq ifr;
        memset(&ifr, 0, sizeof(ifr));
        snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth1");
        if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) {
            perror("bind to eth1");
            }
    
        while(1)
        {
            saddr_size = sizeof (struct sockaddr);
            daddr_size = sizeof (struct sockaddr);
            //Receive a packet
            data_size = recvfrom(sock_raw , buffer , 65536 , 0 ,(struct sockaddr *) &saddr , (socklen_t*)&saddr_size);
    
            if(data_size <0 )
            {
                printf("Recvfrom error , failed to get packets\n");
                return 1;
            }
            else{
            printf("Received %d bytes\n",data_size);
    
            //Huge code to process the packet (optional)
    
            //Send the same packet out
            bytes_sent=write(sock,buffer,data_size);
            printf("Sent %d bytes\n",bytes_sent);
             if (bytes_sent < 0) {
                perror("sendto");
                exit(1);
             }
    
            }
        }
        close(sock_raw);
        return 0;
    }
    

    Thanks to all those who responded.