Browse Source

Merge pull request #7 from HMBSbige/socks5

Bruce Wayne 5 years ago
parent
commit
797950ea8e

+ 109 - 63
NatTypeTester/MainWindow.xaml

@@ -6,7 +6,7 @@
 	xmlns:reactiveUi="http://reactiveui.net"
 	xmlns:viewModels="clr-namespace:NatTypeTester.ViewModels"
 	Title="NatTypeTester"
-	Width="385"
+	Width="600"
 	WindowStartupLocation="CenterScreen"
 	SizeToContent="Height"
 	ResizeMode="CanMinimize"
@@ -41,79 +41,125 @@
 					</ComboBox.ItemTemplate>
 				</ComboBox>
 			</Grid>
-			<TabControl>
-				<TabItem Header="RFC 5780" x:Name="RFC5780Tab">
-					<Grid>
+			<Grid>
+				<Grid.ColumnDefinitions>
+					<ColumnDefinition Width="4*"/>
+					<ColumnDefinition Width="5*" />
+				</Grid.ColumnDefinitions>
+				<Grid Margin="0,0,5,5">
+					<Grid.RowDefinitions>
+						<RowDefinition Height="Auto" />
+						<RowDefinition Height="Auto" />
+					</Grid.RowDefinitions>
+					<Grid Margin="0,0,0,5" Grid.Column="0">
+						<Grid.RowDefinitions>
+							<RowDefinition Height="Auto"/>
+							<RowDefinition Height="Auto"/>
+						</Grid.RowDefinitions>
+						<RadioButton Grid.Row="0" Content="Don't use Proxy" GroupName="ProxyTypeGroup" x:Name="ProxyTypeNoneRadio" Margin="5" IsChecked="True"/>
+						<RadioButton Grid.Row="1" Content="SOCKS5" GroupName="ProxyTypeGroup" x:Name="ProxyTypeSocks5Radio" Margin="5" IsChecked="False" />
+					</Grid>
+					<Grid x:Name="ProxyConfigGrid" Margin="0" Grid.Row="1" IsEnabled="False">
 						<Grid.RowDefinitions>
-							<RowDefinition />
-							<RowDefinition />
-							<RowDefinition />
-							<RowDefinition />
-							<RowDefinition />
-							<RowDefinition />
+							<RowDefinition/>
+							<RowDefinition/>
+							<RowDefinition/>
 						</Grid.RowDefinitions>
 						<Grid.ColumnDefinitions>
-							<ColumnDefinition Width="Auto" />
-							<ColumnDefinition />
-							<ColumnDefinition Width="65" />
+							<ColumnDefinition Width="Auto" MinWidth="100"/>
+							<ColumnDefinition/>
 						</Grid.ColumnDefinitions>
-						<TextBlock Grid.Row="0" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Binding test" />
-						<TextBlock Grid.Row="1" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Mapping behavior" />
-						<TextBlock Grid.Row="2" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Filtering behavior" />
-						<TextBlock Grid.Row="3" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Local end" />
-						<TextBlock Grid.Row="4" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Public end" />
-
-						<TextBox x:Name="BindingTestTextBox" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
-						         Height="23.24" Margin="5" IsReadOnly="True"
-						         VerticalContentAlignment="Center" VerticalAlignment="Center" />
-						<TextBox x:Name="MappingBehaviorTextBox" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
-						         Height="23.24" Margin="5" IsReadOnly="True"
-						         VerticalContentAlignment="Center" VerticalAlignment="Center" />
-						<TextBox x:Name="FilteringBehaviorTextBox" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
-						         Height="23.24" Margin="5" IsReadOnly="True"
+						<TextBox x:Name="ProxyServerTextBox" Grid.Row="0" Grid.Column="1"
+						         Height="23.25" Margin="5" IsReadOnly="False"
 						         VerticalContentAlignment="Center" VerticalAlignment="Center" />
-						<TextBox x:Name="LocalAddressTextBox" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
-						         Height="23.24" Margin="5" IsReadOnly="False"
+						<TextBox x:Name="ProxyUsernameTextBox" Grid.Row="1" Grid.Column="1"
+						         Height="23.25" Margin="5" IsReadOnly="False"
 						         VerticalContentAlignment="Center" VerticalAlignment="Center" />
-						<TextBox x:Name="MappingAddressTextBox" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
-						         Height="23.24" Margin="5" IsReadOnly="True"
+						<TextBox x:Name="ProxyPasswordTextBox" Grid.Row="2" Grid.Column="1"
+						         Height="23.25" Margin="5"
 						         VerticalContentAlignment="Center" VerticalAlignment="Center" />
-
-						<Button x:Name="DiscoveryButton" Grid.Row="5" Grid.Column="2" Content="Test" Margin="5" />
+						<TextBlock Grid.Row="0" Grid.Column="0" Margin="5" VerticalAlignment="Center" Text="Server" />
+						<TextBlock Grid.Row="1" Grid.Column="0" Margin="5" VerticalAlignment="Center" Text="User ID" />
+						<TextBlock Grid.Row="2" Grid.Column="0" Margin="5" VerticalAlignment="Center" Text="Password" />
 					</Grid>
-				</TabItem>
-				<TabItem Header="RFC 3489" x:Name="RFC3489Tab">
-					<Grid>
-						<Grid.RowDefinitions>
-							<RowDefinition />
-							<RowDefinition />
-							<RowDefinition />
-							<RowDefinition />
-						</Grid.RowDefinitions>
-						<Grid.ColumnDefinitions>
-							<ColumnDefinition Width="Auto" />
-							<ColumnDefinition />
-							<ColumnDefinition Width="65" />
-						</Grid.ColumnDefinitions>
+				</Grid>
 
-						<TextBlock Grid.Row="0" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="NAT type" />
-						<TextBlock Grid.Row="1" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Local end" />
-						<TextBlock Grid.Row="2" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Public end" />
+				<TabControl Grid.Column="1" >
+					<TabItem Header="RFC 5780" x:Name="RFC5780Tab">
+						<Grid>
+							<Grid.RowDefinitions>
+								<RowDefinition />
+								<RowDefinition />
+								<RowDefinition />
+								<RowDefinition />
+								<RowDefinition />
+								<RowDefinition />
+							</Grid.RowDefinitions>
+							<Grid.ColumnDefinitions>
+								<ColumnDefinition Width="Auto" />
+								<ColumnDefinition />
+								<ColumnDefinition Width="65" />
+							</Grid.ColumnDefinitions>
+							<TextBlock Grid.Row="0" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Binding test" />
+							<TextBlock Grid.Row="1" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Mapping behavior" />
+							<TextBlock Grid.Row="2" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Filtering behavior" />
+							<TextBlock Grid.Row="3" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Local end" />
+							<TextBlock Grid.Row="4" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Public end" />
 
