UNIX网络编程——UDP回射服务器程序(初级版本)以及漏洞分析
? ? ?該函數(shù)提供的是一個(gè)迭代服務(wù)器,而不是像TCP服務(wù)器那樣可以提供一個(gè)并發(fā)服務(wù)器。其中沒(méi)有對(duì)fork的調(diào)用,因此單個(gè)服務(wù)器進(jìn)程就得處理所有客戶。一般來(lái)說(shuō),大多數(shù)TCP服務(wù)器是并發(fā)的,而大多數(shù)UDP服務(wù)器是迭代的。
? ? ?對(duì)于本套接字,UDP層中隱含有排隊(duì)發(fā)生。事實(shí)上每個(gè)UDP套接字都有一個(gè)接收緩沖區(qū),到達(dá)該套接字的每個(gè)數(shù)據(jù)報(bào)都進(jìn)入這個(gè)套接字接收緩沖區(qū)。當(dāng)進(jìn)程調(diào)用recvfrom時(shí),緩沖區(qū)中的下一個(gè)數(shù)據(jù)報(bào)以FIFO(先入先出)順序返回給進(jìn)程。
?
服務(wù)器程序:
#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.數(shù)據(jù)報(bào)的丟失
??????? 我們的UDP客戶/服務(wù)器例子是不可靠的。如果一個(gè)客戶數(shù)據(jù)報(bào)丟失(譬如說(shuō),被客戶主機(jī)與服務(wù)器主機(jī)之間的某個(gè)路由器丟失),客戶將永遠(yuǎn)阻塞于dg_cli函數(shù)中的recvfrom調(diào)用,等待一個(gè)永遠(yuǎn)不會(huì)達(dá)到的服務(wù)器應(yīng)答。類(lèi)似的,如果客戶數(shù)據(jù)報(bào)到達(dá)服務(wù)器,但是服務(wù)器的應(yīng)答丟失了,客戶也將永遠(yuǎn)阻塞于recvfrom調(diào)用。防止這樣永遠(yuǎn)阻塞的一般方法是給客戶的recvfrom調(diào)用設(shè)置一個(gè)超時(shí)。
?????? ?僅僅給recvfrom調(diào)用設(shè)置超時(shí)并不是完整的解決辦法。舉例來(lái)說(shuō),如果確實(shí)超時(shí)了,我們將無(wú)從判定超時(shí)原因是我們的數(shù)據(jù)報(bào)沒(méi)有到達(dá)服務(wù)器,還是服務(wù)器的應(yīng)答沒(méi)有回到客戶。所以我們可以增加UDP客戶/服務(wù)器程序的可靠性。(后面會(huì)有講解)
?
2.服務(wù)器進(jìn)程未運(yùn)行
???? 我們下一個(gè)要檢查的情形是在不啟動(dòng)服務(wù)器的前提下啟動(dòng)客戶。如果我們這么做后在客戶上鍵入一行文本,那么什么也不發(fā)生??蛻粲肋h(yuǎn)阻塞于它的recvfrom調(diào)用,等待一個(gè)永遠(yuǎn)不出現(xiàn)的服務(wù)器應(yīng)答。
???? 經(jīng)過(guò)抓包分析,服務(wù)器主機(jī)響應(yīng)的是一個(gè)“port unreachable”(端口不可達(dá))ICMP消息。不過(guò)這個(gè)ICMP錯(cuò)誤不返回給客戶進(jìn)程。我們稱(chēng)這個(gè)ICMP錯(cuò)誤為異步錯(cuò)誤。該錯(cuò)誤由sendto引起,但是sendto本身卻成功返回。我們知道從UDP輸出操作成功返回僅僅表示在輸出隊(duì)列中具有存放所形成IP數(shù)據(jù)報(bào)的空間。該ICMP錯(cuò)誤直到后來(lái)才返回,這就是稱(chēng)其為異步的原因。
???? 一個(gè)基本規(guī)則是:對(duì)于一個(gè)UDP套接字,由它引發(fā)的異步錯(cuò)誤卻并不返回給它,除非它已連接。僅在進(jìn)程已將其UDP套接字連接到恰恰一個(gè)對(duì)端后,這些異步錯(cuò)誤才返回給進(jìn)程。
注:只要SO_BSDCOMPAT 套接字選項(xiàng)沒(méi)有開(kāi)啟,linux甚至對(duì)未連接的套接字也返回大多數(shù)ICMP(目的地不可達(dá))錯(cuò)誤。
?
3.驗(yàn)證接收到的響應(yīng)
?????? 知道客戶臨時(shí)端口號(hào)的任何進(jìn)程都可往客戶發(fā)送數(shù)據(jù)報(bào),而且這些數(shù)據(jù)報(bào)會(huì)與正常的服務(wù)器應(yīng)答混雜。
??????我們的解決辦法是修改recvfrom調(diào)用以返回?cái)?shù)據(jù)報(bào)發(fā)送者的IP地址和端口號(hào),保留來(lái)自數(shù)據(jù)報(bào)所發(fā)往服務(wù)器的應(yīng)答,而忽略任何其他數(shù)據(jù)報(bào)。
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)\n",inet_ntoa(preply_addr->sin_addr));continue;}recvline[n] = 0; /* null terminate */fputs(recvline, stdout);} }? ? ?然而這樣做照樣存在一些缺陷,如果服務(wù)器運(yùn)行在一個(gè)只有單個(gè)IP地址的主機(jī)上,那么這個(gè)版本的客戶工作正常。然而如果服務(wù)器主機(jī)是多宿的(多個(gè)IP地址),該客戶就有可能失敗。例如服務(wù)器有2個(gè)IP地址(172.24.37.94和135.197.17.100),客戶連接服務(wù)器(135.197.17.100),但是服務(wù)器響應(yīng)的IP地址是(172.24.37.94),這樣我們的程序就出問(wèn)題。
? ? ?一個(gè)解決辦法是:得到由recvfrom返回的IP地址后,客戶通過(guò)在DNS中查找服務(wù)器主機(jī)的名字來(lái)驗(yàn)證該主機(jī)的域名(而不是它的IP地址)。
? ? ?另一個(gè)解決辦法是:UDP服務(wù)器給服務(wù)器主機(jī)上配置的每個(gè)IP地址創(chuàng)建一個(gè)套接字,用bind捆綁每個(gè)IP地址到各自的套接字,然后再所有這些套接字上使用select(等待其中任何一個(gè)變得可讀),再?gòu)目勺x的套接字給出應(yīng)答。既然用于給出應(yīng)答的套接字上綁定的IP地址就是客戶請(qǐng)求的目的IP地址(否則該數(shù)據(jù)報(bào)不會(huì)被投遞到達(dá)該套接字),這就保證應(yīng)答的源地址與請(qǐng)求的目的地址相同。
轉(zhuǎn)載于:https://www.cnblogs.com/wangfengju/p/6172564.html
總結(jié)
以上是生活随笔為你收集整理的UNIX网络编程——UDP回射服务器程序(初级版本)以及漏洞分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [转]NYOJ-511-移动小球
- 下一篇: Android-TCPDump for