diff --git a/Netorrent.Tests.Integration/Torrents/TorrentTests.cs b/Netorrent.Tests.Integration/Torrents/TorrentTests.cs index 8bf8d841..b1d0402e 100644 --- a/Netorrent.Tests.Integration/Torrents/TorrentTests.cs +++ b/Netorrent.Tests.Integration/Torrents/TorrentTests.cs @@ -454,7 +454,7 @@ CancellationToken cancellationToken var seederTorrent = await seeder.CreateTorrentAsync( path, _fixture.AnnounceUrl, - [.. _fixture.AnnounceUrls] + [_fixture.AnnounceUrls] ); yield return (seederTorrent, seeder); diff --git a/Netorrent.Tests/PublicApi/ApiTest.My_API_Has_No_Changes.approved.txt b/Netorrent.Tests/PublicApi/ApiTest.My_API_Has_No_Changes.approved.txt index aa18629e..a7deef76 100644 --- a/Netorrent.Tests/PublicApi/ApiTest.My_API_Has_No_Changes.approved.txt +++ b/Netorrent.Tests/PublicApi/ApiTest.My_API_Has_No_Changes.approved.txt @@ -207,16 +207,16 @@ namespace Netorrent.TorrentFile.FileStructure } public class MetaInfo : System.IEquatable { - public MetaInfo(Netorrent.TorrentFile.FileStructure.Info Info, string Announce, System.Collections.Generic.List? AnnounceList = null, long? CreationDate = default, string? Comment = null, string? CreatedBy = null, string? Encoding = null, string? Title = null, System.Collections.Generic.List? UrlList = null) { } + public MetaInfo(Netorrent.TorrentFile.FileStructure.Info Info, string Announce, System.Collections.Generic.IReadOnlyList? AnnounceList = null, long? CreationDate = default, string? Comment = null, string? CreatedBy = null, string? Encoding = null, string? Title = null, System.Collections.Generic.IReadOnlyList? UrlList = null) { } public string Announce { get; init; } - public System.Collections.Generic.List? AnnounceList { get; init; } + public System.Collections.Generic.IReadOnlyList? AnnounceList { get; init; } public string? Comment { get; init; } public string? CreatedBy { get; init; } public long? CreationDate { get; init; } public string? Encoding { get; init; } public Netorrent.TorrentFile.FileStructure.Info Info { get; init; } public string? Title { get; init; } - public System.Collections.Generic.List? UrlList { get; init; } + public System.Collections.Generic.IReadOnlyList? UrlList { get; init; } public Netorrent.Bencoding.Structs.BDictionary ToBDictionary() { } } } @@ -265,7 +265,7 @@ namespace Netorrent.TorrentFile public sealed class TorrentClient : System.IAsyncDisposable { public TorrentClient(System.Func? action = null) { } - public System.Threading.Tasks.ValueTask CreateTorrentAsync(string path, string announceUrl, System.Collections.Generic.List? announceUrls = null, System.Collections.Generic.List? webUrls = null, int pieceLength = 262144, System.Threading.CancellationToken cancellationToken = default) { } + public System.Threading.Tasks.ValueTask CreateTorrentAsync(string path, string announceUrl, System.Collections.Generic.List? announceUrls = null, System.Collections.Generic.List? webUrls = null, int pieceLength = 262144, System.Threading.CancellationToken cancellationToken = default) { } public System.Threading.Tasks.ValueTask DisposeAsync() { } public Netorrent.TorrentFile.Torrent LoadTorrent(Netorrent.TorrentFile.FileStructure.MetaInfo metaInfo, string outputDirectory, int[]? downloadedPieces = null) { } public System.Threading.Tasks.ValueTask LoadTorrentAsync(string path, string outputDirectory, int[]? downloadedPieces = null, System.Threading.CancellationToken cancellationToken = default) { } diff --git a/Netorrent.Tests/Torrents/TorrentFileTests.cs b/Netorrent.Tests/Torrents/TorrentFileTests.cs index 1dc9840d..239673d3 100644 --- a/Netorrent.Tests/Torrents/TorrentFileTests.cs +++ b/Netorrent.Tests/Torrents/TorrentFileTests.cs @@ -64,7 +64,9 @@ public async Task Should_Create_Torrent_File_From_Directory(CancellationToken ca await using var torrent = await torrentClient.CreateTorrentAsync( "Data/MultifileTest", "http://test.com", - ["http://test.com"], + [ + ["http://test.com"], + ], ["http://test.com"], cancellationToken: cancellationToken ); @@ -90,7 +92,9 @@ public async Task Should_Create_Torrent_File_From_File(CancellationToken cancell await using var torrent = await torrentClient.CreateTorrentAsync( "Data/MultifileTest/test.txt", "http://test.com", - ["http://test.com"], + [ + ["http://test.com"], + ], ["http://test.com"], cancellationToken: cancellationToken ); @@ -112,7 +116,9 @@ await torrentClient .CreateTorrentAsync( "Data/ASdasd", "http://test.com", - ["http://test.com"], + [ + ["http://test.com"], + ], ["http://test.com"], cancellationToken: cancellationToken ) @@ -128,7 +134,9 @@ await torrentClient .CreateTorrentAsync( "Data/MultifileTest/adasdasd.txt", "http://test.com", - ["http://test.com"], + [ + ["http://test.com"], + ], ["http://test.com"], cancellationToken: cancellationToken ) @@ -146,7 +154,9 @@ public async Task Should_Verify_File(string path, CancellationToken cancellation await using var torrent = await torrentClient.CreateTorrentAsync( path, "http://test.com", - ["http://test.com"], + [ + ["http://test.com"], + ], ["http://test.com"], pieceLength: pieceLength, cancellationToken: cancellationToken @@ -177,7 +187,9 @@ CancellationToken cancellationToken await using var torrent = await torrentClient.CreateTorrentAsync( path, "http://test.com", - ["http://test.com"], + [ + ["http://test.com"], + ], ["http://test.com"], pieceLength: pieceLength, cancellationToken: cancellationToken diff --git a/Netorrent.Tests/Tracker/TrackerTests.cs b/Netorrent.Tests/Tracker/TrackerTests.cs index 665d538e..fdd4e93f 100644 --- a/Netorrent.Tests/Tracker/TrackerTests.cs +++ b/Netorrent.Tests/Tracker/TrackerTests.cs @@ -3,6 +3,7 @@ using System.Threading.Channels; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Netorrent.Exceptions; using Netorrent.Extensions; using Netorrent.Tests.Extensions; using Netorrent.Tests.Fakes; @@ -69,9 +70,7 @@ public async Task Should_get_peers_from_udp_tracker(CancellationToken cancellati new(), ctx.Channel.Writer, new byte[20], - "null", - new(IPAddress.Loopback, 1), - ctx.Logger + new(IPAddress.Loopback, 1) ); using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); @@ -102,7 +101,6 @@ public async Task Should_get_peers_from_http_tracker(CancellationToken cancellat new(), new byte[20], "null", - ctx.Logger, ctx.Channel ); @@ -134,23 +132,13 @@ public async Task Should_not_get_peers_from_http_tracker(CancellationToken cance new(), new byte[20], "null", - ctx.Logger, ctx.Channel ); - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var trackerTask = httpTracker.StartAsync(cts.Token).AsTask(); - - await Task.Delay(5.Seconds, cancellationToken); - cts.Cancel(); - ctx.Channel.Writer.TryComplete(); - - var ipendpoints = await ctx - .Channel.Reader.ReadAllAsync(cancellationToken) - .ToArrayAsync(cancellationToken: cancellationToken) - .AsTask(); - - ipendpoints.Length.ShouldBe(0); + await httpTracker + .StartAsync(cancellationToken) + .AsTask() + .ShouldThrowAsync(); } [Test] @@ -171,24 +159,66 @@ public async Task Should_not_get_peers_from_udp_tracker(CancellationToken cancel new(), ctx.Channel.Writer, new byte[20], - "null", - new(IPAddress.Loopback, 1), - ctx.Logger + new(IPAddress.Loopback, 1) ); - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var trackerTask = udptracker.StartAsync(cts.Token).AsTask(); + await udptracker + .StartAsync(cancellationToken) + .AsTask() + .ShouldThrowAsync(); + } - await Task.Delay(5.Seconds, cancellationToken); - cts.Cancel(); - ctx.Channel.Writer.TryComplete(); + [Test] + public async Task Should_not_get_peers_from_tracker_client(CancellationToken cancellationToken) + { + var ctx = CreateDefaultContext(); - var ipendpoints = await ctx - .Channel.Reader.ReadAllAsync(cancellationToken) - .ToArrayAsync(cancellationToken: cancellationToken) - .AsTask(); + await using var udptrackerManagerv4 = new FakeUdpTrackerTransactionManager( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetwork)], + ctx.Interval, + new Exception() + ); + + await using var udptrackerManagerv6 = new FakeUdpTrackerTransactionManager( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetworkV6)], + ctx.Interval, + new Exception() + ); - ipendpoints.Length.ShouldBe(0); + using var httpTrackerHandlerv4 = new FakeHttpTrackerHandler( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetwork)], + ctx.Interval, + new Exception() + ); + using var httpTrackerHandlerv6 = new FakeHttpTrackerHandler( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetworkV6)], + ctx.Interval, + new Exception() + ); + + var trackerHandlers = new TrackerHandlers( + httpTrackerHandlerv4, + udptrackerManagerv4, + httpTrackerHandlerv6, + udptrackerManagerv6 + ); + + await using var trackerClient = new TrackerClient( + trackerHandlers, + UsedTrackers.Http | UsedTrackers.Udp, + 1, + new(3), + new(), + ctx.Channel, + [ + ["udp://localhost:1", "https://localhost:2"], + ["http://localhost:3", "aaaa://localhost:4"], + ], + new byte[20], + ctx.Logger + ); + + await trackerClient.StartAsync(cancellationToken).ShouldNotThrowAsync(); } [Test] @@ -196,18 +226,30 @@ public async Task Should_get_peers_from_tracker_client(CancellationToken cancell { var ctx = CreateDefaultContext(); - await using var udptrackerManager = new FakeUdpTrackerTransactionManager( - ctx.Ips, + await using var udptrackerManagerv4 = new FakeUdpTrackerTransactionManager( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetwork)], + ctx.Interval + ); + + await using var udptrackerManagerv6 = new FakeUdpTrackerTransactionManager( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetworkV6)], ctx.Interval ); - var httpTrackerHandler = new FakeHttpTrackerHandler(ctx.Ips, ctx.Interval); + using var httpTrackerHandlerv4 = new FakeHttpTrackerHandler( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetwork)], + ctx.Interval + ); + using var httpTrackerHandlerv6 = new FakeHttpTrackerHandler( + [.. ctx.Ips.Where(i => i.AddressFamily == AddressFamily.InterNetworkV6)], + ctx.Interval + ); var trackerHandlers = new TrackerHandlers( - httpTrackerHandler, - udptrackerManager, - httpTrackerHandler, - udptrackerManager + httpTrackerHandlerv4, + udptrackerManagerv4, + httpTrackerHandlerv6, + udptrackerManagerv6 ); await using var trackerClient = new TrackerClient( @@ -218,10 +260,8 @@ public async Task Should_get_peers_from_tracker_client(CancellationToken cancell new(), ctx.Channel, [ - "udp://localhost:1", - "https://localhost:2", - "http://localhost:3", - "aaaa://localhost:4", + ["udp://localhost:1", "https://localhost:2"], + ["http://localhost:3", "aaaa://localhost:4"], ], new byte[20], ctx.Logger @@ -239,6 +279,7 @@ public async Task Should_get_peers_from_tracker_client(CancellationToken cancell IPEndPoint[] resultIps = [.. ctx.Ips, .. ctx.Ips, .. ctx.Ips, .. ctx.Ips]; ipendpoints.Length.ShouldBe(ctx.Ips.Length * 4); - ipendpoints.ShouldBe(resultIps); + var sorted = ipendpoints.OrderBy(i => i.ToString()).ToArray(); + sorted.ShouldBe([.. resultIps.OrderBy(i => i.ToString())]); } } diff --git a/Netorrent/Exceptions/AnnounceException.cs b/Netorrent/Exceptions/AnnounceException.cs new file mode 100644 index 00000000..f50cf0f1 --- /dev/null +++ b/Netorrent/Exceptions/AnnounceException.cs @@ -0,0 +1,3 @@ +namespace Netorrent.Exceptions; + +internal class AnnounceException(string? message = null) : Exception(message); diff --git a/Netorrent/P2P/Tcp/TcpPeersListener.cs b/Netorrent/P2P/Tcp/TcpPeersListener.cs index 5bfcec22..7b041f4b 100644 --- a/Netorrent/P2P/Tcp/TcpPeersListener.cs +++ b/Netorrent/P2P/Tcp/TcpPeersListener.cs @@ -18,7 +18,7 @@ ILogger logger { private readonly ConcurrentDictionary _peersClientByInfoHash = new(); private readonly Channel _incomingConnections = Channel.CreateBounded( - 128 + new BoundedChannelOptions(128) { SingleWriter = false, SingleReader = true } ); public int Port => ((IPEndPoint)tcpListeners[0].LocalEndpoint).Port; diff --git a/Netorrent/TorrentFile/FileStructure/MetaInfo.cs b/Netorrent/TorrentFile/FileStructure/MetaInfo.cs index b121ead8..8f916d13 100644 --- a/Netorrent/TorrentFile/FileStructure/MetaInfo.cs +++ b/Netorrent/TorrentFile/FileStructure/MetaInfo.cs @@ -5,13 +5,13 @@ namespace Netorrent.TorrentFile.FileStructure; public record MetaInfo( Info Info, string Announce, - List? AnnounceList = null, + IReadOnlyList? AnnounceList = null, long? CreationDate = null, string? Comment = null, string? CreatedBy = null, string? Encoding = null, string? Title = null, - List? UrlList = null + IReadOnlyList? UrlList = null ) { public BDictionary ToBDictionary() @@ -23,16 +23,16 @@ public BDictionary ToBDictionary() root.Elements["announce"] = new BString(Announce); } - if (AnnounceList != null && AnnounceList.Count != 0) + if (AnnounceList is not null && AnnounceList.Count != 0) { - var innerTier = new BList([ - .. AnnounceList.Select(u => (IBencodingNode)new BString(u)), + root.Elements["announce-list"] = new BList([ + .. AnnounceList.Select(u => new BList([ + .. u.Select(i => (IBencodingNode)new BString(i)), + ])), ]); - var outer = new BList([innerTier]); - root.Elements["announce-list"] = outer; } - if (UrlList != null && UrlList.Count != 0) + if (UrlList is not null && UrlList.Count != 0) { root.Elements["url-list"] = new BList([ .. UrlList.Select(u => (IBencodingNode)new BString(u)), diff --git a/Netorrent/TorrentFile/Torrent.cs b/Netorrent/TorrentFile/Torrent.cs index d2ff4d27..3dfdcf99 100644 --- a/Netorrent/TorrentFile/Torrent.cs +++ b/Netorrent/TorrentFile/Torrent.cs @@ -115,7 +115,11 @@ IReadOnlySet downloadedPieces dataStatistics, peerId, trackersChannel.Writer, - [metaInfo.Announce, .. metaInfo.AnnounceList ?? []], + metaInfo.AnnounceList?.Select(i => i.ToArray()).ToList() //Don't modify the original announce list + ?? + [ + [metaInfo.Announce], + ], metaInfo.Info.InfoHash, torrentClientOptions.Logger ); diff --git a/Netorrent/TorrentFile/TorrentClient.cs b/Netorrent/TorrentFile/TorrentClient.cs index 066d2be7..67e14192 100644 --- a/Netorrent/TorrentFile/TorrentClient.cs +++ b/Netorrent/TorrentFile/TorrentClient.cs @@ -180,7 +180,7 @@ public Torrent LoadTorrent( public async ValueTask CreateTorrentAsync( string path, string announceUrl, - List? announceUrls = null, + List? announceUrls = null, List? webUrls = null, int pieceLength = 256 * 1024, // 256 KB default CancellationToken cancellationToken = default @@ -211,7 +211,7 @@ public async ValueTask CreateTorrentAsync( internal static async ValueTask CreateMetaInfoFromPathAsync( string path, string announceUrl, - List? announceUrls, + List? announceUrls, List? webUrls, int pieceLength, CancellationToken cancellationToken = default @@ -252,7 +252,7 @@ static bool IsValidUrl(string? url, bool allowHttp = true) throw new ArgumentException($"Invalid announce URL: '{announceUrl}'"); } - string[] allUrls = [.. announceUrls ?? [], .. webUrls ?? []]; + string[] allUrls = [.. announceUrls?.SelectMany(i => i) ?? [], .. webUrls ?? []]; foreach (string url in allUrls) { if (!IsValidUrl(url)) @@ -461,8 +461,7 @@ internal static MetaInfo ParseMetaInfo(BDictionary dictionary) .Elements.GetValueOrDefault("announce-list") ?.As() ?.Elements.Select(i => i.As()!.Value.Elements) - .SelectMany(i => i) - .Select(i => i.As()!.Value) + ?.Select(i => i.Select(i => i.As()!.Value.Data).ToArray()) .ToList(); var urlList = dictionary .Elements.GetValueOrDefault("url-list") diff --git a/Netorrent/Tracker/Http/HttpTracker.cs b/Netorrent/Tracker/Http/HttpTracker.cs index 5dd077e6..ab10cbf1 100644 --- a/Netorrent/Tracker/Http/HttpTracker.cs +++ b/Netorrent/Tracker/Http/HttpTracker.cs @@ -1,7 +1,6 @@ using System.Net; -using System.Net.Sockets; using System.Threading.Channels; -using Microsoft.Extensions.Logging; +using Netorrent.Exceptions; using Netorrent.Extensions; using Netorrent.P2P.Messages; using Netorrent.Statistics; @@ -16,20 +15,12 @@ internal class HttpTracker( PeerId peerId, InfoHash infoHash, string announceUrl, - ILogger logger, ChannelWriter channelWriter ) : ITracker { public async ValueTask StartAsync(CancellationToken cancellationToken) { - var response = await TryAnnounceAsync(Events.Started, cancellationToken) - .ConfigureAwait(false); - - if (response is null) - { - return; - } - + var response = await AnnounceAsync(Events.Started, cancellationToken).ConfigureAwait(false); foreach (var iPEndPoint in response.Peers) { await channelWriter.WriteAsync(iPEndPoint, cancellationToken).ConfigureAwait(false); @@ -39,21 +30,10 @@ public async ValueTask StartAsync(CancellationToken cancellationToken) { var interval = response.Interval.Seconds; - if (logger.IsEnabled(LogLevel.Information)) - { - logger.LogInformation("Waiting {seconds} seconds", interval.TotalSeconds); - } - await Task.Delay(interval, cancellationToken).ConfigureAwait(false); - var newResponse = await TryAnnounceAsync(cancellationToken: cancellationToken) + var newResponse = await AnnounceAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); - - if (newResponse is null) - { - continue; - } - response = newResponse; foreach (var iPEndPoint in response.Peers) @@ -63,16 +43,11 @@ public async ValueTask StartAsync(CancellationToken cancellationToken) } } - private async Task TryAnnounceAsync( + private async Task AnnounceAsync( string? @event = null, CancellationToken cancellationToken = default ) { - if (logger.IsEnabled(LogLevel.Information)) - { - logger.LogInformation("Announcing to {url}", announceUrl); - } - try { var request = new HttpTrackerRequest( @@ -85,7 +60,7 @@ public async ValueTask StartAsync(CancellationToken cancellationToken) true, false, @event, - 50 + 200 ); return await httpTrackerHandler @@ -94,17 +69,12 @@ public async ValueTask StartAsync(CancellationToken cancellationToken) } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogDebug(ex, "Couldn't announce to {trackerUrl}", announceUrl); - } - - return null; + throw new AnnounceException(ex.Message); } } public async ValueTask StopAsync(CancellationToken cancellationToken) { - await TryAnnounceAsync(Events.Stopped, cancellationToken).ConfigureAwait(false); + await AnnounceAsync(Events.Stopped, cancellationToken).ConfigureAwait(false); } } diff --git a/Netorrent/Tracker/TrackerClient.cs b/Netorrent/Tracker/TrackerClient.cs index 9e92ae03..b692c2f1 100644 --- a/Netorrent/Tracker/TrackerClient.cs +++ b/Netorrent/Tracker/TrackerClient.cs @@ -1,8 +1,10 @@ -using System.Net; +using System; +using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Threading.Channels; using Microsoft.Extensions.Logging; +using Netorrent.Exceptions; using Netorrent.Extensions; using Netorrent.P2P.Messages; using Netorrent.Statistics; @@ -21,48 +23,86 @@ internal class TrackerClient( DataStatistics transferStatistics, PeerId peerId, ChannelWriter trackersChannel, - string[] announceList, + List announceList, InfoHash infoHash, ILogger logger ) : IAsyncDisposable { public async Task StartAsync(CancellationToken cancellationToken) { - var urls = - announceList - .Where(url => !string.IsNullOrWhiteSpace(url)) - .Distinct(StringComparer.OrdinalIgnoreCase) - ?? []; - - List trackerTasks = []; - List trackers = []; - - //The trackers should not fail by them self - //They finish successfully because of dns problems, udp timeouts, etc. - try + foreach (var urls in announceList) { + var snapshot = urls.AsValueEnumerable().Shuffle().ToArray(); + await foreach ( - var tracker in CreateTrackers(urls, cancellationToken) + var (url, (Ipv4, Ipv6)) in CreateTrackers(snapshot, cancellationToken) .WithCancellation(cancellationToken) .ConfigureAwait(false) ) { - trackers.Add(tracker); - trackerTasks.Add(tracker.StartAsync(cancellationToken).AsTask()); + try + { + Task?[] tasks = + [ + Ipv4?.StartAsync(cancellationToken).AsTask(), + Ipv6?.StartAsync(cancellationToken).AsTask(), + ]; + + await Task.WhenAll(tasks.Where(i => i is not null).Cast()) + .ConfigureAwait(false); + } + catch (AnnounceException ex) + { + if (logger.IsEnabled(LogLevel.Error)) + { + logger.LogError(ex, "Error Announcing"); + } + } + catch (OperationCanceledException oce) + when (oce.CancellationToken == cancellationToken) + { + try + { + using var ct = new CancellationTokenSource(5.Seconds); + if (Ipv4 is not null) + { + await Ipv4.StopAsync(ct.Token).ConfigureAwait(false); + } + if (Ipv6 is not null) + { + await Ipv6.StopAsync(ct.Token).ConfigureAwait(false); + } + + Promote(urls, url); + } + catch (Exception stopEx) + { + if (logger.IsEnabled(LogLevel.Error)) + { + logger.LogError(stopEx, "Error stopping"); + } + } + + return; + } } - - await Task.WhenAll(trackerTasks).ConfigureAwait(false); - } - finally - { - using var cts = new CancellationTokenSource(5.Seconds); - var trackerDisposeTasks = trackers.Select(i => i.StopAsync(cts.Token).AsTask()); - await Task.WhenAll(trackerDisposeTasks).ConfigureAwait(false); } } - private async IAsyncEnumerable CreateTrackers( - IEnumerable urls, + private static void Promote(string[] tier, string winner) + { + var idx = Array.IndexOf(tier, winner); + if (idx <= 0) + return; + + // Swap to front + var first = tier[0]; + tier[0] = winner; + tier[idx] = first; + } + + private async IAsyncEnumerable<(string url, (ITracker? Ipv4, ITracker? Ipv6))> CreateTrackers( + string[] urls, [EnumeratorCancellation] CancellationToken cancellationToken ) { @@ -87,19 +127,11 @@ await CreateHttpTrackersAsync(uri, cancellationToken).ConfigureAwait(false), _ => LogUnknownTracker(url), }; - foreach (var tracker in trackers) - { - if (tracker is null) - { - continue; - } - - yield return tracker; - } + yield return (url, trackers); } } - private async ValueTask CreateHttpTrackersAsync( + private async ValueTask<(HttpTracker? Ipv4, HttpTracker? Ipv6)> CreateHttpTrackersAsync( Uri uri, CancellationToken cancellationToken ) @@ -108,16 +140,18 @@ CancellationToken cancellationToken var (ipv4, ipv6) = await Dns.GetHostAdressesOrEmptyAsync(uri, cancellationToken) .ConfigureAwait(false); + HttpTracker? trackerv4 = null; + HttpTracker? trackerv6 = null; + if (trackerHandlers.HttpTrackerHandlerIpv4 is not null && ipv4 is not null) { - var trackerv4 = new HttpTracker( + trackerv4 = new HttpTracker( port, transferStatistics, trackerHandlers.HttpTrackerHandlerIpv4, peerId, infoHash, uri.OriginalString, - logger, trackersChannel ); httpsTrackers.Add(trackerv4); @@ -125,23 +159,22 @@ CancellationToken cancellationToken if (trackerHandlers.HttpTrackerHandlerIpv6 is not null && ipv6 is not null) { - var trackerv6 = new HttpTracker( + trackerv6 = new HttpTracker( port, transferStatistics, trackerHandlers.HttpTrackerHandlerIpv6, peerId, infoHash, uri.OriginalString, - logger, trackersChannel ); httpsTrackers.Add(trackerv6); } - return [.. httpsTrackers]; + return (trackerv4, trackerv6); } - private async ValueTask CreateUdpTrackersAsync( + private async ValueTask<(UdpTracker? Ipv4, UdpTracker? Ipv6)> CreateUdpTrackersAsync( Uri uri, CancellationToken cancellationToken ) @@ -150,19 +183,20 @@ CancellationToken cancellationToken var (ipv4, ipv6) = await Dns.GetHostAdressesOrEmptyAsync(uri, cancellationToken) .ConfigureAwait(false); + UdpTracker? trackerv4 = null; + UdpTracker? trackerv6 = null; + if (trackerHandlers.UdpTrackerHandlerIpv4 is not null && ipv4 is not null && uri.Port > 0) { var ipEndpoint = new IPEndPoint(ipv4, uri.Port); - var trackerv4 = new UdpTracker( + trackerv4 = new UdpTracker( trackerHandlers.UdpTrackerHandlerIpv4, port, transferStatistics, peerId, trackersChannel, infoHash, - uri.OriginalString, - ipEndpoint, - logger + ipEndpoint ); udpTrackers.Add(trackerv4); } @@ -170,31 +204,29 @@ CancellationToken cancellationToken if (trackerHandlers.UdpTrackerHandlerIpv6 is not null && ipv6 is not null && uri.Port > 0) { var ipEndpoint = new IPEndPoint(ipv6, uri.Port); - var trackerv6 = new UdpTracker( + trackerv6 = new UdpTracker( trackerHandlers.UdpTrackerHandlerIpv6, port, transferStatistics, peerId, trackersChannel, infoHash, - uri.OriginalString, - ipEndpoint, - logger + ipEndpoint ); udpTrackers.Add(trackerv6); } - return [.. udpTrackers]; + return (trackerv4, trackerv6); } - private ITracker[] LogUnknownTracker(string scheme) + private (ITracker? Ipv4, ITracker? Ipv6) LogUnknownTracker(string scheme) { if (logger.IsEnabled(LogLevel.Debug)) { logger.LogDebug("Unknown {scheme} tracker", scheme); } - return []; + return (null, null); } public async ValueTask DisposeAsync() diff --git a/Netorrent/Tracker/Udp/UdpTracker.cs b/Netorrent/Tracker/Udp/UdpTracker.cs index 2990b953..9d485402 100644 --- a/Netorrent/Tracker/Udp/UdpTracker.cs +++ b/Netorrent/Tracker/Udp/UdpTracker.cs @@ -1,6 +1,6 @@ using System.Net; using System.Threading.Channels; -using Microsoft.Extensions.Logging; +using Netorrent.Exceptions; using Netorrent.Extensions; using Netorrent.P2P.Messages; using Netorrent.Statistics; @@ -17,9 +17,7 @@ internal class UdpTracker( PeerId peerId, ChannelWriter channelWriter, InfoHash infoHash, - string announceUrl, - IPEndPoint iPEndPoint, - ILogger logger + IPEndPoint iPEndPoint ) : ITracker { private UdpTrackerResponse? _lastResponse; @@ -27,23 +25,15 @@ ILogger logger public async ValueTask StartAsync(CancellationToken cancellationToken) { - if (await TryConnectAsync(iPEndPoint, cancellationToken).ConfigureAwait(false) is null) - { - return; - } + await ConnectAsync(iPEndPoint, cancellationToken).ConfigureAwait(false); - _lastResponse = await TryAnnounceAsync( + _lastResponse = await AnnounceAsync( iPEndPoint, @event: Events.Started, cancellationToken: cancellationToken ) .ConfigureAwait(false); - if (_lastResponse is null) - { - return; - } - foreach (var peer in _lastResponse.Peers) { await channelWriter.WriteAsync(peer, cancellationToken).ConfigureAwait(false); @@ -54,19 +44,9 @@ public async ValueTask StartAsync(CancellationToken cancellationToken) var interval = _lastResponse.Interval.Seconds; await Task.Delay(interval, cancellationToken).ConfigureAwait(false); - if (logger.IsEnabled(LogLevel.Information)) - { - logger.LogInformation("Waiting {seconds} seconds", interval); - } - - var newResponse = await TryAnnounceAsync(iPEndPoint, null, cancellationToken) + var newResponse = await AnnounceAsync(iPEndPoint, null, cancellationToken) .ConfigureAwait(false); - if (newResponse is null) - { - continue; - } - _lastResponse = newResponse; foreach (var peer in _lastResponse.Peers) @@ -76,7 +56,7 @@ public async ValueTask StartAsync(CancellationToken cancellationToken) } } - public async Task TryConnectAsync( + public async Task ConnectAsync( IPEndPoint iPEndPoint, CancellationToken cancellationToken ) @@ -89,16 +69,11 @@ CancellationToken cancellationToken } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogDebug(ex, "Couldn't connect to {trackerUrl}", announceUrl); - } - - return null; + throw new AnnounceException(ex.Message); } } - public async Task TryAnnounceAsync( + public async Task AnnounceAsync( IPEndPoint iPEndPoint, string? @event, CancellationToken cancellationToken @@ -106,11 +81,6 @@ CancellationToken cancellationToken { try { - if (logger.IsEnabled(LogLevel.Information)) - { - logger.LogInformation("Announcing to {url}", announceUrl); - } - var connectionId = udpTrackerHandler.GetConnectionIdOrNull(_trackerId); if (connectionId is null || udpTrackerHandler.IsOutdated(connectionId.Value)) @@ -133,7 +103,7 @@ CancellationToken cancellationToken (ushort)port, ConnectionId: connectionId.Value, TransactionId: udpTrackerHandler.MakeTransactionId(), - NumWant: 50 + NumWant: 200 ); return await udpTrackerHandler @@ -142,12 +112,7 @@ CancellationToken cancellationToken } catch (Exception ex) { - if (logger.IsEnabled(LogLevel.Debug)) - { - logger.LogDebug(ex, "Couldn't announce to {trackerUrl}", announceUrl); - } - - return null; + throw new AnnounceException(ex.Message); } } @@ -156,7 +121,7 @@ public async ValueTask StopAsync(CancellationToken cancellationToken) if (iPEndPoint is not null && _lastResponse is not null) { //Udp tracker don't respond to stop so there is no point in awaiting as it will never complete - _ = TryAnnounceAsync(iPEndPoint, Events.Stopped, cancellationToken); + _ = AnnounceAsync(iPEndPoint, Events.Stopped, cancellationToken); } } } diff --git a/Netorrent/Tracker/Udp/UdpTrackerHandler.cs b/Netorrent/Tracker/Udp/UdpTrackerHandler.cs index 145b978e..58561461 100644 --- a/Netorrent/Tracker/Udp/UdpTrackerHandler.cs +++ b/Netorrent/Tracker/Udp/UdpTrackerHandler.cs @@ -174,6 +174,10 @@ await _udpClient .ConfigureAwait(false); transaction.RetryCount++; var seconds = _retryDelay * (transaction.RetryCount + 1); + if (seconds > 60.Seconds) + { + seconds = 60.Seconds; + } transaction.NextRetryTime = DateTime.UtcNow + seconds; } }