Skip to content

Commit

Permalink
Auto Discovery Cleanup (jellyfin#10793)
Browse files Browse the repository at this point in the history
* Call GetSmartApiUrl directly in UdpServer.RespondToV2Message

GetSmartApiUrl already returns PublishedServerUrl if set.

* Rewrite auto discovery using UdpClient and BackgroundService

* Respect network address settings in AutoDiscoveryHost

* Always listen on broadcast address in Linux for auto-discovery

* Await udp server tasks in AutoDiscoveryHost

* Only bind to broadcast addresses for IPv4

* Only bind to broadcast if IPv4 is enabled
  • Loading branch information
barronpm authored and skysbird committed Feb 3, 2024
1 parent 1956846 commit eda058e
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 280 deletions.
4 changes: 3 additions & 1 deletion Jellyfin.Server/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
using System.Text;
using Jellyfin.Api.Middleware;
using Jellyfin.MediaEncoding.Hls.Extensions;
using Jellyfin.Networking;
using Jellyfin.Networking.HappyEyeballs;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.HealthChecks;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Extensions;
using Jellyfin.Server.Infrastructure;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using Microsoft.AspNetCore.Builder;
Expand Down Expand Up @@ -121,6 +121,8 @@ public void ConfigureServices(IServiceCollection services)
.AddCheck<DbContextFactoryHealthCheck<JellyfinDbContext>>(nameof(JellyfinDbContext));

services.AddHlsPlaylistGenerator();

services.AddHostedService<AutoDiscoveryHost>();
}

/// <summary>
Expand Down
129 changes: 129 additions & 0 deletions src/Jellyfin.Networking/AutoDiscoveryHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Model.ApiClient;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Networking;

/// <summary>
/// <see cref="BackgroundService"/> responsible for responding to auto-discovery messages.
/// </summary>
public sealed class AutoDiscoveryHost : BackgroundService
{
/// <summary>
/// The port to listen on for auto-discovery messages.
/// </summary>
private const int PortNumber = 7359;

private readonly ILogger<AutoDiscoveryHost> _logger;
private readonly IServerApplicationHost _appHost;
private readonly IConfigurationManager _configurationManager;
private readonly INetworkManager _networkManager;

/// <summary>
/// Initializes a new instance of the <see cref="AutoDiscoveryHost" /> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger{AutoDiscoveryHost}"/>.</param>
/// <param name="appHost">The <see cref="IServerApplicationHost"/>.</param>
/// <param name="configurationManager">The <see cref="IConfigurationManager"/>.</param>
/// <param name="networkManager">The <see cref="INetworkManager"/>.</param>
public AutoDiscoveryHost(
ILogger<AutoDiscoveryHost> logger,
IServerApplicationHost appHost,
IConfigurationManager configurationManager,
INetworkManager networkManager)
{
_logger = logger;
_appHost = appHost;
_configurationManager = configurationManager;
_networkManager = networkManager;
}

/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var networkConfig = _configurationManager.GetNetworkConfiguration();
if (!networkConfig.AutoDiscovery)
{
return;
}

var udpServers = new List<Task>();
// Linux needs to bind to the broadcast addresses to receive broadcast traffic
if (OperatingSystem.IsLinux() && networkConfig.EnableIPv4)
{
udpServers.Add(ListenForAutoDiscoveryMessage(IPAddress.Broadcast, stoppingToken));
}

udpServers.AddRange(_networkManager.GetInternalBindAddresses()
.Select(intf => ListenForAutoDiscoveryMessage(
OperatingSystem.IsLinux() && intf.AddressFamily == AddressFamily.InterNetwork
? NetworkUtils.GetBroadcastAddress(intf.Subnet)
: intf.Address,
stoppingToken)));

await Task.WhenAll(udpServers).ConfigureAwait(false);
}

private async Task ListenForAutoDiscoveryMessage(IPAddress address, CancellationToken cancellationToken)
{
using var udpClient = new UdpClient(new IPEndPoint(address, PortNumber));
udpClient.MulticastLoopback = false;

while (!cancellationToken.IsCancellationRequested)
{
try
{
var result = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
var text = Encoding.UTF8.GetString(result.Buffer);
if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
{
await RespondToV2Message(udpClient, result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
}
}
catch (SocketException ex)
{
_logger.LogError(ex, "Failed to receive data from socket");
}
catch (OperationCanceledException)
{
_logger.LogDebug("Broadcast socket operation cancelled");
}
}
}

private async Task RespondToV2Message(UdpClient udpClient, IPEndPoint endpoint, CancellationToken cancellationToken)
{
var localUrl = _appHost.GetSmartApiUrl(endpoint.Address);
if (string.IsNullOrEmpty(localUrl))
{
_logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined.");
return;
}

var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);

try
{
_logger.LogDebug("Sending AutoDiscovery response");
await udpClient
.SendAsync(JsonSerializer.SerializeToUtf8Bytes(response).AsMemory(), endpoint, cancellationToken)
.ConfigureAwait(false);
}
catch (SocketException ex)
{
_logger.LogError(ex, "Error sending response message");
}
}
}
136 changes: 0 additions & 136 deletions src/Jellyfin.Networking/Udp/UdpServer.cs

This file was deleted.

Loading

0 comments on commit eda058e

Please sign in to comment.