通过Delphi上的WinSock2的原始UDP数据包

发布于 2025-01-18 03:00:16 字数 6676 浏览 1 评论 0原文

我可以构建一个原始 IP 数据包,其中包含一个包含有用数据(DNS 请求)的 UDP 数据包。 我可以发送它,并看到它是在 Wireshark 中发送的。 Wireshark 将其解析为合法的 DNS 请求,因此除了 DNS 答案之外,一切看起来都很顺利 - 我没有得到任何答案,什么也没有。

我的代码(抱歉,它与产品级代码相去甚远):

var
  D:WSAData;
  SendSocket, ReceiveSocket: TSocket;
  bytes: Integer;

  bOpt : Integer;
  Buf : TPacketBuffer;
  SendAddrIn : TSockAddrIn;
  RecvAddIn: TSockAddrIn;
  sockAddrSize: Integer;
  iTotalSize : Word;

begin
  try
    if WSAStartup($202, D)<>0 then
    begin
      writeln('error..');
      exit;
    end;

    SendSocket:=socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if SendSocket=INVALID_SOCKET then
      writeln(WSAGetLastError);

    // Option: Header Include
    bOpt := 1;
    bytes := SetSockOpt(SendSocket, IPPROTO_IP, IP_HDRINCL, @bOpt, SizeOf(bOpt));
    if bytes = SOCKET_ERROR then
    begin
      Writeln('setsockopt(IP_HDRINCL) failed: '+IntToStr(WSAGetLastError));
      exit;
    end;

    BuildHeaders(SrcIP, SrcPort,
                 DestIP, DestPort,
                 dns,
                 Buf, SendAddrIn, iTotalSize);

    Writeln(inttostr(iTotalSize) + ' bytes to send');

    bytes := SendTo(SendSocket, buf, iTotalSize, 0, @SendAddrIn, SizeOf(SendAddrIn));
    if bytes = SOCKET_ERROR then
      writeln('sendto() failed: '+IntToStr(WSAGetLastError))
    else
      writeln('send '+IntToStr(bytes)+' bytes.');

    ReceiveSocket:=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    RecvAddIn.sin_addr.s_addr := htonl(0);
    RecvAddIn.sin_family := AF_INET;
    RecvAddIn.sin_port := htons(SrcPort);
    if bind(ReceiveSocket, TSockAddr(RecvAddIn), sizeof(RecvAddIn)) = SOCKET_ERROR then
    begin
      writeln('bind() failed: '+IntToStr(WSAGetLastError));
      exit;
    end;

    FillChar(buf, SizeOf(buf), 0);
    sockAddrSize := sizeof(RecvAddIn);
    bytes := RecvFrom(ReceiveSocket, buf, SizeOf(buf), 0, TSockAddr(RecvAddIn), sockAddrSize);
    if bytes = SOCKET_ERROR then
      writeln('RecvFrom() failed: '+IntToStr(WSAGetLastError))
    else
      writeln('RecvFrom '+IntToStr(bytes)+' bytes.');

    CloseSocket(SendSocket);
    CloseSocket(ReceiveSocket);
    WSACleanup;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Wireshark 显示此数据包为:

我尝试创建两个具有相同本地端口的套接字来发送和接收数据,每个套接字都有自己的类型。出了什么问题?...

更新:

谢谢你们的想法。 事实上,接收套接字必须在发送之前完全初始化。 但正如我发现的,主要问题是 UDP 数据包校验和计算。我发现一个简单的“ping”工具生成的校验和不等于我的代码生成的校验和(当然,对于相同的输入值)。当我刚刚使用它们的值时(同样,所有输入值都被保留)- DNS 服务器返回了响应! 为了生成校验和,我使用下一个代码:

function CheckSum(var Buffer; Size : integer) : Word;
type
  TWordArray = array[0..1] of Word;
var
  ChkSum : LongWord;
  i : Integer;
begin
  ChkSum := 0;
  i := 0;
  while Size > 1 do
  begin
    ChkSum := ChkSum + TWordArray(Buffer)[i];
    inc(i);
    Size := Size - SizeOf(Word);
  end;

  if Size=1 then
    ChkSum := ChkSum + Byte(TWordArray(Buffer)[i]);

  ChkSum := (ChkSum shr 16) + (ChkSum and $FFFF);
  ChkSum := ChkSum + (Chksum shr 16);

  Result := Word(ChkSum);
