Version Information
Version of Akka.NET: 1.5.x (behavior confirmed on current dev branch, unchanged since introduction)
Which Akka.NET Modules: Akka.Remote (DotNetty transport)
Describe the bug
On Windows hosts with multiple network adapters — common in environments with VPN clients, Hyper-V/WSL virtual switches, or any configuration where an adapter can fall back to APIPA (169.254.0.0/16) when DHCP fails — Dns.GetHostEntryAsync can return multiple IPAddress entries for a single hostname, and one or more of those entries may be a link-local/APIPA address.
DotNettyTransport.ResolveNameAsync unconditionally selects the last address in the filtered list:
https://github.com/akkadotnet/akka.net/blob/dev/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransport.cs
private async Task<IPEndPoint> ResolveNameAsync(DnsEndPoint address, AddressFamily addressFamily)
{
var resolved = await Dns.GetHostEntryAsync(address.Host).ConfigureAwait(false);
var found = resolved.AddressList.LastOrDefault(a => a.AddressFamily == addressFamily);
if (found == null)
{
throw new KeyNotFoundException(\$\"Couldn't resolve IP endpoint from provided DNS name '{address}' with address family of '{addressFamily}'\");
}
return new IPEndPoint(found, address.Port);
}
When the last matching entry is a link-local address (169.254.x.x), the transport binds to or attempts to connect to an unreachable address. Cluster formation fails with no automatic fallback to other valid addresses in the AddressList.
The LastOrDefault selection appears to be a legacy choice from the original Helios transport — the earlier non-filtering overload contains a comment:
//NOTE: for some reason while Helios takes first element from resolved address list
// on the DotNetty side we need to take the last one in order to be compatible
This is not a deliberate technical selection — it's a backward-compatibility decision from a predecessor transport, and it has no defense against link-local addresses appearing at the end of the DNS result list.
To Reproduce
- Configure a Windows host with at least two active network adapters where one adapter has an APIPA (
169.254.x.x) address assigned — easy to reproduce by having a VPN adapter not yet connected or a DHCP-failed secondary NIC
- Ensure Windows dynamic DNS registration is enabled on all adapters (default behavior for domain-joined machines), so the APIPA address is registered into the DNS zone
- Configure Akka.Remote with
hostname / public-hostname set to the machine's FQDN (required for VPN/split-horizon/ZTNA scenarios where IP literals cannot be used)
- Start the actor system → bind or outbound seed connection attempts use the APIPA address → cluster formation fails
Expected behavior
ResolveNameAsync should filter out addresses that are fundamentally unreachable from remote cluster members:
- IPv4 link-local (
169.254.0.0/16) — RFC 3927, not routable off-link
- Loopback (
127.0.0.0/8 / ::1) — never reachable from another host
- IPv6 link-local (
fe80::/10)
- Multicast / unspecified (
0.0.0.0)
When multiple valid addresses remain, ideally attempt connection to each in order rather than committing to a single selection. At minimum, log a warning when link-local addresses are filtered so operators can diagnose the underlying network configuration.
Actual behavior
The last matching address is selected unconditionally, including link-local addresses, and no retry is attempted against other valid entries in AddressList.
Proposed fix
private async Task<IPEndPoint> ResolveNameAsync(DnsEndPoint address, AddressFamily addressFamily)
{
var resolved = await Dns.GetHostEntryAsync(address.Host).ConfigureAwait(false);
var candidates = resolved.AddressList
.Where(a => a.AddressFamily == addressFamily)
.Where(a => !IPAddress.IsLoopback(a))
.Where(a => !IsIPv4LinkLocal(a))
.Where(a => a.AddressFamily != AddressFamily.InterNetworkV6 || !a.IsIPv6LinkLocal)
.ToArray();
var found = candidates.LastOrDefault()
?? resolved.AddressList.LastOrDefault(a => a.AddressFamily == addressFamily); // fallback preserves prior behavior
if (found == null)
{
throw new KeyNotFoundException(\$\"Couldn't resolve IP endpoint from provided DNS name '{address}' with address family of '{addressFamily}'\");
}
return new IPEndPoint(found, address.Port);
}
private static bool IsIPv4LinkLocal(IPAddress ip)
{
if (ip.AddressFamily != AddressFamily.InterNetwork) return false;
var bytes = ip.GetAddressBytes();
return bytes[0] == 169 && bytes[1] == 254;
}
A warning should be logged when link-local addresses are filtered, to help operators identify misbehaving adapters or dynamic DNS registration issues on multi-NIC hosts.
Environment
- OS: Windows Server (any supported version); issue occurs wherever multi-NIC + dynamic DNS registration can produce APIPA entries in DNS results
- .NET version: Any supported Akka.Remote target framework
- Akka.NET version: 1.5.x (behavior unchanged on
dev)
Version Information
Version of Akka.NET: 1.5.x (behavior confirmed on current
devbranch, unchanged since introduction)Which Akka.NET Modules:
Akka.Remote(DotNetty transport)Describe the bug
On Windows hosts with multiple network adapters — common in environments with VPN clients, Hyper-V/WSL virtual switches, or any configuration where an adapter can fall back to APIPA (
169.254.0.0/16) when DHCP fails —Dns.GetHostEntryAsynccan return multipleIPAddressentries for a single hostname, and one or more of those entries may be a link-local/APIPA address.DotNettyTransport.ResolveNameAsyncunconditionally selects the last address in the filtered list:https://github.com/akkadotnet/akka.net/blob/dev/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransport.cs
When the last matching entry is a link-local address (
169.254.x.x), the transport binds to or attempts to connect to an unreachable address. Cluster formation fails with no automatic fallback to other valid addresses in theAddressList.The
LastOrDefaultselection appears to be a legacy choice from the original Helios transport — the earlier non-filtering overload contains a comment:This is not a deliberate technical selection — it's a backward-compatibility decision from a predecessor transport, and it has no defense against link-local addresses appearing at the end of the DNS result list.
To Reproduce
169.254.x.x) address assigned — easy to reproduce by having a VPN adapter not yet connected or a DHCP-failed secondary NIChostname/public-hostnameset to the machine's FQDN (required for VPN/split-horizon/ZTNA scenarios where IP literals cannot be used)Expected behavior
ResolveNameAsyncshould filter out addresses that are fundamentally unreachable from remote cluster members:169.254.0.0/16) — RFC 3927, not routable off-link127.0.0.0/8/::1) — never reachable from another hostfe80::/10)0.0.0.0)When multiple valid addresses remain, ideally attempt connection to each in order rather than committing to a single selection. At minimum, log a warning when link-local addresses are filtered so operators can diagnose the underlying network configuration.
Actual behavior
The last matching address is selected unconditionally, including link-local addresses, and no retry is attempted against other valid entries in
AddressList.Proposed fix
A warning should be logged when link-local addresses are filtered, to help operators identify misbehaving adapters or dynamic DNS registration issues on multi-NIC hosts.
Environment
dev)