Browse Source

🌟 Project layout changes

- Shadowsocks:          some fundamental stuff
- Shadowsocks.Net:      networking & cryptography library
- Shadowsocks.PAC:      garbage
- Shadowsocks.Protobuf: geosite + geoip parsing
- Shadowsocks.WPF:      WPF GUI
pull/2897/head
database64128 3 years ago
parent
commit
03afd5079f
No known key found for this signature in database GPG Key ID: 1CA27546BEDB8B01
100 changed files with 3144 additions and 2983 deletions
  1. +0
    -9
      Shadowsocks.Common/Models/IoCManager.cs
  2. +0
    -17
      Shadowsocks.Common/Shadowsocks.Common.csproj
  3. +0
    -18
      Shadowsocks.Crypto/Shadowsocks.Crypto.csproj
  4. +2
    -2
      Shadowsocks.Net/CachedNetworkStream.cs
  5. +1
    -1
      Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs
  6. +3
    -4
      Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs
  7. +3
    -6
      Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs
  8. +3
    -4
      Shadowsocks.Net/Crypto/AEAD/AEADNaClCrypto.cs
  9. +1
    -1
      Shadowsocks.Net/Crypto/CipherInfo.cs
  10. +1
    -1
      Shadowsocks.Net/Crypto/CryptoBase.cs
  11. +3
    -3
      Shadowsocks.Net/Crypto/CryptoFactory.cs
  12. +1
    -1
      Shadowsocks.Net/Crypto/CryptoUtils.cs
  13. +1
    -1
      Shadowsocks.Net/Crypto/Exception/CryptoException.cs
  14. +1
    -1
      Shadowsocks.Net/Crypto/ICrypto.cs
  15. +1
    -1
      Shadowsocks.Net/Crypto/RNG.cs
  16. +0
    -0
      Shadowsocks.Net/Crypto/Stream/ExtendedCfbBlockCipher.cs
  17. +1
    -1
      Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs
  18. +1
    -1
      Shadowsocks.Net/Crypto/Stream/StreamChachaNaClCrypto.cs
  19. +1
    -3
      Shadowsocks.Net/Crypto/Stream/StreamCrypto.cs
  20. +1
    -1
      Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs
  21. +1
    -1
      Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs
  22. +2
    -2
      Shadowsocks.Net/Crypto/TCPInfo.cs
  23. +2
    -3
      Shadowsocks.Net/Proxy/DirectConnect.cs
  24. +115
    -117
      Shadowsocks.Net/Proxy/HttpProxy.cs
  25. +2
    -3
      Shadowsocks.Net/Proxy/IProxy.cs
  26. +2
    -2
      Shadowsocks.Net/Proxy/LineReader.cs
  27. +8
    -14
      Shadowsocks.Net/Proxy/Socks5Proxy.cs
  28. +12
    -0
      Shadowsocks.Net/Shadowsocks.Net.csproj
  29. +4
    -4
      Shadowsocks.Net/SystemProxy/ProxyException.cs
  30. +2
    -3
      Shadowsocks.Net/TCPListener.cs
  31. +611
    -611
      Shadowsocks.Net/TCPRelay.cs
  32. +0
    -0
      Shadowsocks.Net/UDPListener.cs
  33. +230
    -232
      Shadowsocks.Net/UDPRelay.cs
  34. +3
    -3
      Shadowsocks.PAC/GeositeUpdater.cs
  35. +133
    -133
      Shadowsocks.PAC/PACDaemon.cs
  36. +209
    -210
      Shadowsocks.PAC/PACServer.cs
  37. +0
    -0
      Shadowsocks.PAC/Resources/abp.js
  38. +0
    -0
      Shadowsocks.PAC/Resources/dlc.dat
  39. +0
    -0
      Shadowsocks.PAC/Resources/user-rule.txt
  40. +12
    -0
      Shadowsocks.PAC/Shadowsocks.PAC.csproj
  41. +0
    -0
      Shadowsocks.Protobuf/Geosite.cs
  42. +11
    -0
      Shadowsocks.Protobuf/Shadowsocks.Protobuf.csproj
  43. +0
    -0
      Shadowsocks.Protobuf/geosite.proto
  44. +2
    -2
      Shadowsocks.WPF/App.xaml
  45. +153
    -153
      Shadowsocks.WPF/Behaviors/AutoStartup.cs
  46. +68
    -68
      Shadowsocks.WPF/Behaviors/FileManager.cs
  47. +3
    -5
      Shadowsocks.WPF/Behaviors/HotkeyReg.cs
  48. +0
    -0
      Shadowsocks.WPF/Behaviors/Hotkeys/HotkeyCallbacks.cs
  49. +0
    -0
      Shadowsocks.WPF/Behaviors/Hotkeys/Hotkeys.cs
  50. +3
    -3
      Shadowsocks.WPF/Behaviors/IPCService.cs
  51. +1
    -1
      Shadowsocks.WPF/Behaviors/LoggerExtension.cs
  52. +3
    -5
      Shadowsocks.WPF/Behaviors/OnlineConfigResolver.cs
  53. +2
    -7
      Shadowsocks.WPF/Behaviors/ProtocolHandler.cs
  54. +68
    -69
      Shadowsocks.WPF/Behaviors/SystemProxy.cs
  55. +125
    -0
      Shadowsocks.WPF/Behaviors/Utilities.cs
  56. +1
    -4
      Shadowsocks.WPF/Localization/LocalizationProvider.cs
  57. +2
    -2
      Shadowsocks.WPF/Models/ForwardProxyConfig.cs
  58. +32
    -32
      Shadowsocks.WPF/Models/HotKeyConfig.cs
  59. +137
    -137
      Shadowsocks.WPF/Models/NlogConfig.cs
  60. +252
    -252
      Shadowsocks.WPF/Models/Server.cs
  61. +16
    -0
      Shadowsocks.WPF/Models/Settings.cs
  62. +0
    -0
      Shadowsocks.WPF/Resources/NLog.config
  63. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Bold.ttf
  64. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-BoldItalic.ttf
  65. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLight.ttf
  66. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLightItalic.ttf
  67. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Italic.ttf
  68. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Light.ttf
  69. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-LightItalic.ttf
  70. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Medium.ttf
  71. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-MediumItalic.ttf
  72. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Regular.ttf
  73. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBold.ttf
  74. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBoldItalic.ttf
  75. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Thin.ttf
  76. BIN
      Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ThinItalic.ttf
  77. +0
    -0
      Shadowsocks.WPF/Resources/privoxy.exe.gz
  78. +0
    -0
      Shadowsocks.WPF/Resources/privoxy_conf.txt
  79. +0
    -0
      Shadowsocks.WPF/Resources/shadowsocks.ico
  80. +0
    -0
      Shadowsocks.WPF/Resources/ss128.pdn
  81. +0
    -0
      Shadowsocks.WPF/Resources/ss32.pdn
  82. +0
    -0
      Shadowsocks.WPF/Resources/ss32Fill.png
  83. +0
    -0
      Shadowsocks.WPF/Resources/ss32In.png
  84. +0
    -0
      Shadowsocks.WPF/Resources/ss32Out.png
  85. +0
    -0
      Shadowsocks.WPF/Resources/ss32Outline.png
  86. +0
    -0
      Shadowsocks.WPF/Resources/ssw128.png
  87. +276
    -277
      Shadowsocks.WPF/Services/PortForwarder.cs
  88. +162
    -170
      Shadowsocks.WPF/Services/PrivoxyRunner.cs
  89. +173
    -176
      Shadowsocks.WPF/Services/Sip003Plugin.cs
  90. +6
    -6
      Shadowsocks.WPF/Services/SystemProxy/RAS.cs
  91. +2
    -2
      Shadowsocks.WPF/Services/SystemProxy/WinINet.cs
  92. +178
    -178
      Shadowsocks.WPF/Services/UpdateChecker.cs
  93. +6
    -15
      Shadowsocks.WPF/Shadowsocks.WPF.csproj
  94. +15
    -0
      Shadowsocks.WPF/ViewModels/DashboardViewModel.cs
  95. +0
    -3
      Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs
  96. +2
    -2
      Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs
  97. +15
    -0
      Shadowsocks.WPF/ViewModels/RoutingViewModel.cs
  98. +15
    -0
      Shadowsocks.WPF/ViewModels/ServersViewModel.cs
  99. +15
    -0
      Shadowsocks.WPF/ViewModels/SettingsViewModel.cs
  100. +27
    -0
      Shadowsocks.WPF/Views/DashboardView.xaml

+ 0
- 9
Shadowsocks.Common/Models/IoCManager.cs View File

@@ -1,9 +0,0 @@
using SimpleInjector;

namespace Shadowsocks.Common.Model
{
public static class IoCManager
{
public static Container Container { get; } = new Container();
}
}

+ 0
- 17
Shadowsocks.Common/Shadowsocks.Common.csproj View File

@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Authors>clowwindy &amp; community 2020</Authors>
<Company>clowwindy &amp; community 2020</Company>
<Product>Shadowsocks Common</Product>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.13.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.5" />
<PackageReference Include="SimpleInjector" Version="5.0.4" />
</ItemGroup>

</Project>

+ 0
- 18
Shadowsocks.Crypto/Shadowsocks.Crypto.csproj View File

@@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Authors>clowwindy &amp; community 2020</Authors>
<Product>Shadowsocks Crypto</Product>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.6" />
<PackageReference Include="NaCl.Core" Version="2.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Shadowsocks.Common\Shadowsocks.Common.csproj" />
</ItemGroup>

</Project>