end;


procedure BuildHeaders(FromIP : string; iFromPort : Word;
                       ToIP : string; iToPort : Word;
                       StrMessage : TBytes; var Buf: TPacketBuffer;
                       var remote : TSockAddrIn; var iTotalSize: Word);
var
  dwFromIP : LongWord;
  dwToIP : LongWord;

  iIPVersion : Word;
  iIPSize : Word;
  ipHdr : T_IP_Header;
  udpHdr : T_UDP_Header;

  iUdpSize : Word;
  iUdpChecksumSize : Word;
  cksum : Word;

  Ptr : ^Byte;

  procedure IncPtr(Value : Integer);
  begin
    ptr := pointer(integer(ptr) + Value);
  end;

begin
  dwFromIP := inet_Addr(PAnsiChar(AnsiString(FromIP)));
  dwToIP := inet_Addr(PAnsiChar(AnsiString(ToIP)));

  iTotalSize := sizeof(ipHdr) + sizeof(udpHdr) + length(strMessage);

  iIPVersion := 4;
  iIPSize := sizeof(ipHdr) div sizeof(LongWord);
  //
  // IP version goes in the high order 4 bits of ip_verlen. The
  // IP header length (in 32-bit words) goes in the lower 4 bits.
  //
  ipHdr.ip_verlen := (iIPVersion shl 4) or iIPSize;
  ipHdr.ip_tos := 0; // IP type of service
  ipHdr.ip_totallength := htons(iTotalSize); // Total packet len
  ipHdr.ip_id := $1545; // Unique identifier: set to 0
  ipHdr.ip_offset := 0; // Fragment offset field
  ipHdr.ip_ttl := 128; 
  ipHdr.ip_protocol := $11; // Protocol(UDP)
  ipHdr.ip_checksum := 0 ; // IP checksum
  ipHdr.ip_srcaddr := dwFromIP; // Source address
  ipHdr.ip_destaddr := dwToIP; // Destination address

  iUdpSize := sizeof(udpHdr) + length(strMessage);

  udpHdr.src_portno := htons(iFromPort) ;
  udpHdr.dst_portno := htons(iToPort) ;
  udpHdr.udp_length := htons(iUdpSize) ;
  udpHdr.udp_checksum := 0 ;
  //
  // Build the UDP pseudo-header for calculating the UDP checksum.
  // The pseudo-header consists of the 32-bit source IP address,
  // the 32-bit destination IP address, a zero byte, the 8-bit
  // IP protocol field, the 16-bit UDP length, and the UDP
  // header itself along with its data (padded with a 0 if
  // the data is odd length).
  //
  iUdpChecksumSize := 0;

  ptr := @buf[0];
  FillChar(Buf, SizeOf(Buf), 0);

  Move(ipHdr.ip_srcaddr, ptr^, SizeOf(ipHdr.ip_srcaddr));
  IncPtr(SizeOf(ipHdr.ip_srcaddr));

  iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_srcaddr);

  Move(ipHdr.ip_destaddr, ptr^, SizeOf(ipHdr.ip_destaddr));
  IncPtr(SizeOf(ipHdr.ip_destaddr));

  iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_destaddr);

  IncPtr(1);

  Inc(iUdpChecksumSize);

  Move(ipHdr.ip_protocol, ptr^, sizeof(ipHdr.ip_protocol));
  IncPtr(sizeof(ipHdr.ip_protocol));
  iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_protocol);

  Move(udpHdr.udp_length, ptr^, sizeof(udpHdr.udp_length));
  IncPtr(sizeof(udpHdr.udp_length));
  iUdpChecksumSize := iUdpChecksumSize + sizeof(udpHdr.udp_length);

  move(udpHdr, ptr^, sizeof(udpHdr));
  IncPtr(sizeof(udpHdr));
  iUdpChecksumSize := iUdpCheckSumSize + sizeof(udpHdr);

  Move(StrMessage[1], ptr^, Length(strMessage));
  IncPtr(Length(StrMessage));

  iUdpChecksumSize := iUdpChecksumSize + length(strMessage);

  cksum := checksum(buf, iUdpChecksumSize);
  udpHdr.udp_checksum := $FA8B;//cksum;

  //
  // Now assemble the IP and UDP headers along with the data
  // so we can send it
  //
  FillChar(Buf, SizeOf(Buf), 0);
  Ptr := @Buf[0];

  Move(ipHdr, ptr^, SizeOf(ipHdr)); IncPtr(SizeOf(ipHdr));
  Move(udpHdr, ptr^, SizeOf(udpHdr)); IncPtr(SizeOf(udpHdr));
  Move(StrMessage[0], ptr^, length(StrMessage));

  remote.sin_family := AF_INET;
  remote.sin_port := htons(iToPort);
  remote.sin_addr.s_addr := dwToIP;
