Skip to content

Commit 1d1e291

Browse files
committed
Fix: Detect local IP address as fallback when routing is not available
1 parent eb26592 commit 1d1e291

File tree

3 files changed

+176
-59
lines changed

3 files changed

+176
-59
lines changed

Source/NETworkManager.Models/Network/NetworkInterface.cs

Lines changed: 148 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.ComponentModel;
4+
using System.Diagnostics;
45
using System.Linq;
56
using System.Net;
67
using System.Net.NetworkInformation;
@@ -16,17 +17,20 @@ namespace NETworkManager.Models.Network;
1617
/// </summary>
1718
public sealed class NetworkInterface
1819
{
19-
#region Events
20-
21-
/// <summary>
22-
/// Occurs when the user has canceled an operation (e.g. UAC prompt).
23-
/// </summary>
24-
public event EventHandler UserHasCanceled;
25-
26-
private void OnUserHasCanceled()
27-
{
28-
UserHasCanceled?.Invoke(this, EventArgs.Empty);
29-
}
20+
#region Variables
21+
22+
/* Ref #3286
23+
private static List<string> NetworkInterfacesBlacklist =
24+
[
25+
"Hyper-V Virtual Switch Extension Filter",
26+
"WFP Native MAC Layer LightWeight Filter",
27+
"Npcap Packet Driver (NPCAP)",
28+
"QoS Packet Scheduler",
29+
"WFP 802.3 MAC Layer LightWeight Filter",
30+
"Ethernet (Kerneldebugger)",
31+
"Filter Driver"
32+
];
33+
*/
3034

3135
#endregion
3236

@@ -47,7 +51,7 @@ public static Task<List<NetworkInterfaceInfo>> GetNetworkInterfacesAsync()
4751
/// <returns>A list of <see cref="NetworkInterfaceInfo"/> describing the available network interfaces.</returns>
4852
public static List<NetworkInterfaceInfo> GetNetworkInterfaces()
4953
{
50-
List<NetworkInterfaceInfo> listNetworkInterfaceInfo = new();
54+
List<NetworkInterfaceInfo> listNetworkInterfaceInfo = [];
5155

5256
foreach (var networkInterface in System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces())
5357
{
@@ -58,6 +62,13 @@ public static List<NetworkInterfaceInfo> GetNetworkInterfaces()
5862
(int)networkInterface.NetworkInterfaceType != 53)
5963
continue;
6064

65+
// Check if part of the Name is in blacklist Ref #3286
66+
//if (NetworkInterfacesBlacklist.Any(networkInterface.Name.Contains))
67+
// continue;
68+
69+
//Debug.WriteLine(networkInterface.Name);
70+
//Debug.WriteLine($" Description: {networkInterface.Description}");
71+
6172
var listIPv4Address = new List<Tuple<IPAddress, IPAddress>>();
6273
var listIPv6AddressLinkLocal = new List<IPAddress>();
6374
var listIPv6Address = new List<IPAddress>();
@@ -107,7 +118,7 @@ public static List<NetworkInterfaceInfo> GetNetworkInterfaces()
107118
}
108119
}
109120

