0

I have an application that is sending hand crafted SOCK_RAW packets from a PF_PACKET socket. The packets are being created and sent as the screenshot from Wireshark shows. The packets being sent are TCP SYN packets with an expected TCP SYN/ACK response. However, no response is being received, again as the screenshot shows. I assume that this is because the router is dropping the packets for some reason. Any ideas what the reason could be? Or is there some other reason why I am not receiving any responses.

Text

The full code is quite long because it takes a lot of code to get the IP address and the MAC address of the router to build the ethernet header with. So I have only included the most relevant code. If that is not enough please leave a comment and I will post the full code.

fd_socket[z] = socket(PF_PACKET, SOCK_RAW|SOCK_NONBLOCK, htons(ETH_P_ALL));
        if(fd_socket[z] == -1)
        {
            perror("socket");
            return EXIT_FAILURE;
        }
    
        /* clear structure */
        memset(&my_addr[z], 0, sizeof(struct sockaddr_ll));
        my_addr[z].sll_family = PF_PACKET;
        my_addr[z].sll_protocol = htons(ETH_P_ALL);
    
        str_devname = ifname;
        //strcpy (str_devname, ifname);
        
        /* initialize interface struct */
        strncpy (s_ifr.ifr_name, str_devname, sizeof(s_ifr.ifr_name));
    
        /* Get the broad cast address */
        ec = ioctl(fd_socket[z], SIOCGIFINDEX, &s_ifr);
        if(ec == -1)
        {
            perror("iotcl");
            return EXIT_FAILURE;
        }
    
        /* update with interface index */
        i_ifindex = s_ifr.ifr_ifindex;
    
        s_ifr.ifr_mtu = 7200;
        /* update the mtu through ioctl */
        ec = ioctl(fd_socket[z], SIOCSIFMTU, &s_ifr);
        if(ec == -1)
        {
            perror("iotcl");
            return EXIT_FAILURE;
        }
    
        /* set sockaddr info */
        memset(&my_addr[z], 0, sizeof(struct sockaddr_ll));
        my_addr[z].sll_family = AF_PACKET;
        my_addr[z].sll_protocol = ETH_P_ALL;
        my_addr[z].sll_ifindex = i_ifindex;
    
        /* bind port */
        if (bind(fd_socket[z], (struct sockaddr *)&my_addr[z], sizeof(struct sockaddr_ll)) == -1)
        {
            perror("bind");
            return EXIT_FAILURE;
        }
    
        /* prepare Tx ring request */
        s_packet_req.tp_block_size = c_buffer_sz;
        s_packet_req.tp_frame_size = c_buffer_sz;
        s_packet_req.tp_block_nr = c_buffer_nb;
        s_packet_req.tp_frame_nr = c_buffer_nb;
    
        /* calculate memory to mmap in the kernel */
        size = s_packet_req.tp_block_size * s_packet_req.tp_block_nr;
    
        /* set packet loss option */
        tmp = mode_loss;
        if (setsockopt(fd_socket[z], SOL_PACKET, PACKET_LOSS, (char *)&tmp, sizeof(tmp))<0)
        {
            perror("setsockopt: PACKET_LOSS");
            return EXIT_FAILURE;
        }
 
        /* send TX ring request */
        if (setsockopt(fd_socket[z], SOL_PACKET, PACKET_TX_RING, (char *)&s_packet_req, sizeof(s_packet_req))<0)
        {
            perror("setsockopt: PACKET_TX_RING");
            return EXIT_FAILURE;
        }
    
        /* change send buffer size */
        if(c_sndbuf_sz) {
            printf("send buff size = %d\n", c_sndbuf_sz);
            if (setsockopt(fd_socket[z], SOL_SOCKET, SO_SNDBUF, &c_sndbuf_sz, sizeof(c_sndbuf_sz))< 0)
            {
                perror("getsockopt: SO_SNDBUF");
                return EXIT_FAILURE;
            }
        }
    
        /* get data offset */
        data_offset = TPACKET_HDRLEN - sizeof(struct sockaddr_ll);
    
        /* mmap Tx ring buffers memory */
        ps_header_start = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_socket[z], 0);
        if (ps_header_start == (void*)-1)
        {
            perror("mmap");
            return EXIT_FAILURE;
        }
    
    
        int i,j;
        int i_index = 0;
        char * data;
        int first_loop = 1;
        struct tpacket_hdr * ps_header;
        int ec_send = 0;
 
        for(i=1; i <= c_packet_nb; i++)
        {
            int i_index_start = i_index;
            int loop = 1;
 
            /* get free buffer */
            do {
                ps_header = ((struct tpacket_hdr *)((void *)ps_header_start + (c_buffer_sz*i_index)));
                data = ((void*) ps_header) + data_offset;
                switch((volatile uint32_t)ps_header->tp_status)
                {
                    case TP_STATUS_AVAILABLE:
                        /* fill data in buffer */
                        if(first_loop) {
                            //Datagram to represent the packet
                            char datagram[4096] , source_ip[32] , *data2, *pseudogram;
    
                            //zero out the packet buffer
                            memset (datagram, 0, 4096);
    
                            //Ethernet header
                            struct ether_header *eh = (struct ether_header *) datagram;
        
                            //IP header
                            struct iphdr *iph = (struct iphdr *) (datagram + sizeof (struct ether_header));
    
                            //TCP header
                            struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ether_header) + sizeof (struct ip));
                            struct sockaddr_in sin;
                            struct pseudo_header psh;
    
                            //Data part
                            data2 = datagram + sizeof(struct ether_header) + sizeof(struct iphdr) + sizeof(struct tcphdr);
                            strcpy(data2 , "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    
                            //some address resolution
                            strcpy(source_ip , inet_ntoa(ipaddr->sin_addr));
                            sin.sin_family = AF_INET;
                            sin.sin_port = htons(80);
                            if (fscanf(fp, "%253s", server) == 1)
                                sin.sin_addr.s_addr = inet_addr (server);   
                            else
                            {
                                done = 1;
                                break;
                            }
                        
                            //Fill in the Ethernet Header
                            eh->ether_dhost[0] = arp_resp->sender_mac[0];
                            eh->ether_dhost[1] = arp_resp->sender_mac[1];
                            eh->ether_dhost[2] = arp_resp->sender_mac[2];
                            eh->ether_dhost[3] = arp_resp->sender_mac[3];
                            eh->ether_dhost[4] = arp_resp->sender_mac[4];
                            eh->ether_dhost[5] = arp_resp->sender_mac[5];

                            memcpy(eh->ether_shost, ifr.ifr_hwaddr.sa_data, HWADDR_len);
                            eh->ether_type = 0x0008;
    
                            //Fill in the IP Header
                            iph->ihl = 5;
                            iph->version = 4;
                            iph->tos = 0;
                            iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr) + strlen(data);
                            iph->id = htonl (54321);    //Id of this packet
                            iph->frag_off = 0;
                            iph->ttl = 255;
                            iph->protocol = IPPROTO_TCP;
                            iph->check = 0;     //Set to 0 before calculating checksum
                            iph->saddr = inet_addr ( source_ip );   //Spoof the source ip address
                            iph->daddr = sin.sin_addr.s_addr;
    
                            //Ip checksum
                            iph->check = csum ((unsigned short *) datagram, iph->tot_len);
    
                            //TCP Header
                            tcph->source = htons (1234);
                            tcph->dest = htons (80);
                            tcph->seq = 0;
                            tcph->ack_seq = 0;
                            tcph->doff = 5; //tcp header size
                            tcph->fin=0;
                            tcph->syn=1;
                            tcph->rst=0;
                            tcph->psh=0;
                            tcph->ack=0;
                            tcph->urg=0;
                            tcph->window = htons (5840);    // maximum allowed window size 
                            tcph->check = 0;    //leave checksum 0 now, filled later by pseudo header
                            tcph->urg_ptr = 0;

                            //Now the TCP checksum
                            psh.source_address = inet_addr( source_ip );
                            psh.dest_address = sin.sin_addr.s_addr;
                            psh.placeholder = 0;
                            psh.protocol = IPPROTO_TCP;
                            psh.tcp_length = htons(sizeof(struct tcphdr) + strlen(data) );
    
                            int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + strlen(data);
                            pseudogram = malloc(psize);
        
                            memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
                            memcpy(pseudogram + sizeof(struct pseudo_header) , tcph , sizeof(struct tcphdr) + strlen(data));
    
                            tcph->check = csum( (unsigned short*) pseudogram , psize);
                        
                            memcpy(data, datagram, 4096);
                            free(pseudogram);
//                      for(j=0;j<c_packet_sz;j++)
//                          data[j] = j;
                        }
                        loop = 0;
                    break;
 
                    case TP_STATUS_WRONG_FORMAT:
                        printf("An error has occured during transfer\n");
                        exit(EXIT_FAILURE);
                    break;
 
                    default:
                        /* nothing to do => schedule : useful if no SMP */
                        usleep(0);
                        break;
                }
            }
            while(loop == 1);
 
            i_index ++;
            if(i_index >= c_buffer_nb)
            {
                i_index = 0;
                first_loop = 0;
            }
 
            /* update packet len */
            ps_header->tp_len = c_packet_sz;
            /* set header flag to USER (trigs xmit)*/
            ps_header->tp_status = TP_STATUS_SEND_REQUEST;
 
            /* if smp mode selected */
            if(!mode_thread)
            {
                /* send all packets */
                if( ((i&c_send_mask)==0) || (ec_send < 0) || (i == c_packet_nb) )
                {
                    /* send all buffers with TP_STATUS_SEND_REQUEST */
                    /* Don't wait end of transfer */
                    //ec_send = (int) task_send((void*)0);
                }
            }
            else if(c_error) {
 
                if(i == (c_packet_nb/2))
                {
                    int ec_close;
                
                    if(c_error == 1) {
                        ec_close = close(fd_socket[z]);
                    }
                    if(c_error == 2) {
                        if (setsockopt(fd_socket[z], SOL_PACKET, PACKET_TX_RING, (char *)&s_packet_req, sizeof(s_packet_req))<0)
                        {
                            perror("setsockopt: PACKET_TX_RING");
                            //return EXIT_FAILURE;
                        }
                    }
                    break;
                }
            }
        }
    
        //int ec_send;
        static int total=0;
        int blocking = 1;
        
        /* send all buffers with TP_STATUS_SEND_REQUEST */
        /* Wait end of transfer */
        ec_send = sendto(fd_socket[z],NULL,0,(blocking? 0 : MSG_DONTWAIT),(struct sockaddr *) ps_sockaddr,sizeof(struct sockaddr_ll));
        
        if(ec_send < 0) {
            perror("sendto");
        }
        else if ( ec_send == 0 ) {
            /* nothing to do => schedule : useful if no SMP */
            usleep(0);
        }
        else {
            total += ec_send/(c_packet_sz);
            printf("send %d packets (+%d bytes)\n",total, ec_send);
            fflush(0);
        }
        //ps_header_start = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd_socket[z], 0);
        if (munmap(ps_header_start, size) == -1)
        {
            perror("munmap");
            exit(EXIT_FAILURE);
        }       
    
        close(fd_socket[z]);

