udp端口不可達 icmp

1,報文格式

報文如下,10.30.13.1往10.30.16.10的80端口發送了一個UDP報文,80端口其實監聽的是TCP。

服務器回覆了一個類型爲端口不可達的ICMP,ICMP數據部分就是請求UDP ip層及其以上的數據。

2,產生的原因

首先原因就是接收udp報文的服務器對應的端口沒有開啓UDP服務器。注意這裏的描述,並不是端口沒有開啓服務,而是沒有開啓UDP服務,如果開啓了TCP服務,照樣也會回port unreachable。

有時候,寫UDP socket程序的時候,在調用sendto或者recvfrom的時候,會發現有Connection refused錯誤返回,錯誤碼是ECONNREFUSED。對於懂得socket接口但是不很很懂網絡的人,可能這根本就不是個問題,他會根據錯誤碼知道遠端沒有這個服務端口,正如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既然無連接,怎麼知道遠端的情況呢?UDP不正如協議標準描述的那樣,發出去就不管了嗎?對於接收,沒有數據就一直等,如果設置了NOWAIT,則直接返回EAGAIN,表示稍後再試。不管怎麼說,也不會有ECONNREFUSED這麼詳細的信息返回纔對啊。
        既然UDP不會從對端返回任何錯誤信息,那麼一定有別的什麼返回了,總不能憑空猜測啊。這就涉及到了網絡協議設計中的數據平面和控制平面了,對於控制平面的消息,可以是帶內傳輸,也可以是帶外傳輸。對於TCP而言,無疑是帶內傳輸的,因爲它本身就是有連接的協議,協議本身會處理任何的錯誤和異常,然而對於UDP而言,因爲其設計目的就是保持簡單性,故不再附帶有任何帶內的控制消息邏輯,互聯網上爲了彌補這一類協議的控制邏輯的缺失,ICMP協議才顯得尤爲重要!實際上,ICMP,根據名稱就可以看出它是一種專門的控制協議,控制和指示IP層發生的事件。
        ECONNREFUSED正是ICMP返回的!然而並不是所有的UDP socket都可以享用ICMP帶來的錯誤提示,畢竟帶外控制消息和協議本身的關聯太鬆散了。UDP socket必須顯式的connect對端纔可以。現在問題又來了,既然UDP根本就是一個無連接的協議,connect的意義何在呢?這其實是socket接口設計的範疇,和協議本身沒有任何關係,當一個UDP socket去connect一個遠端時,並沒有發送任何的數據包,其效果僅僅是在本地建立了一個五元組映射,對應到一個對端,該映射的作用正是爲了和UDP帶外的ICMP控制通道捆綁在一起,使得UDP socket的接口含義更加豐滿。
        我們知道,ICMP錯誤信息返回時,ICMP的包內容就是出錯的那個原始數據包,根據這個原始數據包可以找出一個五元組,根據該五元組就可以對應到一個本地的connect過的UDP socket,進而把錯誤消息傳輸給該socket,應用程序在調用socket接口函數的時候,就可以得到該錯誤消息。如果一個UDP socket沒有調用過connect,那麼即使有ICMP數據包返回,由於socket保持了UDP的完整語義,協議棧也就不保存關於該socket和對端關聯的任何信息,因此也就無法找到一個特定的五元組將錯誤碼傳給它。

3,應用程序怎麼獲知端口不可達。

udp一般應用程序不會獲知icmp 的端口不可達信息。

爲了獲取udp端口不可達的情況,有2種方法:

1):int val = 1;

setsockopt(fd, IPPROTO_IP, IP_RECVERR , &val,sizeof(int));

2):

對udp進行connect操作,並且將sendto改成send

注:如果發送的目的ip,在當前網絡中不存在,會怎麼樣?

      客戶端會先發送arp, 尋找目的主機的mac,因爲目的ip不存在,自然沒有迴應,但是sendto返回成功(sendto返回成功僅僅表示將該報文發到ip的輸出隊列中),該報文不會被髮送出去。

 

4,源程序

注意,阻塞情況下,recvfrom會阻塞,即使收到端口不可達消息,也會阻塞。但是經過 方法1 和 方法2後,recvfrom會返回,返回值是-1,然後 判斷errno是否是ECONNREFUSED來判斷是否收到端口不可達消息。

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
unsigned char revc_buf[1024];
 
int main()
{
	int fd,ret,recv_len,size=1024;
	struct sockaddr_in server_addr,addr;
	int val = 1;
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = inet_addr("192.168.2.254");
	server_addr.sin_port = htons(77);
	
	fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(fd < 0)
	{	
		perror("socket fail ");
		return -1;
	}
	
	printf("socket sucess\n");
 
        //方法1
	#if 1
	setsockopt(fd, IPPROTO_IP, IP_RECVERR , &val,sizeof(int));
	if(sendto(fd, "nihao", strlen("nihao"), 0, (const struct sockaddr *)&(server_addr), sizeof(struct sockaddr_in))<0)
	{
		perror("sendto fail ");
		return -1;
	}
	printf("sendto sucess\n");
	recv_len = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);
	if (ret == -1)
	{
		if (errno == ECONNREFUSED)
		{
			printf("Recv port unreachable\n");
		}
	}
	//方法2
	#elif 0
	ret = connect(fd, (const struct sockaddr *) &(server_addr), sizeof (struct sockaddr_in));
	if(ret < 0)
	{
		printf("connect fail\n");
		return -1;
	}
	
	ret = send(fd, "ni hao", strlen("nihao"),0);
	if(ret < 0)
	{
		printf("write fail\n");
		return -1;
	}
	
	ret = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);
	if (ret == -1) {
		if (errno == ECONNREFUSED)
		{
			printf("Recv port unreachable\n");
		}
	}
 
	#endif
	close(fd);
	
	return 0;
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章