有時候,,寫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è)計中的數(shù)據(jù)平面和控制平面了,,對于控制平面的消息,,可以是帶內(nèi)傳輸,也可以是帶外傳輸,。對于TCP而言,,無疑是帶內(nèi)傳輸?shù)模驗樗旧砭褪怯羞B接的協(xié)議,,協(xié)議本身會處理任何的錯誤和異常,,然而對于UDP而言,因為其設(shè)計目的就是保持簡單性,,故不再附帶有任何帶內(nèi)的控制消息邏輯,,互聯(lián)網(wǎng)上為了彌補這一類協(xié)議的控制邏輯的缺失,ICMP協(xié)議才顯得尤為重要,!實際上,,ICMP,根據(jù)名稱就可以看出它是一種專門的控制協(xié)議,,控制和指示IP層發(fā)生的事件,。 ECONNREFUSED正是ICMP返回的!然而并不是所有的UDP socket都可以享用ICMP帶來的錯誤提示,,畢竟帶外控制消息和協(xié)議本身的關(guān)聯(lián)太松散了。UDP socket必須顯式的connect對端才可以?,F(xiàn)在問題又來了,,既然UDP根本就是一個無連接的協(xié)議,connect的意義何在呢?這其實是socket接口設(shè)計的范疇,,和協(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以及一切帶外返回的錯誤信息,因為你不能保證一定能收到遠(yuǎn)端發(fā)送的ICMP包,,如果中間的某個節(jié)點或者本機禁掉了ICMP,,socket api調(diào)用就無法捕獲這些錯誤。 本文將講解為什么服務(wù)器回復(fù)端口不可達(dá),,以及客戶端socket 如何獲取 端口不可達(dá) 信號,。
首先,做為服務(wù)器,當(dāng)一個報文經(jīng)過查路由,,目的ip是上送本機的時候,,經(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)聽的報文,,直接回復(fù)端口不可達(dá)。 那么作為客戶端,,如何處理服務(wù)器回復(fù)的 端口不可達(dá) 報文呢,? 起始當(dāng)初想法很簡單,我認(rèn)為,,不同的協(xié)議之間是不會干涉的,,即TCP和UDP直接是不會干涉的。 何況這種不倫不類的icmp,?后來想錯了,。
作為客戶端,端口不可達(dá)報文進(jìn)入ip_local_deliver_finish,,它調(diào)用icmp_rcv函數(shù),,進(jìn)行處理。(其實這也是當(dāng)初 我認(rèn)為客戶端udp不會對端口不可達(dá)數(shù)據(jù)進(jìn)行相應(yīng)的原因,,因為udp處理流程是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á)的源程序
|
|