EDIT

A similar program that uses SOCK_RAW on a PF_INET socket also sends hand crafted packets doing a TCP SYN and does in fact receive the expected TCP SYN/ACK response. The only difference in the way the packets are constructed is that the PF_PACKET version has to have the ethernet headers added as well. The below screenshot shows a packet sent and a packet received which shows that the ethernet headers are the same for both the PF_INET version and the PF_PACKET version. So it is a mystery what the actual difference is that leads to this behaviour.

PF_INET Version Wireshark capture

The PF_INET program code is as follows

/*
    Raw TCP packets
*/
#include <stdio.h>  //for printf
#include <string.h> //memset
#include <sys/socket.h> //for socket ofcourse
#include <stdlib.h> //for exit(0);
#include <errno.h> //For errno - the error number
#include <netinet/tcp.h>    //Provides declarations for tcp header
#include <netinet/ip.h> //Provides declarations for ip header
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // sleep()

/* 
    96 bit (12 bytes) pseudo header needed for tcp header checksum calculation 
*/
struct pseudo_header
{
    u_int32_t source_address;
    u_int32_t dest_address;
    u_int8_t placeholder;
    u_int8_t protocol;
    u_int16_t tcp_length;
};

/*
    Generic checksum calculation function
*/
unsigned short csum(unsigned short *ptr,int nbytes) 
{
    register long sum;
    unsigned short oddbyte;
    register short answer;

    sum=0;
    while(nbytes>1) {
        sum+=*ptr++;
        nbytes-=2;
    }
    if(nbytes==1) {
        oddbyte=0;
        *((u_char*)&oddbyte)=*(u_char*)ptr;
        sum+=oddbyte;
    }

    sum = (sum>>16)+(sum & 0xffff);
    sum = sum + (sum>>16);
    answer=(short)~sum;
    
    return(answer);
}

