Indy 10 TIdUDPclient - detect wrong/no Answer - indy10

Using delphi 10.3 and JEDI VCL.
I have a communication with a device, which responds to UDP data.
Now I want to be able to check if I got an answer from the right device, or if if I even got any answer.
Currently I am using the following:
function TDIB.ReadData(ACommandCode: WORD; ASendLength : Cardinal; AReceiveLength : Cardinal; AAddress : Cardinal) : Integer;
var
cmdHeader : PDIBCommandHeader;
UDPSend, UDPRecv : TIdBytes;
client : TIdUDPClient;
begin
gRequestPending := TRUE;
// Reserviere Speicher
SetLength(UDPSend, SIzeOF(TDIBCommandHeader) + Cardinal(ASendLength));
SetLength(UDPRecv, SIzeOF(TDIBCommandHeader) + Cardinal(AReceiveLength));
cmdHeader := #UDPSend[0];
cmdHeader.Init(WORD(ACommandCode), AAddress, MAX(ASendLength, AReceiveLength));
client := TIdUDPClient.Create();
try
client.Host := ValToIPv4(gDIBAddress);
client.Port := TDIBPorts.mainPort;
client.Active := TRUE;
client.sendBuffer (UDPSend);
client.ReceiveBuffer(UDPRecv,TDIB.C_CMDTimeout);
except
on E: Exception do
begin
ShowMessage('Exception');
client.Free;
end;
end;
SetLength(lastUDPData, Length(UDPRecv));
move (UDPRecv[0],lastUDPData[0],Length(UDPRecv));
client.Free;
gRequestPending := FALSE;
end;
Which is fine when the client is responding, but I am not catching any misbehaviour, like when the host machine tries to reach the client and the client is not responding.
From the documentation of Indy10 I am missing something like TIdUDPClient.TimedOut or like that.
I want to be able to tell, if the client is not responding after Xms after I sent the UDP packet and I want to be able to check, if the sender address is the wanted client IP.

I want to be able to tell, if the client is not responding after Xms after I sent the UDP packet
ReceiveBuffer() returns the number of bytes actually received. If no packet is received within the specified timeout, ReceiveBuffer() will return 0.
I want to be able to check, if the sender address is the wanted client IP.
Use one of the ReceiveBuffer() overloads that has a VPeerIP output parameter. That will give you the sender IP if a packet is received, or it will give you an empty string if no packet is received.
Do be aware that UDP has a concept of a 0-byte datagram. ReceiveBuffer() will return 0 for that as well. In the case that 0 is returned, you can use this output string to differentiate between no packet received (VPeerIP = '') vs a 0-byte packet received (VPeerIP <> ''), if needed.

Related

How to connect to a IPv6 link local address discovered using IPv4 and set the correct network interface?