shadowsocks-csharp/Controller/CachedNetworkStream.cs → Shadowsocks.Net/CachedNetworkStream.cs View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
@@ -7,7 +7,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Shadowsocks.Controller
namespace Shadowsocks.Net
{
// cache first packet for duty-chain pattern listener
public class CachedNetworkStream : Stream

Shadowsocks.Crypto/Crypto/AEAD/AEADAesGcmNativeCrypto.cs → Shadowsocks.Net/Crypto/AEAD/AEADAesGcmNativeCrypto.cs View File

@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace Shadowsocks.Crypto.AEAD
namespace Shadowsocks.Net.Crypto.AEAD
{
public class AEADAesGcmNativeCrypto : AEADCrypto
{

Shadowsocks.Crypto/Crypto/AEAD/AEADBouncyCastleCrypto.cs → Shadowsocks.Net/Crypto/AEAD/AEADBouncyCastleCrypto.cs View File

@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Collections.Generic;

namespace Shadowsocks.Crypto.AEAD
namespace Shadowsocks.Net.Crypto.AEAD
{
public class AEADBouncyCastleCrypto : AEADCrypto
{

Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs → Shadowsocks.Net/Crypto/AEAD/AEADCrypto.cs View File

@@ -1,15 +1,12 @@
using Shadowsocks.Net.Crypto.Exception;
using Shadowsocks.Net.Crypto.Stream;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;

using NLog;

using Shadowsocks.Crypto.Exception;
using Shadowsocks.Crypto.Stream;

namespace Shadowsocks.Crypto.AEAD
namespace Shadowsocks.Net.Crypto.AEAD
{
public abstract class AEADCrypto : CryptoBase
{

Shadowsocks.Crypto/Crypto/AEAD/AEADNaClCrypto.cs → Shadowsocks.Net/Crypto/AEAD/AEADNaClCrypto.cs View File

@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;

using NaCl.Core;
using NaCl.Core.Base;
using System;
using System.Collections.Generic;

namespace Shadowsocks.Crypto.AEAD
namespace Shadowsocks.Net.Crypto.AEAD
{
public class AEADNaClCrypto : AEADCrypto
{

Shadowsocks.Crypto/Crypto/CipherInfo.cs → Shadowsocks.Net/Crypto/CipherInfo.cs View File

@@ -1,4 +1,4 @@
namespace Shadowsocks.Crypto
namespace Shadowsocks.Net.Crypto
{
public enum CipherFamily
{

Shadowsocks.Crypto/Crypto/CryptoBase.cs → Shadowsocks.Net/Crypto/CryptoBase.cs View File

@@ -1,6 +1,6 @@
using System;

namespace Shadowsocks.Crypto
namespace Shadowsocks.Net.Crypto
{
public abstract class CryptoBase : ICrypto
{

Shadowsocks.Crypto/Crypto/CryptoFactory.cs → Shadowsocks.Net/Crypto/CryptoFactory.cs View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Reflection;
using System.Text;

using Shadowsocks.Crypto.AEAD;
using Shadowsocks.Crypto.Stream;
using Shadowsocks.Net.Crypto.AEAD;
using Shadowsocks.Net.Crypto.Stream;

namespace Shadowsocks.Crypto
namespace Shadowsocks.Net.Crypto
{
public static class CryptoFactory
{

Shadowsocks.Crypto/Util/CryptoUtils.cs → Shadowsocks.Net/Crypto/CryptoUtils.cs View File

@@ -6,7 +6,7 @@ using System;
using System.Security.Cryptography;
using System.Threading;

namespace Shadowsocks.Crypto
namespace Shadowsocks.Net.Crypto
{
public static class CryptoUtils
{

Shadowsocks.Crypto/Crypto/Exception/CryptoException.cs → Shadowsocks.Net/Crypto/Exception/CryptoException.cs View File

@@ -1,4 +1,4 @@
namespace Shadowsocks.Crypto.Exception
namespace Shadowsocks.Net.Crypto.Exception
{
public class CryptoErrorException : System.Exception
{

Shadowsocks.Crypto/Crypto/ICrypto.cs → Shadowsocks.Net/Crypto/ICrypto.cs View File

@@ -1,6 +1,6 @@
using System;

namespace Shadowsocks.Crypto
namespace Shadowsocks.Net.Crypto
{
public interface ICrypto
{

Shadowsocks.Crypto/Crypto/RNG.cs → Shadowsocks.Net/Crypto/RNG.cs View File

@@ -1,7 +1,7 @@
using System;
using System.Security.Cryptography;

namespace Shadowsocks.Crypto
namespace Shadowsocks.Net.Crypto
{
public static class RNG
{

Shadowsocks.Crypto/Crypto/Stream/ExtendedCfbBlockCipher.cs → Shadowsocks.Net/Crypto/Stream/ExtendedCfbBlockCipher.cs View File


Shadowsocks.Crypto/Crypto/Stream/StreamAesBouncyCastleCrypto.cs → Shadowsocks.Net/Crypto/Stream/StreamAesBouncyCastleCrypto.cs View File

@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Shadowsocks.Crypto.Stream
namespace Shadowsocks.Net.Crypto.Stream
{

public class StreamAesCfbBouncyCastleCrypto : StreamCrypto

Shadowsocks.Crypto/Crypto/Stream/StreamChachaNaClCrypto.cs → Shadowsocks.Net/Crypto/Stream/StreamChachaNaClCrypto.cs View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using NaCl.Core;

namespace Shadowsocks.Crypto.Stream
namespace Shadowsocks.Net.Crypto.Stream
{
public class StreamChachaNaClCrypto : StreamCrypto
{

Shadowsocks.Crypto/Crypto/Stream/StreamCrypto.cs → Shadowsocks.Net/Crypto/Stream/StreamCrypto.cs View File

@@ -1,11 +1,9 @@
using NLog;

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace Shadowsocks.Crypto.Stream
namespace Shadowsocks.Net.Crypto.Stream
{
public abstract class StreamCrypto : CryptoBase
{

Shadowsocks.Crypto/Crypto/Stream/StreamPlainNativeCrypto.cs → Shadowsocks.Net/Crypto/Stream/StreamPlainNativeCrypto.cs View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;

namespace Shadowsocks.Crypto.Stream
namespace Shadowsocks.Net.Crypto.Stream
{
public class StreamPlainNativeCrypto : StreamCrypto
{

Shadowsocks.Crypto/Crypto/Stream/StreamRc4NativeCrypto.cs → Shadowsocks.Net/Crypto/Stream/StreamRc4NativeCrypto.cs View File

@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Shadowsocks.Crypto.Stream
namespace Shadowsocks.Net.Crypto.Stream
{
public class StreamRc4NativeCrypto : StreamCrypto
{

Shadowsocks.Crypto/Crypto/TCPInfo.cs → Shadowsocks.Net/Crypto/TCPInfo.cs View File

@@ -1,6 +1,6 @@
using Shadowsocks.Crypto.AEAD;
using Shadowsocks.Net.Crypto.AEAD;

namespace Shadowsocks.Crypto
namespace Shadowsocks.Net.Crypto
{
public static class TCPParameter
{

shadowsocks-csharp/Proxy/DirectConnect.cs → Shadowsocks.Net/Proxy/DirectConnect.cs View File

@@ -1,11 +1,10 @@
using System;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Shadowsocks.Util.Sockets;

namespace Shadowsocks.Proxy
namespace Shadowsocks.Net.Proxy
{
public class DirectConnect : IProxy
{

shadowsocks-csharp/Proxy/HttpProxy.cs → Shadowsocks.Net/Proxy/HttpProxy.cs View File

@@ -1,117 +1,115 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using Shadowsocks.Controller;
using Shadowsocks.Util.Sockets;
namespace Shadowsocks.Proxy
{
public class HttpProxy : IProxy
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public EndPoint LocalEndPoint => _remote.LocalEndPoint;
public EndPoint ProxyEndPoint { get; private set; }
public EndPoint DestEndPoint { get; private set; }
private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
private const string HTTP_CRLF = "\r\n";
private const string HTTP_CONNECT_TEMPLATE =
"CONNECT {0} HTTP/1.1" + HTTP_CRLF +
"Host: {0}" + HTTP_CRLF +
"Proxy-Connection: keep-alive" + HTTP_CRLF +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + HTTP_CRLF +
"{1}" + // Proxy-Authorization if any
"" + HTTP_CRLF; // End with an empty line
private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF;
public void Shutdown(SocketShutdown how)
{
_remote.Shutdown(how);
}
public void Close()
{
_remote.Dispose();
}
private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled);
private int _respondLineCount = 0;
private bool _established = false;
private bool OnLineRead(string line, object state)
{
logger.Trace(line);
if (_respondLineCount == 0)
{
var m = HttpRespondHeaderRegex.Match(line);
if (m.Success)
{
var resultCode = m.Groups[2].Value;
if ("200" != resultCode)
{
return true;
}
_established = true;
}
}
else
{
if (string.IsNullOrEmpty(line))
{
return true;
}
}
_respondLineCount++;
return false;
}
private NetworkCredential auth;
public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default)
{
ProxyEndPoint = remoteEP;
this.auth = auth;
await _remote.ConnectAsync(remoteEP);
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
}
public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default)
{
DestEndPoint = destEndPoint;
String authInfo = "";
if (auth != null)
{
string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password));
authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey);
}
string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo);
var b = Encoding.UTF8.GetBytes(request);
await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token);
// start line read
LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null);
await reader.Finished;
}
public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)
{
return await _remote.SendAsync(buffer, SocketFlags.None, token);
}
public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token);
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using NLog;

namespace Shadowsocks.Net.Proxy
{
public class HttpProxy : IProxy
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();

public EndPoint LocalEndPoint => _remote.LocalEndPoint;
public EndPoint ProxyEndPoint { get; private set; }
public EndPoint DestEndPoint { get; private set; }

private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp);

private const string HTTP_CRLF = "\r\n";
private const string HTTP_CONNECT_TEMPLATE =
"CONNECT {0} HTTP/1.1" + HTTP_CRLF +
"Host: {0}" + HTTP_CRLF +
"Proxy-Connection: keep-alive" + HTTP_CRLF +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" + HTTP_CRLF +
"{1}" + // Proxy-Authorization if any
"" + HTTP_CRLF; // End with an empty line
private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF;

public void Shutdown(SocketShutdown how)
{
_remote.Shutdown(how);
}

public void Close()
{
_remote.Dispose();
}

private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled);
private int _respondLineCount = 0;
private bool _established = false;

private bool OnLineRead(string line, object state)
{
logger.Trace(line);

if (_respondLineCount == 0)
{
var m = HttpRespondHeaderRegex.Match(line);
if (m.Success)
{
var resultCode = m.Groups[2].Value;
if ("200" != resultCode)
{
return true;
}
_established = true;
}
}
else
{
if (string.IsNullOrEmpty(line))
{
return true;
}
}
_respondLineCount++;

return false;
}

private NetworkCredential auth;

public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default)
{
ProxyEndPoint = remoteEP;
this.auth = auth;
await _remote.ConnectAsync(remoteEP);
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
}

public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default)
{
DestEndPoint = destEndPoint;
String authInfo = "";
if (auth != null)
{
string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password));
authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey);
}
string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo);

var b = Encoding.UTF8.GetBytes(request);

await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token);

// start line read
LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null);
await reader.Finished;
}

public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)
{
return await _remote.SendAsync(buffer, SocketFlags.None, token);
}

public async Task<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
return await _remote.ReceiveAsync(buffer, SocketFlags.None, token);
}
}
}

shadowsocks-csharp/Proxy/IProxy.cs → Shadowsocks.Net/Proxy/IProxy.cs View File

@@ -1,12 +1,11 @@
using System;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace Shadowsocks.Proxy
namespace Shadowsocks.Net.Proxy
{

public interface IProxy
{
EndPoint LocalEndPoint { get; }

shadowsocks-csharp/Util/Sockets/LineReader.cs → Shadowsocks.Net/Proxy/LineReader.cs View File

@@ -1,9 +1,9 @@
using System;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Shadowsocks.Util.Sockets
namespace Shadowsocks.Net.Proxy
{
public class LineReader
{

shadowsocks-csharp/Proxy/Socks5Proxy.cs → Shadowsocks.Net/Proxy/Socks5Proxy.cs View File

@@ -1,13 +1,11 @@
using System;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Shadowsocks.Controller;
using Shadowsocks.Util.Sockets;

namespace Shadowsocks.Proxy
namespace Shadowsocks.Net.Proxy
{
public class Socks5Proxy : IProxy
{
@@ -37,11 +35,11 @@ namespace Shadowsocks.Proxy
await _remote.SendAsync(new byte[] { 5, 1, 0 }, SocketFlags.None);
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 2), SocketFlags.None) != 2)
{
throw new Exception(I18N.GetString("Proxy handshake failed"));
throw new Exception("Proxy handshake failed");
}
if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0)
{
throw new Exception(I18N.GetString("Proxy handshake failed"));
throw new Exception("Proxy handshake failed");
}
}

@@ -80,14 +78,13 @@ namespace Shadowsocks.Proxy
atyp = 4; // IP V6 address
break;
default:
throw new Exception(I18N.GetString("Proxy request failed"));
throw new Exception("Proxy request failed");
}
port = ((IPEndPoint)DestEndPoint).Port;
var addr = ((IPEndPoint)DestEndPoint).Address.GetAddressBytes();
Array.Copy(addr, 0, request, 4, request.Length - 4 - 2);
}

// 构造request包剩余部分
request[0] = 5;
request[1] = 1;
request[2] = 0;
@@ -99,11 +96,11 @@ namespace Shadowsocks.Proxy

if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 4), SocketFlags.None, token) != 4)
{
throw new Exception(I18N.GetString("Proxy request failed"));
throw new Exception("Proxy request failed");
};
if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0)
{
throw new Exception(I18N.GetString("Proxy request failed"));
throw new Exception("Proxy request failed");
}
var addrLen = _receiveBuffer[3] switch
{
@@ -113,11 +110,8 @@ namespace Shadowsocks.Proxy
};
if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, addrLen), SocketFlags.None, token) != addrLen)
{
throw new Exception(I18N.GetString("Proxy request failed"));
throw new Exception("Proxy request failed");
}



}

public async Task<int> SendAsync(ReadOnlyMemory<byte> buffer, CancellationToken token = default)

+ 12
- 0
Shadowsocks.Net/Shadowsocks.Net.csproj View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BouncyCastle.NetCore" Version="1.8.6" />
<PackageReference Include="NaCl.Core" Version="2.0.0" />
</ItemGroup>

</Project>

Shadowsocks.Common/SystemProxy/ProxyException.cs → Shadowsocks.Net/SystemProxy/ProxyException.cs View File

@@ -1,9 +1,9 @@
using System;
using System;
using System.Runtime.Serialization;

namespace Shadowsocks.Common.SystemProxy
namespace Shadowsocks.Net.SystemProxy
{
enum ProxyExceptionType
public enum ProxyExceptionType
{
Unspecific,
FailToRun,
@@ -12,7 +12,7 @@ namespace Shadowsocks.Common.SystemProxy
QueryReturnMalformed
}

class ProxyException : Exception
public class ProxyException : Exception
{
// provide more specific information about exception
public ProxyExceptionType Type { get; }

shadowsocks-csharp/Controller/Service/TCPListener.cs → Shadowsocks.Net/TCPListener.cs View File

@@ -1,5 +1,4 @@
using NLog;
using Shadowsocks.Model;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -7,7 +6,7 @@ using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;

namespace Shadowsocks.Controller
namespace Shadowsocks.Net
{
public interface IStreamService
{

Shadowsocks.Net/TCPRelay.cs
File diff suppressed because it is too large
View File


shadowsocks-csharp/Controller/Service/UDPListener.cs → Shadowsocks.Net/UDPListener.cs View File


shadowsocks-csharp/Controller/Service/UDPRelay.cs → Shadowsocks.Net/UDPRelay.cs View File

@@ -1,232 +1,230 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using NLog;
using Shadowsocks.Controller.Strategy;
using Shadowsocks.Encryption;
using Shadowsocks.Model;
namespace Shadowsocks.Controller
{
class UDPRelay : DatagramService
{
private ShadowsocksController _controller;
// TODO: choose a smart number
private LRUCache<IPEndPoint, UDPHandler> _cache = new LRUCache<IPEndPoint, UDPHandler>(512);
public long outbound = 0;
public long inbound = 0;
public UDPRelay(ShadowsocksController controller)
{
this._controller = controller;
}
public override async Task<bool> Handle(Memory<byte> packet, Socket socket, EndPoint client)
{
if (socket.ProtocolType != ProtocolType.Udp)
{
return false;
}
if (packet.Length < 4)
{
return false;
}
IPEndPoint remoteEndPoint = (IPEndPoint)client;
UDPHandler handler = _cache.get(remoteEndPoint);
if (handler == null)
{
handler = new UDPHandler(socket, _controller.GetAServer(IStrategyCallerType.UDP, remoteEndPoint, null/*TODO: fix this*/), remoteEndPoint);
handler.Receive();
_cache.add(remoteEndPoint, handler);
}
await handler.SendAsync(packet);
return true;
}
public class UDPHandler
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private static MemoryPool<byte> pool = MemoryPool<byte>.Shared;
private Socket _local;
private Socket _remote;
private Server _server;
private byte[] _buffer = new byte[65536];
private IPEndPoint _localEndPoint;
private IPEndPoint _remoteEndPoint;
private IPAddress ListenAddress
{
get
{
return _remote.AddressFamily switch
{
AddressFamily.InterNetwork => IPAddress.Any,
AddressFamily.InterNetworkV6 => IPAddress.IPv6Any,
_ => throw new NotSupportedException(),
};
}
}
public UDPHandler(Socket local, Server server, IPEndPoint localEndPoint)
{
_local = local;
_server = server;
_localEndPoint = localEndPoint;
// TODO async resolving
bool parsed = IPAddress.TryParse(server.server, out IPAddress ipAddress);
if (!parsed)
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server);
ipAddress = ipHostInfo.AddressList[0];
}
_remoteEndPoint = new IPEndPoint(ipAddress, server.server_port);
_remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
_remote.Bind(new IPEndPoint(ListenAddress, 0));
}
public async Task SendAsync(ReadOnlyMemory<byte> data)
{
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password);
using IMemoryOwner<byte> mem = pool.Rent(data.Length + 1000);
// byte[] dataOut = new byte[slicedData.Length + 1000];
int outlen = encryptor.EncryptUDP(data.Span[3..], mem.Memory.Span);
logger.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay up");
if (!MemoryMarshal.TryGetArray(mem.Memory[..outlen], out ArraySegment<byte> outData))
{
throw new InvalidOperationException("Can't extract underly array segment");
};
await _remote?.SendToAsync(outData, SocketFlags.None, _remoteEndPoint);
}
public async Task ReceiveAsync()
{
EndPoint remoteEndPoint = new IPEndPoint(ListenAddress, 0);
logger.Debug($"++++++Receive Server Port, size:" + _buffer.Length);
try
{
while (true)
{
var result = await _remote.ReceiveFromAsync(_buffer, SocketFlags.None, remoteEndPoint);
int bytesRead = result.ReceivedBytes;
using IMemoryOwner<byte> owner = pool.Rent(bytesRead + 3);
Memory<byte> o = owner.Memory;
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password);
int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead));
logger.Debug(_remoteEndPoint, _localEndPoint, outlen, "UDP Relay down");
if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment<byte> data))
{
throw new InvalidOperationException("Can't extract underly array segment");
};
await _local?.SendToAsync(data, SocketFlags.None, _localEndPoint);
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
public void Receive()
{
_ = ReceiveAsync();
}
public void Close()
{
try
{
_remote?.Close();
}
catch (ObjectDisposedException)
{
// TODO: handle the ObjectDisposedException
}
catch (Exception)
{
// TODO: need more think about handle other Exceptions, or should remove this catch().
}
}
}
}
#region LRU cache
// cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054
class LRUCache<K, V> where V : UDPRelay.UDPHandler
{
private int capacity;
private Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>> cacheMap = new Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>>();
private LinkedList<LRUCacheItem<K, V>> lruList = new LinkedList<LRUCacheItem<K, V>>();
public LRUCache(int capacity)
{
this.capacity = capacity;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public V get(K key)
{
LinkedListNode<LRUCacheItem<K, V>> node;
if (cacheMap.TryGetValue(key, out node))
{
V value = node.Value.value;
lruList.Remove(node);
lruList.AddLast(node);
return value;
}
return default(V);
}
[MethodImpl(MethodImplOptions.Synchronized)]
public void add(K key, V val)
{
if (cacheMap.Count >= capacity)
{
RemoveFirst();
}
LRUCacheItem<K, V> cacheItem = new LRUCacheItem<K, V>(key, val);
LinkedListNode<LRUCacheItem<K, V>> node = new LinkedListNode<LRUCacheItem<K, V>>(cacheItem);
lruList.AddLast(node);
cacheMap.Add(key, node);
}
private void RemoveFirst()
{
// Remove from LRUPriority
LinkedListNode<LRUCacheItem<K, V>> node = lruList.First;
lruList.RemoveFirst();
// Remove from cache
cacheMap.Remove(node.Value.key);
node.Value.value.Close();
}
}
class LRUCacheItem<K, V>
{
public LRUCacheItem(K k, V v)
{
key = k;
value = v;
}
public K key;
public V value;
}
#endregion
}
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using NLog;
using Shadowsocks.Net.Crypto;

namespace Shadowsocks.Controller
{
class UDPRelay : DatagramService
{
private ShadowsocksController _controller;

// TODO: choose a smart number
private LRUCache<IPEndPoint, UDPHandler> _cache = new LRUCache<IPEndPoint, UDPHandler>(512);

public long outbound = 0;
public long inbound = 0;

public UDPRelay(ShadowsocksController controller)
{
this._controller = controller;
}

public override async Task<bool> Handle(Memory<byte> packet, Socket socket, EndPoint client)
{
if (socket.ProtocolType != ProtocolType.Udp)
{
return false;
}
if (packet.Length < 4)
{
return false;
}
IPEndPoint remoteEndPoint = (IPEndPoint)client;
UDPHandler handler = _cache.get(remoteEndPoint);
if (handler == null)
{
handler = new UDPHandler(socket, _controller.GetAServer(IStrategyCallerType.UDP, remoteEndPoint, null/*TODO: fix this*/), remoteEndPoint);
handler.Receive();
_cache.add(remoteEndPoint, handler);
}
await handler.SendAsync(packet);
return true;
}

public class UDPHandler
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private static MemoryPool<byte> pool = MemoryPool<byte>.Shared;
private Socket _local;
private Socket _remote;

private Server _server;
private byte[] _buffer = new byte[65536];

private IPEndPoint _localEndPoint;
private IPEndPoint _remoteEndPoint;

private IPAddress ListenAddress
{
get
{
return _remote.AddressFamily switch
{
AddressFamily.InterNetwork => IPAddress.Any,
AddressFamily.InterNetworkV6 => IPAddress.IPv6Any,
_ => throw new NotSupportedException(),
};
}
}

public UDPHandler(Socket local, Server server, IPEndPoint localEndPoint)
{
_local = local;
_server = server;
_localEndPoint = localEndPoint;

// TODO async resolving
bool parsed = IPAddress.TryParse(server.server, out IPAddress ipAddress);
if (!parsed)
{
IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server);
ipAddress = ipHostInfo.AddressList[0];
}
_remoteEndPoint = new IPEndPoint(ipAddress, server.server_port);
_remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
_remote.Bind(new IPEndPoint(ListenAddress, 0));
}

public async Task SendAsync(ReadOnlyMemory<byte> data)
{
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password);
using IMemoryOwner<byte> mem = pool.Rent(data.Length + 1000);

// byte[] dataOut = new byte[slicedData.Length + 1000];
int outlen = encryptor.EncryptUDP(data.Span[3..], mem.Memory.Span);
logger.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay up");
if (!MemoryMarshal.TryGetArray(mem.Memory[..outlen], out ArraySegment<byte> outData))
{
throw new InvalidOperationException("Can't extract underly array segment");
};
await _remote?.SendToAsync(outData, SocketFlags.None, _remoteEndPoint);
}

public async Task ReceiveAsync()
{
EndPoint remoteEndPoint = new IPEndPoint(ListenAddress, 0);
logger.Debug($"++++++Receive Server Port, size:" + _buffer.Length);
try
{
while (true)
{
var result = await _remote.ReceiveFromAsync(_buffer, SocketFlags.None, remoteEndPoint);
int bytesRead = result.ReceivedBytes;

using IMemoryOwner<byte> owner = pool.Rent(bytesRead + 3);
Memory<byte> o = owner.Memory;

IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password);
int outlen = encryptor.DecryptUDP(o.Span[3..], _buffer.AsSpan(0, bytesRead));
logger.Debug(_remoteEndPoint, _localEndPoint, outlen, "UDP Relay down");
if (!MemoryMarshal.TryGetArray(o[..(outlen + 3)], out ArraySegment<byte> data))
{
throw new InvalidOperationException("Can't extract underly array segment");
};
await _local?.SendToAsync(data, SocketFlags.None, _localEndPoint);

}
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}

public void Receive()
{
_ = ReceiveAsync();
}

public void Close()
{
try
{
_remote?.Close();
}
catch (ObjectDisposedException)
{
// TODO: handle the ObjectDisposedException
}
catch (Exception)
{
// TODO: need more think about handle other Exceptions, or should remove this catch().
}
}
}
}

#region LRU cache

// cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054
class LRUCache<K, V> where V : UDPRelay.UDPHandler
{
private int capacity;
private Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>> cacheMap = new Dictionary<K, LinkedListNode<LRUCacheItem<K, V>>>();
private LinkedList<LRUCacheItem<K, V>> lruList = new LinkedList<LRUCacheItem<K, V>>();

public LRUCache(int capacity)
{
this.capacity = capacity;
}

[MethodImpl(MethodImplOptions.Synchronized)]
public V get(K key)
{
LinkedListNode<LRUCacheItem<K, V>> node;
if (cacheMap.TryGetValue(key, out node))
{
V value = node.Value.value;
lruList.Remove(node);
lruList.AddLast(node);
return value;
}
return default(V);
}

[MethodImpl(MethodImplOptions.Synchronized)]
public void add(K key, V val)
{
if (cacheMap.Count >= capacity)
{
RemoveFirst();
}

LRUCacheItem<K, V> cacheItem = new LRUCacheItem<K, V>(key, val);
LinkedListNode<LRUCacheItem<K, V>> node = new LinkedListNode<LRUCacheItem<K, V>>(cacheItem);
lruList.AddLast(node);
cacheMap.Add(key, node);
}

private void RemoveFirst()
{
// Remove from LRUPriority
LinkedListNode<LRUCacheItem<K, V>> node = lruList.First;
lruList.RemoveFirst();

// Remove from cache
cacheMap.Remove(node.Value.key);
node.Value.value.Close();
}
}

class LRUCacheItem<K, V>
{
public LRUCacheItem(K k, V v)
{
key = k;
value = v;
}
public K key;
public V value;
}

#endregion
}