end;

如果有人有另一个运行良好的实现,请分享...

I can build a raw IP packet that contains a UDP packet that contains useful data (DNS request).
I can send it and see that it's sent in Wireshark. Wireshark parses it as a legal DNS request, so everything looks smoothly except the DNS answer - I get no answer, nothing.

My code (sorry, it's far from prod-level code):

var
  D:WSAData;
  SendSocket, ReceiveSocket: TSocket;
  bytes: Integer;

  bOpt : Integer;
  Buf : TPacketBuffer;
  SendAddrIn : TSockAddrIn;
  RecvAddIn: TSockAddrIn;
  sockAddrSize: Integer;
  iTotalSize : Word;

begin
  try
    if WSAStartup($202, D)<>0 then
    begin
      writeln('error..');
      exit;
    end;

    SendSocket:=socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if SendSocket=INVALID_SOCKET then
      writeln(WSAGetLastError);

    // Option: Header Include
    bOpt := 1;
    bytes := SetSockOpt(SendSocket, IPPROTO_IP, IP_HDRINCL, @bOpt, SizeOf(bOpt));
    if bytes = SOCKET_ERROR then
    begin
      Writeln('setsockopt(IP_HDRINCL) failed: '+IntToStr(WSAGetLastError));
      exit;
    end;

    BuildHeaders(SrcIP, SrcPort,
                 DestIP, DestPort,
                 dns,
                 Buf, SendAddrIn, iTotalSize);

    Writeln(inttostr(iTotalSize) + ' bytes to send');

    bytes := SendTo(SendSocket, buf, iTotalSize, 0, @SendAddrIn, SizeOf(SendAddrIn));
    if bytes = SOCKET_ERROR then
      writeln('sendto() failed: '+IntToStr(WSAGetLastError))
    else
      writeln('send '+IntToStr(bytes)+' bytes.');

    ReceiveSocket:=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    RecvAddIn.sin_addr.s_addr := htonl(0);
    RecvAddIn.sin_family := AF_INET;
    RecvAddIn.sin_port := htons(SrcPort);
    if bind(ReceiveSocket, TSockAddr(RecvAddIn), sizeof(RecvAddIn)) = SOCKET_ERROR then
    begin
      writeln('bind() failed: '+IntToStr(WSAGetLastError));
      exit;
    end;

    FillChar(buf, SizeOf(buf), 0);
    sockAddrSize := sizeof(RecvAddIn);
    bytes := RecvFrom(ReceiveSocket, buf, SizeOf(buf), 0, TSockAddr(RecvAddIn), sockAddrSize);
    if bytes = SOCKET_ERROR then
      writeln('RecvFrom() failed: '+IntToStr(WSAGetLastError))
    else
      writeln('RecvFrom '+IntToStr(bytes)+' bytes.');

    CloseSocket(SendSocket);
    CloseSocket(ReceiveSocket);
    WSACleanup;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Wireshark shows this packet as:
Packet seems sent correctly

I tried to create two sockets with the same local port to send and to receive data, each of its own type. What is wrong?..

UPDATE:

Thank you guys for the ideas.
Indeed, the receiving socket has to be fully initialized before any sending.
But as I've found - the main issue is with UDP packet checksum calculation. I've found out that a simple "ping" tool generates a checksum that doesn't equal the one generated by my code (of course, for the same input values). And when I just used their value (again, all the input values were preserved) - the DNS server returned the response!
To generate the checksum I use the next code:

function CheckSum(var Buffer; Size : integer) : Word;
type
  TWordArray = array[0..1] of Word;
var
  ChkSum : LongWord;
  i : Integer;
begin
  ChkSum := 0;
  i := 0;
  while Size > 1 do
  begin
    ChkSum := ChkSum + TWordArray(Buffer)[i];
    inc(i);
    Size := Size - SizeOf(Word);
  end;

  if Size=1 then
    ChkSum := ChkSum + Byte(TWordArray(Buffer)[i]);

  ChkSum := (ChkSum shr 16) + (ChkSum and $FFFF);
  ChkSum := ChkSum + (Chksum shr 16);

  Result := Word(ChkSum);
end;


procedure BuildHeaders(FromIP : string; iFromPort : Word;
                       ToIP : string; iToPort : Word;
                       StrMessage : TBytes; var Buf: TPacketBuffer;
                       var remote : TSockAddrIn; var iTotalSize: Word);
var
  dwFromIP : LongWord;
  dwToIP : LongWord;

  iIPVersion : Word;
  iIPSize : Word;
  ipHdr : T_IP_Header;
  udpHdr : T_UDP_Header;

  iUdpSize : Word;
  iUdpChecksumSize : Word;
  cksum : Word;

  Ptr : ^Byte;

  procedure IncPtr(Value : Integer);
  begin
    ptr := pointer(integer(ptr) + Value);
  end;

begin
  dwFromIP := inet_Addr(PAnsiChar(AnsiString(FromIP)));
  dwToIP := inet_Addr(PAnsiChar(AnsiString(ToIP)));

  iTotalSize := sizeof(ipHdr) + sizeof(udpHdr) + length(strMessage);

  iIPVersion := 4;
  iIPSize := sizeof(ipHdr) div sizeof(LongWord);
  //
  // IP version goes in the high order 4 bits of ip_verlen. The
  // IP header length (in 32-bit words) goes in the lower 4 bits.
  //
  ipHdr.ip_verlen := (iIPVersion shl 4) or iIPSize;
  ipHdr.ip_tos := 0; // IP type of service
  ipHdr.ip_totallength := htons(iTotalSize); // Total packet len
  ipHdr.ip_id := $1545; // Unique identifier: set to 0
  ipHdr.ip_offset := 0; // Fragment offset field
  ipHdr.ip_ttl := 128; 
  ipHdr.ip_protocol := $11; // Protocol(UDP)
  ipHdr.ip_checksum := 0 ; // IP checksum
  ipHdr.ip_srcaddr := dwFromIP; // Source address
  ipHdr.ip_destaddr := dwToIP; // Destination address

  iUdpSize := sizeof(udpHdr) + length(strMessage);

  udpHdr.src_portno := htons(iFromPort) ;
  udpHdr.dst_portno := htons(iToPort) ;
  udpHdr.udp_length := htons(iUdpSize) ;
  udpHdr.udp_checksum := 0 ;
  //
  // Build the UDP pseudo-header for calculating the UDP checksum.
  // The pseudo-header consists of the 32-bit source IP address,
  // the 32-bit destination IP address, a zero byte, the 8-bit
  // IP protocol field, the 16-bit UDP length, and the UDP
  // header itself along with its data (padded with a 0 if
  // the data is odd length).
  //
  iUdpChecksumSize := 0;

  ptr := @buf[0];
  FillChar(Buf, SizeOf(Buf), 0);

  Move(ipHdr.ip_srcaddr, ptr^, SizeOf(ipHdr.ip_srcaddr));
  IncPtr(SizeOf(ipHdr.ip_srcaddr));

  iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_srcaddr);

  Move(ipHdr.ip_destaddr, ptr^, SizeOf(ipHdr.ip_destaddr));
  IncPtr(SizeOf(ipHdr.ip_destaddr));

  iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_destaddr);

  IncPtr(1);

  Inc(iUdpChecksumSize);

  Move(ipHdr.ip_protocol, ptr^, sizeof(ipHdr.ip_protocol));
  IncPtr(sizeof(ipHdr.ip_protocol));
  iUdpChecksumSize := iUdpChecksumSize + sizeof(ipHdr.ip_protocol);

  Move(udpHdr.udp_length, ptr^, sizeof(udpHdr.udp_length));
  IncPtr(sizeof(udpHdr.udp_length));
  iUdpChecksumSize := iUdpChecksumSize + sizeof(udpHdr.udp_length);

  move(udpHdr, ptr^, sizeof(udpHdr));
  IncPtr(sizeof(udpHdr));
  iUdpChecksumSize := iUdpCheckSumSize + sizeof(udpHdr);

  Move(StrMessage[1], ptr^, Length(strMessage));
  IncPtr(Length(StrMessage));

  iUdpChecksumSize := iUdpChecksumSize + length(strMessage);

  cksum := checksum(buf, iUdpChecksumSize);
  udpHdr.udp_checksum := $FA8B;//cksum;

  //
  // Now assemble the IP and UDP headers along with the data
  // so we can send it
  //
  FillChar(Buf, SizeOf(Buf), 0);
  Ptr := @Buf[0];

  Move(ipHdr, ptr^, SizeOf(ipHdr)); IncPtr(SizeOf(ipHdr));
  Move(udpHdr, ptr^, SizeOf(udpHdr)); IncPtr(SizeOf(udpHdr));
  Move(StrMessage[0], ptr^, length(StrMessage));

  remote.sin_family := AF_INET;
  remote.sin_port := htons(iToPort);
  remote.sin_addr.s_addr := dwToIP;
