1. 程式人生 > >Mac系統ping不用提權的原因

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)