Mac系統ping不用提權的原因
前幾天在做udp嗅探的時候,發現一個問題,蘋果系統的ping(ping6)是不用提權的,沒有s位,引起了我的好奇,因為在我使用過的ubuntu系統或者是centos系統也好,這個ping都是帶s位的。在解決主要問題的時候也順便把這個問題給解決了。
mac的ping:
centos的ping:
會建立s許可權,是為了讓一般使用者在執行某些程式的時候,能夠暫時具有該程式擁有者的許可權,ubuntu系統或者centos系統下ping程式為了接收ICMP報文,套接字使用的是SOCK_RAW,而要建立這種套接字,需要有root許可權,所以會帶s位。這裡找到一篇關於自己實現ping的文章,裡面就是用到了SOCK_RAW:
順著這個原因去翻了下蘋果系統的原始碼:
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)