end;

If anyone has another well-working implementation, please share...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

陌路黄昏 2025-01-25 03:00:16

您正在创建单独的插座以发送和接收DNS数据包,但是在发送请求之后,您正在创建接收套接字。在之前,响应到达可能/可能是接收插座准备就绪(使用Wireshark确认),在这种情况下,响应将仅由OS丢弃。

您需要在发送请求之前完全准备接收套接字

You are creating separate sockets to send and receive the DNS packets, but you are creating the receiving socket after sending the request. It is possible/likely that the response arrives before the receiving socket is ready (use Wireshark to confirm that), in which case the response will simply be discarded by the OS.

You need to fully prepare the receiving socket before you send the request.

还如梦归 2025-01-25 03:00:16

好的,我在校验和计算中找到了错误。

下一版正常工作并生成正确的校验和:

function CheckSum(var Buffer; Size : integer) : Word;
type
  TWordArray = array[0..1] of Word;
var
  ChkSum : LongWord;
  i : Integer;
  Item: Word;
begin
  ChkSum := 0;
  i := 0;
  while Size > 1 do
  begin
    Item := TWordArray(Buffer)[i];
    Item := Swap(Item);
    ChkSum := ChkSum + Item;
    inc(i);
    Size := Size - SizeOf(Word);
  end;

  if Size=1 then
    ChkSum := ChkSum + Byte(TWordArray(Buffer)[i]);

  ChkSum := (ChkSum shr 16) + (ChkSum and $FFFF);
  ChkSum := not ChkSum;
//  ChkSum := ChkSum + (Chksum shr 16);

  Result := Word(ChkSum);
end;

如果您看到任何问题,请分享您的想法。

Ok, I've found the bug in the checksum calculation.

The next edition works fine and generates the correct checksum:

function CheckSum(var Buffer; Size : integer) : Word;
type
  TWordArray = array[0..1] of Word;
var
  ChkSum : LongWord;
  i : Integer;
  Item: Word;
begin
  ChkSum := 0;
  i := 0;
  while Size > 1 do
  begin
    Item := TWordArray(Buffer)[i];
    Item := Swap(Item);
    ChkSum := ChkSum + Item;
    inc(i);
    Size := Size - SizeOf(Word);
  end;

  if Size=1 then
    ChkSum := ChkSum + Byte(TWordArray(Buffer)[i]);

  ChkSum := (ChkSum shr 16) + (ChkSum and $FFFF);
  ChkSum := not ChkSum;
//  ChkSum := ChkSum + (Chksum shr 16);

  Result := Word(ChkSum);
end;

If you see any issues with it, please share your thoughts.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文