Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending ICMP packets in a C program

Tags:

c

ping

icmp

sendto

I'm trying to create an ICMP ping test program in C but am having difficulties with successfully sending the packets. The sendto function returns the # of bytes and everything but no packets are actually sent. I've verified this with WireShark on the destination computer. A regular ping on the host works fine and shows up in WireShark though.

Here's my code:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>

unsigned short cksum(unsigned short *addr, int len);

int main(int argc, char *argv[])
{
    int sock;
    char send_buf[400], recv_buf[400], src_name[256], src_ip[15], dst_ip[15];
    struct ip *ip = (struct ip *)send_buf;
    struct icmp *icmp = (struct icmp *)(ip + 1);
    struct hostent *src_hp, *dst_hp;
    struct sockaddr_in src, dst;
    struct timeval t;
    int on;
    int num = 10;
    int failed_count = 0;
    int bytes_sent, bytes_recv;
    int dst_addr_len;
    int result;
    fd_set socks;

    /* Initialize variables */
    on = 1;
    memset(send_buf, 0, sizeof(send_buf));

    /* Check for valid args */
    if(argc < 2)
    {
        printf("\nUsage: %s <dst_server>\n", argv[0]);
        printf("- dst_server is the target\n");
        exit(EXIT_FAILURE);
    }

    /* Check for root access */
    if (getuid() != 0)
    {
        fprintf(stderr, "%s: This program requires root privileges!\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Get source IP address */
    if(gethostname(src_name, sizeof(src_name)) < 0)
    {
        perror("gethostname() error");
        exit(EXIT_FAILURE);
    }
    else
    {
        if((src_hp = gethostbyname(src_name)) == NULL)
        {
            fprintf(stderr, "%s: Can't resolve, unknown source.\n", src_name);
            exit(EXIT_FAILURE);
        }
        else
            ip->ip_src = (*(struct in_addr *)src_hp->h_addr);
    }

    /* Get destination IP address */
    if((dst_hp = gethostbyname(argv[1])) == NULL)
    {
        if((ip->ip_dst.s_addr = inet_addr(argv[1])) == -1)
        {
            fprintf(stderr, "%s: Can't resolve, unknown destination.\n", argv[1]);
            exit(EXIT_FAILURE);
        }
    }
    else
    {
        ip->ip_dst = (*(struct in_addr *)dst_hp->h_addr);
        dst.sin_addr = (*(struct in_addr *)dst_hp->h_addr);
    }
    
    sprintf(src_ip, "%s", inet_ntoa(ip->ip_src));
    sprintf(dst_ip, "%s", inet_ntoa(ip->ip_dst));
    printf("Source IP: '%s' -- Destination IP: '%s'\n", src_ip, dst_ip);

    /* Create RAW socket */
    if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
    {
        perror("socket() error");

        /* If something wrong, just exit */
        exit(EXIT_FAILURE);
    }

    /* Socket options, tell the kernel we provide the IP structure */
    if(setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
    {
        perror("setsockopt() for IP_HDRINCL error");
        exit(EXIT_FAILURE);
    }

    /* IP structure, check the ip.h */
    ip->ip_v = 4;
    ip->ip_hl = 5;
    ip->ip_tos = 0;
    ip->ip_len = htons(sizeof(send_buf));
    ip->ip_id = htons(321);
    ip->ip_off = htons(0);
    ip->ip_ttl = 255;
    ip->ip_p = IPPROTO_ICMP;
    ip->ip_sum = 0;

    /* ICMP structure, check ip_icmp.h */
    icmp->icmp_type = ICMP_ECHO;
    icmp->icmp_code = 0;
    icmp->icmp_id = 123;
    icmp->icmp_seq = 0;
    
    /* Set up destination address family */
    dst.sin_family = AF_INET;
    
    /* Loop based on the packet number */
    for(int i = 1; i <= num; i++)
    {
        /* Header checksums */
        icmp->icmp_cksum = 0;
        ip->ip_sum = cksum((unsigned short *)send_buf, ip->ip_hl);
        icmp->icmp_cksum = cksum((unsigned short *)icmp,
                           sizeof(send_buf) - sizeof(struct icmp));
        
        /* Get destination address length */
        dst_addr_len = sizeof(dst);
        
        /* Set listening timeout */
        t.tv_sec = 5;
        t.tv_usec = 0;
    
        /* Set socket listening descriptors */
        FD_ZERO(&socks);
        FD_SET(sock, &socks);
        
        /* Send packet */
        if((bytes_sent = sendto(sock, send_buf, sizeof(send_buf), 0,
                         (struct sockaddr *)&dst, dst_addr_len)) < 0)
        {
            perror("sendto() error");
            failed_count++;
            printf("Failed to send packet.\n");
            fflush(stdout);
        }
        else
        {
            printf("Sent %d byte packet... ", bytes_sent);
            
            fflush(stdout);
            
            /* Listen for the response or timeout */
            if((result = select(sock + 1, &socks, NULL, NULL, &t)) < 0)
            {
                perror("select() error");
                failed_count++;
                printf("Error receiving packet!\n");
            }
            else if (result > 0)
            {
                printf("Waiting for packet... ");
                fflush(stdout);
                
                if((bytes_recv = recvfrom(sock, recv_buf,
                     sizeof(ip) + sizeof(icmp) + sizeof(recv_buf), 0,
                     (struct sockaddr *)&dst,
                     (socklen_t *)&dst_addr_len)) < 0)
                {
                    perror("recvfrom() error");
                    failed_count++;
                    fflush(stdout);
                }
                else
                    printf("Received %d byte packet!\n", bytes_recv);
            }
            else
            {
                printf("Failed to receive packet!\n");
                failed_count++;
            }
            
            fflush(stdout);
            
            icmp->icmp_seq++;
        }
    }
    
    /* Display success rate */
    printf("Ping test completed with %d%% success rate.\n",
           (((num - failed_count) / num) * 100));
    
    /* close socket */
    close(sock);

    return 0;
}

/* One's Complement checksum algorithm */
unsigned short cksum(unsigned short *addr, int len)
{
    int nleft = len;
    int sum = 0;
    unsigned short *w = addr;
    unsigned short answer = 0;

    while (nleft > 1)
    {
      sum += *w++;
      nleft -= 2;
    }

    if (nleft == 1)
    {
      *(unsigned char *)(&answer) = *(unsigned char *)w;
      sum += answer;
    }

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

Any thoughts at what I'm doing wrong? Also, are the checksums alright? Is it fine to just use 0 for both?

EDIT: OK, so I've managed to get the packets sent properly thanks to the folks below! And the destination computer will send back an echo reply. However, now, my program doesn't pick up the reply. The select() function always times out. I've updated my code above.

EDIT 2: Alright, I've got it working! I needed to set the socket protocol to IPPROTO_ICMP instead of IPPROTO_RAW and it worked great! Thanks again you guys that commented! Really helped out! Looks like I can only mark one answer correct, but you all aided in fixing this. :)

like image 317
Zac Avatar asked Mar 13 '12 17:03

Zac


People also ask

How are ICMP packets sent?

ICMP creates and sends messages to the source IP address indicating that a gateway to the internet, such as a router, service or host, cannot be reached for packet delivery. Any IP network device has the capability to send, receive or process ICMP messages.

Can hackers use ICMP?

Spoofing. Hackers send ICMP packets with falsified IP addresses. The two begin to ping one another endlessly. Tracing. Hackers use the traceroute command to determine your network's topology. That data could be used in a later attack.


2 Answers

Just one thing that I've noticed...

You've got this:

 struct ip *ip = (struct ip *)send_buf;

Then, you are assigning destination field:

ip->ip_dst = (*(struct in_addr *)dst_hp->h_addr)

And then you are erasing it with memset (since send_buff points to the same thing):

memset(send_buf, 0, sizeof(send_buf));

So, when you are trying to get ip_dst here:

dst.sin_addr = ip->ip_dst;

you are getting 0 instead of what you set earlier.

like image 83
ebutusov Avatar answered Oct 06 '22 00:10

ebutusov


At first glance: You can't rely on time struct after select(). You should also set usec.

So in the code, include the specification of t values inside the for loop:

for (i = 1; i <= num; i++) {
    t.tv_sec = 5;
    t.tv_usec = 0;
    ...

Else when you get on second iteration t (can have) changed.

In your sprintf(src_ip, ...) and dst_ip you have omitted format.

like image 44
Morpfh Avatar answered Oct 06 '22 00:10

Morpfh