由于最近在看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的时候,终于显现出来了,也花了我半天的时间,唉....
|
请发表评论