-						<TextBox x:Name="NatTypeTextBox" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
-						Height="23.24" Margin="5" IsReadOnly="True"
-						VerticalContentAlignment="Center" VerticalAlignment="Center"/>
-						<TextBox x:Name="LocalEndTextBox" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
-						Height="23.24" Margin="5"
-						VerticalContentAlignment="Center" VerticalAlignment="Center" />
-						<TextBox x:Name="PublicEndTextBox" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
-						Height="23.24" Margin="5" IsReadOnly="True"
-						VerticalContentAlignment="Center" VerticalAlignment="Center" />
+							<TextBox x:Name="BindingTestTextBox" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2"
+					         Height="23.24" Margin="5" IsReadOnly="True"
+					         VerticalContentAlignment="Center" VerticalAlignment="Center" />
+							<TextBox x:Name="MappingBehaviorTextBox" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
+					         Height="23.24" Margin="5" IsReadOnly="True"
+					         VerticalContentAlignment="Center" VerticalAlignment="Center" />
+							<TextBox x:Name="FilteringBehaviorTextBox" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
+					         Height="23.24" Margin="5" IsReadOnly="True"
+					         VerticalContentAlignment="Center" VerticalAlignment="Center" />
+							<TextBox x:Name="LocalAddressTextBox" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
+					         Height="23.24" Margin="5" IsReadOnly="False"
+					         VerticalContentAlignment="Center" VerticalAlignment="Center" />
+							<TextBox x:Name="MappingAddressTextBox" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2"
+					         Height="23.24" Margin="5" IsReadOnly="True"
+					         VerticalContentAlignment="Center" VerticalAlignment="Center" />
 
-						<Button x:Name="TestButton" Grid.Row="3" Grid.Column="2" Content="Test" Margin="5"/>
-					</Grid>
-				</TabItem>
-			</TabControl>
+							<Button x:Name="DiscoveryButton" Grid.Row="5" Grid.Column="2" Content="Test" Margin="5" />
+						</Grid>
+					</TabItem>
+					<TabItem Header="RFC 3489" x:Name="RFC3489Tab">
+						<Grid>
+							<Grid.RowDefinitions>
+								<RowDefinition Height="29"/>
+								<RowDefinition />
+								<RowDefinition />
+								<RowDefinition />
+								<RowDefinition />
+							</Grid.RowDefinitions>
+							<Grid.ColumnDefinitions>
+								<ColumnDefinition Width="Auto" />
+								<ColumnDefinition />
+								<ColumnDefinition Width="65" />
+							</Grid.ColumnDefinitions>
+
+							<TextBlock Grid.Row="1" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="NAT type" />
+							<TextBlock Grid.Row="2" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Local end" />
+							<TextBlock Grid.Row="3" Grid.Column="0" Margin="5,0" VerticalAlignment="Center" Text="Public end" />
+
+							<TextBox x:Name="NatTypeTextBox" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
+							Height="23.25" Margin="5" IsReadOnly="True"
+							VerticalContentAlignment="Center" VerticalAlignment="Center"/>
+							<TextBox x:Name="LocalEndTextBox" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
+							Height="23.25" Margin="5"
+							VerticalContentAlignment="Center" VerticalAlignment="Center" />
+							<TextBox x:Name="PublicEndTextBox" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
+							Height="23.25" Margin="5" IsReadOnly="True"
+							VerticalContentAlignment="Center" VerticalAlignment="Center" />
+
+							<Button x:Name="TestButton" Grid.Row="4" Grid.Column="2" Content="Test" Margin="5"/>
+						</Grid>
+					</TabItem>
+				</TabControl>
+
+			</Grid>
 		</StackPanel>
 	</Grid>
 </reactiveUi:ReactiveWindow>

+ 52 - 18
NatTypeTester/MainWindow.xaml.cs

@@ -1,11 +1,12 @@
-using System;
+using NatTypeTester.ViewModels;
+using ReactiveUI;
+using STUN.Enums;
+using System;
 using System.Reactive;
 using System.Reactive.Disposables;
 using System.Reactive.Linq;
 using System.Windows;
 using System.Windows.Input;
