diff --git a/Netorrent.Tests/Tracker/TrackerTests.cs b/Netorrent.Tests/Tracker/TrackerTests.cs index 4f401e93..02ab30e1 100644 --- a/Netorrent.Tests/Tracker/TrackerTests.cs +++ b/Netorrent.Tests/Tracker/TrackerTests.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Netorrent.Extensions; using Netorrent.Tests.Fakes; +using Netorrent.TorrentFile; using Netorrent.Tracker; using Netorrent.Tracker.Http; using Netorrent.Tracker.Udp; @@ -207,6 +208,7 @@ public async Task Should_get_peers_from_tracker_client(CancellationToken cancell await using var trackerClient = new TrackerClient( httpTrackerHandler, udptrackerManager, + UsedAdressProtocol.Dual, 1, new(3), new(), diff --git a/Netorrent/Extensions/TcpListenerExtensions.cs b/Netorrent/Extensions/TcpListenerExtensions.cs index 917666ff..49aa788b 100644 --- a/Netorrent/Extensions/TcpListenerExtensions.cs +++ b/Netorrent/Extensions/TcpListenerExtensions.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Sockets; +using Netorrent.TorrentFile; namespace Netorrent.Extensions; @@ -7,11 +8,19 @@ internal static class TcpListenerExtensions { extension(TcpListener) { - public static TcpListener GetFreeTcpListener() + public static TcpListener GetFreeTcpListener( + UsedAdressProtocol usedAdressProtocol, + int port = 0 + ) { - var listener = new TcpListener(IPAddress.IPv6Any, 0); - listener.Server.DualMode = true; - listener.Start(); + var ipAddress = usedAdressProtocol.ToIpAddress(); + var listener = new TcpListener(ipAddress, port); + + if (usedAdressProtocol == UsedAdressProtocol.Dual) + { + listener.Server.DualMode = true; + } + return listener; } } diff --git a/Netorrent/Extensions/UdpClientExtensions.cs b/Netorrent/Extensions/UdpClientExtensions.cs index d5bc1e0d..7543adc7 100644 --- a/Netorrent/Extensions/UdpClientExtensions.cs +++ b/Netorrent/Extensions/UdpClientExtensions.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Sockets; +using Netorrent.TorrentFile; namespace Netorrent.Extensions; @@ -7,11 +8,20 @@ internal static class UdpClientExtensions { extension(UdpClient) { - public static UdpClient GetFreeUdpClient() + public static UdpClient GetFreeUdpClient( + UsedAdressProtocol usedAdressProtocol, + int port = 0 + ) { - var udpClient = new UdpClient(AddressFamily.InterNetworkV6); - udpClient.Client.DualMode = true; - udpClient.Client.Bind(new IPEndPoint(IPAddress.IPv6Any, 0)); + var ipAdress = usedAdressProtocol.ToIpAddress(); + var udpClient = new UdpClient(ipAdress.AddressFamily); + + if (usedAdressProtocol == UsedAdressProtocol.Dual) + { + udpClient.Client.DualMode = true; + } + + udpClient.Client.Bind(new IPEndPoint(ipAdress, port)); return udpClient; } } diff --git a/Netorrent/Extensions/UsedAdressProtocolExtensions.cs b/Netorrent/Extensions/UsedAdressProtocolExtensions.cs new file mode 100644 index 00000000..60df724b --- /dev/null +++ b/Netorrent/Extensions/UsedAdressProtocolExtensions.cs @@ -0,0 +1,33 @@ +using System.Net; +using System.Net.Sockets; +using Netorrent.TorrentFile; + +namespace Netorrent.Extensions; + +internal static class UsedAdressProtocolExtensions +{ + extension(UsedAdressProtocol usedAdressProtocol) + { + public IPAddress ToIpAddress() => + usedAdressProtocol switch + { + UsedAdressProtocol.Ipv4 => IPAddress.Any, + UsedAdressProtocol.Ipv6 => IPAddress.IPv6Any, + UsedAdressProtocol.Dual => IPAddress.IPv6Any, + _ => throw new ArgumentOutOfRangeException(nameof(usedAdressProtocol)), + }; + + public AddressFamily[] ToAddressFamily() => + usedAdressProtocol switch + { + UsedAdressProtocol.Ipv4 => [AddressFamily.InterNetwork], + UsedAdressProtocol.Ipv6 => [AddressFamily.InterNetworkV6], + UsedAdressProtocol.Dual => + [ + AddressFamily.InterNetwork, + AddressFamily.InterNetworkV6, + ], + _ => throw new ArgumentOutOfRangeException(nameof(usedAdressProtocol)), + }; + } +} diff --git a/Netorrent/P2P/Tcp/TcpPeersListener.cs b/Netorrent/P2P/Tcp/TcpPeersListener.cs index 40d48f0c..c24151dc 100644 --- a/Netorrent/P2P/Tcp/TcpPeersListener.cs +++ b/Netorrent/P2P/Tcp/TcpPeersListener.cs @@ -8,15 +8,15 @@ namespace Netorrent.P2P.Tcp; -internal class TcpPeersListener(PeerId peerId, ILogger logger) : IAsyncDisposable +internal class TcpPeersListener(PeerId peerId, TcpListener tcpListener, ILogger logger) + : IAsyncDisposable { - private readonly TcpListener _tcpListener = TcpListener.GetFreeTcpListener(); private readonly ConcurrentDictionary< ReadOnlyMemory, PeersClient > _peersClientByInfoHash = new(ReadOnlyMemoryEqualityComparer.Instance); - public IPEndPoint EndPoint => (IPEndPoint)_tcpListener.LocalEndpoint; + public IPEndPoint EndPoint => (IPEndPoint)tcpListener.LocalEndpoint; private CancellationTokenSource? _cancellationTokenSource; private Task? _runTask; @@ -30,12 +30,12 @@ public void Start() private async Task StartAsync() { - _tcpListener.Start(); + tcpListener.Start(); _cancellationTokenSource = new(); while (!_cancellationTokenSource.Token.IsCancellationRequested) { - var tcpClient = await _tcpListener + var tcpClient = await tcpListener .AcceptTcpClientAsync(_cancellationTokenSource.Token) .ConfigureAwait(false); @@ -112,7 +112,7 @@ public async ValueTask DisposeAsync() await _runTask.ConfigureAwait(false); } catch { } - _tcpListener.Dispose(); + tcpListener.Dispose(); } } } diff --git a/Netorrent/TorrentFile/Torrent.cs b/Netorrent/TorrentFile/Torrent.cs index 83ef9e54..61b6f7e0 100644 --- a/Netorrent/TorrentFile/Torrent.cs +++ b/Netorrent/TorrentFile/Torrent.cs @@ -44,6 +44,7 @@ internal Torrent( string outputDirectory, ILogger logger, TcpPeersListener peersListener, + UsedAdressProtocol usedAdressProtocol, IPAddress? forcedIp = null, bool bitfieldInitialized = false, Func? peerIpProxy = null @@ -98,6 +99,7 @@ [.. metaInfo.Info.Pieces.AsValueEnumerable().Chunk(20)] _trackerClient = new TrackerClient( new HttpTrackerHandler(httpClient), trackerTransaction, + usedAdressProtocol, peersListener.EndPoint.Port, transferStatistics, peerId, diff --git a/Netorrent/TorrentFile/TorrentClient.cs b/Netorrent/TorrentFile/TorrentClient.cs index 6aab60f5..1488cf54 100644 --- a/Netorrent/TorrentFile/TorrentClient.cs +++ b/Netorrent/TorrentFile/TorrentClient.cs @@ -24,11 +24,20 @@ public sealed class TorrentClient : IAsyncDisposable public TorrentClient(Func? action = null) { - var options = new TorrentClientOptions(new(), NullLogger.Instance, null); + var options = new TorrentClientOptions( + new(), + NullLogger.Instance, + UsedAdressProtocol.Dual, + null + ); _options = action?.Invoke(options) ?? options; - _peersListener = new(_peerId, _options.Logger); + _peersListener = new( + _peerId, + TcpListener.GetFreeTcpListener(_options.UsedAdressProtocol), + _options.Logger + ); _trackerTransactionManager = new( - new UdpClientWrapper(UdpClient.GetFreeUdpClient()), + new UdpClientWrapper(UdpClient.GetFreeUdpClient(_options.UsedAdressProtocol)), _options.Logger, 15.Seconds, 1.Seconds, @@ -70,6 +79,7 @@ public async ValueTask LoadTorrentAsync( Path.GetFullPath(outputDirectory), _options.Logger, _peersListener, + _options.UsedAdressProtocol, peerIpProxy: _options.PeerIpProxy ); _torrents.Add(torrent); @@ -92,6 +102,7 @@ public Torrent LoadTorrent(MetaInfo metaInfo, string outputDirectory) Path.GetFullPath(outputDirectory), _options.Logger, _peersListener, + _options.UsedAdressProtocol, _options.ForcedIp, peerIpProxy: _options.PeerIpProxy ); @@ -138,6 +149,7 @@ await CreateMetaInfoFromPathAsync( Path.GetFullPath(Path.GetDirectoryName(path) ?? ""), _options.Logger, _peersListener, + _options.UsedAdressProtocol, _options.ForcedIp, true, peerIpProxy: _options.PeerIpProxy diff --git a/Netorrent/TorrentFile/TorrentClientOptions.cs b/Netorrent/TorrentFile/TorrentClientOptions.cs index 8d7dfa49..c627c522 100644 --- a/Netorrent/TorrentFile/TorrentClientOptions.cs +++ b/Netorrent/TorrentFile/TorrentClientOptions.cs @@ -3,13 +3,37 @@ namespace Netorrent.TorrentFile; +public enum UsedAdressProtocol +{ + /// + /// Create sockets with Dual Mode for both Ipv4 and Ipv6 + /// + /// This is the default option + Dual, + + /// + /// Create sockets with Ipv4 only + /// + Ipv4, + + /// + /// Create sockets with Ipv6 only + /// + Ipv6, +} + /// /// Options for torrent client /// /// Http client used in tracker requests /// Logger used to debug /// Forced ip to use in tracker requests -public record TorrentClientOptions(HttpClient HttpClient, ILogger Logger, IPAddress? ForcedIp) +public record TorrentClientOptions( + HttpClient HttpClient, + ILogger Logger, + UsedAdressProtocol UsedAdressProtocol, + IPAddress? ForcedIp +) { /// /// Only used for testing diff --git a/Netorrent/Tracker/TrackerClient.cs b/Netorrent/Tracker/TrackerClient.cs index 3a4e8cbd..13af3ad8 100644 --- a/Netorrent/Tracker/TrackerClient.cs +++ b/Netorrent/Tracker/TrackerClient.cs @@ -6,14 +6,17 @@ using Netorrent.Extensions; using Netorrent.P2P.Messages; using Netorrent.Statistics; +using Netorrent.TorrentFile; using Netorrent.Tracker.Http; using Netorrent.Tracker.Udp; +using ZLinq; namespace Netorrent.Tracker; internal class TrackerClient( IHttpTrackerHandler httpTrackerHandler, IUdpTrackerTransactionManager trackerTransactionManager, + UsedAdressProtocol usedAdressProtocol, int port, TransferStatistics transferStatistics, PeerId peerId, @@ -91,10 +94,17 @@ private async Task CreateUdpTrackers(Uri uri, CancellationToken ca List udpTrackers = []; var ips = await Dns.GetHostAdressesOrEmptyAsync(uri.Host, cancellationToken) .ConfigureAwait(false); - var ipv4 = ips.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork); - var ipv6 = ips.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetworkV6); - - if (ipv4 != default && uri.Port > 0) + var ipv4 = ips.AsValueEnumerable() + .FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork); + var ipv6 = ips.AsValueEnumerable() + .FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetworkV6); + var supportedAdressFamilies = usedAdressProtocol.ToAddressFamily(); + + if ( + supportedAdressFamilies.Contains(AddressFamily.InterNetwork) + && ipv4 != default + && uri.Port > 0 + ) { var ipEndpoint = new IPEndPoint(ipv4, uri.Port); var trackerv4 = new UdpTracker( @@ -112,7 +122,11 @@ private async Task CreateUdpTrackers(Uri uri, CancellationToken ca udpTrackers.Add(trackerv4); } - if (ipv6 != default && uri.Port > 0) + if ( + supportedAdressFamilies.Contains(AddressFamily.InterNetworkV6) + && ipv6 != default + && uri.Port > 0 + ) { var ipEndpoint = new IPEndPoint(ipv6, uri.Port); var trackerv6 = new UdpTracker(