zoukankan      html  css  js  c++  java
  • UNIX网络编程——UDP回射服务器程序(初级版本)以及漏洞分析

         该函数提供的是一个迭代服务器,而不是像TCP服务器那样可以提供一个并发服务器。其中没有对fork的调用,因此单个服务器进程就得处理所有客户。一般来说,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。

         对于本套接字,UDP层中隐含有排队发生。事实上每个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字接收缓冲区。当进程调用recvfrom时,缓冲区中的下一个数据报以FIFO(先入先出)顺序返回给进程。

     

    服务器程序:

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<errno.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<string.h>
    
    #define SERV_PORT 3333
    #define MAXLINE 1024
    
    #define ERR_EXIT(m) 
        do { 
            perror(m); 
            exit(EXIT_FAILURE); 
        } while (0)
    
    typedef struct sockaddr SA;
    void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen)
    {
    	int			n;
    	socklen_t	len;
    	char		mesg[MAXLINE];
    
    	for ( ; ; ) {
    		len = clilen;
    		n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
    
    		sendto(sockfd, mesg, n, 0, pcliaddr, len);
    	}
    }
    
    
    int
    main(int argc, char **argv)
    {
    	int					sockfd;
    	struct sockaddr_in	servaddr, cliaddr;
    
    	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    	bzero(&servaddr, sizeof(servaddr));
    	servaddr.sin_family      = AF_INET;
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	servaddr.sin_port        = htons(SERV_PORT);
    
    	bind(sockfd, (SA *) &servaddr, sizeof(servaddr));
    
    	dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
    }

    客户端程序:

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    
    #define SERV_PORT 3333
    #define MAXLINE 1024
    #define ERR_EXIT(m) 
            do 
            { 
                    perror(m); 
                    exit(EXIT_FAILURE); 
            } while(0)
    
    typedef struct sockaddr SA;
    void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
    {
    	int	n;
    	char	sendline[MAXLINE], recvline[MAXLINE + 1];
    
    	while (fgets(sendline, MAXLINE, fp) != NULL) {
    
    		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
    
    		n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
    
    		recvline[n] = 0;	/* null terminate */
    		fputs(recvline, stdout);
    	}
    }
    
    int main(int argc, char **argv)
    {
    	int					sockfd;
    	struct sockaddr_in	servaddr;
    
    	if (argc != 2)
    		ERR_EXIT("usage: udpcli <IPaddress>");
    
    	bzero(&servaddr, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	servaddr.sin_port = htons(SERV_PORT);
    	inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    
    	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    	dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));
    
    	exit(0);
    }


    1.数据报的丢失

            我们的UDP客户/服务器例子是不可靠的。如果一个客户数据报丢失(譬如说,被客户主机与服务器主机之间的某个路由器丢失),客户将永远阻塞于dg_cli函数中的recvfrom调用,等待一个永远不会达到的服务器应答。类似的,如果客户数据报到达服务器,但是服务器的应答丢失了,客户也将永远阻塞于recvfrom调用。防止这样永远阻塞的一般方法是给客户的recvfrom调用设置一个超时。

            仅仅给recvfrom调用设置超时并不是完整的解决办法。举例来说,如果确实超时了,我们将无从判定超时原因是我们的数据报没有到达服务器,还是服务器的应答没有回到客户。所以我们可以增加UDP客户/服务器程序的可靠性。(后面会有讲解)
     

    2.服务器进程未运行

         我们下一个要检查的情形是在不启动服务器的前提下启动客户。如果我们这么做后在客户上键入一行文本,那么什么也不发生。客户永远阻塞于它的recvfrom调用,等待一个永远不出现的服务器应答。

         经过抓包分析,服务器主机响应的是一个“port unreachable”(端口不可达)ICMP消息。不过这个ICMP错误不返回给客户进程。我们称这个ICMP错误为异步错误。该错误由sendto引起,但是sendto本身却成功返回。我们知道从UDP输出操作成功返回仅仅表示在输出队列中具有存放所形成IP数据报的空间。该ICMP错误直到后来才返回,这就是称其为异步的原因。

         一个基本规则是:对于一个UDP套接字,由它引发的异步错误却并不返回给它,除非它已连接仅在进程已将其UDP套接字连接到恰恰一个对端后,这些异步错误才返回给进程。

    注:只要SO_BSDCOMPAT 套接字选项没有开启,linux甚至对未连接的套接字也返回大多数ICMP(目的地不可达)错误。

     

    3.验证接收到的响应

           知道客户临时端口号的任何进程都可往客户发送数据报,而且这些数据报会与正常的服务器应答混杂。

          我们的解决办法是修改recvfrom调用以返回数据报发送者的IP地址和端口号,保留来自数据报所发往服务器的应答,而忽略任何其他数据报

    void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
    {
    	int				n;
    	char			sendline[MAXLINE], recvline[MAXLINE + 1];
    	socklen_t		len;
    	struct sockaddr_in	*preply_addr;
    
    	preply_addr = malloc(servlen);
    
    	while (fgets(sendline, MAXLINE, fp) != NULL) {
    
    		sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
    
    		len = servlen;
    		n = recvfrom(sockfd, recvline, MAXLINE, 0, (SA*)preply_addr, &len);
    		if (len != servlen || memcmp(pservaddr, (SA*)preply_addr, len) != 0) {
    			printf("reply from %s (ignored)
    ",inet_ntoa(preply_addr->sin_addr));
    			continue;
    		}
    
    		recvline[n] = 0;	/* null terminate */
    		fputs(recvline, stdout);
    	}
    }

         然而这样做照样存在一些缺陷,如果服务器运行在一个只有单个IP地址的主机上,那么这个版本的客户工作正常。然而如果服务器主机是多宿的(多个IP地址),该客户就有可能失败。例如服务器有2个IP地址(172.24.37.94和135.197.17.100),客户连接服务器(135.197.17.100),但是服务器响应的IP地址是(172.24.37.94),这样我们的程序就出问题。

         一个解决办法是:得到由recvfrom返回的IP地址后,客户通过在DNS中查找服务器主机的名字来验证该主机的域名(而不是它的IP地址)。

         另一个解决办法是:UDP服务器给服务器主机上配置的每个IP地址创建一个套接字,用bind捆绑每个IP地址到各自的套接字,然后再所有这些套接字上使用select(等待其中任何一个变得可读),再从可读的套接字给出应答。既然用于给出应答的套接字上绑定的IP地址就是客户请求的目的IP地址(否则该数据报不会被投递到达该套接字),这就保证应答的源地址与请求的目的地址相同。




  • 相关阅读:
    atitit.ntfs ext 文件系统新特性对比
    Atitit.图片木马的原理与防范 attilax 总结
    Atitit.图片木马的原理与防范 attilax 总结
    Atitit.jdk java8的语法特性详解 attilax 总结
    Atitit.jdk java8的语法特性详解 attilax 总结
    Atitit.远程接口 监控与木马   常用的api 标准化v2 q216
    Atitit.远程接口 监控与木马   常用的api 标准化v2 q216
    Atitit..jdk java 各版本新特性 1.0 1.1 1.2 1.3 1.4 1.5(5.0) 1.6(6.0) 7.0 8.0 9.0 attilax 大总结
    Atitit..jdk java 各版本新特性 1.0 1.1 1.2 1.3 1.4 1.5(5.0) 1.6(6.0) 7.0 8.0 9.0 attilax 大总结
    Atitit.跨平台预定义函数 魔术方法 魔术函数 钩子函数 api兼容性草案 v2 q216  java c# php js.docx
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6332583.html
Copyright © 2011-2022 走看看