ICMP sockets (linux)
Asked Answered
A

2

17

Is it possible to use ICMP sockets under the IP protocol? Maybe something like:

socket(PF_INET, <type>, IPPROTO_ICMP)?

What should I put in the <type> field? I saw some examples using SOCK_RAW, but won't that prevent the OS from doing his job handling the IP protocol?

And another thing. How can the OS know to which process he should send the ICMP datagrams, since there are no ports involved with the protocol?

Aspirate answered 27/11, 2011 at 23:46 Comment(0)
R
10

Yes it is possible, since the ping command does ICMP.

To find out the syscalls involved, you can strace that command (under root).

You could also glance into that command's source code, e.g. Debian's ping

And there is the liboping library to help you...

Raymonderaymonds answered 28/11, 2011 at 0:17 Comment(0)
I
38

Linux have a special ICMP socket type you can use with:

  socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP);

This allows you to only send ICMP echo requests The kernel will handle it specially (match request/responses, fill in the checksum).

This only works if a special sysctl is set. By default not even root can use this kind of socket. You specify the user groups that can access it. To allow root (group 0) to use ICMP sockets, do:

 sysctl -w net.ipv4.ping_group_range="0 0"

Here is an example program to demonstrate the very basic usage of sending an ICMP echo request:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/select.h>

//note, to allow root to use icmp sockets, run:
//sysctl -w net.ipv4.ping_group_range="0 0"

void ping_it(struct in_addr *dst)
{
    struct icmphdr icmp_hdr;
    struct sockaddr_in addr;
    int sequence = 0;
    int sock = socket(AF_INET,SOCK_DGRAM,IPPROTO_ICMP);
    if (sock < 0) {
        perror("socket");
        return ;
    }

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

    memset(&icmp_hdr, 0, sizeof icmp_hdr);
    icmp_hdr.type = ICMP_ECHO;
    icmp_hdr.un.echo.id = 1234;//arbitrary id

    for (;;) {
        unsigned char data[2048];
        int rc;
        struct timeval timeout = {3, 0}; //wait max 3 seconds for a reply
        fd_set read_set;
        socklen_t slen;
        struct icmphdr rcv_hdr;

        icmp_hdr.un.echo.sequence = sequence++;
        memcpy(data, &icmp_hdr, sizeof icmp_hdr);
        memcpy(data + sizeof icmp_hdr, "hello", 5); //icmp payload
        rc = sendto(sock, data, sizeof icmp_hdr + 5,
                        0, (struct sockaddr*)&addr, sizeof addr);
        if (rc <= 0) {
            perror("Sendto");
            break;
        }
        puts("Sent ICMP\n");

        memset(&read_set, 0, sizeof read_set);
        FD_SET(sock, &read_set);

        //wait for a reply with a timeout
        rc = select(sock + 1, &read_set, NULL, NULL, &timeout);
        if (rc == 0) {
            puts("Got no reply\n");
            continue;
        } else if (rc < 0) {
            perror("Select");
            break;
        }

        //we don't care about the sender address in this example..
        slen = 0;
        rc = recvfrom(sock, data, sizeof data, 0, NULL, &slen);
        if (rc <= 0) {
            perror("recvfrom");
            break;
        } else if (rc < sizeof rcv_hdr) {
            printf("Error, got short ICMP packet, %d bytes\n", rc);
            break;
        }
        memcpy(&rcv_hdr, data, sizeof rcv_hdr);
        if (rcv_hdr.type == ICMP_ECHOREPLY) {
            printf("ICMP Reply, id=0x%x, sequence =  0x%x\n",
                            icmp_hdr.un.echo.id, icmp_hdr.un.echo.sequence);
        } else {
            printf("Got ICMP packet with type 0x%x ?!?\n", rcv_hdr.type);
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("usage: %s destination_ip\n", argv[0]);
        return 1;
    }
    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;
    }

    ping_it(&dst);
    return 0;
}

Note that the kernel will reject and fail the sendto() call if the data sent does not have room for a proper ICMP header, and the ICMP type must be 8 (ICMP_ECHO) and the ICMP code must be 0.

Illumine answered 20/11, 2013 at 19:40 Comment(5)
Wouldn't you socket creation be wrong? SOCK_DRAM is part of the Transport layer of the OSI/ISO 7 layer stack... ICMP is part of layer 3 in the network layer. I've been reading Unix Socket Programming by Stevens and in order to do this you need to declare a SOCK_RAW in order to get icmp packets. check out this code hereZosima
@Florida_Jake SOCK_DGRAM is not tied to layer 7. The Unix Socket Programming book doesn't describe how to use the linux specific ICMP echo sockets, the example here works. The traditional way of sending/receiving ICMP messages is indeed to use SOCK_RAW, and build up the ICMP messages yourself. The code I posted uses an alternative, linux specific feature to send and receive ICMP echo messages.Illumine
Nice, worked great for me to quickly get a result. Now is the time to look at IPv6 ping. :)Reluctance
I found the sysctl mention here very helpful, I was trying to make this kind of socket as root and getting permission denied and was like wtf!Whipperin
I just adapted this code to a higher-level language, and it works great. The only clarification I would add is that there is no need to assign icmp_hdr.un.echo.id to anything. Linux will choose a identifier on its own. According to the patch: "The id is set to the number (local port) of the socket, the checksum is always recomputed."Bibliogony
R
10

Yes it is possible, since the ping command does ICMP.

To find out the syscalls involved, you can strace that command (under root).

You could also glance into that command's source code, e.g. Debian's ping

And there is the liboping library to help you...

Raymonderaymonds answered 28/11, 2011 at 0:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.