Our Delphi/Indy application discovers devices using mDNS on IPv4 but some of the addresses found are link-local IPv6 addresses (specified in AAAA records along with other A records). We want to provide the option of using either IPv4 or IPv6 addresses to connect to the devices because some people insist on setting static IPv4 addresses which due to a change in location or configuration become incompatible with the subnet where they are located.
But when the application attempts to establish a TCP connection to one of these link-local IPv6 addresses, the first attempt usually fails because Windows doesn't know which interface to use. (This same behaviour can be observed when just ping-ing the address from the command line. The problem is solved by writing % followed by the appropriate zone ID at the end of the link-local address.)
The TIdTCPClient object being used to connect to the device therefore needs to be associated with the correct network interface. I understand that this can be done by setting the BoundIP property but I do not have the IPv6 address of the interface where the device was discovered because this was done using IPv4. Is there a way of using the TIdSocketHandle object that was provided in the OnUDPRead event when the device was discovered and using that to set the appropriate thing in the TIdTCPClient object?
But when the application attempts to establish a TCP connection to one of these link-local IPv6 addresses, the first attempt usually fails because Windows doesn't know which interface to use.
Are you setting the TIdTCPClient.IPVersion property to Id_IPv6? Indy does not currently determine the IP version to connect with based on the IP address specified (ticket), you have to explicitly specify the IP version up front.
Most of the time, it should be good enough to let Windows decide which interface to use when connecting to an IP address. That is what its routing tables are meant for.
This same behaviour can be observed when just ping-ing the address from the command line. The problem is solved by writing % followed by the appropriate zone ID at the end of the link-local address.
Indy does not currently support zone/scope IDs for IPv6 addresses (ticket).
Is there a way of using the TIdSocketHandle object that was provided in the OnUDPRead event when the device was discovered
Not likely, because you said the discovery was made over IPv4, which means the TIdSocketHandle's UDP socket was bound to an IPv4 interface, not an IPv6 interface. The only possibility would be if the interface in question has both IPv4 and IPv6 addresses assigned to it, in which case you could use Win32 APIs to match up the TIdSocketHandle.IP to a specific interface, and then enumerate the IPv6 addresses on that same interface.
Otherwise, you can use Indy's TIdStack.GetLocalAddressList() method to get all of the local IPv6 interface IPs, and then bind the TIdTCPClient to each one in a loop until a connection to the target device is successful.
Taking Remy's advice, I implemented the following:
uses Winapi.IpTypes, IdGlobal, IdWinsock2, IdStackBSDBase;
function GetAdaptersAddresses(Family: ULONG; Flags: DWORD; Reserved: PVOID; pAdapterAddresses: PIP_ADAPTER_ADDRESSES; var OutBufLen: ULONG): DWORD; stdcall; external 'iphlpapi.dll';
// Given an IPv4 address of a network interface, this returns one of the IPv6 addresses of the same interface.
function GetAdapterIPv6Address (IPv4Address : String) : String;
const
flags = GAA_FLAG_SKIP_ANYCAST or GAA_FLAG_SKIP_MULTICAST or GAA_FLAG_SKIP_DNS_SERVER or GAA_FLAG_SKIP_FRIENDLY_NAME;
var
BufLen : ULONG;
Ret : DWORD;
Adapter, Adapters : PIP_ADAPTER_ADDRESSES;
UnicastAddr : PIP_ADAPTER_UNICAST_ADDRESS;
UnicastAddr6 : PIP_ADAPTER_UNICAST_ADDRESS;
Found : Boolean;
i : Integer;
a : UInt32;
begin
result := '';
a := IPv4ToUInt32(IPv4Address);
BufLen := 0;
Adapters := nil;
Ret := GetAdaptersAddresses(PF_UNSPEC, flags, nil, nil, BufLen);
if (Ret = ERROR_INSUFFICIENT_BUFFER) or (Ret = ERROR_BUFFER_OVERFLOW) then begin
Adapters := AllocMem(BufLen);
try
Ret := GetAdaptersAddresses(PF_UNSPEC, flags, nil, Adapters, BufLen);
if Ret = ERROR_SUCCESS then begin
Adapter := Adapters;
repeat
UnicastAddr6 := nil;
Found := false;
if (Adapter.IfType <> 24 {IF_TYPE_SOFTWARE_LOOPBACK}) and ((Adapter.Flags and IP_ADAPTER_RECEIVE_ONLY) = 0) then begin
UnicastAddr := Adapter^.FirstUnicastAddress;
while UnicastAddr <> nil do begin
if UnicastAddr^.DadState = IpDadStatePreferred then begin
case UnicastAddr^.Address.lpSockaddr.sin_family of
AF_INET : begin
with TIdIn4Addr(PSockAddrIn(UnicastAddr^.Address.lpSockaddr)^.sin_addr) do
if a = ((S_un_b.s_b1 shl 24) or (S_un_b.s_b2 shl 16) or (S_un_b.s_b3 shl 8) or S_un_b.s_b4) then
Found := true;
end;
AF_INET6 :
if UnicastAddr6 = nil then
UnicastAddr6 := UnicastAddr;
end;
end;
UnicastAddr := UnicastAddr^.Next;
end;
end;
Adapter := Adapter.Next;
until (Adapter = nil) or found;
if found and assigned(UnicastAddr6) then begin
for i := 0 to 7 do begin
if i > 0 then
result := result + ':';
with TIdIn6Addr(PSockAddrIn6(UnicastAddr6^.Address.lpSockaddr)^.sin6_addr) do
result := result + IntToHex(ntohs(s6_addr16[i]), 1);
end;
end;
end;
finally
FreeMem(Adapters);
end;
end;
end;
Due to the device discovery being done via IPv4, the IPv4 address of the network interface is known (IP property of TIdSocketHandle) so by using the above function, TIdTCPClient.BoundIP can be set so that the network interface is unambiguous for any link-local IPv6 address found in the discovery process.

