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

Raw UDP packets via Winsock2 on Delphi

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

我的代码(抱歉,离prod-level代码差很远):

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

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

begin
  try
    if WSAStartup(2, 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 := 45; // Unique identifier: set to 0
  ipHdr.ip_offset := 0; // Fragment offset field
  ipHdr.ip_ttl := 128; 
  ipHdr.ip_protocol := ; // 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;

如果有人有另一个 well-working 实现,请分享...

您正在创建单独的套接字来发送和接收 DNS 数据包,但是您是在发送请求之后创建接收套接字。 possible/likely 响应在 之前到达 接收套接字准备好(使用 Wireshark 确认),在这种情况下,响应将被 OS.

您需要在发送请求之前充分准备接收套接字。

好的,我发现了校验和计算中的错误。

下一个版本运行良好并生成正确的校验和:

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;

如果您发现任何问题,请分享您的想法。