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

15.5k views Asked by At

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 ?

2

There are 2 answers

1
anon_16 On BEST ANSWER

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.

1
ua2b On

Regarding edit 2: If setting up a bridge device is an option, why can't you just use a sniffing tool (tcpdump, wireshark, your own creation based on libpcap) on the bridge device and not have to care about forwarding the packets yourself?