int main (void)
{
    //Create a raw socket
    int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
    
    if(s == -1)
    {
        //socket creation failed, may be because of non-root privileges
        perror("Failed to create socket");
        exit(1);
    }
    
    //Datagram to represent the packet
    char datagram[4096] , source_ip[32] , *data , *pseudogram;
    
    //zero out the packet buffer
    memset (datagram, 0, 4096);
    
    //IP header
    struct iphdr *iph = (struct iphdr *) datagram;
    
    //TCP header
    struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
    struct sockaddr_in sin;
    struct pseudo_header psh;
    
    //Data part
    data = datagram + sizeof(struct iphdr) + sizeof(struct tcphdr);
    strcpy(data , "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    
    //some address resolution
    strcpy(source_ip , "192.168.1.170");
    sin.sin_family = AF_INET;
    sin.sin_port = htons(80);
    sin.sin_addr.s_addr = inet_addr ("51.89.233.84");
    
    //Fill in the IP Header
    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 0;
    iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr) + strlen(data);
    iph->id = htonl (54321);    //Id of this packet
    iph->frag_off = 0;
    iph->ttl = 255;
    iph->protocol = IPPROTO_TCP;
    iph->check = 0;     //Set to 0 before calculating checksum
    iph->saddr = inet_addr ( source_ip );   //Spoof the source ip address
    iph->daddr = sin.sin_addr.s_addr;
    
    //Ip checksum
    iph->check = csum ((unsigned short *) datagram, iph->tot_len);
    
    //TCP Header
    tcph->source = htons (1234);
    tcph->dest = htons (80);
    tcph->seq = 0;
    tcph->ack_seq = 0;
    tcph->doff = 5; //tcp header size
    tcph->fin=0;
    tcph->syn=1;
    tcph->rst=0;
    tcph->psh=0;
    tcph->ack=0;
    tcph->urg=0;
    tcph->window = htons (5840);    /* maximum allowed window size */
    tcph->check = 0;    //leave checksum 0 now, filled later by pseudo header
    tcph->urg_ptr = 0;
    
    //Now the TCP checksum
    psh.source_address = inet_addr( source_ip );
    psh.dest_address = sin.sin_addr.s_addr;
    psh.placeholder = 0;
    psh.protocol = IPPROTO_TCP;
    psh.tcp_length = htons(sizeof(struct tcphdr) + strlen(data) );
    
    int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + strlen(data);
    pseudogram = malloc(psize);
    
    memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
    memcpy(pseudogram + sizeof(struct pseudo_header) , tcph , sizeof(struct tcphdr) + strlen(data));
    
    tcph->check = csum( (unsigned short*) pseudogram , psize);
    
    //IP_HDRINCL to tell the kernel that headers are included in the packet
    int one = 1;
    const int *val = &one;
    
    if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
    {
        perror("Error setting IP_HDRINCL");
        exit(0);
    }
    
    //loop if you want to flood :)
    while (1)
    {
        //Send the packet
        if (sendto (s, datagram, iph->tot_len , 0, (struct sockaddr *) &sin, sizeof (sin)) < 0)
        {
            perror("sendto failed");
        }
        //Data send successfully
        else
        {
            printf ("Packet Send. Length : %d \n" , iph->tot_len);
        }
        // sleep for 1 seconds
        sleep(1);
    }
    
    return 0;
}