110-
// Check if autoconfiguration for DNS is enabled (only via registry key)
121+
// Check if autoconfiguration for DNS is enabled (only possible via registry key)
111122
var nameServerKey =
112123
Registry.LocalMachine.OpenSubKey(
113124
$@"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{networkInterface.Id}");
@@ -150,47 +161,47 @@ public static List<NetworkInterfaceInfo> GetNetworkInterfaces()
150161
IsOperational = networkInterface.OperationalStatus == OperationalStatus.Up,
151162
Speed = networkInterface.Speed,
152163
IPv4ProtocolAvailable = ipv4ProtocolAvailable,
153-
IPv4Address = listIPv4Address.ToArray(),
154-
IPv4Gateway = listIPv4Gateway.ToArray(),
164+
IPv4Address = [.. listIPv4Address],
165+
IPv4Gateway = [.. listIPv4Gateway],
155166
DhcpEnabled = ipv4Properties is { IsDhcpEnabled: true },
156-
DhcpServer = ipProperties.DhcpServerAddresses.Where(dhcpServerIPAddress =>
157-
dhcpServerIPAddress.AddressFamily == AddressFamily.InterNetwork).ToArray(),
167+
DhcpServer = [.. ipProperties.DhcpServerAddresses.Where(dhcpServerIPAddress =>
168+
dhcpServerIPAddress.AddressFamily == AddressFamily.InterNetwork)],
158169
DhcpLeaseObtained = dhcpLeaseObtained,
159170
DhcpLeaseExpires = dhcpLeaseExpires,
160171
IPv6ProtocolAvailable = ipv6ProtocolAvailable,
161-
IPv6AddressLinkLocal = listIPv6AddressLinkLocal.ToArray(),
162-
IPv6Address = listIPv6Address.ToArray(),
163-
IPv6Gateway = listIPv6Gateway.ToArray(),
172+
IPv6AddressLinkLocal = [.. listIPv6AddressLinkLocal],
173+
IPv6Address = [.. listIPv6Address],
174+
IPv6Gateway = [.. listIPv6Gateway],
164175
DNSAutoconfigurationEnabled = dnsAutoconfigurationEnabled,
165176
DNSSuffix = ipProperties.DnsSuffix,
166-
DNSServer = ipProperties.DnsAddresses.ToArray()
177+
DNSServer = [.. ipProperties.DnsAddresses]
167178
});
168179
}
169180

170181
return listNetworkInterfaceInfo;
171182
}
172183

173184
/// <summary>
174-
/// Detects the local IP address based on routing to a remote IP address asynchronously.
185+
/// Detects the local IP address from routing to a remote IP address asynchronously.
175186
/// </summary>
176187
/// <param name="remoteIPAddress">The remote IP address to check routing against.</param>
177-
/// <returns>A task that represents the asynchronous operation. The task result contains the local <see cref="IPAddress"/> used to reach the remote address.</returns>
188+
/// <returns>A task that represents the asynchronous operation.
189+
/// The task result contains the local <see cref="IPAddress"/> used to reach the remote address or null on error.</returns>
178190
public static Task<IPAddress> DetectLocalIPAddressBasedOnRoutingAsync(IPAddress remoteIPAddress)
179191
{
180-
return Task.Run(() => DetectLocalIPAddressBasedOnRouting(remoteIPAddress));
192+
return Task.Run(() => DetectLocalIPAddressFromRouting(remoteIPAddress));
181193
}
182194

