小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

關(guān)于UDP接收icmp端口不可達(dá)(port unreachable)

 gljin_cn 2016-04-13
有時候,寫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)的任何信息,因此也就無法找到一個特定的五元組將錯誤碼傳給它。
        以下是一個測試程序:
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. void test( int sd, struct sockaddr *addr, socklen_t len)  
  9. {  
  10.         char buf[4];  
  11.         connect(sd, (struct sockaddr *)addr, len);  
  12.         sendto(sd, buf, 4, 0, (struct sockaddr *)addr, len);  
  13.          perror("write");  
  14.          sendto(sd, buf, 4, 0, (struct sockaddr *)addr, len);  
  15.          perror("write");  
  16.          recvfrom(sd, buf, 4, 0, (struct sockaddr *)addr, len);  
  17.          perror("read");  
  18. }  
  19. int main(int argc, char **argv)  
  20. {  
  21.         int sd;  
  22.         struct sockaddr_in addr;  
  23.         if(argc != 2) {  
  24.                 exit(1);  
  25.         }  
  26.         bzero(&addr, sizeof(addr));  
  27.         addr.sin_family = AF_INET;  
  28.         addr.sin_port = htons(12345);  
  29.         inet_pton(AF_INET, argv[1], &addr.sin_addr);  
  30.         sd = socket(AF_INET, SOCK_DGRAM, 0);  
  31.         test(sd, (struct sockaddr *)&addr, sizeof(addr));  
  32.         return 0;  
  33. }  
編譯為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á)的源程序

  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. unsigned char revc_buf[1024];  
  6.   
  7. int main()  
  8. {  
  9.     int fd,ret,recv_len,size=1024;  
  10.     struct sockaddr_in server_addr,addr;  
  11.     int val = 1;  
  12.     server_addr.sin_family = AF_INET;  
  13.     server_addr.sin_addr.s_addr = inet_addr("192.168.2.254");  
  14.     server_addr.sin_port = htons(77);  
  15.       
  16.     fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);  
  17.     if(fd < 0)  
  18.     {     
  19.         perror("socket fail ");  
  20.         return -1;  
  21.     }  
  22.       
  23.     printf("socket sucess\n");  
  24.   
  25.         //方法2  
  26.     #if 1  
  27.     setsockopt(fd, IPPROTO_IP, IP_RECVERR , &val,sizeof(int));  
  28.     if(sendto(fd, "nihao", strlen("nihao"), 0, (const struct sockaddr *)&(server_addr), sizeof(struct sockaddr_in))<0)  
  29.     {  
  30.         perror("sendto fail ");  
  31.         return -1;  
  32.     }  
  33.     printf("sendto sucess\n");  
  34.     recv_len = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);  
  35.     printf("recv_len:%d sucess\n");  
  36.     <>"font-size:18px;"><>< span="">"code" class="cpp">        //方法1   <>
  37.     #elif 0  
  38.     ret = connect(fd, (const struct sockaddr *) &(server_addr), sizeof (struct sockaddr_in));  
  39.     if(ret < 0)  
  40.     {  
  41.         printf("connect fail\n");  
  42.         return -1;  
  43.     }  
  44.       
  45.     ret = send(fd, "ni hao", strlen("nihao"),0);  
  46.     if(ret < 0)  
  47.     {  
  48.         printf("write fail\n");  
  49.         return -1;  
  50.     }  
  51.       
  52.     ret = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);  
  53.     if(ret < 0)  
  54.     {  
  55.         printf("read fail\n");  
  56.         return -1;  
  57.     }  
  58.     #endif  
  59.     close(fd);  
  60.       
  61.     return 0;  
  62. }  

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多