Mac系统ping不用提权的原因

前几天在做udp嗅探的时候,发现一个问题,苹果系统的ping(ping6)是不用提权的,没有s位,引起了我的好奇,因为在我使用过的ubuntu系统或者是centos系统也好,这个ping都是带s位的。在解决主要问题的时候也顺便把这个问题给解决了。
mac的ping:
mac的ping
centos的ping:
在这里插入图片描述
会创建s权限,是为了让一般用户在执行某些程序的时候,能够暂时具有该程序拥有者的权限,ubuntu系统或者centos系统下ping程序为了接收ICMP报文,套接字使用的是SOCK_RAW,而要创建这种套接字,需要有root权限,所以会带s位。这里找到一篇关于自己实现ping的文章,里面就是用到了SOCK_RAW: https://www.cnblogs.com/skyfsm/p/6348040.html

顺着这个原因去翻了下苹果系统的源码:
https://opensource.apple.com/source/network_cmds/network_cmds-328/ping.tproj/ping.c
在这里插入图片描述
发现它可以使用socket(AF_INET,SOCK_DGRAM, IPPROTP_ICMP)这种套接字,这种套接字怎么来理解呢?

socket函数的原型是socket(int family, int type, int protocol)。其中type参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW等等,它们分别表明字节流、数据报、有序分组、原始套接口,protocol指定相应的传输协议,也就是诸如TCP或UDP协议等等,一般默认是0,也就是IPPROTO_IP协议。我们平常使用最多的是TCP和UDP的协议,即socket(AF_INET,SOCK_STREAM, 0)和socket(AF_INET,SOCK_DGRAM, 0)这两种套接字。

当我们使用socket(AF_INET,SOCK_DGRAM, IPPROTP_ICMP)这种套接字的时候其实就是创建了一个ICMP协议的数据报 socket,我们都知道,数据报是网络传输的最小单元,而且ICMP不需要依附传输层的协议:
在这里插入图片描述

所以这种创建这种套接字是合法的,但并非所有的平台都能创建,这还是要取决于内核/proc/sys/net/ipv4/ping_group_range 这个属性值,是一对整数,指定了允许使用 ICMP 套接字的组 ID的范围(可修改,需要权限)。在Linux一些版本比如Ubuntu,centos,这个默认值是0 1,意味着没人能够使用这个特性,在Android上这个范围是0 2147483647,意味着进程都可以创建这种套接字。Mac也是可以的,所以也说明了为什么ubuntu下的ping是带s位的,而Mac和Android设备上的ping是不用带的,因为使用这种socket已经可以达到ping的功能。

这种套接字是有一定的局限性,不能跟SOCK_RAW相比,但也比它方便,内核会帮我们做一些处理,详情可看 https://lwn.net/Articles/420800/ ,而且这种socket是有漏洞的,可以通过这个漏洞来提权,主要是在Android设备上:http://www.codexiu.cn/android/blog/5827/

使用socket(AF_INET,SOCK_DGRAM, IPPROTP_ICMP)这种套接字来实现ping的功能,根据 https://lwn.net/Articles/420800/ 这篇文章的描述,类型(ECHO,只能为0)、code(只能为0)、校验和(不需要管)、id(不需要管)、序列号(无所谓)。

void icmp_test(const char* ip, int port) {
    struct sockaddr_in addr;
    struct icmp icmp_hdr;
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0)
    {
        printf("socket() errno: %i\n", errno);
        return;
    }
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip);
    addr.sin_port = htons(port);
    memset(&icmp_hdr, 0, sizeof(icmp_hdr));
    
    // 只要设置这一个就好了
    icmp_hdr.icmp_type = ICMP_ECHO;
    // Initialize the packet data (header and payload)    
    struct timeval timeout = {1 , 0};
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));  
    if( sendto(sock, packetdata, sizeof(packetdata), 0, (struct sockaddr*) &addr, sizeof(addr)) >= 0)
    {
        unsigned  char buf[1024];
        memset(buf, 0, sizeof(buf));
        socklen_t socklen;
        i = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &socklen);
        if (i < 0) {
            printf("receive null!, errno : %d", errno);
        } 
    
    } 
    close(sock);
}

如果主机不可达的话recvfrom是收不到任何信息的。至于具体的错误信息比如端口或主机不可到达错误,这个我在实验过程中倒是没有收到这类信息,有可能是我用法有错误。

如果要收到ICMP的差错报文,并非一定要使用socket(AF_INET,SOCK_DGRAM, IPPROTP_ICMP)这种套接字,其实使用基本的套接字比如socket(AF_INET,SOCK_DGRAM, 0)就可以了,然后给套接字设置一些额外的选项。详情可看: http://zkheartboy.blogspot.com/2007/10/blog-post.html ,我的主要问题即udp嗅探端口就是借鉴了这篇博客的代码,根据错误信息来判断是主机还是端口不可达,同时这种套接字也没有漏洞和平台的问题。不过在试验这段代码的时候一直收不到错误信息,后来我想了下可能因为是异步错误,信息不及时,只调用一次recvmsg可能来不及收到,就把它改成while循环去获取,尝试5次,间隔1秒,就好了。
while ( (bread = recvmsg( fd, &msg, MSG_ERRQUEUE ) ) == -1)

猜你喜欢

转载自blog.csdn.net/aa642531/article/details/85461294