|
有時候,寫UDP socket程序的時候,在調(diào)用sendto或者recvfrom的時候,會發(fā)現(xiàn)有Connection refused錯誤返回,錯誤碼是ECONNREFUSED。對于懂得socket接口但是不很很懂網(wǎng)絡(luò)的人,可能這根本就不是個問題,他會根據(jù)錯誤碼知道遠(yuǎn)端沒有這個服務(wù)端口,正如socket api的man手冊中描述的那樣: ECONNREFUSED A remote host refused to allow the network connection (typically because it is not running the requested service). 有時候無知真的是一種幸福!但是如果你十分精通TCP/IP棧,那么就想不通了,UDP既然無連接,怎么知道遠(yuǎn)端的情況呢?UDP不正如協(xié)議標(biāo)準(zhǔn)描述的那樣,發(fā)出去就不管了嗎?對于接收,沒有數(shù)據(jù)就一直等,如果設(shè)置了NOWAIT,則直接返回EAGAIN,表示稍后再試。不管怎么說,也不會有ECONNREFUSED這么詳細(xì)的信息返回才對啊。 既然UDP不會從對端返回任何錯誤信息,那么一定有別的什么返回了,總不能憑空猜測啊。這就涉及到了網(wǎng)絡(luò)協(xié)議設(shè)計(jì)中的數(shù)據(jù)平面和控制平面了,對于控制平面的消息,可以是帶內(nèi)傳輸,也可以是帶外傳輸。對于TCP而言,無疑是帶內(nèi)傳輸?shù)?,因?yàn)樗旧砭褪怯羞B接的協(xié)議,協(xié)議本身會處理任何的錯誤和異常,然而對于UDP而言,因?yàn)槠湓O(shè)計(jì)目的就是保持簡單性,故不再附帶有任何帶內(nèi)的控制消息邏輯,互聯(lián)網(wǎng)上為了彌補(bǔ)這一類協(xié)議的控制邏輯的缺失,ICMP協(xié)議才顯得尤為重要!實(shí)際上,ICMP,根據(jù)名稱就可以看出它是一種專門的控制協(xié)議,控制和指示IP層發(fā)生的事件。 ECONNREFUSED正是ICMP返回的!然而并不是所有的UDP socket都可以享用ICMP帶來的錯誤提示,畢竟帶外控制消息和協(xié)議本身的關(guān)聯(lián)太松散了。UDP socket必須顯式的connect對端才可以。現(xiàn)在問題又來了,既然UDP根本就是一個無連接的協(xié)議,connect的意義何在呢?這其實(shí)是socket接口設(shè)計(jì)的范疇,和協(xié)議本身沒有任何關(guān)系,當(dāng)一個UDP socket去connect一個遠(yuǎn)端時,并沒有發(fā)送任何的數(shù)據(jù)包,其效果僅僅是在本地建立了一個五元組映射,對應(yīng)到一個對端,該映射的作用正是為了和UDP帶外的ICMP控制通道捆綁在一起,使得UDP socket的接口含義更加豐滿。 我們知道,ICMP錯誤信息返回時,ICMP的包內(nèi)容就是出錯的那個原始數(shù)據(jù)包,根據(jù)這個原始數(shù)據(jù)包可以找出一個五元組,根據(jù)該五元組就可以對應(yīng)到一個本地的connect過的UDP socket,進(jìn)而把錯誤消息傳輸給該socket,應(yīng)用程序在調(diào)用socket接口函數(shù)的時候,就可以得到該錯誤消息。如果一個UDP socket沒有調(diào)用過connect,那么即使有ICMP數(shù)據(jù)包返回,由于socket保持了UDP的完整語義,協(xié)議棧也就不保存關(guān)于該socket和對端關(guān)聯(lián)的任何信息,因此也就無法找到一個特定的五元組將錯誤碼傳給它。 以下是一個測試程序: 編譯為UDPclient,執(zhí)行./UDPclient 192.168.1.20,注意,這個地址一定要是個IP可達(dá)的地址,才好測試。按照上面的理論,結(jié)果應(yīng)該是:第一個sendto成功,然后192.168.1.20返回了: ICMP 192.168.1.20 udp port 12345 unreachable, length 40 接下來第二個sendto返回: write: Connection refused 由于第二次沒有發(fā)送任何數(shù)據(jù)包到達(dá)192.168.1.20,所以也不能企望它返回ICMP錯誤信息,因此接下來的recvfrom調(diào)用會阻塞。 最后的一個問題時,你不能太指望這個Connection refused以及一切帶外返回的錯誤信息,因?yàn)槟悴荒鼙WC一定能收到遠(yuǎn)端發(fā)送的ICMP包,如果中間的某個節(jié)點(diǎn)或者本機(jī)禁掉了ICMP,socket api調(diào)用就無法捕獲這些錯誤。 本文將講解為什么服務(wù)器回復(fù)端口不可達(dá),以及客戶端socket 如何獲取 端口不可達(dá) 信號。
首先,做為服務(wù)器,當(dāng)一個報(bào)文經(jīng)過查路由,目的ip是上送本機(jī)的時候,經(jīng)過netfilter 判決后, 調(diào)用ip_local_deliver_finish,它根據(jù)ip頭中的協(xié)議類型(TCP/UDP/ICMP/......),調(diào)用不同的4層接口函數(shù)進(jìn)行處理。
對于udp而言,handler 是udp_rcv,它直接調(diào)用了__udp4_lib_rcv,查找相應(yīng)的sock,
如果sk不存在if(sk != NULL),就回復(fù)icmp destination unreachable,函數(shù)非常簡單
所以作為服務(wù)器,收到一個目的端口并未監(jiān)聽的報(bào)文,直接回復(fù)端口不可達(dá)。 那么作為客戶端,如何處理服務(wù)器回復(fù)的 端口不可達(dá) 報(bào)文呢? 起始當(dāng)初想法很簡單,我認(rèn)為,不同的協(xié)議之間是不會干涉的,即TCP和UDP直接是不會干涉的。 何況這種不倫不類的icmp?后來想錯了。
作為客戶端,端口不可達(dá)報(bào)文進(jìn)入ip_local_deliver_finish,它調(diào)用icmp_rcv函數(shù),進(jìn)行處理。(其實(shí)這也是當(dāng)初 我認(rèn)為客戶端udp不會對端口不可達(dá)數(shù)據(jù)進(jìn)行相應(yīng)的原因,因?yàn)閡dp處理流程是udp_rcv)。 icmp_rcv函數(shù)最重要的是 它調(diào)用了:icmp_pointers[icmph->type].handler(skb); handler = icmp_unreach icmp_unreach函數(shù)最終的一步,就是它最后一步:
是不是很像ip_local_deliver_finish? 是很像,只是ip_local_deliver_finish中,調(diào)用了ipprot->handler,而這里調(diào)用了ipprot->err_handler
對于udp,err_handler = udp_err = __udp4_lib_err 在該函數(shù)中,只有進(jìn)入如下的流程,應(yīng)用程序才會反應(yīng):
先決條件是inet->recverr為非0,或者inet->recverr為0但是udp處于TCP_ESTABLISHED狀態(tài)。 否則應(yīng)用程序休想收到該端口不可達(dá)的數(shù)據(jù),應(yīng)用程序就等著read超時吧。所以說,為了獲取udp端口不可達(dá)的情況 有2種方法:
法1: 對udp進(jìn)行connect操作,并且將sendto改成send
法2:
int val = 1; setsockopt(fd, IPPROTO_IP, IP_RECVERR , &val,sizeof(int));
udp獲知端口不可達(dá)的源程序
|
|
|