由于最近在看UDP打洞,本着力求精简,不用三方控件的原则下,折腾了Sockets.pas这个单元。在弄清如何用Sockets单元中类执行udp server的功能时,发现了一个小BUG。
BUG描述:
无法判断UDP数据包是谁发来的。这个BUG存在于TIpSocket类的ReceiveFrom方法中,功能是从当前的Socket中接收数据包,并可以返回对方的TSockAddr,在UDP接收中,这个很重要,因为UDP需要知道数据包是从哪里发送来的。
此函数的实现为:
function TIpSocket.ReceiveFrom(var buf; bufsize: Integer; ToAddr: TSockAddr; var len: Integer; flags: Integer): Integer;
begin
{$IFDEF MSWINDOWS}
Result := ErrorCheck(WinSock.recvfrom(FSocket, buf, bufsize, flags, ToAddr, len));
{$ENDIF}
{$IFDEF LINUX}
Result := ErrorCheck(Libc.recvfrom(FSocket, buf, bufsize, flags, @ToAddr, @len));
{$ENDIF}
if Result <> SOCKET_ERROR then
DoReceive(pchar(@Buf), Result);
end;
可以发现,ToAddr是传值进入函数而被WinSock.RecvFrom调用的,这意味着,返回的源地址并不能返回。修正起来很简单,把声明改成var传入就可以了
function TIpSocket.ReceiveFrom(var buf; bufsize: Integer; var ToAddr: TSockAddr; var len: Integer; flags: Integer): Integer;
begin
{$IFDEF MSWINDOWS}
Result := ErrorCheck(WinSock.recvfrom(FSocket, buf, bufsize, flags, ToAddr, len));
{$ENDIF}
{$IFDEF LINUX}
Result := ErrorCheck(Libc.recvfrom(FSocket, buf, bufsize, flags, @ToAddr, @len));
{$ENDIF}
if Result <> SOCKET_ERROR then
DoReceive(pchar(@Buf), Result);
end;
修正方案:
使用给vcl打补丁的方法
unit u_SocketsPatcher;
interface
uses
Sockets, WinSock;
implementation
Procedure PatchVCLCode(ProcOld, ProcNew: Pointer);
var
newCode : packed record
JmpRel32 : Byte;
Offset32 : Integer;
end;
begin
newCode.JmpRel32 := $E9;
newCode.Offset32 := Integer(procNew) - Integer(procOld) - 5;
WriteProcessMemory(
GetCurrentProcess,
procOld,
@newCode,
SizeOf(newCode),
DWORD(nil^) );
end;
type
TIPSocketPatch = Class(TIpSocket)
Private
function ReceiveFrom2(var buf; bufsize: Integer; var ToAddr: TSockAddr; var len: Integer; flags: Integer = 0): Integer;
End;
{ TIPSocketPatch }
function TIPSocketPatch.ReceiveFrom2(var buf; bufsize: Integer;
var ToAddr: TSockAddr; var len: Integer; flags: Integer): Integer;
begin
{$IFDEF MSWINDOWS}
Result := ErrorCheck(WinSock.recvfrom(Handle, buf, bufsize, flags, ToAddr, len));
{$ENDIF}
{$IFDEF LINUX}
Result := ErrorCheck(Libc.recvfrom(FSocket, buf, bufsize, flags, @ToAddr, @len));
{$ENDIF}
if Result <> SOCKET_ERROR then
DoReceive(pchar(@Buf), Result);
end;
initialization
PatchVCLCode(@TIpSocket.ReceiveFrom, @TIPSocketPatch.ReceiveFrom2);
end.
在工程中加入此单元的引用即可
BUG的影响
由于DELPHIER们使用UDP很少使用Sockets.pas中的单元而一般用Indy组件,所以这个BUG影响范围很小,即使使用udp,也都是只用TUdpSocet类作为Client端,而不会调用这个方法。所以,这是一个基本上不会被调用的BUG函数,也很少影响到Delphier们。但如果要想自己力求精简的编写一些基础组件的话,这个问题就会被波及,比如此次我弄的这个UDP Hole Punching的时候,终于显现出来了,也花了我半天的时间,唉....