Skip to content

Commit

Permalink
authentication delegation to Server or Proxy side, effectively allowi…
Browse files Browse the repository at this point in the history
…ng player redirects
  • Loading branch information
caunt committed Oct 1, 2024
1 parent cbfbd8f commit ccb2f62
Show file tree
Hide file tree
Showing 19 changed files with 222 additions and 77 deletions.
9 changes: 9 additions & 0 deletions src/API/Events/Authentication/AuthenticationCompletedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Void.Proxy.API.Links;

namespace Void.Proxy.API.Events.Authentication;

public class AuthenticationCompletedEvent : IEvent
{
public required ILink Link { get; init; }
public required AuthenticationSide Side { get; init; }
}
9 changes: 9 additions & 0 deletions src/API/Events/Authentication/AuthenticationFailedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Void.Proxy.API.Links;

namespace Void.Proxy.API.Events.Authentication;

public class AuthenticationFailed : IEvent
{
public required ILink Link { get; init; }
public required AuthenticationSide Side { get; init; }
}
7 changes: 7 additions & 0 deletions src/API/Events/Authentication/AuthenticationSide.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Void.Proxy.API.Events.Authentication;

public enum AuthenticationSide
{
Proxy,
Server
}
9 changes: 9 additions & 0 deletions src/API/Events/Authentication/AuthenticationStartedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Void.Proxy.API.Links;

namespace Void.Proxy.API.Events.Authentication;

public class AuthenticationStartedEvent : IEvent
{
public required ILink Link { get; init; }
public required AuthenticationSide Side { get; init; }
}
9 changes: 9 additions & 0 deletions src/API/Events/Authentication/AuthenticationStartingEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Void.Proxy.API.Links;

namespace Void.Proxy.API.Events.Authentication;

public class AuthenticationStartingEvent : IEventWithResult<AuthenticationSide>
{
public required ILink Link { get; init; }
public AuthenticationSide Result { get; set; } = AuthenticationSide.Proxy;
}
2 changes: 2 additions & 0 deletions src/API/Links/ILink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface ILink : IEventListener, IAsyncDisposable
public IMinecraftChannel PlayerChannel { get; }
public IMinecraftChannel ServerChannel { get; }
public bool IsAlive { get; }

public ValueTask StartAsync(CancellationToken cancellationToken);
}
1 change: 0 additions & 1 deletion src/API/Network/IO/Channels/IMinecraftChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public interface IMinecraftChannel : IDisposable, IAsyncDisposable

public bool IsConfigured { get; }
public bool IsPaused { get; }
public bool IsRedirectionSupported { get; }

public void Add<T>() where T : class, IMinecraftStream, new();
public void Add<T>(T stream) where T : class, IMinecraftStream;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Void.Proxy.API.Network.IO.Channels.Services;

public interface IMinecraftChannelBuilderService
{
public bool IsFallbackBuilder { get; }

public ValueTask SearchChannelBuilderAsync(IPlayer player, CancellationToken cancellationToken = default);

public ValueTask<IMinecraftChannel> BuildPlayerChannelAsync(IPlayer player, CancellationToken cancellationToken = default);
Expand Down
2 changes: 1 addition & 1 deletion src/API/Network/Protocol/ProtocolVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public ProtocolVersion(int version, params string[] names)
Names = names;

if (!Mapping.TryAdd(version, this))
throw new InvalidOperationException($"ProtocolVersion {version} already registered, use Search(<version>) instead");
throw new InvalidOperationException($"ProtocolVersion {version} already registered, use Get(<version>) instead");
}

public static ProtocolVersion Latest => Mapping.MaxBy(kv => kv.Key).Value;
Expand Down
6 changes: 6 additions & 0 deletions src/API/Players/PlayerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public static async ValueTask<IMinecraftChannel> GetChannelAsync(this IPlayer pl
return player.Context.Channel;
}

public static async ValueTask<bool> IsProtocolSupportedAsync(this IPlayer player, CancellationToken cancellationToken = default)
{
var channelBuilder = await player.GetChannelBuilderAsync(cancellationToken);
return !channelBuilder.IsFallbackBuilder;
}

