学一点|原始套接字实现UDP程序

目录
IP_HDRINCL
UDP头部
UDP伪头(为了方便计算UDP的校验和)

#pragma pack(1) #define WIN32_LEAN_AND_MEAN#include #include #include #include #pragma comment(lib,"ws2_32.lib")#define MAX_MESSAGE 4068 #define MAX_PACKET 4096//设置一些默认值 #define DEFAULT_PORT 5150 #define DEFAULT_IP "10.0.0.1" #define DEFAULT_COUNT 5 #define DEFAULT_MESSAGE "This is a test"//Ip头结构 typedef struct ip_hdr { unsigned char ip_verlen; //4位版本和4位长度合并为1个字节 unsigned char ip_tos; //服务类型 unsigned short ip_totallength; //总长度 unsigned short ip_id; //唯一标识 unsigned short ip_offset; //偏移 unsigned char ip_ttl; //存活时间 unsigned char ip_protocol; //协议(TCP、UDP、etc) unsigned short ip_checksum; //校验和 unsigned int ip_srcaddr; //源ip地址 unsigned int ip_destaddr; //目的ip地址 }IP_HDR,*PIP_HDR,FAR*LPIP_HDR; //定义UDP头 typedef struct udp_hdr { unsigned short src_portno; //源端口 unsigned short dst_portno; //目的端口 unsigned short udp_length; //udp packet 长度 unsigned short udp_checksum; //udp 校验和(因为是无连接的,所以该值可选) }UDP_HDR,*PUDP_HDR; //全局变量 unsigned int dwToIP; unsigned int dwFromIP; //IP头使用本机IP地址不能是INADDR_ANY unsigned short iToPort; unsigned short iFromPort; //本地使用的端口号 DWORD dwCount; //发送的消息的次数 char strMessage[MAX_MESSAGE]; //发送的消息//解析命令行参数 void ValidateArgs(int argc, char **argv) { int i; iToPort = DEFAULT_PORT; iFromPort = DEFAULT_PORT; dwToIP = inet_addr(DEFAULT_IP); dwCount = DEFAULT_COUNT; strcpy(strMessage, DEFAULT_MESSAGE); for (i = 1; i < argc; i++) { if (argv[i][0] == '-' || argv[i][0] == '/') { switch (tolower(argv[i][1])) { case 'f': // switch (tolower(argv[i][2])) { case 'p': //"-fp:5" if (strlen(argv[i]) > 4) iFromPort = atoi(&argv[i][4]); break; case 'i': //"-fi:192.168.0.100" if (strlen(argv[i]) > 4) dwFromIP = inet_addr(&argv[i][4]); break; } break; case 't': //to address switch (tolower(argv[i][2])) { case 'p'://"-tp:5150" if (strlen(argv[i]) > 4) iToPort = atoi(&argv[i][4]); break; case 'i'://"-ti:192.168.0.107" if (strlen(argv[i]) > 4) dwToIP = inet_addr(&argv[i][4]); break; } break; case 'n': //"-n:5" if (strlen(argv[i]) > 3) dwCount = atoi(&argv[i][3]); break; case 'm': //"-m:HelloWorld" if (strlen(argv[i]) > 3) strcpy(strMessage,&argv[i][3]); break; } } } }//计算校验和 USHORT checksum(USHORT *buffer, int size) { unsigned long cksum = 0; while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if (size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (USHORT)(~cksum); }//根据伪UDP头计算真实的UDP头部校验和 USHORT computePseudoUDPHeader(LPIP_HDR pIpHdr, PUDP_HDR pUdpHdr) { //构建UDP伪头为了计算UDP检验和 //一个UDP伪头由下述项目构成: //32位源IP //32为目标IP //8位字段(零除外) //8位协议 //16位UDP长度 unsigned short iUdpChecksumSize = 0; char buf[MAX_PACKET] = {0}; char* ptr = buf; //源IP memcpy(ptr, &pIpHdr->ip_srcaddr, sizeof(pIpHdr->ip_srcaddr)); ptr += sizeof(pIpHdr->ip_srcaddr); iUdpChecksumSize += sizeof(pIpHdr->ip_srcaddr); //目标IP memcpy(ptr, &pIpHdr->ip_destaddr, sizeof(pIpHdr->ip_destaddr)); ptr += sizeof(pIpHdr->ip_destaddr); iUdpChecksumSize += sizeof(pIpHdr->ip_destaddr); //8位0域 ptr++; iUdpChecksumSize += 1; //协议 memcpy(ptr, &pIpHdr->ip_protocol, sizeof(pIpHdr->ip_protocol)); ptr += sizeof(pIpHdr->ip_protocol); iUdpChecksumSize += sizeof(pIpHdr->ip_protocol); //长度 memcpy(ptr, &pUdpHdr->udp_length, sizeof(pUdpHdr->udp_length)); ptr += sizeof(pUdpHdr->udp_length); iUdpChecksumSize += sizeof(pUdpHdr->udp_length); //UDP实际头部 memcpy(ptr, pUdpHdr, sizeof(UDP_HDR)); ptr += sizeof(UDP_HDR); iUdpChecksumSize += sizeof(UDP_HDR); for (int i = 0; i < strlen(strMessage); i++, ptr++) { *ptr = strMessage[i]; } iUdpChecksumSize += strlen(strMessage); //计算UDP校验和 pUdpHdr->udp_checksum = checksum((USHORT*)buf, iUdpChecksumSize); }int main(int argc, char **argv) { for (int i = 0; i < argc; i++) printf("argv[%d]=%s\n",i,argv[i]); printf("---------------------\n"); WSADATA wsd; SOCKET s; BOOL bOpt; struct sockaddr_in remote; //IP addressing structures IP_HDR ipHdr; UDP_HDR udpHdr; int ret; DWORD i; unsigned short iTotalSize; unsigned short iUdpSize; unsigned short iIPVersion; unsigned short iIPSize; unsigned short icksum = 0; char buf[MAX_PACKET] = { 0 }; IN_ADDR addr; ValidateArgs(argc, argv); addr.S_un.S_addr = dwFromIP; printf("From IP:<%s>\nPort:%d\n",inet_ntoa(addr),iFromPort); addr.S_un.S_addr = dwToIP; printf("To IP:<%s>\nPort:%d\n", inet_ntoa(addr), iToPort); printf("Message:[%s]\n",strMessage); printf("Count:%d\n",dwCount); if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) { printf("WSAStartup() failed:%d\n",GetLastError()); system("pause"); return -1; } //创建原始套接字 s = WSASocket(AF_INET, SOCK_RAW, IPPROTO_UDP, NULL, 0, 0); if (s == INVALID_SOCKET) { printf("WSASocket() failed:%d\n",WSAGetLastError()); system("pause"); return -1; } //启用 IP header include 选项 bOpt = TRUE; ret = setsockopt(s, IPPROTO_IP, IP_HDRINCL, &bOpt, sizeof(bOpt)); if (ret == SOCKET_ERROR) { printf("setsockopt() failed:%d\n",WSAGetLastError()); system("pasue"); return -1; } //初始化 IP header iTotalSize = sizeof(ipHdr) + sizeof(udpHdr) + strlen(strMessage); iIPVersion = 4; iIPSize = sizeof(ipHdr) / sizeof(unsigned long); //version高四位,size为低四位 ipHdr.ip_verlen = (iIPVersion << 4) | iIPSize; ipHdr.ip_tos = 0; //服务类型 ipHdr.ip_totallength = iTotalSize; ipHdr.ip_id = 0; ipHdr.ip_offset = 0; ipHdr.ip_ttl = 128; ipHdr.ip_protocol = IPPROTO_UDP; //0x11; //Protocol(UDP:17) ipHdr.ip_checksum = 0; //checksum((USHORT*)&ipHdr, sizeof(ipHdr))网络堆栈会计算出IP校验和,所以代码中不用设置 //如果和虚拟机测试,虚拟机使用的是NAT模式,那么需要设置成VMnet8的地址 //如果虚拟机使用的是桥接模式,那么直接使用当前主机IP ipHdr.ip_srcaddr =dwFromIP; ipHdr.ip_destaddr = dwToIP; //初始化UDP header iUdpSize = sizeof(udpHdr) + strlen(strMessage); udpHdr.src_portno = htons(iFromPort); udpHdr.dst_portno = htons(iToPort); udpHdr.udp_length = htons(iUdpSize); udpHdr.udp_checksum = 0; //计算伪UDP头检验和 computePseudoUDPHeader(&ipHdr,&udpHdr); //确定发送的整个IP包数据 ZeroMemory(buf, MAX_PACKET); char*ptr = buf; memcpy(ptr,&ipHdr,sizeof(ipHdr)); ptr += sizeof(ipHdr); memcpy(ptr,&udpHdr,sizeof(udpHdr)); ptr += sizeof(udpHdr); memcpy(ptr, strMessage, strlen(strMessage)); //接收端要使用的协议类型必须要确定 remote.sin_family = AF_INET; //目的IP和短路不设置是可以的,因为IP头中已经指定好了 remote.sin_port = htons(iToPort); remote.sin_addr.S_un.S_addr = dwToIP; for (i = 0; i < dwCount; i++) { //在使用IP_HDRINCL选项的时候,sendto中的to参数会被忽略,因为数据无论如何都会发送给我们在IP头内指定的那个主机 //发送的数据=IP头+UDP头+数据 ret = sendto(s, buf, iTotalSize, 0, (SOCKADDR*)&remote, sizeof(remote)); if (ret == SOCKET_ERROR) { printf("sendto() failed:%d\n",WSAGetLastError()); break; } else { printf("send %d bytes\n",ret); } Sleep(10); } closesocket(s); WSACleanup(); system("pause"); return 0; }