EDIT

On closer inspection of the packets being sent out on the wire I noticed that the IP header checksum is wrong for the PF_PACKET version. Also the byte order needs to be reversed for values added that are more than a single byte in length. Why doesn't the checksum function work for the PF_PACKET version. Here is the csum function:

/*
    Generic checksum calculation function
*/
unsigned short csum(unsigned short *ptr,int nbytes) 
{
    register long sum;
    unsigned short oddbyte;
    register short answer;

    sum=0;
    while(nbytes>1) {
        sum+=*ptr++;
        nbytes-=2;
    }
    if(nbytes==1) {
        oddbyte=0;
        *((u_char*)&oddbyte)=*(u_char*)ptr;
        sum+=oddbyte;
    }

    sum = (sum>>16)+(sum & 0xffff);
    sum = sum + (sum>>16);
    answer=(short)~sum;
    
    return(answer);
}
James Read
  • 419
  • 2
  • 7
  • 13
  • Can you do some Wireshark sniffing of some (normally-generated) TCP packets that the router doesn't drop, and then compare those against yours to see what is different between the "good" and the "bad" packets? – Jeremy Friesner Jan 08 '22 at 23:23
  • @JeremyFriesner I've edited my question to show a different program that does not cause packets to be dropped. As far as I can see the packets are pretty much the same. They have the same ethernet header which is the only difference in the way the packets are constructed. With my program I have to construct the ethernet header as well. – James Read Jan 08 '22 at 23:50
  • @JeremyFriesner Actually on closer inspection I think I've found a difference. The Total Length value in the IP header seems to be wrong in my program. I'm working on trying to figure out why. – James Read Jan 09 '22 at 00:18
  • @JeremyFriesner I had to reverse byte order to get my Total Length value correct. However the side effect of this is that the checksum calculation is wrong and therefore packets are being discarded by the router. I need to figure out why I need to reverse the byte order in my version of the program. In the other version this is not needed. There must be some difference between ```PF_PACKET``` and ```PF_INET```. – James Read Jan 09 '22 at 04:09

