delphi 如何连接到使用IPv4发现的IPv6链路本地地址并设置正确的网络接口?

rqdpfwrv  于 2022-11-04  发布在  其他
关注(0)|答案(2)|浏览(356)

我们的 Delphi /Indy应用程序使用IPv4上的mDNS发现设备,但发现的一些地址是链路本地IPv6地址(在AAAA记录中与其他A记录沿着指定)。我们希望提供使用IPv4或IPv6地址连接到设备的选项,因为有些人坚持设置静态IPv4地址,而这些地址会由于位置或配置的更改而变为与它们所在的子网不兼容。
但是,当应用程序尝试建立到这些链路本地IPv6地址之一的TCP连接时,第一次尝试通常会失败,因为Windows不知道使用哪个接口。(仅从命令行ping地址时也会观察到相同的行为。通过在链路本地地址的末尾写入%并在后面加上相应的区域ID,可以解决此问题。)
因此,用于连接到设备的TIdTCPClient对象需要与正确的网络接口关联。我知道这可以通过设置BoundIP属性来完成,但我没有发现设备的接口的IPv6地址,因为这是使用IPv4完成的。是否有一种方法可以在发现设备时使用OnUDPRead事件中提供的TIdSocketHandle对象,并使用该对象在TIdTCPClient对象中设置适当的内容?

7eumitmz

7eumitmz1#

但是,当应用程序尝试建立到这些链路本地IPv6地址之一的TCP连接时,第一次尝试通常会失败,因为Windows不知道要使用哪个接口。
您是否将TIdTCPClient.IPVersion属性设置为Id_IPv6?Indy当前无法根据指定的IP地址(ticket)确定要连接的IP版本,您必须事先明确指定IP版本。
大多数情况下,让Windows决定连接到IP地址时使用哪个接口就足够了,这就是它的路由表的作用。
如果只从命令行ping地址,也会出现同样的情况。通过在链路本地地址的末尾写入%,然后加上相应的区域ID,可以解决此问题。
Indy目前不支持IPv6地址的区域/范围ID(ticket)。
是否有一种方法可以在发现设备时使用OnUDPRead事件中提供的TIdSocketHandle对象
不太可能,因为您说发现是通过IPv4进行的,这意味着TIdSocketHandle的UDP套接字绑定到了IPv4接口,而不是IPv6接口。唯一的可能性是,如果有问题的接口同时分配了IPv4和IPv6地址,在这种情况下,您可以使用Win32 API将TIdSocketHandle.IP匹配到特定的接口,然后枚举同一接口上的IPv6地址。
否则,您可以使用Indy的TIdStack.GetLocalAddressList()方法获取所有本地IPv6接口IP,然后将TIdTCPClient绑定到循环中的每个接口,直到成功连接到目标设备。

kyks70gy

kyks70gy2#

我采纳了雷米的建议,实施了以下措施:

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;

由于设备发现是通过IPv4完成的,因此网络接口的IPv4地址是已知的(TIdSocketHandleIP属性),因此通过使用上述函数,可以设置TIdTCPClient.BoundIP,以便网络接口对于在发现过程中找到的任何链路本地IPv6地址都是明确的。

相关问题