配置命令行参数如下
"-fi:192.168.18.1":本机IP//因为接受端虚拟机使用NAT模式,所以本机IP配置为VMNet8地址
"-ti:192.168.18.128":接受方监测的IP
"-fp:6666":本机使用的端口号
"-tp:8888":接收端使用的端口号
"-n:5":重复发送5次数据
"-m:helloworld":发出的消息文本
学一点|原始套接字实现UDP程序
文章图片


IP_HDRINCL 对原始套接字来说,它存在的一项限制在于,我们只能对已经定义好的协议进行操作,比如ICMP和IGMP等。不用用于IPPROTO_UDP来创建一个新的原始套接字,也不能对UDP头进行操作;TCP也是一样的。要想对IP头进行操作,同时也能操作TCP或UDP头(或封装在IP内的其他任何协议),必须随原始套接字一道,使用IP_HDRINCL这个套接字选项。利用这个选项,我们不仅可以自己构建IP头,也能构建其他协议头。

UDP头部
16位源端口
16位目标端口
16位UDP长度(头+数据)
16位UDP检验和
由于UDP是一种不能保证数据可靠传输的协议,所以校验和的计算可有可无
UDP伪头(为了方便计算UDP的校验和)
32位源IP地址
32位目的IP地址
0
8位协议
16位UDP长度
16位源端口
16位目标端口
16位UDP长度
16位UDP校验和
数据(任意字节)
//一个UDP伪头由下述项目构成://构建UDP伪头为了计算UDP检验和
//32位源IP
//32位目标IP
【学一点|原始套接字实现UDP程序】//8位零域(零除外)
//8位协议
//16位UDP长度
可以发现这个UDP伪头的格式是和IP头部结构一样的,所以这样就可以使用IP头部一样的校验和计算方式了。
加入这些项目的目的是UDP头以及数据。校验和的计算方法和IP和ICMP的方法是相同的:得出16位1的求余总和。由于数据可能是个奇数,所以有时需要在数据末尾填充一个0字节,以便顺利计算出校验和。这个填充字段并不作为数据的一部分传递。

    推荐阅读