Editor/HumanoidMocap/PhoneNetwork.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;

namespace HumanoidMocap.Editor;

internal sealed record PhoneNetwork(string Name,IPAddress Address,bool HasGateway,bool Virtual)
{
    public string Label=>$"{Name} · {Address}"+(Virtual?" (virtual)":"");
    public bool LocalOnly=>IPAddress.IsLoopback(Address)||Address.GetAddressBytes()[0]==169&&Address.GetAddressBytes()[1]==254;

    public static PhoneNetwork[] Discover()
    {
        var networks=new List<PhoneNetwork>();
        foreach(var adapter in NetworkInterface.GetAllNetworkInterfaces().Where(n=>n.OperationalStatus==OperationalStatus.Up))
        {
            var properties=adapter.GetIPProperties();
            var gateway=properties.GatewayAddresses.Any(g=>g.Address.AddressFamily==AddressFamily.InterNetwork&&!g.Address.Equals(IPAddress.Any));
            var description=adapter.Name+" "+adapter.Description;
            var virtualNetwork=adapter.NetworkInterfaceType is NetworkInterfaceType.Tunnel or NetworkInterfaceType.Loopback
                ||new[]{"virtual","vethernet","wsl","hyper-v","vmware","vpn","wireguard","tailscale","zerotier","tap-"}
                    .Any(word=>description.Contains(word,StringComparison.OrdinalIgnoreCase));
            networks.AddRange(properties.UnicastAddresses.Where(a=>a.Address.AddressFamily==AddressFamily.InterNetwork&&!IPAddress.IsLoopback(a.Address))
                .Select(a=>new PhoneNetwork(adapter.Name,a.Address,gateway,virtualNetwork)));
        }
        if(networks.Count==0)networks.Add(new("This PC only",IPAddress.Loopback,false,false));
        return Order(networks);
    }

    internal static PhoneNetwork[] Order(IEnumerable<PhoneNetwork> networks)=>networks
        .OrderBy(n=>n.LocalOnly).ThenBy(n=>n.Virtual).ThenByDescending(n=>n.HasGateway)
        .ThenBy(n=>n.Name,StringComparer.OrdinalIgnoreCase).ThenBy(n=>n.Address.ToString(),StringComparer.Ordinal).ToArray();
}