Search code examples
c++socketsnetworking

Sendto works on localhost only


I have been trying to fix this but I think I need your help. I am a newbie and I can't seem to find the issue.

The code works when both the source and destination are set to localhost. But when I use real IP addresses other than localhost for both source and destination, it fails. sendto() returns -1.

I don't know how to make `sendto() spit out more details about the error, or what's causing it to return -1.

I'm running Linux, BTW.

Here is the code:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#define MAX_PACKET_SIZE 4096

u_int32_t src_addr, dst_addr;
u_int16_t src_port, dst_port;


unsigned short csum (unsigned short *buf, int nwords)
{
    unsigned long sum;
    for (sum = 0; nwords > 0; nwords--)
        sum += *buf++;
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}

void setup_ip_header(struct iphdr *iph)
{
    iph->saddr = src_addr;                          // SOURCE IP ADDRESS 
    iph->daddr = dst_addr;                          // TARGET IP ADDRESS

    iph->ihl = 5;
    iph->version = 4;
    iph->tos = 0;
    iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr));
    iph->id = htonl(54321); /*setup any arbitrary number for id */
    iph->frag_off = 0;
    iph->ttl = MAXTTL;
    iph->protocol = 6; /* upper layer protocol, TCP*/
    iph->check = 0;

}

void setup_tcp_header(struct tcphdr *tcph)
{
    tcph->source = htons(src_port);                 // SOURCE PORT
    tcph->dest = htons(dst_port);                   // TARGET PORT

    tcph->seq = random();
    tcph->ack_seq = 0;
    tcph->res2 = 0;
    tcph->doff = 5;
    tcph->syn = 1;
    tcph->window = htonl(65535);
    tcph->check = 0;
    tcph->urg_ptr = 0;
}



int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    struct sockaddr_in sin;                                         

    const char *src_addr_str = "192.168.1.50";
    src_addr = inet_addr(src_addr_str);
    src_port = 777;

    const char *dst_addr_str = "192.168.1.51";
    dst_addr = inet_addr(dst_addr_str);
    dst_port = 888;

    char datagram[MAX_PACKET_SIZE];
    struct iphdr *iph = (struct iphdr *)datagram;
    struct tcphdr *tcph = (struct tcphdr *)((u_int8_t *)iph + (5 * sizeof(u_int32_t)));
   
    char new_ip[sizeof "255.255.255.255"];


    int src_socket = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);       

    if(src_socket < 0){
        fprintf(stderr, "Could not open raw socket.\n");
        exit(-1);
    }

    /* a IP_HDRINCL call, to make sure that the kernel knows
    *     the header is included in the data, and doesn't insert
    *     its own header into the packet before our data
    */
    int tmp = 1;
    const int *val = &tmp;
    if(setsockopt(src_socket, IPPROTO_IP, IP_HDRINCL, val, sizeof (tmp)) < 0){
        fprintf(stderr, "Error: setsockopt() - Cannot set HDRINCL!\n");
        exit(-1);
    }


    // SETUP DETAILS ABOUT THE TARGET MACHINE
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = dst_addr;
    sin.sin_port = htons(dst_port);

    // Clear the data
    memset(datagram, 0, MAX_PACKET_SIZE);

    // Set appropriate fields in headers
    setup_ip_header(iph);
    setup_tcp_header(tcph);

    iph->check = csum ((unsigned short *) datagram, iph->tot_len >> 1);


    for(;;){

        int ret = sendto(src_socket, datagram, iph->tot_len, 0, (struct sockaddr *) &sin, sizeof(sin));

        if(ret == -1)
        {
            fprintf(stderr, "sendto() error!!!.\n");
        }
        else
        {
            fprintf(stdout, "Flooding %s at %u...\n", dst_addr_str , dst_port);
        }

       
        sleep(1);

    }


    return 0;
}

I also noticed that the sizeof(sin) returns only 16, not sure if this is normal. Is that normal?

How can I make the compiler spit out more info about an error when it encounters one?


Solution

  • In this comment you said:

    This is the output: sendto() error: Message too long

    You are setting iph->tot_len to the result of htons() and then passing that value as-is to sendto() as the data size. On a little-endian system, you will end up passing a very large number as the data size. In your case, 40 (0x0028) converts to 10240 (0x2800), which causes Undefined Behavior when sendto() tries to send more data than you gave it.

    The iph->tot_len field does need to be in network byte order, but the value you give to sendto() needs to be in host byte order, so you will have to convert the iph->tot_len value back with ntohs():

    sendto(..., ntohs(iph->tot_len), ...);
    

    Better would be to store the size in a separate variable and then convert it only for the iphdr, eg:

    u_short len = sizeof(struct iphdr) + sizeof(struct tcphdr);
    ...
    iph->tot_len = htons(len);
    ...
    sendto(..., len, ...);