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