183195
/// <summary>
184-
/// Detects the local IP address based on routing to a remote IP address.
196+
/// Detects the local IP address from routing to a remote IP address.
185197
/// </summary>
186198
/// <param name="remoteIPAddress">The remote IP address to check routing against.</param>
187-
/// <returns>The local <see cref="IPAddress"/> used to reach the remote address.</returns>
188-
private static IPAddress DetectLocalIPAddressBasedOnRouting(IPAddress remoteIPAddress)
199+
/// <returns>The local <see cref="IPAddress"/> used to reach the remote address or null on error.</returns>
200+
private static IPAddress DetectLocalIPAddressFromRouting(IPAddress remoteIPAddress)
189201
{
190202
var isIPv4 = remoteIPAddress.AddressFamily == AddressFamily.InterNetwork;
191203

192-
using var socket = new Socket(isIPv4 ? AddressFamily.InterNetwork : AddressFamily.InterNetworkV6,
193-
SocketType.Dgram, ProtocolType.Udp);
204+
using var socket = new Socket(remoteIPAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
194205

195206
// return null on error...
196207
try
@@ -201,45 +212,123 @@ private static IPAddress DetectLocalIPAddressBasedOnRouting(IPAddress remoteIPAd
201212
if (socket.LocalEndPoint is IPEndPoint ipAddress)
202213
return ipAddress.Address;
203214
}
204-
catch (SocketException)
215+
catch (SocketException) { }
216+
217+
return null;
218+
}
219+
220+
/// <summary>
221+
/// Asynchronously detects the local IP address associated with a network interface that matches the specified
222+
/// address family.
223+
/// </summary>
224+
/// <param name="addressFamily">The address family to use when searching for a local IP address. Typically, use AddressFamily.InterNetwork for
225+
/// IPv4 or AddressFamily.InterNetworkV6 for IPv6.</param>
226+
/// <returns>A task that represents the asynchronous operation. The task result contains the detected local IP address, or
227+
/// null if no suitable address is found.</returns>
228+
public static Task<IPAddress> DetectLocalIPAddressFromNetworkInterfaceAsync(AddressFamily addressFamily)
229+
{
230+
return Task.Run(() => DetectLocalIPAddressFromNetworkInterface(addressFamily));
231+
}
232+
233+
/// <summary>
234+
/// Detects and returns the first local IP address assigned to an operational network interface that matches the
235+
/// specified address family.
236+
/// </summary>
237+
/// <remarks>For IPv4, the method prefers non-link-local addresses but will return a link-local address if
238+
/// no other is available. For IPv6, the method returns the first global or unique local address if present;
239+
/// otherwise, it returns a link-local address if available. The returned address is selected from operational
240+
/// network interfaces only.</remarks>
241+
/// <param name="addressFamily">The address family to search for. Specify <see cref="AddressFamily.InterNetwork"/> for IPv4 addresses or <see
242+
/// cref="AddressFamily.InterNetworkV6"/> for IPv6 addresses.</param>
243+
/// <returns>An <see cref="IPAddress"/> representing the first detected local IP address for the specified address family, or
244+
/// <see langword="null"/> if no suitable address is found.</returns>
245+
public static IPAddress DetectLocalIPAddressFromNetworkInterface(AddressFamily addressFamily)
246+
{
247+
// Filter operational network interfaces
248+
var networkInterfaces = GetNetworkInterfaces()
249+
.Where(x => x.IsOperational);
250+
251+
var candidates = new List<IPAddress>();
252+
253+
// IPv4
254+
if (addressFamily == AddressFamily.InterNetwork)
255+
{
256+
foreach (var networkInterface in networkInterfaces)
257+
{
258+
foreach (var ipAddress in networkInterface.IPv4Address)
259+
candidates.Add(ipAddress.Item1);
260+
}
261+
262+
// Prefer non-link-local addresses
263+
var nonLinkLocal = candidates.Where(x =>
264+
{
265+
var bytes = x.GetAddressBytes();
266+
267+
return !(bytes[0] == 169 && bytes[1] == 254);
268+
});
269+
270+
// Return first non-link-local or first candidate if none found (might be null - no addresses at all)
271+
return nonLinkLocal.Any() ? nonLinkLocal.First() : candidates.First();
272+
}
273+
274+
// IPv6
275+
if (addressFamily == AddressFamily.InterNetworkV6)
205276
{
277+
// First try to get global or unique local addresses
278+
foreach (var networkInterface in networkInterfaces)
279+
{
280+
candidates.AddRange(networkInterface.IPv6Address);
281+
}
282+
283+
// Return first candidate if any found
284+
if (candidates.Count != 0)
285+
return candidates.First();
286+
287+
// Fallback to link-local addresses
288+
foreach (var networkInterface in networkInterfaces)
289+
{
290+
if (networkInterface.IPv6AddressLinkLocal.Length != 0)
291+
return networkInterface.IPv6AddressLinkLocal.First();
292+
}
206293
}
207294

208295
return null;
209296
}
210297

211298
/// <summary>
212-
/// Detects the gateway IP address based on a local IP address asynchronously.
299+
/// Detects the gateway IP address from a local IP address asynchronously.
213300
/// </summary>
214301
/// <param name="localIPAddress">The local IP address to find the gateway for.</param>
215-
/// <returns>A task that represents the asynchronous operation. The task result contains the gateway <see cref="IPAddress"/>.</returns>
216-
public static Task<IPAddress> DetectGatewayBasedOnLocalIPAddressAsync(IPAddress localIPAddress)
302+
/// <returns>A task that represents the asynchronous operation.
303+
/// The task result contains the gateway as <see cref="IPAddress"/> or null if not found.</returns>
304+
public static Task<IPAddress> DetectGatewayFromLocalIPAddressAsync(IPAddress localIPAddress)
217305
{
218-
return Task.Run(() => DetectGatewayBasedOnLocalIPAddress(localIPAddress));
306+
return Task.Run(() => DetectGatewayFromLocalIPAddress(localIPAddress));
219307
}
220308

221309
/// <summary>
222-
/// Detects the gateway IP address based on a local IP address.
310+
/// Detects the gateway IP address from a local IP address.
223311
/// </summary>
224312
/// <param name="localIPAddress">The local IP address to find the gateway for.</param>
225-
/// <returns>The gateway <see cref="IPAddress"/>.</returns>
226-
private static IPAddress DetectGatewayBasedOnLocalIPAddress(IPAddress localIPAddress)
313+
/// <returns>The gateway as <see cref="IPAddress"/> or null if not found.</returns>
314+
private static IPAddress DetectGatewayFromLocalIPAddress(IPAddress localIPAddress)
227315
{
228316
foreach (var networkInterface in GetNetworkInterfaces())
317+
{
318+
// IPv4
229319
if (localIPAddress.AddressFamily == AddressFamily.InterNetwork)
230320
{
231321
if (networkInterface.IPv4Address.Any(x => x.Item1.Equals(localIPAddress)))
232322
return networkInterface.IPv4Gateway.FirstOrDefault();
233323
}
234-
else if (localIPAddress.AddressFamily == AddressFamily.InterNetworkV6)
324+
325+
// IPv6
326+
if (localIPAddress.AddressFamily == AddressFamily.InterNetworkV6)
235327
{
236328
if (networkInterface.IPv6Address.Contains(localIPAddress))
237329
return networkInterface.IPv6Gateway.FirstOrDefault();
238-
}
239-
else
240-
{
241-
throw new Exception("IPv4 or IPv6 address is required to detect the gateway.");
242-
}
330+
}
331+
}
243332

244333
return null;
245334
}
@@ -394,4 +483,20 @@ private static void RemoveIPAddressFromNetworkInterface(NetworkInterfaceConfig c
394483
}
395484