-using NatTypeTester.ViewModels;
-using ReactiveUI;
 
 namespace NatTypeTester
 {
@@ -16,19 +17,52 @@ namespace NatTypeTester
             InitializeComponent();
             ViewModel = new MainWindowViewModel();
 
-            this.WhenActivated(disposableRegistration =>
+            this.WhenActivated(d =>
             {
                 #region Server
 
                 this.Bind(ViewModel,
                         vm => vm.StunServer,
                         v => v.ServersComboBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.OneWayBind(ViewModel,
                         vm => vm.StunServers,
                         v => v.ServersComboBox.ItemsSource
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
+
+                #endregion
+
+                #region Proxy
+
+                this.Bind(ViewModel,
+                        vm => vm.ProxyServer,
+                        v => v.ProxyServerTextBox.Text
+                ).DisposeWith(d);
+
+                this.Bind(ViewModel,
+                        vm => vm.ProxyUser,
+                        v => v.ProxyUsernameTextBox.Text
+                ).DisposeWith(d);
+
+                this.Bind(ViewModel,
+                        vm => vm.ProxyPassword,
+                        v => v.ProxyPasswordTextBox.Text
+                ).DisposeWith(d);
+
+                this.WhenAnyValue(x => x.ProxyTypeNoneRadio.IsChecked, x => x.ProxyTypeSocks5Radio.IsChecked)
+                    .Subscribe(values =>
+                    {
+                        ProxyConfigGrid.IsEnabled = !values.Item1.GetValueOrDefault(false);
+                        if (values.Item1.GetValueOrDefault(false))
+                        {
+                            ViewModel.ProxyType = ProxyType.Plain;
+                        }
+                        else if (values.Item2.GetValueOrDefault(false))
+                        {
+                            ViewModel.ProxyType = ProxyType.Socks5;
+                        }
+                    }).DisposeWith(d);
 
                 #endregion
 
@@ -37,27 +71,27 @@ namespace NatTypeTester
                 this.OneWayBind(ViewModel,
                         vm => vm.ClassicNatType,
                         v => v.NatTypeTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.Bind(ViewModel,
                         vm => vm.LocalEnd,
                         v => v.LocalEndTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.OneWayBind(ViewModel,
                         vm => vm.PublicEnd,
                         v => v.PublicEndTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.BindCommand(ViewModel,
                                 viewModel => viewModel.TestClassicNatType,
                                 view => view.TestButton)
-                        .DisposeWith(disposableRegistration);
+                        .DisposeWith(d);
 
                 RFC3489Tab.Events().KeyDown
                         .Where(x => x.Key == Key.Enter && TestButton.IsEnabled)
                         .Subscribe(y => { TestButton.Command.Execute(Unit.Default); })
-                        .DisposeWith(disposableRegistration);
+                        .DisposeWith(d);
 
                 #endregion
 
@@ -66,37 +100,37 @@ namespace NatTypeTester
                 this.OneWayBind(ViewModel,
                         vm => vm.BindingTest,
                         v => v.BindingTestTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.OneWayBind(ViewModel,
                         vm => vm.MappingBehavior,
                         v => v.MappingBehaviorTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.OneWayBind(ViewModel,
                         vm => vm.FilteringBehavior,
                         v => v.FilteringBehaviorTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.Bind(ViewModel,
                         vm => vm.LocalAddress,
                         v => v.LocalAddressTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.OneWayBind(ViewModel,
                         vm => vm.MappingAddress,
                         v => v.MappingAddressTextBox.Text
-                ).DisposeWith(disposableRegistration);
+                ).DisposeWith(d);
 
                 this.BindCommand(ViewModel,
                                 viewModel => viewModel.DiscoveryNatType,
                                 view => view.DiscoveryButton)
-                        .DisposeWith(disposableRegistration);
+                        .DisposeWith(d);
 
                 RFC5780Tab.Events().KeyDown
                         .Where(x => x.Key == Key.Enter && DiscoveryButton.IsEnabled)
                         .Subscribe(y => { DiscoveryButton.Command.Execute(Unit.Default); })
-                        .DisposeWith(disposableRegistration);
+                        .DisposeWith(d);
 
                 #endregion
             });

+ 63 - 14
NatTypeTester/ViewModels/MainWindowViewModel.cs

@@ -1,16 +1,18 @@
-using System;
+using DynamicData;
+using DynamicData.Binding;
+using NatTypeTester.Model;
+using ReactiveUI;
+using STUN.Client;
+using STUN.Enums;
+using STUN.Proxy;
+using STUN.Utils;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reactive;
 using System.Reactive.Linq;
 using System.Windows;
-using DynamicData;
-using DynamicData.Binding;
-using NatTypeTester.Model;
-using ReactiveUI;
-using STUN.Client;
-using STUN.Utils;
 
 namespace NatTypeTester.ViewModels
 {
@@ -107,6 +109,38 @@ namespace NatTypeTester.ViewModels
 
         #endregion
 
+        #region Proxy
+
+        private ProxyType _proxyType = ProxyType.Socks5;
+        public ProxyType ProxyType
+        {
+            get => _proxyType;
+            set => this.RaiseAndSetIfChanged(ref _proxyType, value);
+        }
+
+        private string _proxyServer = @"127.0.0.1:1080";
+        public string ProxyServer
+        {
+            get => _proxyServer;
+            set => this.RaiseAndSetIfChanged(ref _proxyServer, value);
+        }
+
+        private string _proxyUser;
+        public string ProxyUser
+        {
+            get => _proxyUser;
+            set => this.RaiseAndSetIfChanged(ref _proxyUser, value);
+        }
+
+        private string _proxyPassword;
+        public string ProxyPassword
+        {
+            get => _proxyPassword;
+            set => this.RaiseAndSetIfChanged(ref _proxyPassword, value);
+        }
+
+        #endregion
+
         public MainWindowViewModel()
         {
             LoadStunServer();
@@ -129,7 +163,8 @@ namespace NatTypeTester.ViewModels
 
             const string path = @"stun.txt";
 
-            if (!File.Exists(path)) return;
+            if (!File.Exists(path))
+                return;
 
             using var sw = new StreamReader(path);
             string line;
@@ -145,20 +180,27 @@ namespace NatTypeTester.ViewModels
 
         private IObservable<Unit> TestClassicNatTypeImpl()
         {
-            return Observable.Start(() =>
+            return Observable.FromAsync(async () =>
             {
                 try
                 {
                     var server = new StunServer();
                     if (server.Parse(StunServer))
                     {
-                        using var client = new StunClient3489(server.Hostname, server.Port,
-                                NetUtils.ParseEndpoint(LocalEnd));
+                        using var proxy = ProxyFactory.CreateProxy(
+                            ProxyType,
+                            NetUtils.ParseEndpoint(LocalEnd),
+                            NetUtils.ParseEndpoint(ProxyServer),
+                            ProxyUser, ProxyPassword
+                            );
+
+                        using var client = new StunClient3489(server.Hostname, server.Port, NetUtils.ParseEndpoint(LocalEnd), proxy);
+
                         client.NatTypeChanged.ObserveOn(RxApp.MainThreadScheduler)
                                 .Subscribe(t => ClassicNatType = $@"{t}");
                         client.PubChanged.ObserveOn(RxApp.MainThreadScheduler).Subscribe(t => PublicEnd = $@"{t}");
                         client.LocalChanged.ObserveOn(RxApp.MainThreadScheduler).Subscribe(t => LocalEnd = $@"{t}");
-                        client.Query();
+                        await client.Query3489Async();
                     }
                     else
                     {
@@ -169,7 +211,7 @@ namespace NatTypeTester.ViewModels
                 {
                     MessageBox.Show(ex.Message, nameof(NatTypeTester), MessageBoxButton.OK, MessageBoxImage.Error);
                 }
-            });
+            }).SubscribeOn(RxApp.TaskpoolScheduler);
         }
 
         private IObservable<Unit> DiscoveryNatTypeImpl()
@@ -181,7 +223,14 @@ namespace NatTypeTester.ViewModels
                     var server = new StunServer();
                     if (server.Parse(StunServer))
                     {
-                        using var client = new StunClient5389UDP(server.Hostname, server.Port, NetUtils.ParseEndpoint(LocalAddress));
+                        using var proxy = ProxyFactory.CreateProxy(
+                            ProxyType,
+                            NetUtils.ParseEndpoint(LocalEnd),
+                            NetUtils.ParseEndpoint(ProxyServer),
+                            ProxyUser, ProxyPassword
+                            );
+
+                        using var client = new StunClient5389UDP(server.Hostname, server.Port, NetUtils.ParseEndpoint(LocalAddress), proxy);
 
                         client.BindingTestResultChanged
                                 .ObserveOn(RxApp.MainThreadScheduler)

+ 22 - 30
STUN/Client/StunClient3489.cs

@@ -1,15 +1,16 @@
 using STUN.Enums;
 using STUN.Interfaces;
 using STUN.Message;
+using STUN.Proxy;
 using STUN.StunResult;
 using STUN.Utils;
 using System;
 using System.Diagnostics;
 using System.Linq;
 using System.Net;
-using System.Net.Sockets;
 using System.Reactive.Linq;
 using System.Reactive.Subjects;
+using System.Threading.Tasks;
 
 namespace STUN.Client
 {
@@ -32,24 +33,25 @@ namespace STUN.Client
 
         #endregion
 
-        public IPEndPoint LocalEndPoint => (IPEndPoint)UdpClient.Client.LocalEndPoint;
+        public IPEndPoint LocalEndPoint => Proxy.LocalEndPoint;
 
         public TimeSpan Timeout
         {
-            get => TimeSpan.FromMilliseconds(UdpClient.Client.ReceiveTimeout);
-            set => UdpClient.Client.ReceiveTimeout = Convert.ToInt32(value.TotalMilliseconds);
+            get => Proxy.Timeout;
+            set => Proxy.Timeout = value;
         }
 
-        protected readonly UdpClient UdpClient;
-
         protected readonly IPAddress Server;
         protected readonly ushort Port;
 
         public IPEndPoint RemoteEndPoint => Server == null ? null : new IPEndPoint(Server, Port);
 
-        public StunClient3489(string server, ushort port = 3478, IPEndPoint local = null, IDnsQuery dnsQuery = null)
+        protected IUdpProxy Proxy;
+
+        public StunClient3489(string server, ushort port = 3478, IPEndPoint local = null, IUdpProxy proxy = null, IDnsQuery dnsQuery = null)
         {
-            Func<string, IPAddress> dnsQuery1;
+            Proxy = proxy ?? new NoneUdpProxy(local);
+
             if (string.IsNullOrEmpty(server))
             {
                 throw new ArgumentException(@"Please specify STUN server !");
@@ -60,28 +62,19 @@ namespace STUN.Client
                 throw new ArgumentException(@"Port value must be >= 1 !");
             }
 
-            if (dnsQuery != null)
-            {
-                dnsQuery1 = dnsQuery.Query;
-            }
-            else
-            {
-                dnsQuery1 = new DefaultDnsQuery().Query;
-            }
+            dnsQuery ??= new DefaultDnsQuery();
 
-            Server = dnsQuery1(server);
+            Server = dnsQuery.Query(server);
             if (Server == null)
             {
                 throw new ArgumentException(@"Wrong STUN server !");
             }
             Port = port;
 
-            UdpClient = local == null ? new UdpClient() : new UdpClient(local);
-
             Timeout = TimeSpan.FromSeconds(1.6);
         }
 
-        public ClassicStunResult Query()
+        public async Task<ClassicStunResult> Query3489Async()
         {
             var res = new ClassicStunResult();
             _natTypeSubj.OnNext(res.NatType);
@@ -89,10 +82,11 @@ namespace STUN.Client
 
             try
             {
+                await Proxy.ConnectAsync();
                 // test I
                 var test1 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
 
-                var (response1, remote1, local1) = Test(test1, RemoteEndPoint, RemoteEndPoint);
+                var (response1, remote1, local1) = await TestAsync(test1, RemoteEndPoint, RemoteEndPoint);
                 if (response1 == null)
                 {
                     res.NatType = NatType.UdpBlocked;
@@ -127,7 +121,7 @@ namespace STUN.Client
                 };
 
                 // test II
-                var (response2, remote2, _) = Test(test2, RemoteEndPoint, changedAddress1);
+                var (response2, remote2, _) = await TestAsync(test2, RemoteEndPoint, changedAddress1);
                 var mappedAddress2 = AttributeExtensions.GetMappedAddressAttribute(response2);
 
                 if (Equals(mappedAddress1.Address, local1) && mappedAddress1.Port == LocalEndPoint.Port)
@@ -156,7 +150,7 @@ namespace STUN.Client
 
                 // Test I(#2)
                 var test12 = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest, MagicCookie = 0 };
-                var (response12, _, _) = Test(test12, changedAddress1, changedAddress1);
+                var (response12, _, _) = await TestAsync(test12, changedAddress1, changedAddress1);
                 var mappedAddress12 = AttributeExtensions.GetMappedAddressAttribute(response12);
 
                 if (mappedAddress12 == null)
@@ -179,7 +173,7 @@ namespace STUN.Client
                     MagicCookie = 0,
                     Attributes = new[] { AttributeExtensions.BuildChangeRequest(false, true) }
                 };
-                var (response3, _, _) = Test(test3, changedAddress1, changedAddress1);
+                var (response3, _, _) = await TestAsync(test3, changedAddress1, changedAddress1);
                 var mappedAddress3 = AttributeExtensions.GetMappedAddressAttribute(response3);
                 if (mappedAddress3 != null)
                 {
@@ -193,15 +187,13 @@ namespace STUN.Client
             }
             finally
             {
+                await Proxy.DisconnectAsync();
                 _natTypeSubj.OnNext(res.NatType);
                 PubSubj.OnNext(res.PublicEndPoint);
             }
         }
 
-        /// <returns>
-        /// (StunMessage, Remote, Local)
-        /// </returns>
-        private (StunMessage5389, IPEndPoint, IPAddress) Test(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive)
+        protected async Task<(StunMessage5389, IPEndPoint, IPAddress)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive)
         {
             try
             {
@@ -214,7 +206,7 @@ namespace STUN.Client
                 {
                     try
                     {
-                        var (receive1, ipe, local) = UdpClient.UdpReceive(b1, remote, receive);
+                        var (receive1, ipe, local) = await Proxy.ReceiveAsync(b1, remote, receive);
 
                         var message = new StunMessage5389();
                         if (message.TryParse(receive1) &&
@@ -238,7 +230,7 @@ namespace STUN.Client
 
         public virtual void Dispose()
         {
-            UdpClient?.Dispose();
+            Proxy?.Dispose();
             _natTypeSubj.OnCompleted();
             PubSubj.OnCompleted();
             LocalSubj.OnCompleted();

+ 62 - 73
STUN/Client/StunClient5389UDP.cs

@@ -4,8 +4,6 @@ using STUN.Message;
 using STUN.StunResult;
 using STUN.Utils;
 using System;
-using System.Diagnostics;
-using System.Linq;
 using System.Net;
 using System.Reactive.Linq;
 using System.Reactive.Subjects;
@@ -32,8 +30,8 @@ namespace STUN.Client
 
         #endregion
 
-        public StunClient5389UDP(string server, ushort port = 3478, IPEndPoint local = null, IDnsQuery dnsQuery = null)
-        : base(server, port, local, dnsQuery)
+        public StunClient5389UDP(string server, ushort port = 3478, IPEndPoint local = null, IUdpProxy proxy = null, IDnsQuery dnsQuery = null)
+        : base(server, port, local, proxy, dnsQuery)
         {
             Timeout = TimeSpan.FromSeconds(3);
         }
@@ -41,21 +39,23 @@ namespace STUN.Client
         public async Task<StunResult5389> QueryAsync()
         {
             var result = new StunResult5389();
-            _bindingSubj.OnNext(result.BindingTestResult);
-            _mappingBehaviorSubj.OnNext(result.MappingBehavior);
-            _filteringBehaviorSubj.OnNext(result.FilteringBehavior);
-            PubSubj.OnNext(result.PublicEndPoint);
-
-            result = await FilteringBehaviorTestAsync();
-            if (result.BindingTestResult != BindingTestResult.Success
-            || result.FilteringBehavior == FilteringBehavior.UnsupportedServer
-            )
-            {
-                return result;
-            }
-
             try
             {
+                _bindingSubj.OnNext(result.BindingTestResult);
+                _mappingBehaviorSubj.OnNext(result.MappingBehavior);
+                _filteringBehaviorSubj.OnNext(result.FilteringBehavior);
+                PubSubj.OnNext(result.PublicEndPoint);
+
+                await Proxy.ConnectAsync();
+
+                result = await FilteringBehaviorTestBaseAsync();
+                if (result.BindingTestResult != BindingTestResult.Success
+                || result.FilteringBehavior == FilteringBehavior.UnsupportedServer
+                )
+                {
+                    return result;
+                }
+
                 if (Equals(result.PublicEndPoint, result.LocalEndPoint))
                 {
                     result.MappingBehavior = MappingBehavior.Direct;
@@ -91,19 +91,27 @@ namespace STUN.Client
             finally
             {
                 _mappingBehaviorSubj.OnNext(result.MappingBehavior);
+                await Proxy.DisconnectAsync();
             }
         }
 
         public async Task<StunResult5389> BindingTestAsync()
         {
-            var result = await BindingTestBaseAsync(RemoteEndPoint, true);
-            return result;
+            try
+            {
+                await Proxy.ConnectAsync();
+                var result = await BindingTestBaseAsync(RemoteEndPoint, true);
+                return result;
+            }
+            finally
+            {
+                await Proxy.DisconnectAsync();
+            }
         }
 
         private async Task<StunResult5389> BindingTestBaseAsync(IPEndPoint remote, bool notifyChanged)
         {
             BindingTestResult res;
-
             var test = new StunMessage5389 { StunMessageType = StunMessageType.BindingRequest };
             var (response1, _, local1) = await TestAsync(test, remote, remote);
             var mappedAddress1 = AttributeExtensions.GetXorMappedAddressAttribute(response1);
@@ -141,62 +149,65 @@ namespace STUN.Client
 
         public async Task<StunResult5389> MappingBehaviorTestAsync()
         {
-            // test I
-            var result1 = await BindingTestBaseAsync(RemoteEndPoint, true);
+            var result = new StunResult5389();
             try
             {
-                if (result1.BindingTestResult != BindingTestResult.Success)
+                await Proxy.ConnectAsync();
+                // test I
+                result = await BindingTestBaseAsync(RemoteEndPoint, true);
+                if (result.BindingTestResult != BindingTestResult.Success)
                 {
-                    return result1;
+                    return result;
                 }
 
-                if (result1.OtherEndPoint == null
-                    || Equals(result1.OtherEndPoint.Address, RemoteEndPoint.Address)
-                    || result1.OtherEndPoint.Port == RemoteEndPoint.Port)
+                if (result.OtherEndPoint == null
+                    || Equals(result.OtherEndPoint.Address, RemoteEndPoint.Address)
+                    || result.OtherEndPoint.Port == RemoteEndPoint.Port)
                 {
-                    result1.MappingBehavior = MappingBehavior.UnsupportedServer;
-                    return result1;
+                    result.MappingBehavior = MappingBehavior.UnsupportedServer;
+                    return result;
                 }
 
-                if (Equals(result1.PublicEndPoint, result1.LocalEndPoint))
+                if (Equals(result.PublicEndPoint, result.LocalEndPoint))
                 {
-                    result1.MappingBehavior = MappingBehavior.Direct;
-                    return result1;
+                    result.MappingBehavior = MappingBehavior.Direct;
+                    return result;
                 }
 
                 // test II
-                var result2 = await BindingTestBaseAsync(new IPEndPoint(result1.OtherEndPoint.Address, RemoteEndPoint.Port), false);
+                var result2 = await BindingTestBaseAsync(new IPEndPoint(result.OtherEndPoint.Address, RemoteEndPoint.Port), false);
                 if (result2.BindingTestResult != BindingTestResult.Success)
                 {
-                    result1.MappingBehavior = MappingBehavior.Fail;
-                    return result1;
+                    result.MappingBehavior = MappingBehavior.Fail;
+                    return result;
                 }
 
-                if (Equals(result2.PublicEndPoint, result1.PublicEndPoint))
+                if (Equals(result2.PublicEndPoint, result.PublicEndPoint))
                 {
-                    result1.MappingBehavior = MappingBehavior.EndpointIndependent;
-                    return result1;
+                    result.MappingBehavior = MappingBehavior.EndpointIndependent;
+                    return result;
                 }
 
                 // test III
-                var result3 = await BindingTestBaseAsync(result1.OtherEndPoint, false);
+                var result3 = await BindingTestBaseAsync(result.OtherEndPoint, false);
                 if (result3.BindingTestResult != BindingTestResult.Success)
                 {
-                    result1.MappingBehavior = MappingBehavior.Fail;
-                    return result1;
+                    result.MappingBehavior = MappingBehavior.Fail;
+                    return result;
                 }
 
-                result1.MappingBehavior = Equals(result3.PublicEndPoint, result2.PublicEndPoint) ? MappingBehavior.AddressDependent : MappingBehavior.AddressAndPortDependent;
+                result.MappingBehavior = Equals(result3.PublicEndPoint, result2.PublicEndPoint) ? MappingBehavior.AddressDependent : MappingBehavior.AddressAndPortDependent;
 
-                return result1;
+                return result;
             }
             finally
             {
-                _mappingBehaviorSubj.OnNext(result1.MappingBehavior);
+                _mappingBehaviorSubj.OnNext(result.MappingBehavior);
+                await Proxy.DisconnectAsync();
             }
         }
 
-        public async Task<StunResult5389> FilteringBehaviorTestAsync()
+        private async Task<StunResult5389> FilteringBehaviorTestBaseAsync()
         {
             // test I
             var result1 = await BindingTestBaseAsync(RemoteEndPoint, true);
@@ -259,40 +270,18 @@ namespace STUN.Client
             }
         }
 
-        private async Task<(StunMessage5389, IPEndPoint, IPAddress)> TestAsync(StunMessage5389 sendMessage, IPEndPoint remote, IPEndPoint receive)
+        public async Task<StunResult5389> FilteringBehaviorTestAsync()
         {
             try
             {
-                var b1 = sendMessage.Bytes.ToArray();
-                //var t = DateTime.Now;
-
-                // Simple retransmissions
-                //https://tools.ietf.org/html/rfc5389#section-7.2.1
-                //while (t + TimeSpan.FromSeconds(6) > DateTime.Now)
-                {
-                    try
-                    {
-
-                        var (receive1, ipe, local) = await UdpClient.UdpReceiveAsync(b1, remote, receive);
-
-                        var message = new StunMessage5389();
-                        if (message.TryParse(receive1) &&
-                            message.ClassicTransactionId.IsEqual(sendMessage.ClassicTransactionId))
-                        {
-                            return (message, ipe, local);
-                        }
-                    }
-                    catch (Exception ex)
-                    {
-                        Debug.WriteLine(ex);
-                    }
-                }
+                await Proxy.ConnectAsync();
+                var result = await FilteringBehaviorTestBaseAsync();
+                return result;
             }
-            catch (Exception ex)
+            finally
             {
-                Debug.WriteLine(ex);
+                await Proxy.DisconnectAsync();
             }
-            return (null, null, null);
         }
 
         public override void Dispose()

+ 8 - 0
STUN/Enums/ProxyType.cs

@@ -0,0 +1,8 @@
+namespace STUN.Enums
+{
+    public enum ProxyType
+    {
+        Plain,
+        Socks5,
+    }
+}

+ 12 - 0
STUN/Enums/TransportType.cs

@@ -0,0 +1,12 @@
+namespace STUN.Enums
+{
+    // Only UDP is supported
+
+    public enum TransportType
+    {
+        Udp,
+        Tcp,
+        Tls,
+        Dtls,
+    }
+}

+ 16 - 0
STUN/Interfaces/IUdpProxy.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace STUN.Interfaces
+{
+    public interface IUdpProxy : IDisposable
+    {
+        TimeSpan Timeout { get; set; }
+        IPEndPoint LocalEndPoint { get; }
+        Task ConnectAsync(CancellationToken token = default);
+        Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(byte[] bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default);
+        Task DisconnectAsync(CancellationToken token = default);
+    }
+}

+ 67 - 0
STUN/Proxy/NoneUdpProxy.cs

@@ -0,0 +1,67 @@
+using STUN.Interfaces;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace STUN.Proxy
+{
+    public class NoneUdpProxy : IUdpProxy
+    {
+        public TimeSpan Timeout
+        {
+            get => TimeSpan.FromMilliseconds(UdpClient.Client.ReceiveTimeout);
+            set => UdpClient.Client.ReceiveTimeout = Convert.ToInt32(value.TotalMilliseconds);
+        }
+
+        public IPEndPoint LocalEndPoint => (IPEndPoint)UdpClient.Client.LocalEndPoint;
+
+        protected UdpClient UdpClient;
+
+        public NoneUdpProxy(IPEndPoint local)
+        {
+            UdpClient = local == null ? new UdpClient() : new UdpClient(local);
+        }
+
+        public Task ConnectAsync(CancellationToken token = default)
+        {
+            return Task.CompletedTask;
+        }
+
+        public Task DisconnectAsync(CancellationToken token = default)
+        {
+            UdpClient.Close();
+            return Task.CompletedTask;
+        }
+
+        public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(byte[] bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
+        {
+            var localEndPoint = (IPEndPoint)UdpClient.Client.LocalEndPoint;
+
+            Debug.WriteLine($@"{localEndPoint} => {remote} {bytes.Length} 字节");
+
+            await UdpClient.SendAsync(bytes, bytes.Length, remote);
+
+            var res = new byte[ushort.MaxValue];
+            var flag = SocketFlags.None;
+
+            var length = UdpClient.Client.ReceiveMessageFrom(res, 0, res.Length, ref flag, ref receive, out var ipPacketInformation);
+
+            var local = ipPacketInformation.Address;
+
+            Debug.WriteLine($@"{(IPEndPoint)receive} => {local} {length} 字节");
+
+            return (res.Take(length).ToArray(),
+                    (IPEndPoint)receive
+                    , local);
+        }
+
+        public void Dispose()
+        {
+            UdpClient?.Dispose();
+        }
+    }
+}

+ 20 - 0
STUN/Proxy/ProxyFactory.cs

@@ -0,0 +1,20 @@
+using STUN.Enums;
+using STUN.Interfaces;
+using System;
+using System.Net;
+
+namespace STUN.Proxy
+{
+    public static class ProxyFactory
+    {
+        public static IUdpProxy CreateProxy(ProxyType type, IPEndPoint local, IPEndPoint proxy, string user = null, string password = null)
+        {
+            return type switch
+            {
+                ProxyType.Plain => new NoneUdpProxy(local),
+                ProxyType.Socks5 => new Socks5UdpProxy(local, proxy, user, password),
+                _ => throw new NotSupportedException(type.ToString())
+            };
+        }
+    }
+}

+ 218 - 0
STUN/Proxy/Socks5UdpProxy.cs

@@ -0,0 +1,218 @@
+using STUN.Interfaces;
+using STUN.Utils;
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace STUN.Proxy
+{
+    public class Socks5UdpProxy : IUdpProxy
+    {
+        private readonly TcpClient _assoc;
+        private readonly IPEndPoint _socksTcpEndPoint;
+
+        private IPEndPoint _assocEndPoint;
+
+        public TimeSpan Timeout
+        {
+            get => TimeSpan.FromMilliseconds(_udpClient.Client.ReceiveTimeout);
+            set => _udpClient.Client.ReceiveTimeout = Convert.ToInt32(value.TotalMilliseconds);
+        }
+
+        public IPEndPoint LocalEndPoint => (IPEndPoint)_udpClient.Client.LocalEndPoint;
+
+        private readonly UdpClient _udpClient;
+
+        private readonly string _user;
+        private readonly string _password;
+
+        public Socks5UdpProxy(IPEndPoint local, IPEndPoint proxy)
+        {
+            _udpClient = local == null ? new UdpClient() : new UdpClient(local);
+            _assoc = new TcpClient(proxy.AddressFamily);
+            _socksTcpEndPoint = proxy;
+        }
+
+        public Socks5UdpProxy(IPEndPoint local, IPEndPoint proxy, string user, string password) : this(local, proxy)
+        {
+            _user = user;
+            _password = password;
+        }
+
+        public async Task ConnectAsync(CancellationToken token = default)
+        {
+            try
+            {
+                var buf = new byte[1024];
+                await _assoc.ConnectAsync(_socksTcpEndPoint.Address, _socksTcpEndPoint.Port);
+                var s = _assoc.GetStream();
+                var requestPasswordAuth = !string.IsNullOrEmpty(_user);
+
+                #region Handshake
+                // we have no GSS-API support
+                var handShake = requestPasswordAuth ? new byte[] { 5, 2, 0, 2 } : new byte[] { 5, 1, 0 };
+                await s.WriteAsync(handShake, 0, handShake.Length, token);
+
+                // 5 auth(ff=deny)
+                if (await s.ReadAsync(buf, 0, 2, token) != 2)
+                    throw new ProtocolViolationException();
+                if (buf[0] != 5)
+                    throw new ProtocolViolationException();
+                #endregion
+
+                #region Auth
+                var auth = buf[1];
+                switch (auth)
+                {
+                    case 0:
+                        break;
+                    case 2:
+                        var uByte = Encoding.UTF8.GetBytes(_user);
+                        var pByte = Encoding.UTF8.GetBytes(_password);
+                        buf[0] = 1;
+                        buf[1] = (byte)uByte.Length;
+                        Array.Copy(uByte, 0, buf, 2, uByte.Length);
+                        buf[uByte.Length + 2] = (byte)pByte.Length;
+                        Array.Copy(pByte, 0, buf, uByte.Length + 3, pByte.Length);
+                        // 1 userLen user passLen pass
+                        await s.WriteAsync(buf, 0, uByte.Length + pByte.Length + 3, token);
+                        // 1 state(0=ok)
+                        if (await s.ReadAsync(buf, 0, 2, token) != 2)
+                            throw new ProtocolViolationException();
+                        if (buf[0] != 1)
+                            throw new ProtocolViolationException();
+                        if (buf[1] != 0)
+                            throw new UnauthorizedAccessException();
+                        break;
+                    case 0xff:
+                        throw new UnauthorizedAccessException();
+                    default:
+                        throw new ProtocolViolationException();
+                }
+                #endregion
+
+                #region UDP Assoc Send
+                buf[0] = 5;
+                buf[1] = 3;
+                buf[2] = 0;
+
+                var abyte = GetEndPointByte(new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort));
+                var addrLen = abyte.Length;
+                Array.Copy(abyte, 0, buf, 3, addrLen);
+                // 5 cmd(3=udpassoc) 0 atyp(1=v4 3=dns 4=v5) addr port
+                await s.WriteAsync(buf, 0, addrLen + 3, token);
+                #endregion
+
+                #region UDP Assoc Response
+                if (await s.ReadAsync(buf, 0, 4, token) != 4)
+                    throw new ProtocolViolationException();
+                if (buf[0] != 5 || buf[2] != 0)
+                    throw new ProtocolViolationException();
+                if (buf[1] != 0)
+                    throw new UnauthorizedAccessException();
+
+                addrLen = GetAddressLength(buf[3]);
+
+                var addr = new byte[addrLen];
+                if (await s.ReadAsync(addr, 0, addrLen, token) != addrLen)
+                    throw new ProtocolViolationException();
+                var assocIP = new IPAddress(addr);
+                if (await s.ReadAsync(buf, 0, 2, token) != 2)
+                    throw new ProtocolViolationException();
+                var assocPort = buf[0] * 256 + buf[1];
+                #endregion
+
+                _assocEndPoint = new IPEndPoint(assocIP, assocPort);
+            }
+            catch (Exception e)
+            {
+                Debug.WriteLine(e);
+                await DisconnectAsync(token);
+                throw;
+            }
+        }
+
+        public async Task<(byte[], IPEndPoint, IPAddress)> ReceiveAsync(byte[] bytes, IPEndPoint remote, EndPoint receive, CancellationToken token = default)
+        {
+            var state = _assoc.GetState();
+            if (state != TcpState.Established)
+                throw new InvalidOperationException("No UDP association, maybe already disconnected or not connected");
+
+            var remoteBytes = GetEndPointByte(remote);
+            var proxyBytes = new byte[bytes.Length + remoteBytes.Length + 3];
+            Array.Copy(remoteBytes, 0, proxyBytes, 3, remoteBytes.Length);
+            Array.Copy(bytes, 0, proxyBytes, remoteBytes.Length + 3, bytes.Length);
+
+            await _udpClient.SendAsync(proxyBytes, proxyBytes.Length, _assocEndPoint);
+            var res = new byte[ushort.MaxValue];
+            var flag = SocketFlags.None;
+            EndPoint ep = new IPEndPoint(0, 0);
+            var length = _udpClient.Client.ReceiveMessageFrom(res, 0, res.Length, ref flag, ref ep, out var ipPacketInformation);
+
+            if (res[0] != 0 || res[1] != 0 || res[2] != 0)
+            {
+                throw new Exception();
+            }
+
+            var addressLen = GetAddressLength(res[3]);
+
+            var ipByte = new byte[addressLen];
+            Array.Copy(res, 4, ipByte, 0, addressLen);
+
+            var ip = new IPAddress(ipByte);
+            var port = res[addressLen + 4] * 256 + res[addressLen + 5];
+            var ret = new byte[length - addressLen - 6];
+            Array.Copy(res, addressLen + 6, ret, 0, length - addressLen - 6);
+            return (
+                ret,
+                new IPEndPoint(ip, port),
+                ipPacketInformation.Address);
+        }
+
+        public Task DisconnectAsync(CancellationToken token = default)
+        {
+            try
+            {
+                _assoc.Close();
+            }
+            catch
+            {
+                // ignored
+            }
+
+            return Task.CompletedTask;
+        }
+
+        private static byte[] GetEndPointByte(IPEndPoint ep)
+        {
+            var ipByte = ep.Address.GetAddressBytes();
+            var ret = new byte[ipByte.Length + 3];
+            ret[0] = (byte)(ipByte.Length == 4 ? 1 : 4);
+            Array.Copy(ipByte, 0, ret, 1, ipByte.Length);
+            ret[ipByte.Length + 1] = (byte)(ep.Port / 256);
+            ret[ipByte.Length + 2] = (byte)(ep.Port % 256);
+            return ret;
+        }
+
+        private static int GetAddressLength(byte b)
+        {
+            return b switch
+            {
+                1 => 4,
+                4 => 16,
+                _ => throw new NotSupportedException()
+            };
+        }
+
+        public void Dispose()
+        {
+            _udpClient?.Dispose();
+            _assoc?.Dispose();
+        }
+    }
+}

+ 7 - 70
STUN/Utils/NetUtils.cs

@@ -1,9 +1,8 @@
 using STUN.Client;
 using STUN.StunResult;
-using System;
-using System.Diagnostics;
 using System.Linq;
 using System.Net;
+using System.Net.NetworkInformation;
 using System.Net.Sockets;
 using System.Threading.Tasks;
 
@@ -42,81 +41,19 @@ namespace STUN.Utils
             return null;
         }
 
-        public static (string, string, string) NatTypeTestCore(string local, string server, ushort port)
-        {
-            try
-            {
-                if (string.IsNullOrWhiteSpace(server))
-                {
-                    Debug.WriteLine(@"[ERROR]: Please specify STUN server !");
-                    return (string.Empty, DefaultLocalEnd, string.Empty);
-                }
-
-                using var client = new StunClient3489(server, port, ParseEndpoint(local));
-
-                var result = client.Query();
-
-                return (
-                        result.NatType.ToString(),
-                        $@"{client.LocalEndPoint}",
-                        $@"{result.PublicEndPoint}"
-                );
-            }
-            catch (Exception ex)
-            {
-                Debug.WriteLine($@"[ERROR]: {ex}");
-                return (string.Empty, DefaultLocalEnd, string.Empty);
-            }
-        }
-
         public static async Task<StunResult5389> NatBehaviorDiscovery(string server, ushort port, IPEndPoint local)
         {
+            // proxy is not supported yet
             using var client = new StunClient5389UDP(server, port, local);
             return await client.QueryAsync();
         }
 
-        public static (byte[], IPEndPoint, IPAddress) UdpReceive(this UdpClient client, byte[] bytes, IPEndPoint remote, EndPoint receive)
+        public static TcpState GetState(this TcpClient tcpClient)
         {
-            var localEndPoint = (IPEndPoint)client.Client.LocalEndPoint;
-
-            Debug.WriteLine($@"{localEndPoint} => {remote} {bytes.Length} 字节");
-
-            client.Send(bytes, bytes.Length, remote);
-
-            var res = new byte[ushort.MaxValue];
-            var flag = SocketFlags.None;
-
-            var length = client.Client.ReceiveMessageFrom(res, 0, res.Length, ref flag, ref receive, out var ipPacketInformation);
-
-            var local = ipPacketInformation.Address;
-
-            Debug.WriteLine($@"{(IPEndPoint)receive} => {local} {length} 字节");
-
-            return (res.Take(length).ToArray(),
-                    (IPEndPoint)receive
-                    , local);
-        }
-
-        public static async Task<(byte[], IPEndPoint, IPAddress)> UdpReceiveAsync(this UdpClient client, byte[] bytes, IPEndPoint remote, EndPoint receive)
-        {
-            var localEndPoint = (IPEndPoint)client.Client.LocalEndPoint;
-
-            Debug.WriteLine($@"{localEndPoint} => {remote} {bytes.Length} 字节");
-
-            await client.SendAsync(bytes, bytes.Length, remote);
-
-            var res = new byte[ushort.MaxValue];
-            var flag = SocketFlags.None;
-
-            var length = client.Client.ReceiveMessageFrom(res, 0, res.Length, ref flag, ref receive, out var ipPacketInformation);
-
-            var local = ipPacketInformation.Address;
-
-            Debug.WriteLine($@"{(IPEndPoint)receive} => {local} {length} 字节");
-
-            return (res.Take(length).ToArray(),
-                    (IPEndPoint)receive
-                    , local);
+            var foo = IPGlobalProperties.GetIPGlobalProperties()
+              .GetActiveTcpConnections()
+              .SingleOrDefault(x => x.LocalEndPoint.Equals(tcpClient.Client.LocalEndPoint));
+            return foo?.State ?? TcpState.Unknown;
         }
     }
 }

+ 31 - 1
UnitTest/UnitTest.cs

@@ -2,10 +2,12 @@
 using STUN.Client;
 using STUN.Enums;
 using STUN.Message.Attributes;
+using STUN.Proxy;
+using STUN.Utils;
+using System;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
-using STUN.Utils;
 
 namespace UnitTest
 {
@@ -153,5 +155,33 @@ namespace UnitTest
                           || result.FilteringBehavior == FilteringBehavior.AddressAndPortDependent
             );
         }
+
+        [TestMethod]
+        public async Task ProxyTest()
+        {
+            using var proxy = ProxyFactory.CreateProxy(ProxyType.Socks5, IPEndPoint.Parse(@"0.0.0.0:0"), IPEndPoint.Parse(@"127.0.0.1:10000"));
+            using var client = new StunClient5389UDP(@"stun.syncthing.net", 3478, new IPEndPoint(IPAddress.Any, 0), proxy);
+            var result = await client.QueryAsync();
+
+            Assert.AreEqual(result.BindingTestResult, BindingTestResult.Success);
+            Assert.IsNotNull(result.LocalEndPoint);
+            Assert.IsNotNull(result.PublicEndPoint);
+            Assert.IsNotNull(result.OtherEndPoint);
+            Assert.AreNotEqual(result.LocalEndPoint.Address, IPAddress.Any);
+            Assert.IsTrue(result.MappingBehavior == MappingBehavior.Direct ||
+                          result.MappingBehavior == MappingBehavior.EndpointIndependent ||
+                          result.MappingBehavior == MappingBehavior.AddressDependent ||
+                          result.MappingBehavior == MappingBehavior.AddressAndPortDependent);
+            Assert.IsTrue(result.FilteringBehavior == FilteringBehavior.EndpointIndependent ||
+                          result.FilteringBehavior == FilteringBehavior.AddressDependent ||
+                          result.FilteringBehavior == FilteringBehavior.AddressAndPortDependent);
+
+            Console.WriteLine(result.BindingTestResult);
+            Console.WriteLine(result.MappingBehavior);
+            Console.WriteLine(result.FilteringBehavior);
+            Console.WriteLine(result.OtherEndPoint);
+            Console.WriteLine(result.LocalEndPoint);
+            Console.WriteLine(result.PublicEndPoint);
+        }
     }
 }