Thread: Simple ping routine, not receiving anything.

  1. #1
    Registered User
    Join Date
    Apr 2019
    Posts
    121

    Simple ping routine, not receiving anything.

    Hi,

    I'm trying to create a simple ping routine, that can be used in other programs to test internet connectivity. I'm stuck, as the following code seems to send the request, but `recvfrom` blocks forever.

    Since I don't normally deal with sockets like this, I'm completely lost as to what is wrong. And I don't think I can look at more code that doesn't satisfy my need.

    Here's what I have:
    Code:
    // Declare includes.
    #include <arpa/inet.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <libgen.h>
    #include <netinet/in.h>
    #include <netinet/ip_icmp.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <unistd.h>
    
    // Declare defines.
    #define BUFFER_SIZE      1024
    #define PING_ADDRESS        "8.8.8.8"
    #define RETURN_FAILURE      0
    #define RETURN_SUCCESS      1
    
    // Declare function prototypes.
    int pinger (char *address);
    
    int main (int argc, __attribute__ ((unused)) char *argv[])
    {
    // Check number of arguments.
        if(argc != 1)
        {
            fprintf(stderr, "Usage: %s", program_invocation_short_name);
            exit(EXIT_FAILURE);
        }
    
    // Check that user is root or that sudo is used.
        if(geteuid() != 0)
        {
            fprintf(stderr, "%s: must be run as root or with 'sudo'", program_invocation_short_name);
            exit(EXIT_FAILURE);
        }
    
    // Run process.
        printf("Pinging %s was %s\n", PING_ADDRESS, pinger(PING_ADDRESS) == RETURN_SUCCESS ? "successful." : "unsuccessful.");
    
    // Exit cleanly.
        exit(EXIT_SUCCESS);
    }
    
    int pinger (char *address)
    {
    // Declare variables.
        char buffer[BUFFER_SIZE] = {0};
        struct icmp icmp_hdr = {0};
        struct sockaddr_in dest_addr = {0};
        struct sockaddr_in from_addr = {0};
        socklen_t fromlen = {0};
        int sockfd = {0};
    
    // Populate the icmp structure.
        icmp_hdr.icmp_type = ICMP_ECHO;
        icmp_hdr.icmp_code = 0;
        icmp_hdr.icmp_cksum = 0;
        icmp_hdr.icmp_id = getpid();
        icmp_hdr.icmp_seq = 1;
    
    // Populate the socket address structure.
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_addr.s_addr = inet_addr(address);
        memset(&dest_addr.sin_zero, 0, sizeof(dest_addr.sin_zero));
    
    // Create a raw socket.
        if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
        {
            fprintf(stderr, "%s: %s error: socket failed (%s)", program_invocation_short_name, __func__, strerror(errno));
            return(RETURN_FAILURE);
        }
    
    // Send icmp request.
        if(sendto(sockfd, &icmp_hdr, sizeof(struct icmp), 0, (struct sockaddr *) &dest_addr, sizeof(struct sockaddr_in)) == -1)
        {
            fprintf(stderr, "%s: %s error: sendto failed (%s)", program_invocation_short_name, __func__, strerror(errno));
            return(RETURN_FAILURE);
        }
    
    // Receive icmp response.
        if(recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *) &from_addr, &fromlen) == -1)
        {
            fprintf(stderr, "%s: %s error: recvfrom failed (%s)", program_invocation_short_name, __func__, strerror(errno));
            return(RETURN_FAILURE);
        }
    
    // Return success.
        return(RETURN_SUCCESS);
    }
    Any help or insight is greatly appreciated. Ty.

  2. #2
    Registered User rstanley's Avatar
    Join Date
    Jun 2014
    Location
    New York, NY
    Posts
    1,115
    When compiling with gcc on Linux, I get the following errors:
    Code:
    com bar.c
    bar.c: In function ‘main’:
    bar.c:28:38: error: ‘program_invocation_short_name’ undeclared (first use in this function)
       28 |         fprintf(stderr, "Usage: %s", program_invocation_short_name);
          |                                      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    bar.c:28:38: note: each undeclared identifier is reported only once for each function it appears in
    bar.c: In function ‘pinger’:
    bar.c:50:12: error: variable ‘icmp_hdr’ has initializer but incomplete type
       50 |     struct icmp icmp_hdr = {0};
          |            ^~~~
    bar.c:50:29: warning: excess elements in struct initializer
       50 |     struct icmp icmp_hdr = {0};
          |                             ^
    bar.c:50:29: note: (near initialization for ‘icmp_hdr’)
    bar.c:50:17: error: storage size of ‘icmp_hdr’ isn’t known
       50 |     struct icmp icmp_hdr = {0};
          |                 ^~~~~~~~
    bar.c:71:61: error: ‘program_invocation_short_name’ undeclared (first use in this function)
       71 |         fprintf(stderr, "%s: %s error: socket failed (%s)", program_invocation_short_name, __func__, strerror(errno));
          |                                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    bar.c:76:41: error: invalid application of ‘sizeof’ to incomplete type ‘struct icmp’
       76 |     if(sendto(sockfd, &icmp_hdr, sizeof(struct icmp), 0, (struct sockaddr *) &dest_addr, sizeof(struct sockaddr_in)) == -1)
          |                                         ^~~~~~
    bar.c:50:17: warning: unused variable ‘icmp_hdr’ [-Wunused-variable]
       50 |     struct icmp icmp_hdr = {0};
          |                 ^~~~~~~~
    Clear these warnings/errors first. Turn on warnings options and turn up to the highest level in whatever O/S & Compiler you are using.

  3. #3
    Registered User
    Join Date
    Apr 2019
    Posts
    121
    Quote Originally Posted by rstanley View Post
    When compiling with gcc on Linux, I get the following errors:
    Clear these warnings/errors first. Turn on warnings options and turn up to the highest level in whatever O/S & Compiler you are using.
    Sorry, I don't get those errors, but it's because of my gcc compile options. I'm compiling with:
    Code:
    gcc -Wall -Werror -Wextra -Wfatal-errors -D_GNU_SOURCE -O3 -o ./pinger pinger.c

  4. #4
    Registered User
    Join Date
    Apr 2019
    Posts
    121
    I just noticed something strange. I ran the program, and while it was hanging and not responding, I opened a terminal and ran `ping 8.8.8.8`. While ping ran, my program actually returned 'successful.'

    So I'm thinking that my code might be correct, it's just missing something. But what?

    Back to view more code...

  5. #5
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    I think you need to set icmp_cksum properly.
    Ping in C - GeeksforGeeks
    A little inaccuracy saves tons of explanation. - H.H. Munro

  6. #6
    Registered User
    Join Date
    Apr 2019
    Posts
    121
    Quote Originally Posted by john.c View Post
    I think you need to set icmp_cksum properly.
    Ping in C - GeeksforGeeks
    I did come across that link, but the site clearly doesn't check it's work once posted. I found a bunch of problems with this code. For example, the following function:
    Code:
    // Calculating the Check Sum
     unsigned short checksum(void* b, int len)
     {
         unsigned short* buf = b;
         unsigned int sum = 0;
    
         unsigned short result;
    
      
    
         for (sum = 0; len & gt; 1; len -= 2)
    
             sum += *buf++;
    
         if (len == 1)
    
             sum += *(unsigned char*)buf;
    
         sum = (sum & gt; > 16) + (sum & amp; 0xFFFF);
    
         sum += (sum & gt; > 16);
    
         result = ~sum;
    
         return result;
    
     }
    I'm not great with `ones' compliments`, so when I come across `sum += (sum & gt; > 16);` or this `sum = (sum & gt; > 16) + (sum & amp; 0xFFFF);`, it kills my spirit and I must stop looking at it.

    I'll try to decipher this, but I'm not holding hope. Thanks for your reply.

  7. #7
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    I hadn't looked at it closely, but it clearly has some issues (&gt; is how you write > in html, as you probably know). It does make me wonder if it's even a (robustly) correct calculation. Hers's the actual RFC: https://www.ietf.org/rfc/rfc1071.txt
    A little inaccuracy saves tons of explanation. - H.H. Munro

  8. #8
    Registered User
    Join Date
    Apr 2019
    Posts
    121
    I have seen the following in some code, but it doesn't seem to change/fix anything.

    Code:
    icmp_hdr.icmp_cksum = htons(~(ICMP_ECHO << 8));
    I'll give the RFC a look.

  9. #9
    Registered User
    Join Date
    Apr 2019
    Posts
    121
    Quote Originally Posted by Yonut View Post
    I just noticed something strange. I ran the program, and while it was hanging and not responding, I opened a terminal and ran `ping 8.8.8.8`. While ping ran, my program actually returned 'successful.'
    Why would the program only work once a proper ping is issued? Why would the second ping correct the checksum of the original ping? I'm thinking this might have something to do with bind.

  10. #10
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    Quote Originally Posted by Yonut View Post
    Why would the program only work once a proper ping is issued? Why would the second ping correct the checksum of the original ping? I'm thinking this might have something to do with bind.
    Perhaps your program was waiting for a response and stole the response from the other ping program (if that's possible).
    Here's an implementation based on the RFC example C code that might work:
    Code:
    #include <stdio.h>
    #include <inttypes.h>
    
    // Compute Internet Checksum for "count" bytes
    // beginning at location "addr".
    uint16_t checksum(void* addr, int count)
    {
        uint16_t* buf = addr;
        uint32_t sum = 0;
    
        for ( ; count > 1; count -= 2)
            sum += *(uint16_t*)buf++;
    
        //  Add left-over byte, if any
        if (count > 0)
            sum += *(unsigned char*)addr;
    
        //  Fold 32-bit sum to 16 bits
        while (sum >> 16)
            sum = (sum & 0xffff) + (sum >> 16);
    
        return (uint16_t)~sum;
    }
    
    int main() {
        // ICMP_ECHO == 8
        char dat[] = "/x08/x00/x00/x00/x12/x34/x00/x00";
        uint16_t cs = checksum(dat, 8);
        printf("%04" PRIX16 "\n", cs); // yields A740
        return 0;
    }
    Last edited by john.c; 1 Week Ago at 03:11 PM.
    A little inaccuracy saves tons of explanation. - H.H. Munro

  11. #11
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    Looking at this again, I see that I accidentally used / instead of \ for my hex characters in the string (!), and also since my machine is little-endian I need to swap the bytes at the end of the calculation. This gets the correct result for the first example in the RFC.
    Code:
    #include <stdio.h>
    #include <inttypes.h>
    
    // Compute Internet Checksum for "count" bytes
    // beginning at location "addr".
    uint16_t checksum(void* addr, unsigned count)
    {
        uint16_t* buf = addr;
        uint32_t sum = 0;
    
        for ( ; count > 1; count -= 2)
            sum += *buf++;
    
        //  Add left-over byte, if any
        if (count > 0)
            sum += *(unsigned char*)addr;
    
        //  Fold 32-bit sum to 16 bits
        while (sum >> 16)
            sum = (sum & 0xffff) + (sum >> 16);
    
        // swap bytes since my machine is little endian
        uint16_t result = (uint16_t)~sum;
        result = (result << 8) | ((result >> 8) & 0xFF);
    
        return result;
    }
    
    int main() {
        char dat[] = "\x45\x00\x00\x73\x00\x00\x40\x00\x40\x11"
                     "\x00\x00\xc0\xa8\x00\x01\xc0\xa8\x00\xc7";
        int len = sizeof dat - 1; // -1 for \0 byte added to end of string
    
        uint16_t cs = checksum(dat, len);
        printf("%04" PRIX16 "\n", cs);  // yields B861
    
        return 0;
    }
    A little inaccuracy saves tons of explanation. - H.H. Munro

  12. #12
    Registered User
    Join Date
    Apr 2019
    Posts
    121
    The first function you posted didn't seem to help. So I threw in the towel.

    During my searches the following became clear, regarding what I was trying to do...
    1) ICMP request are the lowest priority, has to wait for everything else.
    2) ICMP requires root access.
    3) Just too many variables as to what I m supposed to do.

    So I copped out and just wrote a simple http tester that checks for access to my service providers DNS server. Super quick, and it solves the 3 problems listed above.

    Code:
    int test_internet_conectivity (void)
    {
    // Declare variables.
        struct addrinfo hints = {0};
        struct addrinfo *res = NULL;
        int ret = {0};
        int sockfd = {0};
    
    // Populate the structure.
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;
    
    // Get network address translation.
        if((ret = getaddrinfo(IP_ADDRESS, "http", &hints, &res)) != 0)
        {
            fprintf(stderr, "%s: %s error: getaddrinfo failed (%s) (%s)", program_invocation_short_name, __func__, gai_strerror(ret), IP_ADDRESS);
            return(RETURN_FAILURE);
        }
    
    // Open the socket.
        if((sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1)
        {
            fprintf(stderr, "%s: %s error: socket failed (%s)", program_invocation_short_name, __func__, strerror(errno));
    // Free allocated memory.
            freeaddrinfo(res);
            return(RETURN_FAILURE);
        }
    
    // Connect to web server.
        if(connect(sockfd, res->ai_addr, res->ai_addrlen) == -1)
        {
            fprintf(stderr, "%s: %s error: connect failed (%s) (%s)", program_invocation_short_name, __func__, strerror(errno), IP_ADDRESS);
    // Free allocated memory and close the socket.
            freeaddrinfo(res);
            close(sockfd);
            return(RETURN_FAILURE);
        }
    
    // Free allocated memory and close the socket.
        freeaddrinfo(res);
        close(sockfd);
    // Alert of success.
        return(RETURN_SUCCESS);
    }
    John, I will try your new function a little later, but I need let my brain decompress from today's events.

  13. #13
    Registered User
    Join Date
    Dec 2017
    Posts
    1,633
    I see you may not want this anymore, but had a little time to look at it today and I think the following works.

    The dump of the returned message looks like this:
    Code:
    45 00 00 30 00 00 00 00 3C 01
    AC D1 08 08 08 08 C0 A8 01 44
    00 00 B8 F0 46 0F 01 00 00 00
    00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00
    
    I believe it is interpreted like this:
    
    Version/IHL:    45           IPv4; length 5 * 32-bits = 20 bytes
    ToS:            00           Type of Service (low priority)
    Length:         00 30        Total length: 48 bytes (big endian)
    Identification: 00 00
    flags & offset: 00 00
    TTL:            3C           Time To Live (max hops before discarding)
    Protocol:       01
    Checksum:       AC D1
    Source IP:      08 08 08 08
    Destination IP: 12 34 56 78  my address, which I've changed
    
    Type:           00           ICMP_ECHOREPLY
    Code:           00
    Checksum:       B8 F0
    Data:
      ID:           46 0F        this is the PID we sent
      Sequence:     01 00        the sequence number we sent
    Payload:        00 00 00 00 00 00 00 00 00 00
                    00 00 00 00 00 00 00 00 00 00  dummy 20-byte payload
    Code:
    #include <stdbool.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <fcntl.h>
    #include <libgen.h>
    #include <netinet/in.h>
    #include <netinet/ip_icmp.h>
    #include <sys/socket.h>
     
    #define BUFFER_SIZE   1024
    #define PING_ADDRESS  "8.8.8.8"
     
    bool pinger (char *address);
     
    int main()
    {
        // Check that user has root access.
        if (geteuid() != 0) {
            fprintf(stderr, "%s: must be run as root or with 'sudo'\n",
                program_invocation_short_name);
            exit(EXIT_FAILURE);
        }
     
        printf("Pinging %s was %ssuccessful.\n", PING_ADDRESS,
            pinger(PING_ADDRESS) ? "" : "un");
    
        return 0;
    }
    
    uint16_t checksum(void* addr, unsigned count)
    {
        uint16_t* buf = addr;
        uint32_t sum = 0;
    
        for ( ; count > 1; count -= 2)
            sum += *buf++;
    
        //  Add left-over byte, if any
        if (count > 0)
            sum += *(unsigned char*)addr;
    
        //  Fold 32-bit sum to 16 bits
        while (sum >> 16)
            sum = (sum & 0xffff) + (sum >> 16);
    
        return (uint16_t)~sum;
    }
    
    bool do_error(const char *msg) {
        fprintf(stderr, "%s: %s error: %s (%s)",
            program_invocation_short_name, __func__, msg, strerror(errno));
        return false;
    }
    
    bool pinger (char *address)
    {
        char buffer[BUFFER_SIZE] = {0};
        struct icmp icmp_hdr = {0};
        struct sockaddr_in dest_addr = {0};
        struct sockaddr_in from_addr = {0};
        socklen_t fromlen = {0};
        int sockfd = {0};
     
        icmp_hdr.icmp_type = ICMP_ECHO;
        icmp_hdr.icmp_code = 0;
        icmp_hdr.icmp_cksum = 0;
        icmp_hdr.icmp_id = getpid();
        icmp_hdr.icmp_seq = 1;
    
        printf("PID: %04X\n", (unsigned)icmp_hdr.icmp_id);
    
        icmp_hdr.icmp_cksum = checksum(&icmp_hdr, sizeof icmp_hdr);
     
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_addr.s_addr = inet_addr(address);
        memset(&dest_addr.sin_zero, 0, sizeof(dest_addr.sin_zero));
     
        // Create a raw socket.
        if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
            return do_error("socket failed");
    
        // Send icmp request.
        if (sendto(sockfd, &icmp_hdr, sizeof(struct icmp), 0,
            (struct sockaddr *) &dest_addr, sizeof(struct sockaddr_in)) == -1)
            return do_error("sendto failed");
    
        // Receive icmp response.
        ssize_t len = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
                               (struct sockaddr *) &from_addr, &fromlen);
        if (len == -1)
            return do_error("recvfrom failed");
     
        for (ssize_t i = 0; i < len; ++i)
            printf("%02X ", (unsigned char)buffer[i]);
        printf("\n");
    
        return true;
    }
    A little inaccuracy saves tons of explanation. - H.H. Munro

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Is this an example of a co-routine?
    By c_weed in forum Tech Board
    Replies: 1
    Last Post: 06-20-2012, 11:36 AM
  2. Replies: 1
    Last Post: 03-21-2008, 08:15 PM
  3. Help with my XOR routine..
    By Neo1 in forum C++ Programming
    Replies: 9
    Last Post: 10-04-2007, 03:30 PM
  4. working, but slow simple graphing routine in gnuplot
    By noguru in forum C Programming
    Replies: 2
    Last Post: 07-26-2006, 11:31 AM
  5. Simple client - receiving data from server and keyboard w/o blocking
    By Spitball in forum Networking/Device Communication
    Replies: 5
    Last Post: 01-08-2005, 12:32 PM

Tags for this Thread