Winsock - Retransmission Seq incremented

I get the following when communicating with a custom client.
With custom client i mean a housemade PCB with a FPGA which runs the Triple-Speed Ethernet Intel FPGA IP. It does not make a difference if a switch is between the PC and the PCB.
Workflow seen from the Server(Windows PC) where i detect this behaviour with wireshark:
Connect to client (Syn - Syn/Ack - Ack) Winsock2.connect
Sending data > MTU with Winsock2.WSASend (4092 Bytes on a 4088 Bytes MTU)
Packet gets "fragmented" into 2 packets - do not fragment bit is set
A Retransmission happens (because the client answered too slow?)
I am using delphi 10.4 and use the Winsock2 functions. Bevore each send i check with select if fdwrite is set if FD_Isset. Nagle is deactivated.
The "retransmission" does not happen everytime and i could not detect any sort of pattern when they occur. Except most of the time it is when the client needs more than 30ms to send his ACK.
When the "retransmission" happens it is not packet 1 or two whicht is re-send, but packet 1 with an offset of 60 which is the payload of packet 2. The sequence number of packet 1 is incremented by 60 too. Even the data is correct, it's correctly incremented by 60.
When I send 6000 Bytes i get the same behaviour with an incremented seq of 1968 which is correct too.
What is happening here?
Can i detect this with winsock2? Can i set the RTO with winsock? Why is the sequence number incremented and not packet 1, as it is, retransmitted?
Source Code of the send function:
function TZWinTCPSock.SendData (out ErrMsg : TAPILogStruct; SendOffset :
Cardinal = 0) : Boolean;
var
WSABuff : WSABUF;
res : Integer;
IPFlags : Cardinal;
t : Cardinal;
WSAErr : Cardinal;
begin
Result := FALSE;
WSAErr := WSAGetLastError;
try
if not CheckSockValid(ErrMsg) then // checks if fd_write is set
begin
exit(false);
end;
try
WSABuff.len := FMem.SendLength; // 4092 at this time Cardinal
WSABuff.buf := #FMem.SendData[SendOffset]; // 8192 Bytes reserved TArray<Byte>
IPFlags := 0;
res := WSASend(FSocket,#WSABuff,1,FMem.sentBytes,IPFlags,nil,nil);
if Res <> SOCKET_ERROR then
begin
if FMem.SendLength <> FMem.SentBytes then
begin
exit(false);
end
else
begin
Result := TRUE;
if WSAGetLastError <> WSAErr then // unexpected WSA error
begin
exit(FALSE);
end;
end;
end
else
begin
FLastWSAErr := WSAGetLastError;
if FLastWSAErr = WSAECONNRESET then
begin
Disconnect(ErrMsg);
exit(false);
end;
end;
except
on E : Exception do
begin
// Some error handling
end;
end;
finally
end;
end;
Edit1
The packets have the don't fragment Bit set.
I tried to detect this "retransmission" with the windows admin center, but I don't see anything popping up.
Got an answer on Microsoft Q&A.
Looks like it was a tail loss probe problem where the destination host is at fault, because the reply took too long and srtt timed out.

Check local port is free from Inno Setup without netstat usage

My question was asked here before, but I'm trying to implement a neater solution for my project. So, as title states, I'm creating a complex installer of the server application that has to check local IP address and choose a open port, so that the application could be properly configured. Inno Setup 5.6.1 is used.
Getting local IP addresses was not a problem, this solution helped me a lot. Then it came to the port's checking and here I've found the following three options:
Using external DLL from installer. Actually, previous solution consisted of a C++ DLL, that exported two exact convenience functions, they worked great, installer used them, but sometimes, rarely on some Windows versions this DLL didn't want to get loaded causing error. That's why all the more messing with Pascal Script here.
Launching netstat via cmd and getting the output. This is still an option, though I feel this solution is crutchy like hell and would like to avoid it. Details could be found in other SO answer.
Getting information from WinAPI call. Looks best if possible.
As was mentioned above, getting IP address can be implemented via straightforward (ok, not really, it's a Pascal Script) WinAPI calls. So, I tried to do the same trick with ports, trying to call GetTcpTable():
[Code]
const
ERROR_INSUFFICIENT_BUFFER = 122;
function GetTcpTable(pTcpTable: Array of Byte; var pdwSize: Cardinal;
bOrder: WordBool): DWORD;
external 'GetTcpTable#IpHlpApi.dll stdcall';
{ ------------------------ }
function CheckPortIsOpen(port: Integer): Boolean;
var
TableSize : Cardinal;
Buffer : Array of Byte; { Alas, no pointers here }
RecordCount : Integer;
i, j : Integer;
portNumber : Cardinal;
IpAddr : String;
begin
Result := True;
TableSize := 0;
if GetTcpTable(Buffer, TableSize, False) = ERROR_INSUFFICIENT_BUFFER then
begin
SetLength(Buffer, TableSize);
if GetTcpTable(Buffer, TableSize, True) = 0 then
begin
{ some magic calculation from GetIpAddrTable calling example }
RecordCount := (Buffer[1] * 256) + Buffer[0];
For i := 0 to RecordCount -1 do
begin
portNumber := Buffer[i*20 + 8]; { Should work! }
{ Debugging code here }
if (i < 5) then begin
IpAddr := '';
For J := 0 to 3 do
begin
if J > 0 then
IpAddr := IpAddr + '_';
IpAddr := IpAddr + IntToStr(Buffer[I*20+ 4 + J]);
end;
SuppressibleMsgBox(IpAddr, mbError, MB_OK, MB_OK);
end;
{ ------ }
if port = portNumber then
Result := False;
end;
end;
end;
end;
This GetTcpTable also returns information about addresses and ports (table of TCP connections to be exact), so trying to get any connection address is good for debugging purposes. More about this attempt:
RecordCount is calculated the same way as in the code I used as an example, because obtained struct there is very similar to the nasty struct I need.
That i*20 + 8 is written that way, because 20 = sizeof(single record struct) and 8 = 2 * sizeof(DWORD). Local TCP connection address is being "parsed" one-by-one-byte at an offset of 1 DWORD, as you can see.
So, everything is great fun... it just is not working =((
And yes, I've tried to print all the bytes one-by-one, to search for the desired data manually and understand the correct offset. To my disappointment, nothing looking like IPs and ports was found, the numbers were quite mysterious.
I know that sometimes the simplest solution is best, not the smartest, but if anyone could give me a key cooking this WinAPI function in a proper way, I would be deeply grateful.
Your magic calculations are off.
portNumber := Buffer[i*20 + 8]; { Should work! }
Since Buffer is a byte array the above extracts one byte only. But the local port number is a DWORD in the TCP table. Though the documentation you linked states:
The local port number in network byte order for the TCP connection on the local computer.
The maximum size of an IP port number is 16 bits, so only the lower 16 bits should be used. The upper 16 bits may contain uninitialized data.
So we need two bytes. And we need to switch them, note "network byte order" above.
You are also forgetting to account for the 4 byte record count at the beginning of the table. So the local port number should become:
portNumber := Buffer[i*20 + 12] * 256 +
Buffer[i*20 + 13];

How to ensure that messages sent with TTetheringAppProfile.SendStream were delivered properly?

I have a simple mobile app, that takes a series of photos and sends it via SendStream() to the connected Profile.
myTetherAppProfile.SendStream(myTetherManager.RemoteProfiles[idConnected],
'ImageData',
bmpStreamData);
The occurring problem here is that the receiver-app doesn't get all the image-streams depending on the connection-strength (The ResourceReceived-Event isn't triggered on the receiver-app).
This would be no problem if I get a response that the delivery failed. But I don't get this (SendStream() returns "True")
Is there a possibility other than implementing a "please answer with another message if you received my image"-function to achieve stable transmissions even with bad connection? Or is App-Tethering by default designed to be lossy?
Also after a big stack of images I sometimes get the "connection reset by peer"-error. (I'm not sure if this error is related to the actual problem, so I preferred posting it.)
Looking at the relevant code from System.Tether.AppProfile (XE8 version), it appears to be a bug. See my inline comments below. Please report to https://quality.embarcadero.com
function TTetheringAppProfile.SendStream(const AProfile: TTetheringProfileInfo; const Description: string;
const AStream: TStream): Boolean;
var
LProfileInfo: TTetheringProfileInfo;
LConnection: TTetheringConnection;
LCommand: TTetheringCommand;
begin
if not FindProfile(AProfile.ProfileIdentifier, LProfileInfo) then
raise ETetheringException.CreateFmt(SNoProfile, [AProfile.ProfileIdentifier]);
CheckProfileIsConnected(AProfile);
LConnection := GetConnectionTo(AProfile);
TMonitor.Enter(LConnection);
try
LCommand := LConnection.Protocol.SendCommandWithResponse(SendStreamCommand, Version, Description);
if LCommand.Command = SendStreamOkResponse then
begin
Result := LConnection.Protocol.TransferStream(AStream);
if Result then
begin <-- Result here is guaranteed to be True
LCommand := LConnection.Protocol.ReceiveCommand;
if LCommand.Command = SendStreamContentOKResponse then
Result := True; <-- Sets Result to True if succeeds,
<-- but nothing to set Result to False if call failed.
end;
end
else
Result := False;
finally
TMonitor.Exit(LConnection);
end;
end;

How to get local IP4 address using Indy?

I'm using TIdStack.LocalAddress to get the local IP address. On an OS X system which has both IP6 and IP4 this returns the IP6 address, which isn't what I want.
What's the best way to find the local IP4 address? I could simply use the shortest entry in TIdStack.LocalAddresses, for example.
The TIdStack.LocalAddress property simply returns the first IP that is listed in the TIdStack.LocalAddresses property (note - these properties are deprecated because they are not thread-safe. You should use the TIdStack.AddLocalAddressesToList() method instead). A PC/device can have multiple local IPs, such as if it is connected to multiple networks, supports both IPv4 and IPv6, etc. The order of the IPs in the LocalAddresses list is determined by the OS, not by Indy. From the sounds of it, you will have to obtain the complete list and loop through it looking for the IPv4 address you are interested in.
TIdStackVCLPosix, which Indy uses for Mac OSX, is actually the only TIdStack implementation that currently supports reporting both IPv4 and IPv6 addresses in the LocalAddresses property (other TIdStack implementations only support IPv4 at this time). However, the list is a plain TStrings, it does not differentiate whether a given IP is IPv4 or IPv6 (it does not use the TStrings.Objects property to provide that info). If you need to differentiate, you will have to parse the IPs manually. In a future release, Indy will replace the TIdStack.LocalAddress(es) properties with a different implementation that natively provides the IP version info.
For example:
var
IPs: TStringList;
IP: String;
I: Integer;
Err: Boolean;
begin
IPs := TStringList.Create;
try
GStack.AddLocalAddressesToList(IPs);
for I := 0 to IPs.Count-1 do
begin
IP := IPs[I];
// TIdStack.IsIP() currently only supports IPv4, but
// it will be updated to support IPv6 in a future
// release...
//
// if GStack.IsIP(IP) then
// if GStack.IsIPv4(IP) then
IPv4ToDWord(IP, Err);
if not Err then
Break;
IP := '';
// alternatively:
{
IPAddr := TIdIPAddress.MakeAddressObject(IPs[I]);
IP := IPAddr.IPv4AsString; // returns blank if not IPv4
IPAddr.Free;
if IP <> '' then
Break;
}
end;
finally
IPs.Free;
end;
if IP <> '' then
begin
// use IP as needed...
end;
end;

Resources