stepanVich
stepanVich

Reputation: 338

ICMP request with data

I want to send ICMP request message with some data on the end. I can only send simple ICMP request with no data yet. When i want add some characters at the end of icmp structure that is in buffer, no ICMP request is send. When I delete the ending character or change size of sending message in sendto function to sizeof(icmp), the message is send normally. What is wrong?

Argument in the ping function is fine.

void ping(struct sockaddr_in *addr) {
    int sd;
    const int val=255;
    struct sockaddr_in r_addr;
    sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
    if ( sd < 0 ) {
        perror("socket");
        return;
    }
    if ( setsockopt(sd, SOL_IP, IP_TTL, &val, sizeof(val)) != 0)
        perror("Set TTL option");
    if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 )
        perror("Request nonblocking I/O");
    socklen_t len=sizeof(r_addr);
    struct icmp *icmp_send, *icmp_recv;
    icmp_send->icmp_type = ICMP_ECHO;
    icmp_send->icmp_code = 0;
    icmp_send->icmp_id = getpid();
    icmp_send->icmp_seq = 1;
    icmp_send->icmp_cksum = 0;
    icmp_send->icmp_cksum = checksum(icmp_send, sizeof(icmp_send));
    unsigned char buff[2000];
    unsigned char*p = buff;
    memcpy(buff, icmp_send, sizeof(icmp)); 
    p += sizeof(icmp);
    *p = 'A';
    if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1 , 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
                printf("Send err.\n");
    }
}

Upvotes: 2

Views: 4678

Answers (1)

Philippe Sultan
Philippe Sultan

Reputation: 2378

You need to recompute the checksum in the ICMP header with the bytes you append to the header.

Best way to achieve that is to initialize * icmp_send to point to the your buff string, then mangle your fields there. This would avoid you to memcpy from * icmp_send to buff. Here is an example.

unsigned char buff[2000];
unsigned char *p = buff;
struct icmp *icmp_send;

icmp_send = (struct icmp *)buff;
icmp_send->icmp_type = ICMP_ECHO;
icmp_send->icmp_code = 0;
icmp_send->icmp_id = getpid();
icmp_send->icmp_cksum = 0;
icmp_send->icmp_seq = htons(1);
p += sizeof(icmp_send);
*p = 'A';

icmp_send->icmp_cksum = checksum(buff, sizeof(icmp_send) + 1);

if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1, 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
            printf("Send err.\n");
} 

In the example above, you may adjust the size of the payload to transport (sizeof(icmp_send) + 1) to any value.

UPDATE : As commented by Matteo, casting buff to a struct icmp pointer to access then modify the ICMP fields might not be safe if your platform requires you to handle access to misaligned fields in structures properly. Writing your structure separately and then memcpy to the buff before sending it over the network is the way to go in this case.

Back to your code, you'll get more details in the code sample and complete code hereafter. The idea here is to compute and copy values between pointer as * icmp_send and buff are unrelated.

This code sends an ICMP echo request with data, receives an ICMP echo reply. Tested along with tcpdump to validate that packets are being exchanged between hosts.

...
unsigned char*p = buff;
p += sizeof(icmp_send);
*p = 'A';

// 'A' has been appended into buff right after the header in the previous
// lines, we therefore need to compute the checksum using buff, without
// overwriting 'A', this is why we copy the 8 byte long ICMP header here.
//
// buff looks like [ ICMP HEADER (8 bytes)]['A' (1 byte)]
//
// Before computing the checksum, we also make sure its value in the header
// is set to 0 (mandatory).
icmp_send->icmp_cksum = 0;
memcpy(buff, icmp_send, sizeof(icmp_send));

// Now we can compute the checksum, and have to instruct our checksum()
// function that we want to include the data ('A') by setting the length
// in the second argument to 8 (ICMP header length) + 1 (data length of 'A')
icmp_send->icmp_cksum = checksum(buff, sizeof(icmp_send) + 1);

// Checksum is now ok, lets copy it again to buff. Here we copy the whole
// header but the idea is just to update the ICMP checksum field.
memcpy(buff, icmp_send, sizeof(icmp_send));

// buff contains header + data, ok to send it. Note that you don't need to
// compute the checksum of the IP packet. The system does it for you because
// your socket was created with the IPPROTO_ICMP
if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1, 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
            printf("Send err.\n");
}

Complete code hereafter (OT : make sure to use hton functions when sending data over the network, if needed)

unsigned short checksum(void *b, int len)
{   unsigned short *buf = b;
    unsigned int sum=0;
    unsigned short result;

    for ( sum = 0; len > 1; len -= 2 )
        sum += *buf++;
    if ( len == 1 )
        sum += *(unsigned char*)buf;
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    result = ~sum;
    return result;
}

void ping(struct sockaddr_in *addr) {
    int sd;
    const int val=255;
    struct sockaddr_in r_addr;
    sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
    if ( sd < 0 ) {
        perror("socket");
        return;
    }
    if ( setsockopt(sd, SOL_IP, IP_TTL, &val, sizeof(val)) != 0)
        perror("Set TTL option");
    if ( fcntl(sd, F_SETFL, O_NONBLOCK) != 0 )
        perror("Request nonblocking I/O");
    socklen_t len=sizeof(r_addr);
    struct icmp *icmp_send, *icmp_recv;
    icmp_send->icmp_type = ICMP_ECHO;
    icmp_send->icmp_code = 0;
    icmp_send->icmp_id = getpid();
    icmp_send->icmp_seq = htons(1);
    unsigned char buff[2000];
    unsigned char*p = buff;
    p += sizeof(icmp_send);
    *p = 'A';

    icmp_send->icmp_cksum = 0;
    memcpy(buff, icmp_send, sizeof(icmp_send)) ;

    icmp_send->icmp_cksum = checksum(buff, sizeof(icmp_send) + 1);
    memcpy(buff, icmp_send, sizeof(icmp_send)) ;

    if ( sendto(sd, (unsigned char*)buff, sizeof(icmp_send) + 1, 0, (struct sockaddr*)addr, sizeof(*addr)) <= 0 ) {
                printf("Send err.\n");
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("usage: %s destination_ip\n", argv[0]);
        return 1;
    }

    struct sockaddr_in addr;
    struct in_addr dst;

    if (inet_aton(argv[1], &dst) == 0) {

        perror("inet_aton");
        printf("%s isn't a valid IP address\n", argv[1]);
        return 1;
    }


    memset(&addr, 0, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_addr = dst;

    ping(&addr);
    return 0;
}

Hope this helps!

Upvotes: 4

Related Questions