ICMP套接字(Linux)

17

是否可以在IP协议下使用ICMP套接字?可能会是这样的:

socket(PF_INET, <type>, IPPROTO_ICMP)

<type>字段中应该放什么?我看到一些示例使用SOCK_RAW,但那样不会阻止操作系统处理IP协议吗?

还有一件事。由于该协议没有涉及端口,操作系统如何知道要将ICMP数据报发送给哪个进程?

2个回答

38

Linux有一种特殊的ICMP套接字类型,您可以使用:

  socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP);

这使您仅发送ICMP回显请求,内核会特殊处理它(匹配请求/响应,填写校验和)。
只有在设置了特殊sysctl时才能起作用。默认情况下,甚至root用户也无法使用此类套接字。您可以指定可以访问它的用户组。要允许root(组0)使用ICMP套接字,请执行以下操作:
 sysctl -w net.ipv4.ping_group_range="0 0"

以下是一个示例程序,演示了发送ICMP回显请求的基本用法:
#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;
}

请注意,如果发送的数据没有足够的空间容纳正确的ICMP头部,内核将拒绝并使sendto()调用失败,并且ICMP类型必须为8(ICMP_ECHO),ICMP代码必须为0。请保留HTML标签。

你的套接字创建不会有问题吗?SOCK_DRAM是OSI/ISO 7层协议栈中传输层的一部分... ICMP是网络层中的第3层。我一直在阅读Stevens的Unix套接字编程,为了做到这一点,您需要声明一个SOCK_RAW以获取icmp数据包。请查看此处的代码链接 - Jacob Bryan
3
SOCK_DGRAM并不绑定于第七层。《Unix Socket编程》一书未描述如何使用Linux特定的ICMP回显套接字,但此处的示例可行。传统的发送/接收ICMP消息的方式确实是使用SOCK_RAW,自己构建ICMP消息。我发布的代码使用了一种替代的、特定于Linux的功能来发送和接收ICMP回显消息。 - nos
很好,对我来说非常有效,可以快速得到结果。现在是时候看看IPv6 ping了。 :) - Pavel Šimerda
我发现这里提到的sysctl非常有用,我尝试以root身份创建此类套接字并收到了拒绝访问的错误消息,感觉很烦! - Kenneth Wilke
2
我刚刚将这段代码转换为一种更高级别的语言,结果非常好。我要补充的唯一说明是不需要将icmp_hdr.un.echo.id分配给任何东西。 Linux会自己选择一个标识符。根据补丁的说法:“id设置为套接字的数字(本地端口),校验和总是重新计算”。 - Andrew Thaddeus Martin

10

是的,这是可能的,因为 ping 命令使用 ICMP 协议。

要查找所涉及的系统调用,您可以使用 strace 命令(在 root 下)。

您还可以查看该命令的源代码,例如 Debian 的 ping

此外,还有 liboping 库可供参考...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接