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地址(否则该数据报不会被投递到达该套接字),这就保证应答的源地址与请求的目的地址相同。




  • 相关阅读:
    硬件加速器为人工智能应用服务
    js 获取指定字符串个数
    js 仿微信投诉—引入vue.js,拆分组件为单个js
    css 图片波浪效果
    svg path命令
    谷歌浏览器—打断点调试页面
    js 实现加载百分比效果
    js 实现纵向轮播
    css 图片高度自适应
    js 禁止/允许页面滚动
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6172564.html
Copyright © 2011-2022 走看看