目录
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":发出的消息文本
文章图片
IP_HDRINCL 对原始套接字来说,它存在的一项限制在于,我们只能对已经定义好的协议进行操作,比如ICMP和IGMP等。不用用于IPPROTO_UDP来创建一个新的原始套接字,也不能对UDP头进行操作;TCP也是一样的。要想对IP头进行操作,同时也能操作TCP或UDP头(或封装在IP内的其他任何协议),必须随原始套接字一道,使用IP_HDRINCL这个套接字选项。利用这个选项,我们不仅可以自己构建IP头,也能构建其他协议头。
UDP头部
16位源端口 |
16位目标端口 |
16位UDP长度(头+数据) |
16位UDP检验和 |
UDP伪头(为了方便计算UDP的校验和)
32位源IP地址 |
||
32位目的IP地址 |
||
0 |
8位协议 |
16位UDP长度 |
16位源端口 |
16位目标端口 |
|
16位UDP长度 |
16位UDP校验和 |
|
数据(任意字节) |
//32位源IP
//32位目标IP
【学一点|原始套接字实现UDP程序】//8位零域(零除外)
//8位协议
//16位UDP长度
可以发现这个UDP伪头的格式是和IP头部结构一样的,所以这样就可以使用IP头部一样的校验和计算方式了。
加入这些项目的目的是UDP头以及数据。校验和的计算方法和IP和ICMP的方法是相同的:得出16位1的求余总和。由于数据可能是个奇数,所以有时需要在数据末尾填充一个0字节,以便顺利计算出校验和。这个填充字段并不作为数据的一部分传递。