Skip to content

Commit 4077777

Browse files
committed
Deprecate mDNS scanning (Closes #1454) (#1455)
1 parent 77c2403 commit 4077777

22 files changed

+132
-402
lines changed

Daybreak.API/Services/ApiAdvertisingService.cs

Lines changed: 0 additions & 56 deletions
This file was deleted.

Daybreak.API/Services/WebApplicationBuilderExtensions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Daybreak.API.Services.Interop;
22
using Daybreak.Shared.Services.BuildTemplates;
3-
using Daybreak.Shared.Services.MDns;
43
using System.Diagnostics.CodeAnalysis;
54

65
namespace Daybreak.API.Services;
@@ -11,8 +10,6 @@ public static WebApplicationBuilder WithDaybreakServices(this WebApplicationBuil
1110
{
1211
builder.Services.AddSingleton<HashingService>();
1312
builder.Services.AddSingleton<IBuildTemplateManager, BuildTemplateManager>();
14-
builder.Services.AddSingleton<IMDomainNameService, MDomainNameService>();
15-
builder.Services.AddHostedService<ApiAdvertisingService>();
1613
builder.Services.AddSingleton<MemoryScanningService>();
1714
builder.Services.AddSingleton<ChatService>();
1815
builder.Services.AddSingleton<MainPlayerService>();

Daybreak.Core/Configuration/ProjectConfiguration.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
using Daybreak.Shared.Services.Injection;
6868
using Daybreak.Shared.Services.InternetChecker;
6969
using Daybreak.Shared.Services.LaunchConfigurations;
70-
using Daybreak.Shared.Services.MDns;
7170
using Daybreak.Shared.Services.Menu;
7271
using Daybreak.Shared.Services.Metrics;
7372
using Daybreak.Shared.Services.Mods;
@@ -159,7 +158,6 @@ public override void RegisterServices(IServiceCollection services)
159158
services.AddSingleton<INotificationStorage, InMemoryNotificationStorage>();
160159
services.AddSingleton<IModsManager, ModsManager>();
161160
services.AddSingleton<IPluginsService, PluginsService>();
162-
services.AddSingleton<IMDomainNameService, MDomainNameService>();
163161
services.AddSingleton<IAttachedApiAccessor, AttachedApiAccessor>();
164162
services.AddSingleton<PrivilegeContext>();
165163
services.AddSingleton<ViewRedirectContext>();
@@ -255,6 +253,7 @@ public override void RegisterServices(IServiceCollection services)
255253
services.AddHostedSingleton<ITradeAlertingService, TradeAlertingService>();
256254
services.AddHostedSingleton<IGuildWarsExecutableManager, GuildWarsExecutableManager>();
257255
services.AddHostedSingleton<IEventNotifierService, EventNotifierService>();
256+
services.AddHostedSingleton<IApiScanningService, ApiScanningService>();
258257
services.AddHostedSingleton<GameScreenshotsTheme>();
259258
services.AddHostedService<StartupActionManager>();
260259
services.AddHostedService<ProcessorUsageMonitor>();

Daybreak.Core/Daybreak.Core.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040

4141
<ItemGroup>
4242
<PackageReference Include="Elastic.OpenTelemetry" />
43-
<PackageReference Include="ksemenenko.ColorThief" />
4443
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
4544
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" />
4645
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" />

Daybreak.Linux/Services/MDns/PortScanningDomainRegistrar.cs renamed to Daybreak.Core/Services/Api/ApiScanningService.cs

Lines changed: 35 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,42 @@
11
using System.Net.Http.Json;
22
using System.Text.Json;
3-
using Daybreak.Linux.Services.Wine;
43
using Daybreak.Shared.Models.Api;
5-
using Daybreak.Shared.Services.MDns;
4+
using Daybreak.Shared.Services.Api;
65
using Microsoft.Extensions.Hosting;
76
using Microsoft.Extensions.Logging;
87
using System.Extensions.Core;
98

10-
namespace Daybreak.Linux.Services.MDns;
9+
namespace Daybreak.Services.Api;
1110

1211
/// <summary>
13-
/// Linux-specific implementation of <see cref="IMDomainRegistrar"/>.
14-
/// Since mDNS announcements from Wine don't propagate to the host,
15-
/// this implementation scans a known port range (5080–5100) for running
16-
/// Daybreak API instances and matches them to Guild Wars processes
17-
/// by querying the API's health endpoint for its Wine PID, then
18-
/// converting it to a Linux PID via <see cref="IWinePidMapper"/>.
12+
/// Scans a known port range (5080–5100) for running Daybreak API instances
13+
/// and matches them to Guild Wars processes by querying the API's health endpoint.
1914
/// All probes fire in parallel with a 500ms total timeout.
2015
/// </summary>
21-
public sealed class PortScanningDomainRegistrar(
22-
IWinePidMapper winePidMapper,
23-
ILogger<PortScanningDomainRegistrar> logger)
24-
: IMDomainRegistrar, IHostedService, IDisposable
16+
public sealed class ApiScanningService(
17+
IPidProvider pidProvider,
18+
ILogger<ApiScanningService> logger)
19+
: IApiScanningService, IHostedService, IDisposable
2520
{
2621
private const int StartPort = 5080;
2722
private const int EndPort = 5100;
2823
private const string HealthPath = "/api/v1/rest/health";
29-
private const string DaybreakApiServicePrefix = "daybreak-api-";
30-
private static readonly TimeSpan ProbeTimeout = TimeSpan.FromMilliseconds(500);
31-
private static readonly TimeSpan ScanInterval = TimeSpan.FromSeconds(15);
24+
private const string GuildWarsExecutable = "Gw.exe";
25+
private static readonly TimeSpan ProbeTimeout = TimeSpan.FromMilliseconds(2000);
26+
private static readonly TimeSpan ScanInterval = TimeSpan.FromSeconds(10);
3227
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
3328

34-
private readonly IWinePidMapper winePidMapper = winePidMapper;
35-
private readonly ILogger<PortScanningDomainRegistrar> logger = logger;
29+
private readonly IPidProvider pidProvider = pidProvider;
30+
private readonly ILogger<ApiScanningService> logger = logger;
3631
private readonly HttpClient httpClient = new() { Timeout = ProbeTimeout };
3732

3833
// Rebuilt on each scan — survives Daybreak restarts since it's based on live port probing.
39-
private volatile Dictionary<string, List<Uri>> discoveredServices = [];
34+
private volatile Dictionary<int, Uri> discoveredApis = [];
4035
private CancellationTokenSource? backgroundCts;
4136

4237
Task IHostedService.StartAsync(CancellationToken cancellationToken)
4338
{
44-
this.backgroundCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
39+
this.backgroundCts = new CancellationTokenSource();
4540
_ = Task.Factory.StartNew(
4641
() => this.ScanPeriodically(this.backgroundCts.Token),
4742
this.backgroundCts.Token,
@@ -62,31 +57,31 @@ public void Dispose()
6257
this.httpClient.Dispose();
6358
}
6459

65-
public IReadOnlyList<Uri>? Resolve(string service)
60+
public Uri? GetApiUriByProcessId(int processId)
6661
{
67-
if (this.discoveredServices.TryGetValue(service, out var uris) && uris.Count > 0)
62+
if (this.discoveredApis.TryGetValue(processId, out var uri))
6863
{
69-
return uris;
64+
return uri;
7065
}
7166

7267
return default;
7368
}
7469

75-
public IReadOnlyList<Uri>? QueryByServiceName(Func<string, bool> query)
70+
public IReadOnlyList<(int ProcessId, Uri Uri)>? QueryByProcessId(Func<int, bool> predicate)
7671
{
77-
var results = new List<Uri>();
78-
foreach (var (serviceName, uris) in this.discoveredServices)
72+
var results = new List<(int ProcessId, Uri Uri)>();
73+
foreach (var (processId, uri) in this.discoveredApis)
7974
{
80-
if (query(serviceName))
75+
if (predicate(processId))
8176
{
82-
results.AddRange(uris);
77+
results.Add((processId, uri));
8378
}
8479
}
8580

8681
return results.Count > 0 ? results : default;
8782
}
8883

89-
public void QueryAllServices()
84+
public void RequestScan()
9085
{
9186
_ = Task.Run(() => this.ScanPortsAsync(CancellationToken.None));
9287
}
@@ -103,7 +98,7 @@ private async Task ScanPeriodically(CancellationToken cancellationToken)
10398
private async Task ScanPortsAsync(CancellationToken cancellationToken)
10499
{
105100
var scopedLogger = this.logger.CreateScopedLogger();
106-
var newServices = new Dictionary<string, List<Uri>>();
101+
var newApis = new Dictionary<int, Uri>();
107102

108103
using var timeoutCts = new CancellationTokenSource(ProbeTimeout);
109104
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
@@ -129,36 +124,27 @@ private async Task ScanPortsAsync(CancellationToken cancellationToken)
129124
continue;
130125
}
131126

132-
var (port, processId) = task.Result;
133-
if (processId is null)
127+
var (port, reportedPid) = task.Result;
128+
if (reportedPid is null)
134129
{
135130
continue;
136131
}
137132

138-
// The API reports its Wine PID. Convert to Linux PID.
139-
var linuxPid = this.winePidMapper.WinePidToLinuxPid(processId.Value, "Gw.exe");
140-
var effectivePid = linuxPid ?? processId.Value;
141-
142-
var serviceName = $"{DaybreakApiServicePrefix}{effectivePid}";
133+
// Convert the reported PID (Wine PID on Linux) to system PID
134+
var systemPid = this.pidProvider.ResolveSystemPid(reportedPid.Value, GuildWarsExecutable);
143135
var uri = new Uri($"http://localhost:{port}");
144136

145137
scopedLogger.LogDebug(
146-
"Found Daybreak API on port {Port} with Wine PID {WinePid} (Linux PID {LinuxPid})",
138+
"Found Daybreak API on port {Port} with reported PID {ReportedPid} (system PID {SystemPid})",
147139
port,
148-
processId.Value,
149-
effectivePid);
150-
151-
if (!newServices.TryGetValue(serviceName, out var uriList))
152-
{
153-
uriList = [];
154-
newServices[serviceName] = uriList;
155-
}
140+
reportedPid.Value,
141+
systemPid);
156142

157-
uriList.Add(uri);
143+
newApis[systemPid] = uri;
158144
}
159145

160-
this.discoveredServices = newServices;
161-
scopedLogger.LogDebug("Port scan complete. Found {Count} Daybreak API instance(s)", newServices.Count);
146+
this.discoveredApis = newApis;
147+
scopedLogger.LogDebug("Port scan complete. Found {Count} Daybreak API instance(s)", newApis.Count);
162148
}
163149

164150
private async Task<(int Port, int? ProcessId)> ProbePortAsync(int port, CancellationToken cancellationToken)

Daybreak.Core/Services/Api/DaybreakApiService.cs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Daybreak.Shared.Models.Mods;
66
using Daybreak.Shared.Services.Api;
77
using Daybreak.Shared.Services.Injection;
8-
using Daybreak.Shared.Services.MDns;
98
using Daybreak.Shared.Services.Notifications;
109
using Daybreak.Shared.Services.Options;
1110
using Daybreak.Shared.Utils;
@@ -20,7 +19,7 @@ namespace Daybreak.Services.Api;
2019
public sealed class DaybreakApiService(
2120
IOptionsProvider optionsProvider,
2221
IAttachedApiAccessor attachedApiAccessor,
23-
IMDomainRegistrar mDomainRegistrar,
22+
IApiScanningService apiScanningService,
2423
IStubInjector stubInjector,
2524
INotificationService notificationService,
2625
IHttpClient<ScopedApiContext> scopedApiClient,
@@ -32,13 +31,10 @@ public sealed class DaybreakApiService(
3231
private const string EntryPoint = "ThreadInit";
3332
private const string LocalHost = "localhost";
3433
private const string DaybreakApiName = "Api/Daybreak.API.dll";
35-
private const string ProcessIdPlaceholder = "{PID}";
36-
private const string DaybreakApiServiceName = $"daybreak-api-{ProcessIdPlaceholder}";
37-
private const string ServiceSubType = "daybreak-api";
3834

3935
private readonly IOptionsProvider optionsProvider = optionsProvider.ThrowIfNull();
4036
private readonly IAttachedApiAccessor attachedApiAccessor = attachedApiAccessor.ThrowIfNull();
41-
private readonly IMDomainRegistrar mDomainRegistrar = mDomainRegistrar.ThrowIfNull();
37+
private readonly IApiScanningService apiScanningService = apiScanningService.ThrowIfNull();
4238
private readonly IStubInjector stubInjector = stubInjector.ThrowIfNull();
4339
private readonly INotificationService notificationService = notificationService.ThrowIfNull();
4440
private readonly IHttpClient<ScopedApiContext> scopedApiClient = scopedApiClient.ThrowIfNull();
@@ -124,12 +120,10 @@ public Task OnCustomManagement(CancellationToken cancellationToken)
124120
return default;
125121
}
126122

127-
var serviceName = DaybreakApiServiceName.Replace(ProcessIdPlaceholder, guildWarsProcess.Id.ToString());
128-
var serviceUris = this.mDomainRegistrar.Resolve(serviceName);
129-
var serviceUri = serviceUris?.Count > 0 ? serviceUris[0] : default;
123+
var serviceUri = this.apiScanningService.GetApiUriByProcessId(guildWarsProcess.Id);
130124
if (serviceUri is null)
131125
{
132-
scopedLogger.LogWarning("Failed to find Daybreak API service by name {serviceName}", serviceName);
126+
scopedLogger.LogWarning("Failed to find Daybreak API service for process {ProcessId}", guildWarsProcess.Id);
133127
return default;
134128
}
135129

@@ -150,8 +144,8 @@ public Task OnCustomManagement(CancellationToken cancellationToken)
150144
public async Task<ScopedApiContext?> FindDaybreakApiContextByCredentials(LoginCredentials loginCredentials, CancellationToken cancellationToken)
151145
{
152146
var scopedLogger = this.logger.CreateScopedLogger();
153-
var serviceUris = this.mDomainRegistrar.QueryByServiceName(n => n.StartsWith(ServiceSubType));
154-
foreach(var uri in serviceUris ?? [])
147+
var discoveredApis = this.apiScanningService.QueryByProcessId(_ => true);
148+
foreach (var (_, uri) in discoveredApis ?? [])
155149
{
156150
var uriBuilder = new UriBuilder(uri)
157151
{
@@ -173,7 +167,7 @@ public Task OnCustomManagement(CancellationToken cancellationToken)
173167

174168
public void RequestInstancesAnnouncement()
175169
{
176-
this.mDomainRegistrar.QueryAllServices();
170+
this.apiScanningService.RequestScan();
177171
}
178172

179173
public Task OnGuildWarsCreated(GuildWarsCreatedContext guildWarsCreatedContext, CancellationToken cancellationToken) =>

0 commit comments

Comments
 (0)