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;
Related
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.
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.
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];
I've created a HTTP server via the IdHTTP-component and now I want to access it via the internet. I've posted a string from a text file.
I can accesss it via:
http://localhost
But how do I acces it via the internet? I've tried http://[myexternalIPaddress]:80 but I do not get a reply.
This is my code:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
myFile : TextFile;
text: String;
begin
AssignFile(myFile, 'C:\Users\xxx\Desktop\test.txt');
Reset(myFile);
ReadLn(myFile, text);
AResponseInfo.ContentText := text;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdHTTPServer1.Active := True;
end;
You need to configure your Windows Firewall (if enabled) to allow inbound connections to port 80, and you also need to configure your network router (if you have one) to forward inbound connections to port 80 on your router's public IP to port 80 on your server machine.
I need to detect the Windows Firewall state (i.e. whether it is enabled or not) in order to display a message warning that a Firewall rule may need to be configured to allow inbound connections on specific ports when the Firewall is enabled, but not when it isn't. See below code example:
[Code]
//Check if Windows Firewall is enabled
function IsWindowsFirewallEnabled(): Boolean;
begin
//Method required here
Result := True;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
//Display a warning message on a Server install if Windows Firewall is enabled
if CurPageID = wpSelectComponents and IsComponentSelected('Server') and IsWindowsFirewallEnabled then
begin
MsgBox('Windows Firewall is currently enabled.' + #13#10 + #13#10 +
'You may need to enable inbound connections on ports 2638, 445 and 7.'
mbInformation, MB_OK);
Result := True;
end;
end;
What I need is a method for the IsWindowsFirewallEnabled function. One way I have read about, and ironically has now more or less been suggested below whilst I was in the middle of updating the question with this information anyway, would appear to be reading the EnableFirewall value from the Registry:
//Check if Windows Firewall is enabled
function IsWindowsFirewallEnabled(): Boolean;
var
crdFirewallState: Cardinal;
begin
RegQueryDwordValue(HKLM, 'SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile',
'EnableFirewall', crdFirewallState);
if crdFirewallState = 1 then
Result := True;
end;
However, I am not convinced by this method as the Registry values for all the profiles show enabled on my work PC, but looking in Control Panel the Domain profile shows disabled (I assume this is related to a Group Policy).
Note, that this needs to work for both Windows XP and Server 2003, and for Windows Vista and Server 2008 and above.
Therefore, what's the most reliable or recommended way to do this?
You would need to determine the registry entry and then query it in a manner similar to this using Innosetup's registry query ability.
var
Country: String;
begin
if RegQueryStringValue(HKEY_CURRENT_USER, 'Control Panel\International',
'sCountry', Country) then
begin
// Successfully read the value
MsgBox('Your country: ' + Country, mbInformation, MB_OK);
end;
end;
http://www.jrsoftware.org/ishelp/index.php?topic=isxfunc_regquerystringvalue
Allegedly this is the information for the registry key:
Path: HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\WindowsFirewall\DomainProfile
Location: Local Machine
Value Name: EnableFirewall
Data Type: DWORD (DWORD Value)
Enabled Value: 0
Disabled Value: 1