shadowsocks-csharp/Controller/Service/GeositeUpdater.cs → Shadowsocks.PAC/GeositeUpdater.cs View File

@@ -1,4 +1,4 @@
using NLog;
using NLog;
using Shadowsocks.Properties;
using Shadowsocks.Util;
using System;
@@ -13,7 +13,7 @@ using System.Net.Http;
using System.Threading.Tasks;
using System.Security.Cryptography;

namespace Shadowsocks.Controller
namespace Shadowsocks.PAC
{
public class GeositeResultEventArgs : EventArgs
{
@@ -21,7 +21,7 @@ namespace Shadowsocks.Controller

public GeositeResultEventArgs(bool success)
{
this.Success = success;
Success = success;
}
}


shadowsocks-csharp/Controller/Service/PACDaemon.cs → Shadowsocks.PAC/PACDaemon.cs View File

@@ -1,133 +1,133 @@
using NLog;
using Shadowsocks.Model;
using Shadowsocks.Properties;
using Shadowsocks.Util;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shadowsocks.Controller
{
/// <summary>
/// Processing the PAC file content
/// </summary>
public class PACDaemon
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public const string PAC_FILE = "pac.txt";
public const string USER_RULE_FILE = "user-rule.txt";
public const string USER_ABP_FILE = "abp.txt";
private Configuration config;
FileSystemWatcher PACFileWatcher;
FileSystemWatcher UserRuleFileWatcher;
public event EventHandler PACFileChanged;
public event EventHandler UserRuleFileChanged;
public PACDaemon(Configuration config)
{
this.config = config;
TouchPACFile();
TouchUserRuleFile();
this.WatchPacFile();
this.WatchUserRuleFile();
}
public string TouchPACFile()
{
if (!File.Exists(PAC_FILE))
{
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect);
}
return PAC_FILE;
}
internal string TouchUserRuleFile()
{
if (!File.Exists(USER_RULE_FILE))
{
File.WriteAllText(USER_RULE_FILE, Resources.user_rule);
}
return USER_RULE_FILE;
}
internal string GetPACContent()
{
if (!File.Exists(PAC_FILE))
{
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect);
}
return File.ReadAllText(PAC_FILE, Encoding.UTF8);
}
private void WatchPacFile()
{
PACFileWatcher?.Dispose();
PACFileWatcher = new FileSystemWatcher(Program.WorkingDirectory);
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
PACFileWatcher.Filter = PAC_FILE;
PACFileWatcher.Changed += PACFileWatcher_Changed;
PACFileWatcher.Created += PACFileWatcher_Changed;
PACFileWatcher.Deleted += PACFileWatcher_Changed;
PACFileWatcher.Renamed += PACFileWatcher_Changed;
PACFileWatcher.EnableRaisingEvents = true;
}
private void WatchUserRuleFile()
{
UserRuleFileWatcher?.Dispose();
UserRuleFileWatcher = new FileSystemWatcher(Program.WorkingDirectory);
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
UserRuleFileWatcher.Filter = USER_RULE_FILE;
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.EnableRaisingEvents = true;
}
#region FileSystemWatcher.OnChanged()
// FileSystemWatcher Changed event is raised twice
// http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice
// Add a short delay to avoid raise event twice in a short period
private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (PACFileChanged != null)
{
logger.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
PACFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (UserRuleFileChanged != null)
{
logger.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
UserRuleFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
#endregion
}
}
using NLog;
using Shadowsocks.Model;
using Shadowsocks.Properties;
using Shadowsocks.Util;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shadowsocks.PAC
{
/// <summary>
/// Processing the PAC file content
/// </summary>
public class PACDaemon
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public const string PAC_FILE = "pac.txt";
public const string USER_RULE_FILE = "user-rule.txt";
public const string USER_ABP_FILE = "abp.txt";
private Configuration config;
FileSystemWatcher PACFileWatcher;
FileSystemWatcher UserRuleFileWatcher;
public event EventHandler PACFileChanged;
public event EventHandler UserRuleFileChanged;
public PACDaemon(Configuration config)
{
this.config = config;
TouchPACFile();
TouchUserRuleFile();
this.WatchPacFile();
this.WatchUserRuleFile();
}
public string TouchPACFile()
{
if (!File.Exists(PAC_FILE))
{
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect);
}
return PAC_FILE;
}
internal string TouchUserRuleFile()
{
if (!File.Exists(USER_RULE_FILE))
{
File.WriteAllText(USER_RULE_FILE, Resources.user_rule);
}
return USER_RULE_FILE;
}
internal string GetPACContent()
{
if (!File.Exists(PAC_FILE))
{
GeositeUpdater.MergeAndWritePACFile(config.geositeDirectGroups, config.geositeProxiedGroups, config.geositePreferDirect);
}
return File.ReadAllText(PAC_FILE, Encoding.UTF8);
}
private void WatchPacFile()
{
PACFileWatcher?.Dispose();
PACFileWatcher = new FileSystemWatcher(Program.WorkingDirectory);
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
PACFileWatcher.Filter = PAC_FILE;
PACFileWatcher.Changed += PACFileWatcher_Changed;
PACFileWatcher.Created += PACFileWatcher_Changed;
PACFileWatcher.Deleted += PACFileWatcher_Changed;
PACFileWatcher.Renamed += PACFileWatcher_Changed;
PACFileWatcher.EnableRaisingEvents = true;
}
private void WatchUserRuleFile()
{
UserRuleFileWatcher?.Dispose();
UserRuleFileWatcher = new FileSystemWatcher(Program.WorkingDirectory);
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
UserRuleFileWatcher.Filter = USER_RULE_FILE;
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed;
UserRuleFileWatcher.EnableRaisingEvents = true;
}
#region FileSystemWatcher.OnChanged()
// FileSystemWatcher Changed event is raised twice
// http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice
// Add a short delay to avoid raise event twice in a short period
private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (PACFileChanged != null)
{
logger.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
PACFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (UserRuleFileChanged != null)
{
logger.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}.");
Task.Factory.StartNew(() =>
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
System.Threading.Thread.Sleep(10);
UserRuleFileChanged(this, new EventArgs());
((FileSystemWatcher)sender).EnableRaisingEvents = true;
});
}
}
#endregion
}
}

shadowsocks-csharp/Controller/Service/PACServer.cs → Shadowsocks.PAC/PACServer.cs View File

@@ -1,210 +1,209 @@
using Shadowsocks.Encryption;
using Shadowsocks.Model;
using Shadowsocks.Util;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Web;
using NLog;
namespace Shadowsocks.Controller
{
public class PACServer : StreamService
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public const string RESOURCE_NAME = "pac";
private string PacSecret
{
get
{
if (string.IsNullOrEmpty(_cachedPacSecret))
{
_cachedPacSecret = HttpServerUtilityUrlToken.Encode(RNG.GetBytes(32));
}
return _cachedPacSecret;
}
}
private string _cachedPacSecret = "";
public string PacUrl { get; private set; } = "";
private Configuration _config;
private PACDaemon _pacDaemon;
public PACServer(PACDaemon pacDaemon)
{
_pacDaemon = pacDaemon;
}
public void UpdatePACURL(Configuration config)
{
_config = config;
string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : "";
string contentHash = GetHash(_pacDaemon.GetPACContent());
PacUrl = $"http://{config.LocalHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}";
logger.Debug("Set PAC URL:" + PacUrl);
}
private static string GetHash(string content)
{
return HttpServerUtilityUrlToken.Encode(CryptoUtils.MD5(Encoding.ASCII.GetBytes(content)));
}
public override bool Handle(CachedNetworkStream stream, object state)
{
byte[] fp = new byte[256];
int len = stream.ReadFirstBlock(fp);
return Handle(fp, len, stream.Socket, state);
}
[Obsolete]
public override bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
try
{
/*
* RFC 7230
*
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
*/
string request = Encoding.UTF8.GetString(firstPacket, 0, length);
string[] lines = request.Split('\r', '\n');
bool hostMatch = false, pathMatch = false, useSocks = false;
bool secretMatch = !_config.secureLocalPac;
if (lines.Length < 2) // need at lease RequestLine + Host
{
return false;
}
// parse request line
string requestLine = lines[0];
// GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1
string[] requestItems = requestLine.Split(' ');
if (requestItems.Length == 3 && requestItems[0] == "GET")
{
int index = requestItems[1].IndexOf('?');
if (index < 0)
{
index = requestItems[1].Length;
}
string resourceString = requestItems[1].Substring(0, index).Remove(0, 1);
if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase))
{
pathMatch = true;
if (!secretMatch)
{
string queryString = requestItems[1].Substring(index);
if (queryString.Contains(PacSecret))
{
secretMatch = true;
}
}
}
}
// parse request header
for (int i = 1; i < lines.Length; i++)
{
if (string.IsNullOrEmpty(lines[i]))
continue;
string[] kv = lines[i].Split(new char[] { ':' }, 2);
if (kv.Length == 2)
{
if (kv[0] == "Host")
{
if (kv[1].Trim() == ((IPEndPoint)socket.LocalEndPoint).ToString())
{
hostMatch = true;
}
}
//else if (kv[0] == "User-Agent")
//{
// // we need to drop connections when changing servers
// if (kv[1].IndexOf("Chrome") >= 0)
// {
// useSocks = true;
// }
//}
}
}
if (hostMatch && pathMatch)
{
if (!secretMatch)
{
socket.Close(); // Close immediately
}
else
{
SendResponse(socket, useSocks);
}
return true;
}
return false;
}
catch (ArgumentException)
{
return false;
}
}
public void SendResponse(Socket socket, bool useSocks)
{
try
{
IPEndPoint localEndPoint = (IPEndPoint)socket.LocalEndPoint;
string proxy = GetPACAddress(localEndPoint, useSocks);
string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent();
string responseHead =
$@"HTTP/1.1 200 OK
Server: ShadowsocksWindows/{UpdateChecker.Version}
Content-Type: application/x-ns-proxy-autoconfig
Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length}
Connection: Close
";
byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent);
socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket);
}
catch (Exception e)
{
logger.LogUsefulException(e);
socket.Close();
}
}
private void SendCallback(IAsyncResult ar)
{
Socket conn = (Socket)ar.AsyncState;
try
{
conn.Shutdown(SocketShutdown.Send);
}
catch
{ }
}
private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks)
{
return localEndPoint.AddressFamily == AddressFamily.InterNetworkV6
? $"{(useSocks ? "SOCKS5" : "PROXY")} [{localEndPoint.Address}]:{_config.localPort};"
: $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint.Address}:{_config.localPort};";
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using NLog;
using Shadowsocks.Net;
using Shadowsocks.Utilities;
using Shadowsocks.Net.Crypto;

