Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6478419
  • 博文数量: 579
  • 博客积分: 1548
  • 博客等级: 上尉
  • 技术积分: 16635
  • 用 户 组: 普通用户
  • 注册时间: 2012-12-12 15:29
个人简介

http://www.csdn.net/ http://www.arm.com/zh/ https://www.kernel.org/ http://www.linuxpk.com/ http://www.51develop.net/ http://linux.chinaitlab.com/ http://www.embeddedlinux.org.cn http://bbs.pediy.com/

文章分类

全部博文(579)

文章存档

2018年(18)

2015年(91)

2014年(159)

2013年(231)

2012年(80)

分类: LINUX

2015-04-27 16:32:16

有时候,写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和对端关联的任何信息,因此也就无法找到一个特定的五元组将错误码传给它。
        以下是一个测试程序:
  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,执行./UDPclient 192.168.1.20,注意,这个地址一定要是个IP可达的地址,才好测试。按照上面的理论,结果应该是:第一个sendto成功,然后192.168.1.20返回了:
ICMP 192.168.1.20 udp port 12345 unreachable, length 40
接下来第二个sendto返回:
write: Connection refused
由于第二次没有发送任何数据包到达192.168.1.20,所以也不能企望它返回ICMP错误信息,因此接下来的recvfrom调用会阻塞。
        最后的一个问题时,你不能太指望这个Connection refused以及一切带外返回的错误信息,因为你不能保证一定能收到远端发送的ICMP包,如果中间的某个节点或者本机禁掉了ICMP,socket api调用就无法捕获这些错误。



本文将讲解为什么服务器回复端口不可达,以及客户端socket 如何获取 端口不可达 信号。




首先,做为服务器,当一个报文经过查路由,目的ip是上送本机的时候,经过netfilter 判决后,

调用ip_local_deliver_finish,它根据ip头中的协议类型(TCP/UDP/ICMP/......),调用不同的4层接口函数进行处理。

对于udp而言,handler 是udp_rcv,它直接调用了__udp4_lib_rcv,查找相应的sock,

如果sk不存在if(sk != NULL),就回复icmp destination unreachable,函数非常简单



所以作为服务器,收到一个目的端口并未监听的报文,直接回复端口不可达。

那么作为客户端,如何处理服务器回复的 端口不可达 报文呢?

起始当初想法很简单,我认为,不同的协议之间是不会干涉的,即TCP和UDP直接是不会干涉的。

何况这种不伦不类的icmp?后来想错了。


作为客户端,端口不可达报文进入ip_local_deliver_finish,它调用icmp_rcv函数,进行处理。(其实这也是当初

我认为客户端udp不会对端口不可达数据进行相应的原因,因为udp处理流程是udp_rcv)。

icmp_rcv函数最重要的是 它调用了:icmp_pointers[icmph->type].handler(skb);

handler = icmp_unreach

icmp_unreach函数最终的一步,就是它最后一步:


是不是很像ip_local_deliver_finish?

是很像,只是ip_local_deliver_finish中,调用了ipprot->handler,而这里调用了ipprot->err_handler


对于udp,err_handler = udp_err = __udp4_lib_err

在该函数中,只有进入如下的流程,应用程序才会反应:


先决条件是inet->recverr为非0,或者inet->recverr为0但是udp处于TCP_ESTABLISHED状态。

否则应用程序休想收到该端口不可达的数据,应用程序就等着read超时吧。所以说,为了获取udp端口不可达的情况

有2种方法:


法1:

对udp进行connect操作,并且将sendto改成send

法2:

int val = 1;

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






udp获知端口不可达的源程序

  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;">"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. }  
阅读(37219) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~