zoukankan      html  css  js  c++  java
  • 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
    ");
    
            //方法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
    ");
            ret  = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);
            if (ret == -1)
            {
                    if (errno == ECONNREFUSED)
                    {
                            printf("Recv port unreachable
    ");
                    }
            }
            //方法2
            #elif 0
            ret = connect(fd, (const struct sockaddr *) &(server_addr), sizeof (struct sockaddr_in));
            if(ret < 0)
            {
                    printf("connect fail
    ");
                    return -1;
            }
            
            ret = send(fd, "ni hao", strlen("nihao"),0);
            if(ret < 0)
            {
                    printf("write fail
    ");
                    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
    ");
                    }
            }
     
            #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
    ");
            ret = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);
            if (ret == -1)
            {
                    if (errno == ECONNREFUSED)
                    {
                            printf("Recv port unreachable
    ");
                    }
            }



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

  • 相关阅读:
    删除文件时,提示 "操作无法完成..." 怎么处理
    对象的理解
    TP5架构下链接SQL数据库的一种方法
    关于URL隐藏index.php方法
    非典型的千万用户后台之路
    就这样,再见2015
    理想的程序员
    4个小例子告诉你:如何成为一名数据极客
    馆中窥职:小公司没那么糟糕
    JAVA设计模式详解(六)----------状态模式
  • 原文地址:https://www.cnblogs.com/dream397/p/14610631.html
Copyright © 2011-2022 走看看