namespace Shadowsocks.PAC
{
public class PACServer : StreamService
{
private static Logger logger = LogManager.GetCurrentClassLogger();

public const string RESOURCE_NAME = "pac";

private string PacSecret
{
get
{
if (string.IsNullOrEmpty(_cachedPacSecret))
{
_cachedPacSecret = Base64Url.Encode(RNG.GetBytes(32));
}
return _cachedPacSecret;
}
}
private string _cachedPacSecret = "";
public string PacUrl { get; private set; } = "";

private Configuration _config;
private PACDaemon _pacDaemon;

public PACServer(PACDaemon pacDaemon)
{
_pacDaemon = pacDaemon;
}

public void UpdatePACURL(Configuration config)
{
_config = config;
string usedSecret = _config.secureLocalPac ? $"&secret={PacSecret}" : "";
string contentHash = GetHash(_pacDaemon.GetPACContent());
PacUrl = $"http://{config.LocalHost}:{config.localPort}/{RESOURCE_NAME}?hash={contentHash}{usedSecret}";
logger.Debug("Set PAC URL:" + PacUrl);
}

private static string GetHash(string content)
{

return Base64Url.Encode(CryptoUtils.MD5(Encoding.ASCII.GetBytes(content)));
}

public override bool Handle(CachedNetworkStream stream, object state)
{
byte[] fp = new byte[256];
int len = stream.ReadFirstBlock(fp);
return Handle(fp, len, stream.Socket, state);
}

[Obsolete]
public override bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}

try
{
/*
* RFC 7230
*
GET /hello.txt HTTP/1.1
User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
Host: www.example.com
Accept-Language: en, mi
*/

string request = Encoding.UTF8.GetString(firstPacket, 0, length);
string[] lines = request.Split('\r', '\n');
bool hostMatch = false, pathMatch = false, useSocks = false;
bool secretMatch = !_config.secureLocalPac;

if (lines.Length < 2) // need at lease RequestLine + Host
{
return false;
}

// parse request line
string requestLine = lines[0];
// GET /pac?t=yyyyMMddHHmmssfff&secret=foobar HTTP/1.1
string[] requestItems = requestLine.Split(' ');
if (requestItems.Length == 3 && requestItems[0] == "GET")
{
int index = requestItems[1].IndexOf('?');
if (index < 0)
{
index = requestItems[1].Length;
}
string resourceString = requestItems[1].Substring(0, index).Remove(0, 1);
if (string.Equals(resourceString, RESOURCE_NAME, StringComparison.OrdinalIgnoreCase))
{
pathMatch = true;
if (!secretMatch)
{
string queryString = requestItems[1].Substring(index);
if (queryString.Contains(PacSecret))
{
secretMatch = true;
}
}
}
}

// parse request header
for (int i = 1; i < lines.Length; i++)
{
if (string.IsNullOrEmpty(lines[i]))
continue;

string[] kv = lines[i].Split(new char[] { ':' }, 2);
if (kv.Length == 2)
{
if (kv[0] == "Host")
{
if (kv[1].Trim() == ((IPEndPoint)socket.LocalEndPoint).ToString())
{
hostMatch = true;
}
}
//else if (kv[0] == "User-Agent")
//{
// // we need to drop connections when changing servers
// if (kv[1].IndexOf("Chrome") >= 0)
// {
// useSocks = true;
// }
//}
}
}

if (hostMatch && pathMatch)
{
if (!secretMatch)
{
socket.Close(); // Close immediately
}
else
{
SendResponse(socket, useSocks);
}
return true;
}
return false;
}
catch (ArgumentException)
{
return false;
}
}

public void SendResponse(Socket socket, bool useSocks)
{
try
{
IPEndPoint localEndPoint = (IPEndPoint)socket.LocalEndPoint;

string proxy = GetPACAddress(localEndPoint, useSocks);

string pacContent = $"var __PROXY__ = '{proxy}';\n" + _pacDaemon.GetPACContent();
string responseHead =
$@"HTTP/1.1 200 OK
Server: ShadowsocksWindows/{UpdateChecker.Version}
Content-Type: application/x-ns-proxy-autoconfig
Content-Length: { Encoding.UTF8.GetBytes(pacContent).Length}
Connection: Close

";
byte[] response = Encoding.UTF8.GetBytes(responseHead + pacContent);
socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket);
}
catch (Exception e)
{
logger.LogUsefulException(e);
socket.Close();
}
}

private void SendCallback(IAsyncResult ar)
{
Socket conn = (Socket)ar.AsyncState;
try
{
conn.Shutdown(SocketShutdown.Send);
}
catch
{ }
}

private string GetPACAddress(IPEndPoint localEndPoint, bool useSocks)
{
return localEndPoint.AddressFamily == AddressFamily.InterNetworkV6
? $"{(useSocks ? "SOCKS5" : "PROXY")} [{localEndPoint.Address}]:{_config.localPort};"
: $"{(useSocks ? "SOCKS5" : "PROXY")} {localEndPoint.Address}:{_config.localPort};";
}
}
}

Shadowsocks/Assets/abp.js → Shadowsocks.PAC/Resources/abp.js View File


Shadowsocks/Assets/dlc.dat → Shadowsocks.PAC/Resources/dlc.dat View File


Shadowsocks/Assets/user-rule.txt → Shadowsocks.PAC/Resources/user-rule.txt View File


+ 12
- 0
Shadowsocks.PAC/Shadowsocks.PAC.csproj View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" />
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" />
</ItemGroup>

</Project>

shadowsocks-csharp/Model/Geosite/Geosite.cs → Shadowsocks.Protobuf/Geosite.cs View File


+ 11
- 0
Shadowsocks.Protobuf/Shadowsocks.Protobuf.csproj View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.13.0" />
</ItemGroup>

</Project>

shadowsocks-csharp/Model/Geosite/geosite.proto → Shadowsocks.Protobuf/geosite.proto View File


+ 2
- 2
Shadowsocks.WPF/App.xaml View File

@@ -2,12 +2,12 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Shadowsocks.WPF"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<md:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
<materialDesign:CustomColorTheme BaseTheme="Inherit" PrimaryColor="#3d5afe" SecondaryColor="#00c853" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>


shadowsocks-csharp/Controller/System/AutoStartup.cs → Shadowsocks.WPF/Behaviors/AutoStartup.cs View File

@@ -1,153 +1,153 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using NLog;
using Shadowsocks.Util;
namespace Shadowsocks.Controller
{
static class AutoStartup
{
private static Logger logger = LogManager.GetCurrentClassLogger();
// Don't use Application.ExecutablePath
// see https://stackoverflow.com/questions/12945805/odd-c-sharp-path-issue
private static string Key = "Shadowsocks_" + Program.ExecutablePath.GetHashCode();
public static bool Set(bool enabled)
{
RegistryKey runKey = null;
try
{
runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true);
if (runKey == null)
{
logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run");
return false;
}
if (enabled)
{
runKey.SetValue(Key, Program.ExecutablePath);
}
else
{
runKey.DeleteValue(Key);
}
// When autostartup setting change, change RegisterForRestart state to avoid start 2 times
RegisterForRestart(!enabled);
return true;
}
catch (Exception e)
{
logger.LogUsefulException(e);
return false;
}
finally
{
if (runKey != null)
{
try
{
runKey.Close();
runKey.Dispose();
}
catch (Exception e)
{ logger.LogUsefulException(e); }
}
}
}
public static bool Check()
{
RegistryKey runKey = null;
try
{
runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true);
if (runKey == null)
{
logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run");
return false;
}
string[] runList = runKey.GetValueNames();
foreach (string item in runList)
{
if (item.Equals(Key, StringComparison.OrdinalIgnoreCase))
return true;
else if (item.Equals("Shadowsocks", StringComparison.OrdinalIgnoreCase)) // Compatibility with older versions
{
string value = Convert.ToString(runKey.GetValue(item));
if (Program.ExecutablePath.Equals(value, StringComparison.OrdinalIgnoreCase))
{
runKey.DeleteValue(item);
runKey.SetValue(Key, Program.ExecutablePath);
return true;
}
}
}
return false;
}
catch (Exception e)
{
logger.LogUsefulException(e);
return false;
}
finally
{
if (runKey != null)
{
try
{
runKey.Close();
runKey.Dispose();
}
catch (Exception e)
{ logger.LogUsefulException(e); }
}
}
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern int RegisterApplicationRestart([MarshalAs(UnmanagedType.LPWStr)] string commandLineArgs, int Flags);
[DllImport("kernel32.dll", SetLastError = true)]
static extern int UnregisterApplicationRestart();
[Flags]
enum ApplicationRestartFlags
{
RESTART_ALWAYS = 0,
RESTART_NO_CRASH = 1,
RESTART_NO_HANG = 2,
RESTART_NO_PATCH = 4,
RESTART_NO_REBOOT = 8,
}
// register restart after system reboot/update
public static void RegisterForRestart(bool register)
{
// requested register and not autostartup
if (register && !Check())
{
// escape command line parameter
string[] args = new List<string>(Program.Args)
.Select(p => p.Replace("\"", "\\\"")) // escape " to \"
.Select(p => p.IndexOf(" ") >= 0 ? "\"" + p + "\"" : p) // encapsule with "
.ToArray();
string cmdline = string.Join(" ", args);
// first parameter is process command line parameter
// needn't include the name of the executable in the command line
RegisterApplicationRestart(cmdline, (int)(ApplicationRestartFlags.RESTART_NO_CRASH | ApplicationRestartFlags.RESTART_NO_HANG));
logger.Debug("Register restart after system reboot, command line:" + cmdline);
}
// requested unregister, which has no side effect
else if (!register)
{
UnregisterApplicationRestart();
logger.Debug("Unregister restart after system reboot");
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using NLog;
using Shadowsocks.Util;
namespace Shadowsocks.WPF.Behaviors
{
static class AutoStartup
{
private static Logger logger = LogManager.GetCurrentClassLogger();
// Don't use Application.ExecutablePath
// see https://stackoverflow.com/questions/12945805/odd-c-sharp-path-issue
private static string Key = "Shadowsocks_" + Program.ExecutablePath.GetHashCode();
public static bool Set(bool enabled)
{
RegistryKey runKey = null;
try
{
runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true);
if (runKey == null)
{
logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run");
return false;
}
if (enabled)
{
runKey.SetValue(Key, Program.ExecutablePath);
}
else
{
runKey.DeleteValue(Key);
}
// When autostartup setting change, change RegisterForRestart state to avoid start 2 times
RegisterForRestart(!enabled);
return true;
}
catch (Exception e)
{
logger.LogUsefulException(e);
return false;
}
finally
{
if (runKey != null)
{
try
{
runKey.Close();
runKey.Dispose();
}
catch (Exception e)
{ logger.LogUsefulException(e); }
}
}
}
public static bool Check()
{
RegistryKey runKey = null;
try
{
runKey = Utils.OpenRegKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true);
if (runKey == null)
{
logger.Error(@"Cannot find HKCU\Software\Microsoft\Windows\CurrentVersion\Run");
return false;
}
string[] runList = runKey.GetValueNames();
foreach (string item in runList)
{
if (item.Equals(Key, StringComparison.OrdinalIgnoreCase))
return true;
else if (item.Equals("Shadowsocks", StringComparison.OrdinalIgnoreCase)) // Compatibility with older versions
{
string value = Convert.ToString(runKey.GetValue(item));
if (Program.ExecutablePath.Equals(value, StringComparison.OrdinalIgnoreCase))
{
runKey.DeleteValue(item);
runKey.SetValue(Key, Program.ExecutablePath);
return true;
}
}
}
return false;
}
catch (Exception e)
{
logger.LogUsefulException(e);
return false;
}
finally
{
if (runKey != null)
{
try
{
runKey.Close();
runKey.Dispose();
}
catch (Exception e)
{ logger.LogUsefulException(e); }
}
}
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern int RegisterApplicationRestart([MarshalAs(UnmanagedType.LPWStr)] string commandLineArgs, int Flags);
[DllImport("kernel32.dll", SetLastError = true)]
static extern int UnregisterApplicationRestart();
[Flags]
enum ApplicationRestartFlags
{
RESTART_ALWAYS = 0,
RESTART_NO_CRASH = 1,
RESTART_NO_HANG = 2,
RESTART_NO_PATCH = 4,
RESTART_NO_REBOOT = 8,
}
// register restart after system reboot/update
public static void RegisterForRestart(bool register)
{
// requested register and not autostartup
if (register && !Check())
{
// escape command line parameter
string[] args = new List<string>(Program.Args)
.Select(p => p.Replace("\"", "\\\"")) // escape " to \"
.Select(p => p.IndexOf(" ") >= 0 ? "\"" + p + "\"" : p) // encapsule with "
.ToArray();
string cmdline = string.Join(" ", args);
// first parameter is process command line parameter
// needn't include the name of the executable in the command line
RegisterApplicationRestart(cmdline, (int)(ApplicationRestartFlags.RESTART_NO_CRASH | ApplicationRestartFlags.RESTART_NO_HANG));
logger.Debug("Register restart after system reboot, command line:" + cmdline);
}
// requested unregister, which has no side effect
else if (!register)
{
UnregisterApplicationRestart();
logger.Debug("Unregister restart after system reboot");
}
}
}
}

shadowsocks-csharp/Controller/FileManager.cs → Shadowsocks.WPF/Behaviors/FileManager.cs View File

@@ -1,68 +1,68 @@
using NLog;
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace Shadowsocks.Controller
{
public static class FileManager
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public static bool ByteArrayToFile(string fileName, byte[] content)
{
try
{
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
fs.Write(content, 0, content.Length);
return true;
}
catch (Exception ex)
{
logger.Error(ex);
}
return false;
}
public static void UncompressFile(string fileName, byte[] content)
{
// Because the uncompressed size of the file is unknown,
// we are using an arbitrary buffer size.
byte[] buffer = new byte[4096];
int n;
using(var fs = File.Create(fileName))
using (var input = new GZipStream(new MemoryStream(content),
CompressionMode.Decompress, false))
{
while ((n = input.Read(buffer, 0, buffer.Length)) > 0)
{
fs.Write(buffer, 0, n);
}
}
}
public static string NonExclusiveReadAllText(string path)
{
return NonExclusiveReadAllText(path, Encoding.Default);
}
public static string NonExclusiveReadAllText(string path, Encoding encoding)
{
try
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var sr = new StreamReader(fs, encoding))
{
return sr.ReadToEnd();
}
}
catch (Exception ex)
{
logger.Error(ex);
throw ex;
}
}
}
}
using NLog;
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
namespace Shadowsocks.WPF.Behaviors
{
public static class FileManager
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public static bool ByteArrayToFile(string fileName, byte[] content)
{
try
{
using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
fs.Write(content, 0, content.Length);
return true;
}
catch (Exception ex)
{
logger.Error(ex);
}
return false;
}
public static void UncompressFile(string fileName, byte[] content)
{
// Because the uncompressed size of the file is unknown,
// we are using an arbitrary buffer size.
byte[] buffer = new byte[4096];
int n;
using(var fs = File.Create(fileName))
using (var input = new GZipStream(new MemoryStream(content),
CompressionMode.Decompress, false))
{
while ((n = input.Read(buffer, 0, buffer.Length)) > 0)
{
fs.Write(buffer, 0, n);
}
}
}
public static string NonExclusiveReadAllText(string path)
{
return NonExclusiveReadAllText(path, Encoding.Default);
}
public static string NonExclusiveReadAllText(string path, Encoding encoding)
{
try
{
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var sr = new StreamReader(fs, encoding))
{
return sr.ReadToEnd();
}
}
catch (Exception ex)
{
logger.Error(ex);
throw ex;
}
}
}
}

shadowsocks-csharp/Controller/HotkeyReg.cs → Shadowsocks.WPF/Behaviors/HotkeyReg.cs View File

@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using NLog;
using Shadowsocks.Controller.Hotkeys;
using Shadowsocks.Model;
using System;
using System.Windows.Forms;