396485
#endregion
486+
487+
488+
#region Events
489+
490+
/// <summary>
491+
/// Occurs when the user has canceled an operation (e.g. UAC prompt).
492+
/// </summary>
493+
public event EventHandler UserHasCanceled;
494+
495+
private void OnUserHasCanceled()
496+
{
497+
UserHasCanceled?.Invoke(this, EventArgs.Empty);
498+
}
499+
500+
#endregion
501+
397502
}

Source/NETworkManager/ViewModels/IPScannerViewModel.cs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ private void ScanAction()
347347

348348
private void DetectSubnetAction()
349349
{
350-
DetectIPRange().ConfigureAwait(false);
350+
DetectSubnet().ConfigureAwait(false);
351351
}
352352

353353
/// <summary>
@@ -530,34 +530,46 @@ private void Stop()
530530
}
531531

532532
/// <summary>
533-
/// Detects the local IP subnet.
533+
/// Attempts to detect the local subnet and updates the host information accordingly.
534534
/// </summary>
535-
private async Task DetectIPRange()
535+
/// <remarks>If the subnet or local IP address cannot be detected, an error message is displayed to the
536+
/// user. The method updates the Host property with the detected subnet in CIDR notation when successful.</remarks>
537+
/// <returns>A task that represents the asynchronous subnet detection operation.</returns>
538+
private async Task DetectSubnet()
536539
{
537540
IsSubnetDetectionRunning = true;
538541

542+
// Try to detect local IP address based on routing to public IP
539543
var localIP = await NetworkInterface.DetectLocalIPAddressBasedOnRoutingAsync(IPAddress.Parse(GlobalStaticConfiguration.Dashboard_PublicIPv4Address));
540544

541-
// Could not detect local ip address
545+
// Fallback: Try to detect local IP address from network interfaces -> Prefer non link-local addresses
546+
localIP ??= await NetworkInterface.DetectLocalIPAddressFromNetworkInterfaceAsync(System.Net.Sockets.AddressFamily.InterNetwork);
547+
548+
// If local IP address detected, try to find subnetmask from network interfaces
542549
if (localIP != null)
543550
{
544-
var subnetmaskDetected = false;
551+
var subnetDetected = false;
545552

546-
// Get subnetmask, based on ip address
547-
foreach (var networkInterface in (await NetworkInterface.GetNetworkInterfacesAsync()).Where(
548-
networkInterface => networkInterface.IPv4Address.Any(x => x.Item1.Equals(localIP))))
549-
{
550-
subnetmaskDetected = true;
553+
// Get network interfaces, where local IP address is assigned
554+
var networkInterface = (await NetworkInterface.GetNetworkInterfacesAsync())
555+
.First(x => x.IPv4Address.Any(y => y.Item1.Equals(localIP)));
556+
557+
// If found, get subnetmask
558+
if (networkInterface != null) {
551559

552-
Host = $"{localIP}/{Subnetmask.ConvertSubnetmaskToCidr(networkInterface.IPv4Address.First().Item2)}";
560+
// Find the correct IP address and the associated subnetmask
561+
var ipAddressWithSubnet = networkInterface.IPv4Address.First(x => x.Item1.Equals(localIP));
562+
563+
Host = $"{ipAddressWithSubnet.Item1}/{Subnetmask.ConvertSubnetmaskToCidr(ipAddressWithSubnet.Item2)}";
564+
565+
subnetDetected = true;
553566

554567
// Fix: If the user clears the TextBox and then clicks again on the button, the TextBox remains empty...
555568
OnPropertyChanged(nameof(Host));
556-
557-
break;
558569
}
559570

560-
if (!subnetmaskDetected)
571+
// Show error message if subnet could not be detected
572+
if (!subnetDetected)
561573
{
562574
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.IsActive);
563575

Source/NETworkManager/ViewModels/NetworkConnectionWidgetViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@ await NetworkInterface.DetectLocalIPAddressBasedOnRoutingAsync(
901901
Log.Debug("CheckConnectionRouterAsync - Computer IPv4 address detected: " + detectedLocalIPv4Address);
902902

903903
var detectedRouterIPv4 =
904-
await NetworkInterface.DetectGatewayBasedOnLocalIPAddressAsync(
904+
await NetworkInterface.DetectGatewayFromLocalIPAddressAsync(
905905
detectedLocalIPv4Address);
906906

907907
if (detectedRouterIPv4 != null)
@@ -944,7 +944,7 @@ await NetworkInterface.DetectLocalIPAddressBasedOnRoutingAsync(
944944
Log.Debug("CheckConnectionRouterAsync - Computer IPv6 address detected: " + detectedComputerIPv6);
945945

946946
var detectedRouterIPv6 =
947-
await NetworkInterface.DetectGatewayBasedOnLocalIPAddressAsync(detectedComputerIPv6);
947+
await NetworkInterface.DetectGatewayFromLocalIPAddressAsync(detectedComputerIPv6);
948948

949949
if (detectedRouterIPv6 != null)
950950
{

0 commit comments

Comments
 (0)