tcp udp端口不存在

   ICMP在IP系统间传递差错和管理报文,是任何IP系统必须实现的组成部分。Linux 2.6.34中ICMP模块的实现在linux/icmp.h,net/icmp.h和ipv4/icmp.c中,导出了icmp_err_convert数组和icmp_send函数,供其它网络子系统使用。在其它网络子系统中,当检测到错误时,调用icmp_send产生并发送相应的ICMP差错消息到源主机;当源主机收到ICMP不可达差错消息,传递到原始套接字和传输层,而它们使用icmp_err_convert把对应的消息代码转换成套接字层比较容易理解的错误代码。在内核空间中可发送的ICMP消息包括查询应答和差错报文,下面总结了产生这两类消息的网络子系统(及函数)与错误转换。


应答消息

 

 

   应答消息由ICMP模块的内部函数icmp_reply而非icmp_send发送。根据RFC1122 3.2.2.9规范, 除非一个主机作为地址掩码代理,否则不能发送回复,这对应ICMP的icmp_address实现为空,因此上表没有列出地址掩码应答项(内核符号为ICMP_ADDRESSREPLY)。

差错消息

 

 

   差错消息由中间路由器或目的主机产生,当数据报不能成功提交给目的主机时。从上表可见,在IP层的接收、本地处理、转发和输出各过程中,都可能产生差错消息;在传输层如果对应的端口没有打开,那么UDP会产生ICMP端口不可达差错,而TCP则会使用自己的差错处理机制发送一个RST复位包,这也是上表没有列出TCP子系统的原因。对于重定向差错,由ICMP模块的icmp_redirect调用ip_rt_redirect更新路由;其它差错则由icmp_unreach处理。


错误转换

 

 

   第2列为icmp_err_convert数组索引,第4列也就是调用socket API出错时返回的errno,最后1列为icmp_err_convert中的fatal成员取值,0表示非致命错误,1表示致命错误,需要报告给用户进程。错误转换会被RAW的raw_err、TCP的tcp_v4_err和UDP的udp_err用到,对于ICMP_DEST_UNREACH类型的差错,使用上表转换;ICMP_SOURCE_QUENCH类型的忽略不处理;ICMP_PARAMETERPROB类型的转换成EPROTO(协议错误);ICMP_TIME_EXCEEDED类型的转换成EHOSTUNREACH。
   在这要注意,从ICMP_PORT_UNREACH到ECONNREFUSED的转换,不适用于TCP,原因已在上节说明;而对于UDP的未连接套接字,如果主机在线而端口没打开,调用sendto得不到ECONNREFUSED错误,但recvfrom会阻塞,这是因为虽然内核收到了ICMP差错,但没上报给应用进程。尽管如此,如果想得到ECONNREFUSED错误,那么可以写个ICMP守护进程,应用进程先把它的套接字描述符通过unix域套接口传递到ICMP守护进程,而守护进程使用raw socket来接收ICMP差错,再发给应用进程。

 

tcp

 telnet 10.10.X.y   9999  

 

 RST 报文

 

 

udp

 

 

 

root@ubuntu:~#  tcpdump -i enahisic2i0   host 10.0.xx.x     
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enahisic2i0, link-type EN10MB (Ethernet), capture size 262144 bytes
14:35:46.226936 IP ubuntu.51519 > 10.0.xx.xx.9999: UDP, length 1
14:35:46.227055 IP 10.10.xx.xx > ubuntu: ICMP 10.10.xx.xx udp port 9999 unreachable, length 37

 

Linux内核对UDP处理:

 

(1):作为服务器接受到一个UDP请求:

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

调用ip_local_deliver_finish,它根据ip头中的协议类型(TCP/UDP/ICMP/......),调用不同的4层接口函数进行处理。所以之前说了,即使开启了TCP服务,服务器建立的socket的hash和udp超找socket的hash不一致,也会回端口不可达。

 

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

如果sk不存在if(sk != NULL),就回复icmp destination unreachable(这就是服务器没有对应端口接受UDP的处理流程),函数非常简单

 

 

 

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

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

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

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

 

(2)作为客户端收到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

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

__udp4_lib_err先根据skb->data中dip和sip,查找socket,skb->data是icmp的负载

故先调用 __udp4_lib_lookup 查找socket,传参时,sip和dip需要反一下。

 

__udp4_lib_err:

 

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

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

有2种方法:

 

(1):

int val = 1;

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

(2):

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

 

 

4:

udp获知端口不可达的源程序(方法1:设置Socket选项;方法2:对UDP进行Connect)

注意,阻塞情况下,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("10.1.1.8");
        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");
        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");
                }
        }
        //方法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
        getchar();
        close(fd);

        return 0;
}

 

 

 




方法一
        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");
        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");
                }
        }

 




root@ubuntu:~/c++# ./udp
socket sucess
Recv port unreachable

 

 
        //方法2

 

 

posted on 2021-04-02 14:39  tycoon3  阅读(972)  评论(0编辑  收藏  举报

导航