namespace Shadowsocks.Controller
namespace Shadowsocks.WPF.Behaviors
{
static class HotkeyReg
{

shadowsocks-csharp/Controller/System/Hotkeys/HotkeyCallbacks.cs → Shadowsocks.WPF/Behaviors/Hotkeys/HotkeyCallbacks.cs View File


shadowsocks-csharp/Controller/System/Hotkeys/Hotkeys.cs → Shadowsocks.WPF/Behaviors/Hotkeys/Hotkeys.cs View File


shadowsocks-csharp/Controller/Service/IPCService.cs → Shadowsocks.WPF/Behaviors/IPCService.cs View File

@@ -1,9 +1,9 @@
using System;
using System;
using System.IO.Pipes;
using System.Net;
using System.Text;

namespace Shadowsocks.Controller
namespace Shadowsocks.WPF.Behaviors
{
class RequestAddUrlEventArgs : EventArgs
{
@@ -11,7 +11,7 @@ namespace Shadowsocks.Controller

public RequestAddUrlEventArgs(string url)
{
this.Url = url;
Url = url;
}
}


Shadowsocks.Common/Utilities/LoggerExtension.cs → Shadowsocks.WPF/Behaviors/LoggerExtension.cs View File

@@ -1,4 +1,4 @@
using Shadowsocks.Common.SystemProxy;
using Shadowsocks.Net.SystemProxy;

using System;
using System.ComponentModel;

shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs → Shadowsocks.WPF/Behaviors/OnlineConfigResolver.cs View File

@@ -1,13 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Shadowsocks.Model;
using Shadowsocks.Models;

namespace Shadowsocks.Controller.Service
namespace Shadowsocks.WPF.Behaviors
{
public class OnlineConfigResolver
{

shadowsocks-csharp/Controller/System/ProtocolHandler.cs → Shadowsocks.WPF/Behaviors/ProtocolHandler.cs View File

@@ -1,13 +1,8 @@
using Microsoft.Win32;
using Microsoft.Win32;
using NLog;
using Shadowsocks.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Shadowsocks.Controller
namespace Shadowsocks.WPF.Behaviors
{
static class ProtocolHandler
{

shadowsocks-csharp/Controller/System/SystemProxy.cs → Shadowsocks.WPF/Behaviors/SystemProxy.cs View File

@@ -1,70 +1,69 @@
using System;
using System.Windows.Forms;
using NLog;
using Shadowsocks.Model;
using Shadowsocks.Util.SystemProxy;
namespace Shadowsocks.Controller
{
public static class SystemProxy
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public static void Update(Configuration config, bool forceDisable, PACServer pacSrv, bool noRetry = false)
{
bool global = config.global;
bool enabled = config.enabled;
if (forceDisable || !WinINet.operational)
{
enabled = false;
}
try
{
if (enabled)
{
if (global)
{
WinINet.ProxyGlobal("localhost:" + config.localPort.ToString(), "<local>");
}
else
{
string pacUrl;
if (config.useOnlinePac && !string.IsNullOrEmpty(config.pacUrl))
{
pacUrl = config.pacUrl;
}
else
{
pacUrl = pacSrv.PacUrl;
}
WinINet.ProxyPAC(pacUrl);
}
}
else
{
WinINet.Restore();
}
}
catch (ProxyException ex)
{
logger.LogUsefulException(ex);
if (ex.Type != ProxyExceptionType.Unspecific && !noRetry)
{
var ret = MessageBox.Show(I18N.GetString("Error occured when process proxy setting, do you want reset current setting and retry?"), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (ret == DialogResult.Yes)
{
WinINet.Reset();
Update(config, forceDisable, pacSrv, true);
}
}
else
{
MessageBox.Show(I18N.GetString("Unrecoverable proxy setting error occured, see log for detail"), I18N.GetString("Shadowsocks"), MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
using NLog;
using Shadowsocks.Net.SystemProxy;
using Shadowsocks.WPF.Services.SystemProxy;
using System.Windows;

namespace Shadowsocks.WPF.Behaviors
{
public static class SystemProxy
{
private static Logger logger = LogManager.GetCurrentClassLogger();

public static void Update(Configuration config, bool forceDisable, PACServer pacSrv, bool noRetry = false)
{
bool global = config.global;
bool enabled = config.enabled;

if (forceDisable || !WinINet.operational)
{
enabled = false;
}

try
{
if (enabled)
{
if (global)
{
WinINet.ProxyGlobal("localhost:" + config.localPort.ToString(), "<local>");
}
else
{
string pacUrl;
if (config.useOnlinePac && !string.IsNullOrEmpty(config.pacUrl))
{
pacUrl = config.pacUrl;
}
else
{

pacUrl = pacSrv.PacUrl;
}
WinINet.ProxyPAC(pacUrl);
}
}
else
{
WinINet.Restore();
}
}
catch (ProxyException ex)
{
logger.LogUsefulException(ex);
if (ex.Type != ProxyExceptionType.Unspecific && !noRetry)
{
var ret = MessageBox.Show(I18N.GetString("Error occured when process proxy setting, do you want reset current setting and retry?"), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
if (ret == DialogResult.Yes)
{
WinINet.Reset();
Update(config, forceDisable, pacSrv, true);
}
}
else
{
MessageBox.Show(I18N.GetString("Unrecoverable proxy setting error occured, see log for detail"), I18N.GetString("Shadowsocks"), MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
}

+ 125
- 0
Shadowsocks.WPF/Behaviors/Utilities.cs View File

@@ -0,0 +1,125 @@
using Microsoft.Win32;
using NLog;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ZXing;
using ZXing.Common;
using ZXing.QrCode;

namespace Shadowsocks.WPF.Behaviors
{
public static class Utilities
{
private static Logger logger = LogManager.GetCurrentClassLogger();

private static string _tempPath = null;

// return path to store temporary files
public static string GetTempPath()
{
if (_tempPath == null)
{
bool isPortableMode = Configuration.Load().portableMode;
try
{
if (isPortableMode)
{
_tempPath = Directory.CreateDirectory("ss_win_temp").FullName;
// don't use "/", it will fail when we call explorer /select xxx/ss_win_temp\xxx.log
}
else
{
_tempPath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), @"Shadowsocks\ss_win_temp_" + Program.ExecutablePath.GetHashCode())).FullName;
}
}
catch (Exception e)
{
logger.Error(e);
throw;
}
}
return _tempPath;
}

// return a full path with filename combined which pointed to the temporary directory
public static string GetTempPath(string filename) => Path.Combine(GetTempPath(), filename);

public static string ScanQRCodeFromScreen()
{
foreach (Screen screen in Screen.AllScreens)
{
using (Bitmap fullImage = new Bitmap(screen.Bounds.Width,
screen.Bounds.Height))
{
using (Graphics g = Graphics.FromImage(fullImage))
{
g.CopyFromScreen(screen.Bounds.X,
screen.Bounds.Y,
0, 0,
fullImage.Size,
CopyPixelOperation.SourceCopy);
}
int maxTry = 10;
for (int i = 0; i < maxTry; i++)
{
int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry);
int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry);
Rectangle cropRect = new Rectangle(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2);
Bitmap target = new Bitmap(screen.Bounds.Width, screen.Bounds.Height);

double imageScale = (double)screen.Bounds.Width / (double)cropRect.Width;
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height),
cropRect,
GraphicsUnit.Pixel);
}
var source = new BitmapLuminanceSource(target);
var bitmap = new BinaryBitmap(new HybridBinarizer(source));
QRCodeReader reader = new QRCodeReader();
var result = reader.decode(bitmap);
if (result != null)
return result.Text;
}
}
}
return null;
}

public static void OpenInBrowser(string url)
{
try
{
Process.Start(url);
}
catch
{
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Process.Start(new ProcessStartInfo(url)
{
UseShellExecute = true,
Verb = "open"
});
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}
}
}

+ 1
- 4
Shadowsocks.WPF/Localization/LocalizationProvider.cs View File

@@ -1,12 +1,9 @@
using Shadowsocks.Common.Model;

using System.Reflection;

using WPFLocalizeExtension.Extensions;

namespace Shadowsocks.WPF.Localization
{
public class LocalizationProvider : ILocalizationProvider
public class LocalizationProvider
{
private static readonly string CallingAssemblyName = Assembly.GetCallingAssembly().GetName().Name;



shadowsocks-csharp/Model/ForwardProxyConfig.cs → Shadowsocks.WPF/Models/ForwardProxyConfig.cs View File

@@ -1,6 +1,6 @@
using System;
using System;

namespace Shadowsocks.Model
namespace Shadowsocks.WPF.Models
{
[Serializable]
public class ForwardProxyConfig

shadowsocks-csharp/Model/HotKeyConfig.cs → Shadowsocks.WPF/Models/HotKeyConfig.cs View File

@@ -1,33 +1,33 @@
using System;
namespace Shadowsocks.Model
{
/*
* Format:
* <modifiers-combination>+<key>
*
*/
[Serializable]
public class HotkeyConfig
{
public string SwitchSystemProxy;
public string SwitchSystemProxyMode;
public string SwitchAllowLan;
public string ShowLogs;
public string ServerMoveUp;
public string ServerMoveDown;
public bool RegHotkeysAtStartup;
public HotkeyConfig()
{
SwitchSystemProxy = "";
SwitchSystemProxyMode = "";
SwitchAllowLan = "";
ShowLogs = "";
ServerMoveUp = "";
ServerMoveDown = "";
RegHotkeysAtStartup = false;
}
}
using System;
namespace Shadowsocks.WPF.Models
{
/*
* Format:
* <modifiers-combination>+<key>
*
*/
[Serializable]
public class HotkeyConfig
{
public string SwitchSystemProxy;
public string SwitchSystemProxyMode;
public string SwitchAllowLan;
public string ShowLogs;
public string ServerMoveUp;
public string ServerMoveDown;
public bool RegHotkeysAtStartup;
public HotkeyConfig()
{
SwitchSystemProxy = "";
SwitchSystemProxyMode = "";
SwitchAllowLan = "";
ShowLogs = "";
ServerMoveUp = "";
ServerMoveDown = "";
RegHotkeysAtStartup = false;
}
}
}

shadowsocks-csharp/Model/NlogConfig.cs → Shadowsocks.WPF/Models/NlogConfig.cs View File

@@ -1,137 +1,137 @@
using NLog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace Shadowsocks.Model
{
public class NLogConfig
{
public enum LogLevel
{
Fatal,
Error,
Warn,
Info,
Debug,
Trace,
}
private static string _NLOG_CONFIG_FILE_NAME=string.Empty;
public static string NLOG_CONFIG_FILE_NAME
{
get
{
if (string.IsNullOrEmpty(_NLOG_CONFIG_FILE_NAME))
{
_NLOG_CONFIG_FILE_NAME = Path.Combine(Environment.CurrentDirectory, "NLog.config");
}
return _NLOG_CONFIG_FILE_NAME;
}
}
const string TARGET_MIN_LEVEL_ATTRIBUTE = "minlevel";
const string LOGGER_FILE_NAME_ATTRIBUTE = "fileName";
XmlDocument doc = new XmlDocument();
XmlElement logFileNameElement;
XmlElement logLevelElement;
/// <summary>
/// Load the NLog config xml file content
/// </summary>
public static NLogConfig LoadXML()
{
NLogConfig config = new NLogConfig();
config.doc.Load(NLOG_CONFIG_FILE_NAME);
config.logLevelElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:logger[@name='*']");
config.logFileNameElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:target[@name='file']");
return config;
}
/// <summary>
/// Save the content to NLog config xml file
/// </summary>
public static void SaveXML(NLogConfig nLogConfig)
{
nLogConfig.doc.Save(NLOG_CONFIG_FILE_NAME);
}
/// <summary>
/// Get the current minLogLevel from xml file
/// </summary>
/// <returns></returns>
public LogLevel GetLogLevel()
{
LogLevel level = LogLevel.Warn;
string levelStr = logLevelElement.GetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE);
Enum.TryParse(levelStr, out level);
return level;
}
/// <summary>
/// Get the target fileName from xml file
/// </summary>
/// <returns></returns>
public string GetLogFileName()
{
return logFileNameElement.GetAttribute(LOGGER_FILE_NAME_ATTRIBUTE);
}
/// <summary>
/// Set the minLogLevel to xml file
/// </summary>
/// <param name="logLevel"></param>
public void SetLogLevel(LogLevel logLevel)
{
logLevelElement.SetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE, logLevel.ToString("G"));
}
/// <summary>
/// Set the target fileName to xml file
/// </summary>
/// <param name="fileName"></param>
public void SetLogFileName(string fileName)
{
logFileNameElement.SetAttribute(LOGGER_FILE_NAME_ATTRIBUTE, fileName);
}
/// <summary>
/// Select a single XML node/elemant
/// </summary>
/// <param name="doc"></param>
/// <param name="xpath"></param>
/// <returns></returns>
private static XmlNode SelectSingleNode(XmlDocument doc, string xpath)
{
XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable);
manager.AddNamespace("nlog", "http://www.nlog-project.org/schemas/NLog.xsd");
//return doc.SelectSingleNode("//nlog:logger[(@shadowsocks='managed') and (@name='*')]", manager);
return doc.SelectSingleNode(xpath, manager);
}
/// <summary>
/// Extract the pre-defined NLog configuration file is does not exist. Then reload the Nlog configuration.
/// </summary>
public static void TouchAndApplyNLogConfig()
{
if (!File.Exists(NLOG_CONFIG_FILE_NAME))
{
File.WriteAllText(NLOG_CONFIG_FILE_NAME, Properties.Resources.NLog_config);
LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME);
}
}
/// <summary>
/// NLog reload the config file and apply to current LogManager
/// </summary>
public static void LoadConfiguration()
{
LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME);
}
}
}
using NLog;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace Shadowsocks.WPF.Models
{
public class NLogConfig
{
public enum LogLevel
{
Fatal,
Error,
Warn,
Info,
Debug,
Trace,
}
private static string _NLOG_CONFIG_FILE_NAME=string.Empty;
public static string NLOG_CONFIG_FILE_NAME
{
get
{
if (string.IsNullOrEmpty(_NLOG_CONFIG_FILE_NAME))
{
_NLOG_CONFIG_FILE_NAME = Path.Combine(Environment.CurrentDirectory, "NLog.config");
}
return _NLOG_CONFIG_FILE_NAME;
}
}
const string TARGET_MIN_LEVEL_ATTRIBUTE = "minlevel";
const string LOGGER_FILE_NAME_ATTRIBUTE = "fileName";
XmlDocument doc = new XmlDocument();
XmlElement logFileNameElement;
XmlElement logLevelElement;
/// <summary>
/// Load the NLog config xml file content
/// </summary>
public static NLogConfig LoadXML()
{
NLogConfig config = new NLogConfig();
config.doc.Load(NLOG_CONFIG_FILE_NAME);
config.logLevelElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:logger[@name='*']");
config.logFileNameElement = (XmlElement)SelectSingleNode(config.doc, "//nlog:target[@name='file']");
return config;
}
/// <summary>
/// Save the content to NLog config xml file
/// </summary>
public static void SaveXML(NLogConfig nLogConfig)
{
nLogConfig.doc.Save(NLOG_CONFIG_FILE_NAME);
}
/// <summary>
/// Get the current minLogLevel from xml file
/// </summary>
/// <returns></returns>
public LogLevel GetLogLevel()
{
LogLevel level = LogLevel.Warn;
string levelStr = logLevelElement.GetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE);
Enum.TryParse(levelStr, out level);
return level;
}
/// <summary>
/// Get the target fileName from xml file
/// </summary>
/// <returns></returns>
public string GetLogFileName()
{
return logFileNameElement.GetAttribute(LOGGER_FILE_NAME_ATTRIBUTE);
}
/// <summary>
/// Set the minLogLevel to xml file
/// </summary>
/// <param name="logLevel"></param>
public void SetLogLevel(LogLevel logLevel)
{
logLevelElement.SetAttribute(TARGET_MIN_LEVEL_ATTRIBUTE, logLevel.ToString("G"));
}
/// <summary>
/// Set the target fileName to xml file
/// </summary>
/// <param name="fileName"></param>
public void SetLogFileName(string fileName)
{
logFileNameElement.SetAttribute(LOGGER_FILE_NAME_ATTRIBUTE, fileName);
}
/// <summary>
/// Select a single XML node/elemant
/// </summary>
/// <param name="doc"></param>
/// <param name="xpath"></param>
/// <returns></returns>
private static XmlNode SelectSingleNode(XmlDocument doc, string xpath)
{
XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable);
manager.AddNamespace("nlog", "http://www.nlog-project.org/schemas/NLog.xsd");
//return doc.SelectSingleNode("//nlog:logger[(@shadowsocks='managed') and (@name='*')]", manager);
return doc.SelectSingleNode(xpath, manager);
}
/// <summary>
/// Extract the pre-defined NLog configuration file is does not exist. Then reload the Nlog configuration.
/// </summary>
public static void TouchAndApplyNLogConfig()
{
if (!File.Exists(NLOG_CONFIG_FILE_NAME))
{
File.WriteAllText(NLOG_CONFIG_FILE_NAME, Properties.Resources.NLog_config);
LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME);
}
}
/// <summary>
/// NLog reload the config file and apply to current LogManager
/// </summary>
public static void LoadConfiguration()
{
LogManager.LoadConfiguration(NLOG_CONFIG_FILE_NAME);
}
}
}

shadowsocks-csharp/Model/Server.cs → Shadowsocks.WPF/Models/Server.cs View File

@@ -1,252 +1,252 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using Shadowsocks.Controller;
using System.Text.RegularExpressions;
using System.Linq;
using Newtonsoft.Json;
using System.ComponentModel;
namespace Shadowsocks.Model
{
[Serializable]
public class Server
{
public const string DefaultMethod = "chacha20-ietf-poly1305";
public const int DefaultPort = 8388;
#region ParseLegacyURL
private static readonly Regex UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase);
private static readonly Regex DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase);
#endregion ParseLegacyURL
private const int DefaultServerTimeoutSec = 5;
public const int MaxServerTimeoutSec = 20;
public string server;
public int server_port;
public string password;
public string method;
// optional fields
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string plugin;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string plugin_opts;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string plugin_args;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string remarks;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string group;
public int timeout;
public override int GetHashCode()
{
return server.GetHashCode() ^ server_port;
}
public override bool Equals(object obj) => obj is Server o2 && server == o2.server && server_port == o2.server_port;
public override string ToString()
{
if (string.IsNullOrEmpty(server))
{
return I18N.GetString("New server");
}
string serverStr = $"{FormalHostName}:{server_port}";
return string.IsNullOrEmpty(remarks)
? serverStr
: $"{remarks} ({serverStr})";
}
public string GetURL(bool legacyUrl = false)
{
if (legacyUrl && string.IsNullOrWhiteSpace(plugin))
{
// For backwards compatiblity, if no plugin, use old url format
string p = $"{method}:{password}@{server}:{server_port}";
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(p));
return string.IsNullOrEmpty(remarks)
? $"ss://{base64}"
: $"ss://{base64}#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}";
}
UriBuilder u = new UriBuilder("ss", null);
string b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{method}:{password}"));
u.UserName = b64.Replace('+', '-').Replace('/', '_').TrimEnd('=');
u.Host = server;
u.Port = server_port;
u.Fragment = HttpUtility.UrlEncode(remarks, Encoding.UTF8);
if (!string.IsNullOrWhiteSpace(plugin))
{
NameValueCollection param = HttpUtility.ParseQueryString("");
string pluginPart = plugin;
if (!string.IsNullOrWhiteSpace(plugin_opts))
{
pluginPart += ";" + plugin_opts;
}
param["plugin"] = pluginPart;
u.Query = param.ToString();
}
return u.ToString();
}
[JsonIgnore]
public string FormalHostName
{
get
{
// CheckHostName() won't do a real DNS lookup
return (Uri.CheckHostName(server)) switch
{
// Add square bracket when IPv6 (RFC3986)
UriHostNameType.IPv6 => $"[{server}]",
// IPv4 or domain name
_ => server,
};
}
}
public Server()
{
server = "";
server_port = DefaultPort;
method = DefaultMethod;
plugin = "";
plugin_opts = "";
plugin_args = "";
password = "";
remarks = "";
timeout = DefaultServerTimeoutSec;
}
private static Server ParseLegacyURL(string ssURL)
{
var match = UrlFinder.Match(ssURL);
if (!match.Success)
return null;
Server server = new Server();
var base64 = match.Groups["base64"].Value.TrimEnd('/');
var tag = match.Groups["tag"].Value;
if (!string.IsNullOrEmpty(tag))
{
server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8);
}
Match details;
try
{
details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String(
base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))));
}
catch (FormatException)
{
return null;
}
if (!details.Success)
return null;
server.method = details.Groups["method"].Value;
server.password = details.Groups["password"].Value;
server.server = details.Groups["hostname"].Value;
server.server_port = int.Parse(details.Groups["port"].Value);
return server;
}
public static Server ParseURL(string serverUrl)
{
string _serverUrl = serverUrl.Trim();
if (!_serverUrl.StartsWith("ss://", StringComparison.InvariantCultureIgnoreCase))
{
return null;
}
Server legacyServer = ParseLegacyURL(serverUrl);
if (legacyServer != null) //legacy
{
return legacyServer;
}
else //SIP002
{
Uri parsedUrl;
try
{
parsedUrl = new Uri(serverUrl);
}
catch (UriFormatException)
{
return null;
}
Server server = new Server
{
remarks = HttpUtility.UrlDecode(parsedUrl.GetComponents(
UriComponents.Fragment, UriFormat.Unescaped), Encoding.UTF8),
server = parsedUrl.IdnHost,
server_port = parsedUrl.Port,
};
// parse base64 UserInfo
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64
string userInfo;
try
{
userInfo = Encoding.UTF8.GetString(Convert.FromBase64String(
base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')));
}
catch (FormatException)
{
return null;
}
string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2);
if (userInfoParts.Length != 2)
{
return null;
}
server.method = userInfoParts[0];
server.password = userInfoParts[1];
NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query);
string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2);
if (pluginParts.Length > 0)
{
server.plugin = pluginParts[0] ?? "";
}
if (pluginParts.Length > 1)
{
server.plugin_opts = pluginParts[1] ?? "";
}
return server;
}
}
public static List<Server> GetServers(string ssURL)
{
return ssURL
.Split('\r', '\n', ' ')
.Select(u => ParseURL(u))
.Where(s => s != null)
.ToList();
}
public string Identifier()
{
return server + ':' + server_port;
}
}
}
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Web;
using Shadowsocks.Controller;
using System.Text.RegularExpressions;
using System.Linq;
using Newtonsoft.Json;
using System.ComponentModel;
namespace Shadowsocks.WPF.Models
{
[Serializable]
public class Server
{
public const string DefaultMethod = "chacha20-ietf-poly1305";
public const int DefaultPort = 8388;
#region ParseLegacyURL
private static readonly Regex UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase);
private static readonly Regex DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase);
#endregion ParseLegacyURL
private const int DefaultServerTimeoutSec = 5;
public const int MaxServerTimeoutSec = 20;
public string server;
public int server_port;
public string password;
public string method;
// optional fields
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string plugin;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string plugin_opts;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string plugin_args;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string remarks;
[DefaultValue("")]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
public string group;
public int timeout;
public override int GetHashCode()
{
return server.GetHashCode() ^ server_port;
}
public override bool Equals(object obj) => obj is Server o2 && server == o2.server && server_port == o2.server_port;
public override string ToString()
{
if (string.IsNullOrEmpty(server))
{
return I18N.GetString("New server");
}
string serverStr = $"{FormalHostName}:{server_port}";
return string.IsNullOrEmpty(remarks)
? serverStr
: $"{remarks} ({serverStr})";
}
public string GetURL(bool legacyUrl = false)
{
if (legacyUrl && string.IsNullOrWhiteSpace(plugin))
{
// For backwards compatiblity, if no plugin, use old url format
string p = $"{method}:{password}@{server}:{server_port}";
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(p));
return string.IsNullOrEmpty(remarks)
? $"ss://{base64}"
: $"ss://{base64}#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}";
}
UriBuilder u = new UriBuilder("ss", null);
string b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{method}:{password}"));
u.UserName = b64.Replace('+', '-').Replace('/', '_').TrimEnd('=');
u.Host = server;
u.Port = server_port;
u.Fragment = HttpUtility.UrlEncode(remarks, Encoding.UTF8);
if (!string.IsNullOrWhiteSpace(plugin))
{
NameValueCollection param = HttpUtility.ParseQueryString("");
string pluginPart = plugin;
if (!string.IsNullOrWhiteSpace(plugin_opts))
{
pluginPart += ";" + plugin_opts;
}
param["plugin"] = pluginPart;
u.Query = param.ToString();
}
return u.ToString();
}
[JsonIgnore]
public string FormalHostName
{
get
{
// CheckHostName() won't do a real DNS lookup
return (Uri.CheckHostName(server)) switch
{
// Add square bracket when IPv6 (RFC3986)
UriHostNameType.IPv6 => $"[{server}]",
// IPv4 or domain name
_ => server,
};
}
}
public Server()
{
server = "";
server_port = DefaultPort;
method = DefaultMethod;
plugin = "";
plugin_opts = "";
plugin_args = "";
password = "";
remarks = "";
timeout = DefaultServerTimeoutSec;
}
private static Server ParseLegacyURL(string ssURL)
{
var match = UrlFinder.Match(ssURL);
if (!match.Success)
return null;
Server server = new Server();
var base64 = match.Groups["base64"].Value.TrimEnd('/');
var tag = match.Groups["tag"].Value;
if (!string.IsNullOrEmpty(tag))
{
server.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8);
}
Match details;
try
{
details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String(
base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))));
}
catch (FormatException)
{
return null;
}
if (!details.Success)
return null;
server.method = details.Groups["method"].Value;
server.password = details.Groups["password"].Value;
server.server = details.Groups["hostname"].Value;
server.server_port = int.Parse(details.Groups["port"].Value);
return server;
}
public static Server ParseURL(string serverUrl)
{
string _serverUrl = serverUrl.Trim();
if (!_serverUrl.StartsWith("ss://", StringComparison.InvariantCultureIgnoreCase))
{
return null;
}
Server legacyServer = ParseLegacyURL(serverUrl);
if (legacyServer != null) //legacy
{
return legacyServer;
}
else //SIP002
{
Uri parsedUrl;
try
{
parsedUrl = new Uri(serverUrl);
}
catch (UriFormatException)
{
return null;
}
Server server = new Server
{
remarks = HttpUtility.UrlDecode(parsedUrl.GetComponents(
UriComponents.Fragment, UriFormat.Unescaped), Encoding.UTF8),
server = parsedUrl.IdnHost,
server_port = parsedUrl.Port,
};
// parse base64 UserInfo
string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64
string userInfo;
try
{
userInfo = Encoding.UTF8.GetString(Convert.FromBase64String(
base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '=')));
}
catch (FormatException)
{
return null;
}
string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2);
if (userInfoParts.Length != 2)
{
return null;
}
server.method = userInfoParts[0];
server.password = userInfoParts[1];
NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query);
string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2);
if (pluginParts.Length > 0)
{
server.plugin = pluginParts[0] ?? "";
}
if (pluginParts.Length > 1)
{
server.plugin_opts = pluginParts[1] ?? "";
}
return server;
}
}
public static List<Server> GetServers(string ssURL)
{
return ssURL
.Split('\r', '\n', ' ')
.Select(u => ParseURL(u))
.Where(s => s != null)
.ToList();
}
public string Identifier()
{
return server + ':' + server_port;
}
}
}

