diff --git a/src/Nonemm.App/Windows/EntryWindow.axaml.cs b/src/Nonemm.App/Windows/EntryWindow.axaml.cs index e63cbb2..e57c854 100644 --- a/src/Nonemm.App/Windows/EntryWindow.axaml.cs +++ b/src/Nonemm.App/Windows/EntryWindow.axaml.cs @@ -198,6 +198,10 @@ public sealed partial class EntryWindow : Window { yield return ("station network", session.ApplyNetworkSettings); } + if (session.Settings.StationLinkEnabled) + { + yield return ("station link", session.ApplyStationLinkSettings); + } // always, whatever the CW keyer is: this is also what builds the voice // keyer, which plays out of the sound card rather than a keying port yield return ("keyer", session.ApplyKeyerSettings); diff --git a/src/Nonemm.Network/StationLink.cs b/src/Nonemm.Network/StationLink.cs index 60a8232..1c1375b 100644 --- a/src/Nonemm.Network/StationLink.cs +++ b/src/Nonemm.Network/StationLink.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Text; using Nonemm.Core; @@ -525,9 +526,12 @@ public sealed class StationLink : IDisposable StationBeacon beacon = new(computerName, Address(), port, version, mine.Operator); myAddress = beacon.Address; byte[] message = Encoding.UTF8.GetBytes(beacon.ToWire()); - await beacons! - .SendAsync(message, new IPEndPoint(IPAddress.Broadcast, port), cancellation) - .ConfigureAwait(false); + foreach (IPAddress target in BroadcastAddresses()) + { + await beacons! + .SendAsync(message, new IPEndPoint(target, port), cancellation) + .ConfigureAwait(false); + } mine.Address = beacon.Address; mine.LastHeardUtc = DateTime.UtcNow; Forget(); @@ -1138,6 +1142,38 @@ public sealed class StationLink : IDisposable } } + /// The broadcast address of each network this computer is on. The limited + /// broadcast (255.255.255.255) leaves by one interface only, so a computer + /// with a second adapter or a VPN would miss the stations on the others. + private static List BroadcastAddresses() + { + List found = [IPAddress.Broadcast]; + foreach (NetworkInterface adapter in NetworkInterface.GetAllNetworkInterfaces()) + { + if (adapter.OperationalStatus != OperationalStatus.Up + || adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback + || !adapter.SupportsMulticast) + { + continue; + } + foreach (UnicastIPAddressInformation info in adapter.GetIPProperties().UnicastAddresses) + { + if (info.Address.AddressFamily != AddressFamily.InterNetwork || info.IPv4Mask is null) + { + continue; + } + byte[] address = info.Address.GetAddressBytes(); + byte[] mask = info.IPv4Mask.GetAddressBytes(); + IPAddress directed = new(address.Select((b, i) => (byte)(b | ~mask[i])).ToArray()); + if (!found.Contains(directed)) + { + found.Add(directed); + } + } + } + return found; + } + /// This computer's address on the network it can reach the others on. It is /// only for the beacon: the other end uses the address the packet came /// from, so a wrong answer here costs nothing.