86 lines
3.7 KiB
C#
86 lines
3.7 KiB
C#
using System.Net;
|
|
using Makaretu.Dns;
|
|
|
|
namespace MusicBridge.Agent.Network;
|
|
|
|
// All lifecycle calls run on the console loop, not on network-change callback threads.
|
|
internal sealed class MdnsPublisher(string fingerprint, int port) : IDisposable
|
|
{
|
|
private MulticastService? multicast;
|
|
private ServiceDiscovery? discovery;
|
|
private ServiceProfile? profile;
|
|
private string addressesKey = "";
|
|
private DateTimeOffset nextAnnouncement;
|
|
private DateTimeOffset lastRefresh;
|
|
public string Status { get; private set; } = "mDNS: ожидание локальной сети.";
|
|
|
|
public static ServiceProfile CreateProfile(string fingerprint, int port, IPAddress[] addresses)
|
|
{
|
|
if (port is < 1 or > 65535 || addresses.Length == 0 || addresses.Any(a => !LocalAddresses.IsEligible(a)))
|
|
throw new ArgumentException("Неверный порт или адрес объявления mDNS.");
|
|
var descriptor = DiscoveryDescriptor.Create(fingerprint);
|
|
var result = new ServiceProfile(descriptor.InstanceName, "_musicbridge._tcp", (ushort)port, addresses);
|
|
foreach (var pair in descriptor.TxtRecords()) result.AddProperty(pair.Key, pair.Value);
|
|
foreach (var record in result.Resources) record.TTL = TimeSpan.FromSeconds(120);
|
|
return result;
|
|
}
|
|
|
|
public void Refresh(IPAddress[] addresses)
|
|
{
|
|
try
|
|
{
|
|
// Rebind after a long pause (sleep/resume), even if Windows kept the same IP.
|
|
var now = DateTimeOffset.UtcNow;
|
|
if (lastRefresh != default && (now - lastRefresh > TimeSpan.FromSeconds(20) || now < lastRefresh)) Stop();
|
|
lastRefresh = now;
|
|
var key = string.Join(",", addresses.Select(a => a.ToString()));
|
|
if (discovery is null || key != addressesKey)
|
|
{
|
|
Stop();
|
|
if (addresses.Length == 0) { Status = "mDNS: нет адреса домашней сети."; return; }
|
|
multicast = new MulticastService(nics => nics.Where(nic => nic.GetIPProperties().UnicastAddresses.Any(a => addresses.Contains(a.Address))))
|
|
{
|
|
UseIpv4 = true, UseIpv6 = false
|
|
};
|
|
profile = CreateProfile(fingerprint, port, addresses);
|
|
discovery = new ServiceDiscovery(multicast);
|
|
discovery.Advertise(profile);
|
|
multicast.Start();
|
|
addressesKey = key;
|
|
nextAnnouncement = DateTimeOffset.MinValue;
|
|
}
|
|
if (DateTimeOffset.UtcNow >= nextAnnouncement)
|
|
{
|
|
var message = new Makaretu.Dns.Message { QR = true, AA = true };
|
|
message.Answers.Add(new PTRRecord
|
|
{
|
|
Name = profile!.QualifiedServiceName, DomainName = profile.FullyQualifiedName, TTL = TimeSpan.FromSeconds(120)
|
|
});
|
|
foreach (var record in profile.Resources) message.Answers.Add(record);
|
|
multicast!.SendAnswer(message, checkDuplicate: false);
|
|
nextAnnouncement = DateTimeOffset.UtcNow.AddSeconds(30);
|
|
}
|
|
Status = $"mDNS: объявляется {profile!.FullyQualifiedName}";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Stop();
|
|
Status = $"mDNS недоступен: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private void Stop()
|
|
{
|
|
try { if (profile is not null) discovery?.Unadvertise(profile); }
|
|
catch (Exception) { /* Best-effort goodbye when a network has disappeared. */ }
|
|
discovery?.Dispose();
|
|
multicast?.Dispose();
|
|
discovery = null;
|
|
multicast = null;
|
|
profile = null;
|
|
addressesKey = "";
|
|
}
|
|
|
|
public void Dispose() => Stop();
|
|
}
|