久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

關(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è)計中的數(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)的任何信息,,因此也就無法找到一個特定的五元組將錯誤碼傳給它。
        以下是一個測試程序:
  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以及一切帶外返回的錯誤信息,因為你不能保證一定能收到遠(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á)的源程序

  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ā)布,不代表本站觀點,。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多