1 Answers1

1

The ip4 checksum is only calculated over the ip header, if I get it correctly. So if you pass the total lenght of the whole packet to the checlsum calculation function, I would not be surprised, if you get a wrong checksum. I wonder though why it happend to work in the second program.

Jakob Stark
  • 3,346
  • 6
  • 22
  • Thanks, I've tried playing with the nbytes parameter to the csum function but am still getting the wrong checksum. I also wonder why the checksum is correct for the other program. Can you suggest a fix? I can provide the full program listing if that is useful. – James Read Jan 09 '22 at 16:11
  • You could try to pass `sizeof (struct iphdr)` to the csum function instead of `iph->tot_len`. – Jakob Stark Jan 09 '22 at 16:27
  • Yeah I tried that. The checksum is still wrong. I figured out why the checksum still works for the other program. The memory had been zeroed. So the checksum was adding zeros for the extra bytes which has no effect on the checksum calculation. I guess what I really need to figure out is why multibyte values need to be reversed for my program. Then we will get closer to understanding why the checksum calculation is off. Any ideas? – James Read Jan 09 '22 at 16:30
  • Hm, most network protocols, including ip4 use big endian byte order (the most significant byte comes first). If you are using a x86 or x86-64 processor, which you very likely do, it uses little endian byte order. That implies, that multibyte data fields need to be byte reversed upon reading and writing. Try to use the htons and ntohs functions also in your csum function, every time you are reading or writing to memory – Jakob Stark Jan 09 '22 at 17:04
  • That puts the bytes in order but unfortunately the checksum is still wrong. But what is bugging me is why the other program doesn't need the byte order reversing. – James Read Jan 09 '22 at 17:24
  • Fixed it. I was using the wrong address for the checksum function. I needed to use ```datagram + sizeof (struct ether_header)``` rather than just ```datagram```. Thanks for your help. – James Read Jan 09 '22 at 18:09