udp端口不可达 icmp

mac2026-01-31  1

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; }

 

 

最新回复(0)