+ 16
- 0
Shadowsocks.WPF/Models/Settings.cs View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Shadowsocks.WPF.Models
{
public class Settings
{
public Settings()
{

}


}
}

Shadowsocks/Assets/NLog.config → Shadowsocks.WPF/Resources/NLog.config View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Bold.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-BoldItalic.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLight.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ExtraLightItalic.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Italic.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Light.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-LightItalic.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Medium.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-MediumItalic.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Regular.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBold.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-SemiBoldItalic.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-Thin.ttf View File


BIN
Shadowsocks.WPF/Resources/RobotoMono/RobotoMono-ThinItalic.ttf View File


Shadowsocks/Assets/privoxy.exe.gz → Shadowsocks.WPF/Resources/privoxy.exe.gz View File


Shadowsocks/Assets/privoxy_conf.txt → Shadowsocks.WPF/Resources/privoxy_conf.txt View File


Shadowsocks.WPF/Assets/shadowsocks.ico → Shadowsocks.WPF/Resources/shadowsocks.ico View File


Shadowsocks.WPF/Assets/ss128.pdn → Shadowsocks.WPF/Resources/ss128.pdn View File


Shadowsocks.WPF/Assets/ss32.pdn → Shadowsocks.WPF/Resources/ss32.pdn View File


Shadowsocks.WPF/Assets/ss32Fill.png → Shadowsocks.WPF/Resources/ss32Fill.png View File


Shadowsocks.WPF/Assets/ss32In.png → Shadowsocks.WPF/Resources/ss32In.png View File


Shadowsocks.WPF/Assets/ss32Out.png → Shadowsocks.WPF/Resources/ss32Out.png View File


Shadowsocks.WPF/Assets/ss32Outline.png → Shadowsocks.WPF/Resources/ss32Outline.png View File


Shadowsocks.WPF/Assets/ssw128.png → Shadowsocks.WPF/Resources/ssw128.png View File


shadowsocks-csharp/Controller/Service/PortForwarder.cs → Shadowsocks.WPF/Services/PortForwarder.cs View File

