以下是计算UDP校验和以及发送原始数据的源代码
#include <Winsock2.h>
#include <stdio.h>
#define IP_HDRINCL 2 // Header is included with data.
typedef struct _IPHEADER
{
UCHAR VerHeadLen; //版本号和头长度
UCHAR TOS; //服务类型
USHORT Length; //IP包总长度
USHORT ID; //IP包唯一标识
USHORT Flags; //标志
UCHAR TTL; //生存时间
UCHAR Protocol; //协议
USHORT Checksum; //校验和
ULONG SourceIP; //源IP地址
ULONG DestIP; //目标IP
}IPHEADER;
typedef struct _UDPHEADER
{
USHORT SourcePort; //源端口
USHORT DestPort; //目的端口
USHORT Length; //包长度
USHORT CheckSum; //校验和
}UDPHEADER;
USHORT checksum(USHORT * buff, int size)
{
ULONG cksum = 0;
while (size > 1)
{
cksum += *(buff++);
size -= sizeof(USHORT);
}
if(size)
{
cksum += *((UCHAR*)buff);
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (USHORT)(~cksum);
}
void UdpHeaderChecksum(IPHEADER *IpHeader,UDPHEADER *UdpHeader,char * payload,int payloadlen)
{
char buf[1024];
char *ptr = buf;
int chksumlen = 0;
ULONG zero = 0;
//包含源IP地址和目的IP地址
memcpy(ptr,&IpHeader->SourceIP,sizeof(IpHeader->SourceIP));
ptr += sizeof(IpHeader->SourceIP);
chksumlen += sizeof(IpHeader->SourceIP);
memcpy(ptr,&IpHeader->DestIP,sizeof(IpHeader->DestIP));
ptr += sizeof(IpHeader->DestIP);
chksumlen += sizeof(IpHeader->DestIP);
//8位0域
memcpy(ptr,&zero,1);
ptr += 1;
chksumlen += 1;
//协议
memcpy(ptr,&IpHeader->Protocol,sizeof(IpHeader->Protocol));
ptr += sizeof(IpHeader->Protocol);
chksumlen += sizeof(IpHeader->Protocol);
//UDP长度
memcpy(ptr,&UdpHeader->Length,sizeof(UdpHeader->Length));
ptr += sizeof(UdpHeader->Length);
chksumlen += sizeof(UdpHeader->Length);
//UDP端口号
memcpy(ptr,&UdpHeader->SourcePort,sizeof(UdpHeader->SourcePort));
ptr += sizeof(UdpHeader->SourcePort);
chksumlen += sizeof(UdpHeader->SourcePort);
memcpy(ptr,&UdpHeader->DestPort,sizeof(UdpHeader->DestPort));
ptr += sizeof(UdpHeader->DestPort);
chksumlen += sizeof(UdpHeader->DestPort);
//UDP长度
memcpy(ptr,&UdpHeader->Length,sizeof(UdpHeader->Length));
ptr += sizeof(UdpHeader->Length);
chksumlen += sizeof(UdpHeader->Length);
//16位UDP校验和
memcpy(ptr,&zero,sizeof(UdpHeader->CheckSum));
ptr += sizeof(USHORT);
chksumlen += sizeof(USHORT);
//净荷
memcpy(ptr,payload,payloadlen);
ptr += payloadlen;
chksumlen += payloadlen;
//补齐到下一个16位边界
for(int i = 0; i < payloadlen %2; i++)
{
*ptr = 0;
ptr++;
chksumlen++;
}
//计算校验和,填充到UDP头
UdpHeader->CheckSum = checksum((USHORT*)buf,chksumlen);
}
int main()
{
int i = 0;
WSADATA wsaData;
int wsaret = WSAStartup(0x101,&wsaData);
if(wsaret != 0)
{
printf("WSAStartup失败/n");
}
char szDestIP[] = "192.168.12.110";
char szSourceIP[] = "192.168.12.169";
USHORT nDestPort = 1234;
USHORT nSourcePort = 8888;
char szMsg[] = "123456";
int nMsgLen = strlen(szMsg);
//创建原始套接字
SOCKET sRaw = ::socket(AF_INET,SOCK_RAW,IPPROTO_UDP);
//有效IP头包含选项
BOOL bIncl = TRUE;
i = ::setsockopt(sRaw,IPPROTO_IP,IP_HDRINCL,(char*)&bIncl,sizeof(bIncl));
char buff[1024] = {0};
int TotalLength = sizeof(IPHEADER) + sizeof(UDPHEADER) + nMsgLen;
//IP头
IPHEADER * IpHeader = (IPHEADER*)buff;
IpHeader->VerHeadLen = (4 << 4 | (sizeof(_IPHEADER)/sizeof(ULONG)));
IpHeader->Length = TotalLength;
IpHeader->TTL = 128;
IpHeader->Protocol = IPPROTO_UDP;
IpHeader->SourceIP = ::inet_addr(szSourceIP);
IpHeader->DestIP = ::inet_addr(szDestIP);
IpHeader->Checksum = checksum((USHORT*)IpHeader,sizeof(IPHEADER));
//UDP头
UDPHEADER * UdpHeader = (UDPHEADER*)(buff + sizeof(IPHEADER));
UdpHeader->SourcePort = htons(nSourcePort);
UdpHeader->DestPort = htons(nDestPort);
UdpHeader->Length = htons(sizeof(UDPHEADER) + nMsgLen);
UdpHeader->CheckSum = 0;
UdpHeaderChecksum(IpHeader,UdpHeader,szMsg,nMsgLen);
char* pData = buff + sizeof(IPHEADER) + sizeof(UDPHEADER);
memcpy(pData,szMsg,nMsgLen);
//设置目的地址
SOCKADDR_IN Addr = {0};
Addr.sin_family = AF_INET;
Addr.sin_port = htons(nDestPort);
memset(Addr.sin_zero,0,8);
Addr.sin_addr.S_un.S_addr = ::inet_addr(szDestIP);
//发送原始UDP封包
int nRet = 0;
for(i = 0; i < 5; i++)
{
nRet = sendto(sRaw,buff,TotalLength,0,(sockaddr*)&Addr,sizeof(sockaddr));
if(nRet == SOCKET_ERROR)
{
printf("sendto() faild:%d/n",WSAGetLastError());
break;
}
else
{
printf("send %d bytes/n",nRet);
}
}
closesocket(sRaw);
return 0;
}
刚开始时,服务程序根本接收不到我发的包,用ethereal抓包时发现数据确实是发送出去了,但显示信息表示UDP校验和不正确。 后来发现计算UDP校验和时,我把IP头也计算进去,实际上,UDP的校验和只需要计算从UDP头开始到后面的净荷。
找到问题后觉得十分简单,但如果有些朋友也犯了和我一样的错误的话,希望有所帮助。