本文将讲解为什么服务器回复端口不可达,以及客户端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获知端口不可达的源程序
#include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <string.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"); //方法2 #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); printf("recv_len:%d sucess\n"); <span style="font-size:18px;"></span><pre name="code" class="cpp"> //方法1 #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 < 0) { printf("read fail\n"); return -1; } #endif close(fd); return 0; }