2016-09-25 145 views
5

我正在發送一些ping數據包,通過我的linux機器上的一個原始套接字。C原始套接字發送地址的目的是什麼?

int sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); 

這意味着,我指定的IP數據包報頭時,我寫入套接字(IP_HDRINCL被隱含)。

寫入與send插座失敗,告訴我我需要指定一個地址。

如果我使用sendto那麼它工作。對於sendto我必須指定一個sockaddr_in結構使用,其中包括字段sin_family,sin_portsin_addr

不過,我已經注意到了幾件事:

  • sin_familyAF_INET - 創建插座時已經指定。
  • sin_port自然未被使用(端口不是IP的概念)。
  • 只要它是外部地址(IP數據包指定爲8.8.8.8,sin_addr指定爲1.1.1.1),我使用哪個地址並不重要。

看來sendto的額外字段實際上在很大程度上沒有任何用處。那麼,是否有一個技術原因,我必須使用sendto而不是send,還是僅僅是API中的疏忽?

回答

6

send寫入套接字失敗,告訴我我需要指定一個地址。

它失敗,因爲send()函數只能在連接插座中使用(如所陳述here)。通常,您將使用send()進行TCP通信(面向連接),並且sendto()可用於發送UDP數據報(無連接)。

由於您想發送「ping」數據包,或者更正確的ICMP數據報,它們顯然是無連接的,所以您必須使用sendto()函數。

看起來sendto的額外字段實際上用於很大的 程度。那麼,是否有技術上的原因,我必須使用sendto 而不是send或者它只是API中的疏忽?

簡短的回答:

當您不允許使用send(),那麼就只剩下一種選擇,叫sendto()

龍答:

它不只是在API中的監督。如果你想用一個普通的套接字發送一個UDP數據報(例如SOCK_DGRAM),sendto()需要你在結構sockaddr_in中提供的有關目標地址和端口的信息,對不對?內核會將該信息插入到結果IP頭中,因爲結構sockaddr_in只有您指定接收者的位置。換句話說:在這種情況下,內核必須從結構中獲取目標信息,因爲您不提供額外的IP標頭。

由於sendto()不僅用於UDP,還用於原始套接字,它必須是或多或少的「通用」功能,可以覆蓋所有不同的使用情況,即使某些參數(如端口號)不相關/最終使用。

例如,通過使用IPPROTO_RAW(它自動暗示IP_HDRINCL),表明您希望自己創建IP標頭。因此,sendto()的最後兩個參數實際上是冗餘信息,因爲它們已經包含在您作爲第二個參數傳遞給sendto()的數據緩衝區中。請注意,即使將IP_HDRINCL用於原始套接字,如果將相應的字段設置爲0,內核也會填入IP數據報的源地址和校驗和。

如果你想編寫自己的ping程序,你也可以在你的socket()功能從IPPROTO_RAW改變的最後一個參數IPPROTO_ICMP並讓內核創建IP報頭的你,讓你有一個東西少操心。現在您可以輕鬆瞭解兩個sendto()-參數*dest_addraddrlen如何再次變得有意義,因爲它是唯一提供目標地址的地方。

語言和API非常陳舊,並隨着時間的推移而增長。從今天的角度來看,一些API看起來很怪異,但是如果不破壞大量的現有代碼,您就無法更改舊界面。有時你必須習慣於多年或幾十年前定義/設計的東西。

希望能回答你的問題。

1

當套接字處於TCP SOCK_STREAM連接狀態時,使用send()調用。

從手冊頁:

在send()調用可用於只有當插座連接器處於連接狀態 (使預期的接收者是已知的)。

由於您的應用程序顯然不與任何其他套接字連接,我們不能指望send()工作。