@@ -1,277 +1,276 @@
using System;
using System.Net;
using System.Net.Sockets;
using NLog;
using Shadowsocks.Util.Sockets;
namespace Shadowsocks.Controller
{
class PortForwarder : StreamService
{
private readonly int _targetPort;
public PortForwarder(int targetPort)
{
_targetPort = targetPort;
}
public override bool Handle(CachedNetworkStream stream, object state)
{
byte[] fp = new byte[256];
int len = stream.ReadFirstBlock(fp);
if (stream.Socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
new Handler().Start(fp, len, stream.Socket, _targetPort);
return true;
}
[Obsolete]
public override bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
new Handler().Start(firstPacket, length, socket, _targetPort);
return true;
}
private class Handler
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private byte[] _firstPacket;
private int _firstPacketLength;
private Socket _local;
private Socket _remote;
private bool _closed = false;
private bool _localShutdown = false;
private bool _remoteShutdown = false;
private const int RecvSize = 2048;
// remote receive buffer
private byte[] remoteRecvBuffer = new byte[RecvSize];
// connection receive buffer
private byte[] connetionRecvBuffer = new byte[RecvSize];
// instance-based lock
private readonly object _Lock = new object();
public void Start(byte[] firstPacket, int length, Socket socket, int targetPort)
{
_firstPacket = firstPacket;
_firstPacketLength = length;
_local = socket;
try
{
// Local Port Forward use IP as is
EndPoint remoteEP = SocketUtil.GetEndPoint(_local.AddressFamily == AddressFamily.InterNetworkV6 ? "[::1]" : "127.0.0.1", targetPort);
// Connect to the remote endpoint.
_remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
_remote.BeginConnect(remoteEP, ConnectCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void ConnectCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_remote.EndConnect(ar);
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
HandshakeReceive();
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void HandshakeReceive()
{
if (_closed)
{
return;
}
try
{
_remote.BeginSend(_firstPacket, 0, _firstPacketLength, 0, StartPipe, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void StartPipe(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_remote.EndSend(ar);
_remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0,
PipeRemoteReceiveCallback, null);
_local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0,
PipeConnectionReceiveCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void PipeRemoteReceiveCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
int bytesRead = _remote.EndReceive(ar);
if (bytesRead > 0)
{
_local.BeginSend(remoteRecvBuffer, 0, bytesRead, 0, PipeConnectionSendCallback, null);
}
else
{
_local.Shutdown(SocketShutdown.Send);
_localShutdown = true;
CheckClose();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void PipeConnectionReceiveCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
int bytesRead = _local.EndReceive(ar);
if (bytesRead > 0)
{
_remote.BeginSend(connetionRecvBuffer, 0, bytesRead, 0, PipeRemoteSendCallback, null);
}
else
{
_remote.Shutdown(SocketShutdown.Send);
_remoteShutdown = true;
CheckClose();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void PipeRemoteSendCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_remote.EndSend(ar);
_local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0,
PipeConnectionReceiveCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void PipeConnectionSendCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_local.EndSend(ar);
_remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0,
PipeRemoteReceiveCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}
private void CheckClose()
{
if (_localShutdown && _remoteShutdown)
{
Close();
}
}
public void Close()
{
lock (_Lock)
{
if (_closed)
{
return;
}
_closed = true;
}
if (_local != null)
{
try
{
_local.Shutdown(SocketShutdown.Both);
_local.Close();
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
if (_remote != null)
{
try
{
_remote.Shutdown(SocketShutdown.Both);
_remote.Dispose();
}
catch (SocketException e)
{
logger.LogUsefulException(e);
}
}
}
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using NLog;
using Shadowsocks.Net;

namespace Shadowsocks.WPF.Services
{
public class PortForwarder : StreamService
{
private readonly int _targetPort;

public PortForwarder(int targetPort)
{
_targetPort = targetPort;
}

public override bool Handle(CachedNetworkStream stream, object state)
{
byte[] fp = new byte[256];
int len = stream.ReadFirstBlock(fp);

if (stream.Socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
new Handler().Start(fp, len, stream.Socket, _targetPort);
return true;
}

[Obsolete]
public override bool Handle(byte[] firstPacket, int length, Socket socket, object state)
{
if (socket.ProtocolType != ProtocolType.Tcp)
{
return false;
}
new Handler().Start(firstPacket, length, socket, _targetPort);
return true;
}

private class Handler
{
private static Logger logger = LogManager.GetCurrentClassLogger();

private byte[] _firstPacket;
private int _firstPacketLength;
private Socket _local;
private Socket _remote;
private bool _closed = false;
private bool _localShutdown = false;
private bool _remoteShutdown = false;
private const int RecvSize = 2048;
// remote receive buffer
private byte[] remoteRecvBuffer = new byte[RecvSize];
// connection receive buffer
private byte[] connetionRecvBuffer = new byte[RecvSize];

// instance-based lock
private readonly object _Lock = new object();

public void Start(byte[] firstPacket, int length, Socket socket, int targetPort)
{
_firstPacket = firstPacket;
_firstPacketLength = length;
_local = socket;
try
{
// Local Port Forward use IP as is
EndPoint remoteEP = new IPEndPoint(_local.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, targetPort);
// Connect to the remote endpoint.
_remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
_remote.BeginConnect(remoteEP, ConnectCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void ConnectCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_remote.EndConnect(ar);
_remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
HandshakeReceive();
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void HandshakeReceive()
{
if (_closed)
{
return;
}
try
{
_remote.BeginSend(_firstPacket, 0, _firstPacketLength, 0, StartPipe, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void StartPipe(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_remote.EndSend(ar);
_remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0,
PipeRemoteReceiveCallback, null);
_local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0,
PipeConnectionReceiveCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void PipeRemoteReceiveCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
int bytesRead = _remote.EndReceive(ar);
if (bytesRead > 0)
{
_local.BeginSend(remoteRecvBuffer, 0, bytesRead, 0, PipeConnectionSendCallback, null);
}
else
{
_local.Shutdown(SocketShutdown.Send);
_localShutdown = true;
CheckClose();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void PipeConnectionReceiveCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
int bytesRead = _local.EndReceive(ar);
if (bytesRead > 0)
{
_remote.BeginSend(connetionRecvBuffer, 0, bytesRead, 0, PipeRemoteSendCallback, null);
}
else
{
_remote.Shutdown(SocketShutdown.Send);
_remoteShutdown = true;
CheckClose();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void PipeRemoteSendCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_remote.EndSend(ar);
_local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0,
PipeConnectionReceiveCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void PipeConnectionSendCallback(IAsyncResult ar)
{
if (_closed)
{
return;
}
try
{
_local.EndSend(ar);
_remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0,
PipeRemoteReceiveCallback, null);
}
catch (Exception e)
{
logger.LogUsefulException(e);
Close();
}
}

private void CheckClose()
{
if (_localShutdown && _remoteShutdown)
{
Close();
}
}

public void Close()
{
lock (_Lock)
{
if (_closed)
{
return;
}
_closed = true;
}
if (_local != null)
{
try
{
_local.Shutdown(SocketShutdown.Both);
_local.Close();
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
if (_remote != null)
{
try
{
_remote.Shutdown(SocketShutdown.Both);
_remote.Dispose();
}
catch (SocketException e)
{
logger.LogUsefulException(e);
}
}
}
}
}
}

shadowsocks-csharp/Controller/Service/PrivoxyRunner.cs → Shadowsocks.WPF/Services/PrivoxyRunner.cs View File

@@ -1,170 +1,162 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using NLog;
using Shadowsocks.Model;
using Shadowsocks.Properties;
using Shadowsocks.Util;
using Shadowsocks.Util.ProcessManagement;
namespace Shadowsocks.Controller
{
class PrivoxyRunner
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private static int _uid;
private static string _uniqueConfigFile;
private static Job _privoxyJob;
private Process _process;
private int _runningPort;
static PrivoxyRunner()
{
try
{
_uid = Program.WorkingDirectory.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance.
_uniqueConfigFile = $"privoxy_{_uid}.conf";
_privoxyJob = new Job();
FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe);
}
catch (IOException e)
{
logger.LogUsefulException(e);
}
}
public int RunningPort => _runningPort;
public void Start(Configuration configuration)
{
if (_process == null)
{
Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy");
foreach (Process p in existingPrivoxy.Where(IsChildProcess))
{
KillProcess(p);
}
string privoxyConfig = Resources.privoxy_conf;
_runningPort = GetFreePort(configuration.isIPv6Enabled);
privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString());
privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString());
privoxyConfig = configuration.isIPv6Enabled
? privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "[::]" : "[::1]")
.Replace("__SOCKS_HOST__", "[::1]")
: privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1")
.Replace("__SOCKS_HOST__", "127.0.0.1");
FileManager.ByteArrayToFile(Utils.GetTempPath(_uniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig));
_process = new Process
{
// Configure the process using the StartInfo properties.
StartInfo =
{
FileName = "ss_privoxy.exe",
Arguments = _uniqueConfigFile,
WorkingDirectory = Utils.GetTempPath(),
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
CreateNoWindow = true
}
};
_process.Start();
/*
* Add this process to job obj associated with this ss process, so that
* when ss exit unexpectedly, this process will be forced killed by system.
*/
_privoxyJob.AddProcess(_process.Handle);
}
}
public void Stop()
{
if (_process != null)
{
KillProcess(_process);
_process.Dispose();
_process = null;
}
}
private static void KillProcess(Process p)
{
try
{
p.CloseMainWindow();
p.WaitForExit(100);
if (!p.HasExited)
{
p.Kill();
p.WaitForExit();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
/*
* We won't like to kill other ss instances' ss_privoxy.exe.
* This function will check whether the given process is created
* by this process by checking the module path or command line.
*
* Since it's required to put ss in different dirs to run muti instances,
* different instance will create their unique "privoxy_UID.conf" where
* UID is hash of ss's location.
*/
private static bool IsChildProcess(Process process)
{
try
{
/*
* Under PortableMode, we could identify it by the path of ss_privoxy.exe.
*/
var path = process.MainModule.FileName;
return Utils.GetTempPath("ss_privoxy.exe").Equals(path);
}
catch (Exception ex)
{
/*
* Sometimes Process.GetProcessesByName will return some processes that
* are already dead, and that will cause exceptions here.
* We could simply ignore those exceptions.
*/
logger.LogUsefulException(ex);
return false;
}
}
private int GetFreePort(bool isIPv6 = false)
{
int defaultPort = 8123;
try
{
// TCP stack please do me a favor
TcpListener l = new TcpListener(isIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0);
l.Start();
var port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
catch (Exception e)
{
// in case access denied
logger.LogUsefulException(e);
return defaultPort;
}
}
}
}
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using NLog;
using Shadowsocks.Model;
using Shadowsocks.Properties;
using Shadowsocks.Util;
using Shadowsocks.Util.ProcessManagement;

namespace Shadowsocks.WPF.Services
{
class PrivoxyRunner
{
private static Logger logger = LogManager.GetCurrentClassLogger();

private static int _uid;
private static string _uniqueConfigFile;
private Process _process;
private int _runningPort;

static PrivoxyRunner()
{
try
{
_uid = Program.WorkingDirectory.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance.
_uniqueConfigFile = $"privoxy_{_uid}.conf";

FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe);
}
catch (IOException e)
{
logger.LogUsefulException(e);
}
}

public int RunningPort => _runningPort;

public void Start(Configuration configuration)
{
if (_process == null)
{
Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy");
foreach (Process p in existingPrivoxy.Where(IsChildProcess))
{
KillProcess(p);
}
string privoxyConfig = Resources.privoxy_conf;
_runningPort = GetFreePort(configuration.isIPv6Enabled);
privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString());
privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString());
privoxyConfig = configuration.isIPv6Enabled
? privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "[::]" : "[::1]")
.Replace("__SOCKS_HOST__", "[::1]")
: privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1")
.Replace("__SOCKS_HOST__", "127.0.0.1");
FileManager.ByteArrayToFile(Utils.GetTempPath(_uniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig));

_process = new Process
{
// Configure the process using the StartInfo properties.
StartInfo =
{
FileName = "ss_privoxy.exe",
Arguments = _uniqueConfigFile,
WorkingDirectory = Utils.GetTempPath(),
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
CreateNoWindow = true
}
};
_process.Start();
}
}

public void Stop()
{
if (_process != null)
{
KillProcess(_process);
_process.Dispose();
_process = null;
}
}

private static void KillProcess(Process p)
{
try
{
p.CloseMainWindow();
p.WaitForExit(100);
if (!p.HasExited)
{
p.Kill();
p.WaitForExit();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}

/*
* We won't like to kill other ss instances' ss_privoxy.exe.
* This function will check whether the given process is created
* by this process by checking the module path or command line.
*
* Since it's required to put ss in different dirs to run muti instances,
* different instance will create their unique "privoxy_UID.conf" where
* UID is hash of ss's location.
*/

private static bool IsChildProcess(Process process)
{
try
{
/*
* Under PortableMode, we could identify it by the path of ss_privoxy.exe.
*/
var path = process.MainModule.FileName;

return Utils.GetTempPath("ss_privoxy.exe").Equals(path);

}
catch (Exception ex)
{
/*
* Sometimes Process.GetProcessesByName will return some processes that
* are already dead, and that will cause exceptions here.
* We could simply ignore those exceptions.
*/
logger.LogUsefulException(ex);
return false;
}
}

private int GetFreePort(bool isIPv6 = false)
{
int defaultPort = 8123;
try
{
// TCP stack please do me a favor
TcpListener l = new TcpListener(isIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0);
l.Start();
var port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
catch (Exception e)
{
// in case access denied
logger.LogUsefulException(e);
return defaultPort;
}
}
}
}

shadowsocks-csharp/Controller/Service/Sip003Plugin.cs → Shadowsocks.WPF/Services/Sip003Plugin.cs View File

@@ -1,177 +1,174 @@
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using Shadowsocks.Model;
using Shadowsocks.Util.ProcessManagement;
namespace Shadowsocks.Controller.Service
{
// https://github.com/shadowsocks/shadowsocks-org/wiki/Plugin
public sealed class Sip003Plugin : IDisposable
{
public IPEndPoint LocalEndPoint { get; private set; }
public int ProcessId => _started ? _pluginProcess.Id : 0;
private readonly object _startProcessLock = new object();
private readonly Job _pluginJob;
private readonly Process _pluginProcess;
private bool _started;
private bool _disposed;
public static Sip003Plugin CreateIfConfigured(Server server, bool showPluginOutput)
{
if (server == null)
{
throw new ArgumentNullException(nameof(server));
}
if (string.IsNullOrWhiteSpace(server.plugin))
{
return null;
}
return new Sip003Plugin(
server.plugin,
server.plugin_opts,
server.plugin_args,
server.server,
server.server_port,
showPluginOutput);
}
private Sip003Plugin(string plugin, string pluginOpts, string pluginArgs, string serverAddress, int serverPort, bool showPluginOutput)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (string.IsNullOrWhiteSpace(serverAddress))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(serverAddress));
}
if (serverPort <= 0 || serverPort > 65535)
{
throw new ArgumentOutOfRangeException("serverPort");
}
_pluginProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = plugin,
Arguments = pluginArgs,
UseShellExecute = false,
CreateNoWindow = !showPluginOutput,
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Program.WorkingDirectory ?? Environment.CurrentDirectory,
Environment =
{
["SS_REMOTE_HOST"] = serverAddress,
["SS_REMOTE_PORT"] = serverPort.ToString(),
["SS_PLUGIN_OPTIONS"] = pluginOpts
}
}
};
_pluginJob = new Job();
}
public bool StartIfNeeded()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
lock (_startProcessLock)
{
if (_started && !_pluginProcess.HasExited)
{
return false;
}
var localPort = GetNextFreeTcpPort();
LocalEndPoint = new IPEndPoint(IPAddress.Loopback, localPort);
_pluginProcess.StartInfo.Environment["SS_LOCAL_HOST"] = LocalEndPoint.Address.ToString();
_pluginProcess.StartInfo.Environment["SS_LOCAL_PORT"] = LocalEndPoint.Port.ToString();
_pluginProcess.StartInfo.Arguments = ExpandEnvironmentVariables(_pluginProcess.StartInfo.Arguments, _pluginProcess.StartInfo.EnvironmentVariables);
try
{
_pluginProcess.Start();
}
catch (System.ComponentModel.Win32Exception ex)
{
// do not use File.Exists(...), it can not handle the scenarios when the plugin file is in system environment path.
// https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values
//if ((uint)ex.ErrorCode == 0x80004005)
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d
if (ex.NativeErrorCode == 0x00000002)
{
throw new FileNotFoundException(I18N.GetString("Cannot find the plugin program file"), _pluginProcess.StartInfo.FileName, ex);
}
throw new ApplicationException(I18N.GetString("Plugin Program"), ex);
}
_pluginJob.AddProcess(_pluginProcess.Handle);
_started = true;
}
return true;
}
public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null)
{
name = name.ToLower();
// Expand the environment variables from the new process itself
if (environmentVariables != null)
{
foreach(string key in environmentVariables.Keys)
{
name = name.Replace($"%{key.ToLower()}%", environmentVariables[key]);
}
}
// Also expand the environment variables from current main process (system)
name = Environment.ExpandEnvironmentVariables(name);
return name;
}
static int GetNextFreeTcpPort()
{
var l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
int port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
public void Dispose()
{
if (_disposed)
{
return;
}
try
{
if (!_pluginProcess.HasExited)
{
_pluginProcess.Kill();
_pluginProcess.WaitForExit();
}
}
catch (Exception) { }
finally
{
try
{
_pluginProcess.Dispose();
_pluginJob.Dispose();
}
catch (Exception) { }
_disposed = true;
}
}
}
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using Shadowsocks.Model;
using Shadowsocks.Util.ProcessManagement;

namespace Shadowsocks.WPF.Services
{
// https://github.com/shadowsocks/shadowsocks-org/wiki/Plugin
public sealed class Sip003Plugin : IDisposable
{
public IPEndPoint LocalEndPoint { get; private set; }
public int ProcessId => _started ? _pluginProcess.Id : 0;

private readonly object _startProcessLock = new object();
private readonly Process _pluginProcess;
private bool _started;
private bool _disposed;

public static Sip003Plugin CreateIfConfigured(Server server, bool showPluginOutput)
{
if (server == null)
{
throw new ArgumentNullException(nameof(server));
}

if (string.IsNullOrWhiteSpace(server.plugin))
{
return null;
}

return new Sip003Plugin(
server.plugin,
server.plugin_opts,
server.plugin_args,
server.server,
server.server_port,
showPluginOutput);
}

private Sip003Plugin(string plugin, string pluginOpts, string pluginArgs, string serverAddress, int serverPort, bool showPluginOutput)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (string.IsNullOrWhiteSpace(serverAddress))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(serverAddress));
}
if (serverPort <= 0 || serverPort > 65535)
{
throw new ArgumentOutOfRangeException("serverPort");
}

_pluginProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = plugin,
Arguments = pluginArgs,
UseShellExecute = false,
CreateNoWindow = !showPluginOutput,
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Program.WorkingDirectory ?? Environment.CurrentDirectory,
Environment =
{
["SS_REMOTE_HOST"] = serverAddress,
["SS_REMOTE_PORT"] = serverPort.ToString(),
["SS_PLUGIN_OPTIONS"] = pluginOpts
}
}
};
}

public bool StartIfNeeded()
{
if (_disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}

lock (_startProcessLock)
{
if (_started && !_pluginProcess.HasExited)
{
return false;
}

var localPort = GetNextFreeTcpPort();
LocalEndPoint = new IPEndPoint(IPAddress.Loopback, localPort);

_pluginProcess.StartInfo.Environment["SS_LOCAL_HOST"] = LocalEndPoint.Address.ToString();
_pluginProcess.StartInfo.Environment["SS_LOCAL_PORT"] = LocalEndPoint.Port.ToString();
_pluginProcess.StartInfo.Arguments = ExpandEnvironmentVariables(_pluginProcess.StartInfo.Arguments, _pluginProcess.StartInfo.EnvironmentVariables);
try
{
_pluginProcess.Start();
}
catch (System.ComponentModel.Win32Exception ex)
{
// do not use File.Exists(...), it can not handle the scenarios when the plugin file is in system environment path.
// https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values
//if ((uint)ex.ErrorCode == 0x80004005)
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d
if (ex.NativeErrorCode == 0x00000002)
{
throw new FileNotFoundException(I18N.GetString("Cannot find the plugin program file"), _pluginProcess.StartInfo.FileName, ex);
}
throw new ApplicationException(I18N.GetString("Plugin Program"), ex);
}
_pluginJob.AddProcess(_pluginProcess.Handle);
_started = true;
}

return true;
}

public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null)
{
name = name.ToLower();
// Expand the environment variables from the new process itself
if (environmentVariables != null)
{
foreach(string key in environmentVariables.Keys)
{
name = name.Replace($"%{key.ToLower()}%", environmentVariables[key]);
}
}
// Also expand the environment variables from current main process (system)
name = Environment.ExpandEnvironmentVariables(name);
return name;
}

static int GetNextFreeTcpPort()
{
var l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
int port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}

public void Dispose()
{
if (_disposed)
{
return;
}

try
{
if (!_pluginProcess.HasExited)
{
_pluginProcess.Kill();
_pluginProcess.WaitForExit();
}
}
catch (Exception) { }
finally
{
try
{
_pluginProcess.Dispose();
_pluginJob.Dispose();
}
catch (Exception) { }

_disposed = true;
}
}
}
}

shadowsocks-csharp/Util/SystemProxy/RAS.cs → Shadowsocks.WPF/Services/SystemProxy/RAS.cs View File

@@ -1,20 +1,19 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Shadowsocks.Util.SystemProxy
namespace Shadowsocks.WPF.Services.SystemProxy
{

enum RasFieldSizeConst
public enum RasFieldSizeConst
{
MaxEntryName = 256,
MaxPath = 260,
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct RasEntryName
public struct RasEntryName
{
public int dwSize;

@@ -26,7 +25,8 @@ namespace Shadowsocks.Util.SystemProxy
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = RAS.MaxPath + 1)]
public string szPhonebookPath;
}
class RAS

public class RAS
{
public const int MaxEntryName = 256;
public const int MaxPath = 260;

shadowsocks-csharp/Util/SystemProxy/WinINet.cs → Shadowsocks.WPF/Services/SystemProxy/WinINet.cs View File

@@ -1,11 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using NLog;

namespace Shadowsocks.Util.SystemProxy
namespace Shadowsocks.WPF.Services.SystemProxy
{
#region Windows API data structure definition
public enum InternetOptions

shadowsocks-csharp/Controller/Service/UpdateChecker.cs → Shadowsocks.WPF/Services/UpdateChecker.cs View File

@@ -1,178 +1,178 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using Newtonsoft.Json.Linq;
using NLog;
using Shadowsocks.Localization;
using Shadowsocks.Model;
using Shadowsocks.Util;
using Shadowsocks.Views;
namespace Shadowsocks.Controller
{
public class UpdateChecker
{
private readonly Logger logger;
private readonly HttpClient httpClient;
// https://developer.github.com/v3/repos/releases/
private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases";
private Configuration _config;
private Window versionUpdatePromptWindow;
private JToken _releaseObject;
public string NewReleaseVersion { get; private set; }
public string NewReleaseZipFilename { get; private set; }
public event EventHandler CheckUpdateCompleted;
public static readonly string Version = Assembly.GetEntryAssembly().GetName().Version.ToString();
private readonly Version _version;
public UpdateChecker()
{
logger = LogManager.GetCurrentClassLogger();
httpClient = Program.MainController.GetHttpClient();
_version = new Version(Version);
_config = Program.MainController.GetCurrentConfiguration();
}
/// <summary>
/// Checks for updates and asks the user if updates are found.
/// </summary>
/// <param name="millisecondsDelay">A delay in milliseconds before checking.</param>
/// <returns></returns>
public async Task CheckForVersionUpdate(int millisecondsDelay = 0)
{
// delay
logger.Info($"Waiting for {millisecondsDelay}ms before checking for version update.");
await Task.Delay(millisecondsDelay);
// update _config so we would know if the user checked or unchecked pre-release checks
_config = Program.MainController.GetCurrentConfiguration();
// start
logger.Info($"Checking for version update.");
try
{
// list releases via API
var releasesListJsonString = await httpClient.GetStringAsync(UpdateURL);
// parse
var releasesJArray = JArray.Parse(releasesListJsonString);
foreach (var releaseObject in releasesJArray)
{
var releaseTagName = (string)releaseObject["tag_name"];
var releaseVersion = new Version(releaseTagName);
if (releaseTagName == _config.skippedUpdateVersion) // finished checking
break;
if (releaseVersion.CompareTo(_version) > 0 &&
(!(bool)releaseObject["prerelease"] || _config.checkPreRelease && (bool)releaseObject["prerelease"])) // selected
{
logger.Info($"Found new version {releaseTagName}.");
_releaseObject = releaseObject;
NewReleaseVersion = releaseTagName;
AskToUpdate(releaseObject);
return;
}
}
logger.Info($"No new versions found.");
CheckUpdateCompleted?.Invoke(this, new EventArgs());
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
/// <summary>
/// Opens a window to show the update's information.
/// </summary>
/// <param name="releaseObject">The update release object.</param>
private void AskToUpdate(JToken releaseObject)
{
if (versionUpdatePromptWindow == null)
{
versionUpdatePromptWindow = new Window()
{
Title = LocalizationProvider.GetLocalizedValue<string>("VersionUpdate"),
Height = 480,
Width = 640,
MinHeight = 480,
MinWidth = 640,
Content = new VersionUpdatePromptView(releaseObject)
};
versionUpdatePromptWindow.Closed += VersionUpdatePromptWindow_Closed;
versionUpdatePromptWindow.Show();
}
versionUpdatePromptWindow.Activate();
}
private void VersionUpdatePromptWindow_Closed(object sender, EventArgs e)
{
versionUpdatePromptWindow = null;
}
/// <summary>
/// Downloads the selected update and notifies the user.
/// </summary>
/// <returns></returns>
public async Task DoUpdate()
{
try
{
var assets = (JArray)_releaseObject["assets"];
// download all assets
foreach (JObject asset in assets)
{
var filename = (string)asset["name"];
var browser_download_url = (string)asset["browser_download_url"];
var response = await httpClient.GetAsync(browser_download_url);
using (var downloadedFileStream = File.Create(Utils.GetTempPath(filename)))
await response.Content.CopyToAsync(downloadedFileStream);
logger.Info($"Downloaded {filename}.");
// store .zip filename
if (filename.EndsWith(".zip"))
NewReleaseZipFilename = filename;
}
logger.Info("Finished downloading.");
// notify user
CloseVersionUpdatePromptWindow();
Process.Start("explorer.exe", $"/select, \"{Utils.GetTempPath(NewReleaseZipFilename)}\"");
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
/// <summary>
/// Saves the skipped update version.
/// </summary>
public void SkipUpdate()
{
var version = (string)_releaseObject["tag_name"] ?? "";
_config.skippedUpdateVersion = version;
Program.MainController.SaveSkippedUpdateVerion(version);
logger.Info($"The update {version} has been skipped and will be ignored next time.");
CloseVersionUpdatePromptWindow();
}
/// <summary>
/// Closes the update prompt window.
/// </summary>
public void CloseVersionUpdatePromptWindow()
{
if (versionUpdatePromptWindow != null)
{
versionUpdatePromptWindow.Close();
versionUpdatePromptWindow = null;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using Newtonsoft.Json.Linq;
using NLog;
using Shadowsocks.Localization;
using Shadowsocks.Model;
using Shadowsocks.Util;
using Shadowsocks.Views;
namespace Shadowsocks.WPF.Services
{
public class UpdateChecker
{
private readonly Logger logger;
private readonly HttpClient httpClient;
// https://developer.github.com/v3/repos/releases/
private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases";
private Configuration _config;
private Window versionUpdatePromptWindow;
private JToken _releaseObject;
public string NewReleaseVersion { get; private set; }
public string NewReleaseZipFilename { get; private set; }
public event EventHandler CheckUpdateCompleted;
public static readonly string Version = Assembly.GetEntryAssembly().GetName().Version.ToString();
private readonly Version _version;
public UpdateChecker()
{
logger = LogManager.GetCurrentClassLogger();
httpClient = Program.MainController.GetHttpClient();
_version = new Version(Version);
_config = Program.MainController.GetCurrentConfiguration();
}
/// <summary>
/// Checks for updates and asks the user if updates are found.
/// </summary>
/// <param name="millisecondsDelay">A delay in milliseconds before checking.</param>
/// <returns></returns>
public async Task CheckForVersionUpdate(int millisecondsDelay = 0)
{
// delay
logger.Info($"Waiting for {millisecondsDelay}ms before checking for version update.");
await Task.Delay(millisecondsDelay);
// update _config so we would know if the user checked or unchecked pre-release checks
_config = Program.MainController.GetCurrentConfiguration();
// start
logger.Info($"Checking for version update.");
try
{
// list releases via API
var releasesListJsonString = await httpClient.GetStringAsync(UpdateURL);
// parse
var releasesJArray = JArray.Parse(releasesListJsonString);
foreach (var releaseObject in releasesJArray)
{
var releaseTagName = (string)releaseObject["tag_name"];
var releaseVersion = new Version(releaseTagName);
if (releaseTagName == _config.skippedUpdateVersion) // finished checking
break;
if (releaseVersion.CompareTo(_version) > 0 &&
(!(bool)releaseObject["prerelease"] || _config.checkPreRelease && (bool)releaseObject["prerelease"])) // selected
{
logger.Info($"Found new version {releaseTagName}.");
_releaseObject = releaseObject;
NewReleaseVersion = releaseTagName;
AskToUpdate(releaseObject);
return;
}
}
logger.Info($"No new versions found.");
CheckUpdateCompleted?.Invoke(this, new EventArgs());
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
/// <summary>
/// Opens a window to show the update's information.
/// </summary>
/// <param name="releaseObject">The update release object.</param>
private void AskToUpdate(JToken releaseObject)
{
if (versionUpdatePromptWindow == null)
{
versionUpdatePromptWindow = new Window()
{
Title = LocalizationProvider.GetLocalizedValue<string>("VersionUpdate"),
Height = 480,
Width = 640,
MinHeight = 480,
MinWidth = 640,
Content = new VersionUpdatePromptView(releaseObject)
};
versionUpdatePromptWindow.Closed += VersionUpdatePromptWindow_Closed;
versionUpdatePromptWindow.Show();
}
versionUpdatePromptWindow.Activate();
}
private void VersionUpdatePromptWindow_Closed(object sender, EventArgs e)
{
versionUpdatePromptWindow = null;
}
/// <summary>
/// Downloads the selected update and notifies the user.
/// </summary>
/// <returns></returns>
public async Task DoUpdate()
{
try
{
var assets = (JArray)_releaseObject["assets"];
// download all assets
foreach (JObject asset in assets)
{
var filename = (string)asset["name"];
var browser_download_url = (string)asset["browser_download_url"];
var response = await httpClient.GetAsync(browser_download_url);
using (var downloadedFileStream = File.Create(Utils.GetTempPath(filename)))
await response.Content.CopyToAsync(downloadedFileStream);
logger.Info($"Downloaded {filename}.");
// store .zip filename
if (filename.EndsWith(".zip"))
NewReleaseZipFilename = filename;
}
logger.Info("Finished downloading.");
// notify user
CloseVersionUpdatePromptWindow();
Process.Start("explorer.exe", $"/select, \"{Utils.GetTempPath(NewReleaseZipFilename)}\"");
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
/// <summary>
/// Saves the skipped update version.
/// </summary>
public void SkipUpdate()
{
var version = (string)_releaseObject["tag_name"] ?? "";
_config.skippedUpdateVersion = version;
Program.MainController.SaveSkippedUpdateVerion(version);
logger.Info($"The update {version} has been skipped and will be ignored next time.");
CloseVersionUpdatePromptWindow();
}
/// <summary>
/// Closes the update prompt window.
/// </summary>
public void CloseVersionUpdatePromptWindow()
{
if (versionUpdatePromptWindow != null)
{
versionUpdatePromptWindow.Close();
versionUpdatePromptWindow = null;
}
}
}
}

+ 6
- 15
Shadowsocks.WPF/Shadowsocks.WPF.csproj View File

@@ -20,25 +20,11 @@
<PackageReference Include="ReactiveUI.Fody" Version="12.1.1" />
<PackageReference Include="ReactiveUI.Validation" Version="1.8.6" />
<PackageReference Include="ReactiveUI.WPF" Version="12.1.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20371.2" />
<PackageReference Include="WPFLocalizeExtension" Version="3.8.0" />
<PackageReference Include="ZXing.Net" Version="0.16.6" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" />
</ItemGroup>

<ItemGroup>
<Resource Include="Assets\ss128.pdn" />
<Resource Include="Assets\ss32.pdn" />
<Resource Include="Assets\ss32Fill.png" />
<Resource Include="Assets\ss32In.png" />
<Resource Include="Assets\ss32Out.png" />
<Resource Include="Assets\ss32Outline.png" />
<Resource Include="Assets\ssw128.png" />
<Resource Include="Assets\shadowsocks.ico" />
</ItemGroup>

<ItemGroup>
<Compile Update="Localization\Strings.Designer.cs">
<DesignTime>True</DesignTime>
@@ -58,4 +44,9 @@
<Folder Include="Resources\" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Shadowsocks.Net\Shadowsocks.Net.csproj" />
<ProjectReference Include="..\Shadowsocks\Shadowsocks.csproj" />
</ItemGroup>

</Project>

+ 15
- 0
Shadowsocks.WPF/ViewModels/DashboardViewModel.cs View File

@@ -0,0 +1,15 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;

namespace Shadowsocks.WPF.ViewModels
{
public class DashboardViewModel : ReactiveObject
{
public DashboardViewModel()
{

}
}
}

+ 0
- 3
Shadowsocks.WPF/ViewModels/MainWindowViewModel.cs View File

@@ -1,7 +1,4 @@
using ReactiveUI;
using Shadowsocks.Controller.Service;

using System;

namespace Shadowsocks.WPF.ViewModels
{


+ 2
- 2
Shadowsocks.WPF/ViewModels/OnlineConfigViewModel.cs View File

@@ -1,9 +1,9 @@
using ReactiveUI;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using ReactiveUI.Validation.Extensions;
using ReactiveUI.Validation.Helpers;
using Shadowsocks.Controller;
using Shadowsocks.Localization;
using Shadowsocks.WPF.Localization;
using Shadowsocks.Model;
using Shadowsocks.View;
using System;


+ 15
- 0
Shadowsocks.WPF/ViewModels/RoutingViewModel.cs View File

@@ -0,0 +1,15 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;

namespace Shadowsocks.WPF.ViewModels
{
public class RoutingViewModel : ReactiveObject
{
public RoutingViewModel()
{

}
}
}

+ 15
- 0
Shadowsocks.WPF/ViewModels/ServersViewModel.cs View File

@@ -0,0 +1,15 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;

namespace Shadowsocks.WPF.ViewModels
{
public class ServersViewModel : ReactiveObject
{
public ServersViewModel()
{

}
}
}

+ 15
- 0
Shadowsocks.WPF/ViewModels/SettingsViewModel.cs View File

@@ -0,0 +1,15 @@
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Text;

namespace Shadowsocks.WPF.ViewModels
{
public class SettingsViewModel : ReactiveObject
{
public SettingsViewModel()
{

}
}
}

+ 27
- 0
Shadowsocks.WPF/Views/DashboardView.xaml View File

@@ -0,0 +1,27 @@
<reactiveui:ReactiveUserControl
x:Class="Shadowsocks.WPF.Views.DashboardView"
x:TypeArguments="vms:DashboardViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Shadowsocks.WPF.Views"
xmlns:vms="clr-namespace:Shadowsocks.WPF.ViewModels"
xmlns:reactiveui="http://reactiveui.net"
xmlns:lex="http://wpflocalizeextension.codeplex.com"
lex:LocalizeDictionary.DesignCulture="en"
lex:ResxLocalizationProvider.DefaultAssembly="Shadowsocks"
lex:ResxLocalizationProvider.DefaultDictionary="Strings"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
</Grid>
</reactiveui:ReactiveUserControl>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save