Thread View: borland.public.cppbuilder.internet.socket
5 messages
5 total messages
Started by Dennis Cote
Wed, 16 Apr 2008 17:10
Error handling in Indy 10
Author: Dennis Cote
Date: Wed, 16 Apr 2008 17:10
Date: Wed, 16 Apr 2008 17:10
123 lines
6367 bytes
6367 bytes
Hi All,
I have an application that has recently been converted to use Indy 10.
This application runs correctly on Win XP. I am now testing the
application under Vista. It does not work under Vista and I believe the
issue has to do with Administrator rights, but that is not the immediate
concern.
My concern is with the nature of the error checking being done in the
Indy code. A failure to open a socket is generating a RangeError exception.
Just before it fails my application ends up in NewSocketHandle while
trying to open an TIdICMPClient. This function is copied below.
function TIdStackBSDBase.NewSocketHandle(const ASocketType:TIdSocketType;
const AProtocol: TIdSocketProtocol;
const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION;
const AOverlapped: Boolean = False): TIdStackSocketHandle;
begin
Result := CheckForSocketError(WSSocket(IdIPFamily[AIPVersion],
ASocketType, AProtocol, AOverlapped));
end;
I have stepped through the assembly code of this routine to track the
problem. This routine is essentially the one line.
IdStackBSDBase.pas.389: Result :=
CheckForSocketError(WSSocket(IdIPFamily[AIPVersion], ASocketType,
00837321 8B45FC mov eax,[ebp-$04]
00837324 50 push eax
00837325 0FB64508 movzx eax,[ebp+$08]
00837329 50 push eax
0083732A 8BCE mov ecx,esi
0083732C 85C9 test ecx,ecx
0083732E 7905 jns $00837335
00837330 E8F35FE9FF call System::__linkproc__ BoundErr()
00837335 0FB6450C movzx eax,[ebp+$0c]
00837339 8B1485246E9900 mov edx,[eax*4+$996e24]
00837340 8BC3 mov eax,ebx
00837342 8B30 mov esi,[eax]
00837344 FF96C4000000 call dword ptr [esi+$000000c4]
0083734A 8BD0 mov edx,eax
0083734C 85D2 test edx,edx
0083734E 7905 jns $00837355
00837350 E8D35FE9FF call System::__linkproc__ BoundErr()
00837355 8BC3 mov eax,ebx
00837357 E81C0D0000 call Idstack::TIdStack::CheckForSocketError(int)
0083735C 85C0 test eax,eax
0083735E 7905 jns $00837365
00837360 E8C35FE9FF call System::__linkproc__ BoundErr()
This code pushes the arguments to WSSocket() and then makes the virtual
function call at 00837344. This ends up at WSSocket in the Windows stack.
function TIdStackWindows.WSSocket(AFamily, AStruct, AProtocol: Integer;
const AOverlapped: Boolean = False): TIdStackSocketHandle;
begin
if AOverlapped then begin
Result := WSASocket(AFamily, AStruct,
AProtocol,nil,0,WSA_FLAG_OVERLAPPED);
end else begin
Result := IdWinsock2.Socket(AFamily, AStruct, AProtocol);
end;
end;
The argument AOverlapped is false, so it makes the call to
IdWinsock2.Socket().
IdStackWindows.pas.600: Result := IdWinsock2.Socket(AFamily, AStruct,
AProtocol);
00835494 50 push eax
00835495 51 push ecx
00835496 52 push edx
00835497 A12CF19900 mov eax,[$0099f12c]
0083549C 8B00 mov eax,[eax]
0083549E FFD0 call eax
IdStackWindows.pas.602: end;
008354A0 5D pop ebp
008354A1 C20800 ret $0008
This function is failing (probably because of administrator rights) and
returning a value of 0xFFFFFFFF (or -1) in the ax register to
NewSocketHandle.
NewSocketHandle now does a range check on the result at 0083734A through
0083734E. I'm not sure if that is correct but it seems like it might be
(see below). In any case, the range check is failing because the result
is negative (i.e the sign flag is set by the test instruction). This
causes the code to call System::BoundErr() which generates the
RangeError exception. The result of WSSocket() never makes it to the
function CheckForSocketError() which is obviously intended to check this
result because of this range check.
Now I'm no Delphi expert (i.e. it took me quite a while to confirm that
LongWord and Longword are the same type because Delphi is case
insensitive) but I tracked the types to see why this range check is
being done. It seems reasonable to me, but I may be mistaken.
WSSocket() is defined to return a TIdStackSocketHandle result. This type
is defined as a TSocket at line 115 of IdStatckConsts.pas. This type is
in turn defined as PtrUint at line 219 of IdWinsock2.pas. Finally this
type is defined as LongWord at line 674 of IdGlobal.pas.
The type of the argumnet to function CheckForSocketError() is defined as
Integer at line 571 of IdStack.pas.
So NewSocketHandle() is trying to pass the LongWord result from
WSSocket() to the Integer argument of CheckForSocketError(). Since
LongWord is an unsiged type, its range is larger than the Integer
arguments type. Hence the range check is required.
It seems to me that this range check will always fail if WSSocket
returns 0xFFFFFFFF (or -1) for a failure, and hence we will never get to
see the real reason for the failure that CheckForSocketError() is
supposed to produce. I think some type casting must be needed to tell
the compiler not to check the range of the result before passing it to
CheckForSocketError(), but I'm not sure how that is done in Delphi. I
also suspect this same error may exist in other places in the code.
Can anyone say if this analysis is correct, and if so how the result
should be cast from unsigned to signed without the range check?
Dennis Cote
Re: Error handling in Indy 10
Author: Remy Lebeau (TeamB)
Date: Wed, 16 Apr 2008 20:23
Date: Wed, 16 Apr 2008 20:23
96 lines
4496 bytes
4496 bytes
"Dennis Cote" <denniscote@gmail.com> wrote in message
news:480632fb$1@newsgroups.borland.com...
> I am now testing the application under Vista. It does not
> work under Vista and I believe the issue has to do with
> Administrator rights
You are using the TIdICMPClient component, which uses a RAW socket
internally (as opposed to using a TCP or UDP socket). Admin rights are
required in order to use RAW sockets on NT4 onwards (which means your code
should not have worked on XP, either, unless you are running as an admin).
Of course, Microsoft circumvents its own rules by allowing icmp.dll to use
RAW sockets without admin rights.
> I think some type casting must be needed to tell the
> compiler not to check the range of the result before
> passing it to CheckForSocketError() but I'm not
> sure how that is done in Delphi.
Rather than casting, you could disable range checking itself for
NewSocketHandle(). Indy does this in a couple of other places, for example:
function TIdStackBSDBase.NewSocketHandle(const ASocketType:
TIdSocketType;
const AProtocol: TIdSocketProtocol;
const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION;
const AOverlapped: Boolean = False): TIdStackSocketHandle;
begin
{$IFOPT R+}
{$DEFINE RestoreRangeCheckingON}
{$R-}
{$ENDIF}
Result := CheckForSocketError(WSSocket(IdIPFamily[AIPVersion],
ASocketType, AProtocol, AOverlapped));
{$IFDEF RestoreRangeCheckingON}
{$R+}
{$UNDEF RestoreRangeCheckingON}
{$ENDIF}
end;
Otherwise, you would have to hard-code the casting, which can be pretty
ugly:
function TIdStackBSDBase.NewSocketHandle(const ASocketType:
TIdSocketType;
const AProtocol: TIdSocketProtocol;
const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION;
const AOverlapped: Boolean = False): TIdStackSocketHandle;
begin
Result :=
TIdStackSocketHandle(CheckForSocketError(Integer(WSSocket(IdIPFamily[AIPVersion],
ASocketType, AProtocol, AOverlapped))));
end;
Here is a cleaner version:
function TIdStackBSDBase.NewSocketHandle(const ASocketType:
TIdSocketType;
const AProtocol: TIdSocketProtocol;
const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION;
const AOverlapped: Boolean = False): TIdStackSocketHandle;
begin
Result := WSSocket(IdIPFamily[AIPVersion], ASocketType, AProtocol,
AOverlapped);
CheckForSocketError(Integer(Result));
end;
> I also suspect this same error may exist in other places in the code.
Actually, it doesn't. Everywhere else that calls CheckForSocketError()
passes it an Integer.
> Can anyone say if this analysis is correct, and if so how the
> result should be cast from unsigned to signed without the
> range check?
Since CheckForSocketError() is expecting an Integer error code, not a
TSocket handle, I would suggest changing TIdStackBSDBase.NewSocketHandle()
to the following instead:
function TIdStackBSDBase.NewSocketHandle(const ASocketType:
TIdSocketType;
const AProtocol: TIdSocketProtocol;
const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION;
const AOverlapped: Boolean = False): TIdStackSocketHandle;
begin
Result := WSSocket(IdIPFamily[AIPVersion], ASocketType, AProtocol,
AOverlapped);
if Result = Id_INVALID_SOCKET then begin
RaiseLastSocketError;
end;
end;
Gambit
Re: Error handling in Indy 10
Author: Dennis Cote
Date: Wed, 16 Apr 2008 21:00
Date: Wed, 16 Apr 2008 21:00
72 lines
3660 bytes
3660 bytes
Remy Lebeau (TeamB) wrote:
>
> You are using the TIdICMPClient component, which uses a RAW socket
> internally (as opposed to using a TCP or UDP socket). Admin rights are
> required in order to use RAW sockets on NT4 onwards (which means your code
> should not have worked on XP, either, unless you are running as an admin).
> Of course, Microsoft circumvents its own rules by allowing icmp.dll to use
> RAW sockets without admin rights.
>
Yes, I am running as an administrator under XP and Vista.
>
> Rather than casting, you could disable range checking itself for
> NewSocketHandle(). Indy does this in a couple of other places, for example:
>
> function TIdStackBSDBase.NewSocketHandle(const ASocketType:
> TIdSocketType;
> const AProtocol: TIdSocketProtocol;
> const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION;
> const AOverlapped: Boolean = False): TIdStackSocketHandle;
> begin
> {$IFOPT R+}
> {$DEFINE RestoreRangeCheckingON}
> {$R-}
> {$ENDIF}
> Result := CheckForSocketError(WSSocket(IdIPFamily[AIPVersion],
> ASocketType, AProtocol, AOverlapped));
> {$IFDEF RestoreRangeCheckingON}
> {$R+}
> {$UNDEF RestoreRangeCheckingON}
> {$ENDIF}
> end;
>
This would also disable the other range check done during the call of
WSSocket(). I'm don't think that is needed, and it should probably be
left until it is known to cause a problem.
>
>> I also suspect this same error may exist in other places in the code.
>
> Actually, it doesn't. Everywhere else that calls CheckForSocketError()
> passes it an Integer.
>
Good to hear.
>
> Since CheckForSocketError() is expecting an Integer error code, not a
> TSocket handle, I would suggest changing TIdStackBSDBase.NewSocketHandle()
> to the following instead:
>
> function TIdStackBSDBase.NewSocketHandle(const ASocketType:
> TIdSocketType;
> const AProtocol: TIdSocketProtocol;
> const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION;
> const AOverlapped: Boolean = False): TIdStackSocketHandle;
> begin
> Result := WSSocket(IdIPFamily[AIPVersion], ASocketType, AProtocol,
> AOverlapped);
> if Result = Id_INVALID_SOCKET then begin
> RaiseLastSocketError;
> end;
> end;
>
Thanks Remy. I will make this change in my local source file. Will this
also be changed in the Indy SVN source, or will I have to apply a patch
to future downloads?
Dennis Cote
Re: Error handling in Indy 10
Author: Remy Lebeau (TeamB)
Date: Wed, 16 Apr 2008 23:08
Date: Wed, 16 Apr 2008 23:08
12 lines
1373 bytes
1373 bytes
"Dennis Cote" <denniscote@gmail.com> wrote in message news:480668fa$1@newsgroups.borland.com... > Thanks Remy. I will make this change in my local source file. > Will this also be changed in the Indy SVN source, or will I > have to apply a patch to future downloads? If it works for you, then I will merge it into SVN. Gambit
Re: Error handling in Indy 10
Author: Dennis Cote
Date: Thu, 17 Apr 2008 15:16
Date: Thu, 17 Apr 2008 15:16
13 lines
1323 bytes
1323 bytes
Remy Lebeau (TeamB) wrote: > > If it works for you, then I will merge it into SVN. > It works for me. I get correct operation of the ICMP client under XP. I also now get an Access Denied exception under Vista (as expected) rather than the Range Error exception when WSSocket() fails. Dennis Cote
Thread Navigation
This is a paginated view of messages in the thread with full content displayed inline.
Messages are displayed in chronological order, with the original post highlighted in green.
Use pagination controls to navigate through all messages in large threads.
Back to All Threads