private static async ValueTask<IMinecraftChannelBuilderService> GetChannelBuilderAsync(this IPlayer player, CancellationToken cancellationToken = default)
{
var channelBuilder = player.Context.Services.GetRequiredService<IMinecraftChannelBuilderService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ public class SimpleChannelBuilderService(ILogger<SimpleChannelBuilderService> lo
public const int MaxHandshakeSize = 4096;

private Memory<byte> _buffer = Memory<byte>.Empty;
private ChannelBuilder _builder = (_, networkStream, _) => ValueTask.FromResult<IMinecraftChannel>(new SimpleChannel(new SimpleNetworkStream(networkStream)));
private bool _found;
private ChannelBuilder _builder = FallbackBuilder;
private bool _executed;

public bool IsFallbackBuilder { get; private set; }

public async ValueTask SearchChannelBuilderAsync(IPlayer player, CancellationToken cancellationToken = default)
{
if (_found)
if (_executed)
return;

logger.LogTrace("Searching for channel builder for a {Player} player", player);
Expand All @@ -35,12 +37,17 @@ public async ValueTask SearchChannelBuilderAsync(IPlayer player, CancellationTok
await events.ThrowAsync(searchProtocolCodec, cancellationToken);

if (searchProtocolCodec.Result is not null)
{
_builder = searchProtocolCodec.Result;
}
else
{
IsFallbackBuilder = true;
logger.LogWarning("Channel builder not found for a {Player} player", player);
}

_buffer = searchProtocolCodec.Buffer;
_found = true;
_executed = true;
}

public async ValueTask<IMinecraftChannel> BuildPlayerChannelAsync(IPlayer player, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -80,4 +87,9 @@ private async ValueTask<IMinecraftChannel> BuildChannelAsync(Direction direction

return channel;
}

private static ValueTask<IMinecraftChannel> FallbackBuilder(Direction direction, NetworkStream networkStream, CancellationToken cancellationToken)
{
return ValueTask.FromResult<IMinecraftChannel>(new SimpleChannel(new SimpleNetworkStream(networkStream)));
}
}
1 change: 0 additions & 1 deletion src/Common/Network/IO/Channels/SimpleChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public class SimpleChannel(IMinecraftStreamBase head) : IMinecraftChannel

public bool IsConfigured => head is IMinecraftStream;
public bool IsPaused => _pause is { Task.IsCompleted: false };
public bool IsRedirectionSupported => false;

public void Add<T>() where T : class, IMinecraftStream, new()
{
Expand Down
116 changes: 59 additions & 57 deletions src/Platform/Links/Link.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,44 @@

namespace Void.Proxy.Links;

public class Link : ILink
public class Link(IPlayer player, IServer server, IMinecraftChannel playerChannel, IMinecraftChannel serverChannel, ILogger logger, IEventService events) : ILink
{
private readonly CancellationTokenSource _ctsPlayerToServer;
private readonly CancellationTokenSource _ctsPlayerToServerForce;
private readonly CancellationTokenSource _ctsServerToPlayer;
private readonly CancellationTokenSource _ctsServerToPlayerForce;
private readonly IEventService _events;
private readonly AsyncLock _lock;
private readonly ILogger _logger;
private readonly Task _playerToServerTask;
private readonly Task _serverToPlayerTask;

public Link(IPlayer player, IServer server, IMinecraftChannel playerChannel, IMinecraftChannel serverChannel, ILogger logger, IEventService events)
{
Player = player;
Server = server;
PlayerChannel = playerChannel;
ServerChannel = serverChannel;
private readonly CancellationTokenSource _ctsPlayerToServer = new();
private readonly CancellationTokenSource _ctsPlayerToServerForce = new();
private readonly CancellationTokenSource _ctsServerToPlayer = new();
private readonly CancellationTokenSource _ctsServerToPlayerForce = new();
private readonly AsyncLock _lock = new();

private Task? _playerToServerTask;
private Task? _serverToPlayerTask;

_logger = logger;
_events = events;
public IPlayer Player { get; init; } = player;
public IServer Server { get; init; } = server;
public IMinecraftChannel PlayerChannel { get; init; } = playerChannel;
public IMinecraftChannel ServerChannel { get; init; } = serverChannel;

_lock = new AsyncLock();
public bool IsAlive => _playerToServerTask?.Status == TaskStatus.Running && _serverToPlayerTask?.Status == TaskStatus.Running;

public async ValueTask StartAsync(CancellationToken cancellationToken)
{
if (this is { _playerToServerTask: not null } or { _serverToPlayerTask: not null })
throw new InvalidOperationException("Link was already started");

_ctsPlayerToServer = new CancellationTokenSource();
_ctsPlayerToServerForce = new CancellationTokenSource();
_ctsServerToPlayer = new CancellationTokenSource();
_ctsServerToPlayerForce = new CancellationTokenSource();
await events.ThrowAsync(new LinkStartingEvent { Link = this }, cancellationToken);

events.RegisterListeners(this);

_playerToServerTask = ExecuteAsync(PlayerChannel, ServerChannel, Direction.Serverbound, _ctsPlayerToServer.Token, _ctsPlayerToServerForce.Token);
_serverToPlayerTask = ExecuteAsync(ServerChannel, PlayerChannel, Direction.Clientbound, _ctsServerToPlayer.Token, _ctsServerToPlayerForce.Token);
}

public IPlayer Player { get; init; }
public IServer Server { get; init; }
public IMinecraftChannel PlayerChannel { get; init; }
public IMinecraftChannel ServerChannel { get; init; }

public bool IsAlive => _playerToServerTask.Status == TaskStatus.Running && _serverToPlayerTask.Status == TaskStatus.Running;
logger.LogInformation("Started forwarding {Link} traffic", this);
await events.ThrowAsync(new LinkStartedEvent { Link = this }, cancellationToken);
}

public async ValueTask DisposeAsync()
{
// if the player does not support redirections, that's the end for him
if (!PlayerChannel.IsRedirectionSupported)
if (true /*!PlayerChannel.IsRedirectionSupported*/)
{
PlayerChannel.Close();
await PlayerChannel.DisposeAsync();
Expand All @@ -63,42 +57,45 @@ public async ValueTask DisposeAsync()
ServerChannel.Close();
await ServerChannel.DisposeAsync();

if (await WaitWithTimeout(_serverToPlayerTask))
if (_serverToPlayerTask is not null)
{
_logger.LogTrace("Timed out waiting Server {Server} disconnection from Player {Player}, closing manually", Server, Player);
await _ctsServerToPlayer.CancelAsync();

if (await WaitWithTimeout(_serverToPlayerTask))
{
_logger.LogTrace("Timed out waiting Server {Server} disconnection from Player {Player} manually, closing forcefully", Server, Player);
await _ctsServerToPlayerForce.CancelAsync();
logger.LogTrace("Timed out waiting Server {Server} disconnection from Player {Player}, closing manually", Server, Player);
await _ctsServerToPlayer.CancelAsync();

if (await WaitWithTimeout(_serverToPlayerTask))
throw new Exception($"Cannot dispose Link {this} (player=>server)");
{
logger.LogTrace("Timed out waiting Server {Server} disconnection from Player {Player} manually, closing forcefully", Server, Player);
await _ctsServerToPlayerForce.CancelAsync();

if (await WaitWithTimeout(_serverToPlayerTask))
throw new Exception($"Cannot dispose Link {this} (player=>server)");
}
}

await _serverToPlayerTask;
}

if (await WaitWithTimeout(_playerToServerTask))
if (_playerToServerTask is not null)
{
_logger.LogTrace("Timed out waiting Player {Player} disconnection from Server {Server}, closing manually", Player, Server);
await _ctsPlayerToServer.CancelAsync();

if (await WaitWithTimeout(_playerToServerTask))
{
_logger.LogTrace("Timed out waiting Player {Player} disconnection from Server {Server} manually, closing forcefully", Player, Server);
await _ctsPlayerToServerForce.CancelAsync();
logger.LogTrace("Timed out waiting Player {Player} disconnection from Server {Server}, closing manually", Player, Server);
await _ctsPlayerToServer.CancelAsync();

if (await WaitWithTimeout(_playerToServerTask))
throw new Exception($"Cannot dispose Link {this} (server=>player)");
}
}
{
logger.LogTrace("Timed out waiting Player {Player} disconnection from Server {Server} manually, closing forcefully", Player, Server);
await _ctsPlayerToServerForce.CancelAsync();

await Task.WhenAll(_playerToServerTask, _serverToPlayerTask);
}
if (await WaitWithTimeout(_playerToServerTask))
throw new Exception($"Cannot dispose Link {this} (server=>player)");
}
}

public override string ToString()
{
return Player + " <=> " + Server;
await _playerToServerTask;
}
}

protected async Task ExecuteAsync(IMinecraftChannel sourceChannel, IMinecraftChannel destinationChannel, Direction direction, CancellationToken cancellationToken, CancellationToken forceCancellationToken)
Expand All @@ -117,7 +114,7 @@ protected async Task ExecuteAsync(IMinecraftChannel sourceChannel, IMinecraftCha

using var message = await sourceChannel.ReadMessageAsync(readingCancellationTokenSource.Token);

var cancelled = await _events.ThrowWithResultAsync(new MessageReceivedEvent
var cancelled = await events.ThrowWithResultAsync(new MessageReceivedEvent
{
From = (Side)direction,
To = direction == Direction.Serverbound ? Side.Server : Side.Client,
Expand All @@ -132,7 +129,7 @@ protected async Task ExecuteAsync(IMinecraftChannel sourceChannel, IMinecraftCha
using var _ = await _lock.LockAsync();
await destinationChannel.WriteMessageAsync(message, forceCancellationToken);

await _events.ThrowAsync(new MessageSentEvent
await events.ThrowAsync(new MessageSentEvent
{
From = (Side)direction,
To = direction == Direction.Serverbound ? Side.Server : Side.Client,
Expand All @@ -152,18 +149,23 @@ await _events.ThrowAsync(new MessageSentEvent
}
catch (Exception exception)
{
_logger.LogError(exception, "Unhandled {Direction} exception from {Player}", direction, Player);
logger.LogError(exception, "Unhandled {Direction} exception from {Player}", direction, Player);
}
finally
{
await PlayerChannel.FlushAsync(forceCancellationToken);
await ServerChannel.FlushAsync(forceCancellationToken);

if (sourceChannel == PlayerChannel) // throw event only once
_ = _events.ThrowAsync(new LinkStoppingEvent { Link = this }, forceCancellationToken).CatchExceptions();
_ = events.ThrowAsync(new LinkStoppingEvent { Link = this }, forceCancellationToken).CatchExceptions();
}
}

public override string ToString()
{
return Player + " <=> " + Server;
}

private static async Task<bool> WaitWithTimeout(Task task, int milliseconds = 5000)
{
var timeout = Task.Delay(milliseconds);
Expand Down
23 changes: 18 additions & 5 deletions src/Platform/Links/LinkService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Nito.AsyncEx;
using Void.Proxy.API.Events;
using Void.Proxy.API.Events.Authentication;
using Void.Proxy.API.Events.Links;
using Void.Proxy.API.Events.Player;
using Void.Proxy.API.Events.Services;
Expand Down Expand Up @@ -70,13 +71,25 @@ private async ValueTask ConnectAsync(IPlayer player, IServer server, Cancellatio
if (firstConnection)
await _events.ThrowAsync(new PlayerConnectedEvent { Player = player }, cancellationToken);

var link = await _events.ThrowWithResultAsync(new CreateLinkEvent { Player = player, Server = server, PlayerChannel = playerChannel, ServerChannel = serverChannel }, cancellationToken) ?? new Link(player, server, playerChannel, serverChannel, _logger, _events);
await _events.ThrowAsync(new LinkStartingEvent { Link = link }, cancellationToken);
var link = await _events.ThrowWithResultAsync(new CreateLinkEvent
{
Player = player,
Server = server,
PlayerChannel = playerChannel,
ServerChannel = serverChannel
}, cancellationToken) ?? new Link(player, server, playerChannel, serverChannel, _logger, _events);

_events.RegisterListeners(link);
_links.Add(link);

_logger.LogInformation("Started forwarding {Link} traffic", link);
await _events.ThrowAsync(new LinkStartedEvent { Link = link }, cancellationToken);
var side = await _events.ThrowWithResultAsync(new AuthenticationStartingEvent { Link = link }, cancellationToken);

if (side is AuthenticationSide.Proxy && !await player.IsProtocolSupportedAsync(cancellationToken))
{
_logger.LogWarning("Player {Player} protocol is not supported, forcing authentication to Server side", player);
side = AuthenticationSide.Server;
}

await _events.ThrowAsync(new AuthenticationStartedEvent { Link = link, Side = side }, cancellationToken);
await link.StartAsync(cancellationToken);
}
}
4 changes: 2 additions & 2 deletions src/Plugins/ModsSupport/Forge/ForgeMarker.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Void.Proxy.Plugins.ProtocolSupport.Java.v1_20_2_to_latest.Forge;
namespace Void.Proxy.Plugins.ModsSupport.Forge;

// (HandshakePacket packet)
// var addressParts = packet.ServerAddress.Split('\0', StringSplitOptions.RemoveEmptyEntries);
Expand All @@ -9,7 +9,7 @@
// else if (addressParts.Length > 1)
// Console.WriteLine($"Player {link.Player} had extra marker(s) {string.Join(", ", addressParts[1..])} in handshake, ignoring");
//
// link.SetProtocolVersion(ProtocolVersion.Search(packet.ProtocolVersion));
// link.SetProtocolVersion(ProtocolVersion.Get(packet.ProtocolVersion));
// link.SwitchState(packet.NextState);
// link.SaveHandshake(packet);

Expand Down
Loading

0 comments on commit ccb2f62

Please sign in to comment.