Browse Source

Merge pull request #1298 from rwasef1830/sip003

SIP002 and SIP003 support.
tags/4.0.6
Allen Zhu GitHub 7 years ago
parent
commit
71120e5c3e
8 changed files with 546 additions and 80 deletions
  1. +134
    -0
      shadowsocks-csharp/Controller/Service/Sip003Plugin.cs
  2. +10
    -2
      shadowsocks-csharp/Controller/Service/TCPRelay.cs
  3. +67
    -4
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  4. +89
    -20
      shadowsocks-csharp/Model/Server.cs
  5. +107
    -54
      shadowsocks-csharp/View/ConfigForm.Designer.cs
  6. +6
    -0
      shadowsocks-csharp/View/ConfigForm.cs
  7. +1
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj
  8. +132
    -0
      test/UnitTest.cs

+ 134
- 0
shadowsocks-csharp/Controller/Service/Sip003Plugin.cs View File

@@ -0,0 +1,134 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using Shadowsocks.Model;

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 Process _pluginProcess;
private bool _started;
private bool _disposed;

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

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

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

private Sip003Plugin(string plugin, string pluginOpts, string serverAddress, int serverPort)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (string.IsNullOrWhiteSpace(serverAddress))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(serverAddress));
}
if ((ushort)serverPort != serverPort)
{
throw new ArgumentOutOfRangeException("serverPort");
}

var appPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase).LocalPath);

_pluginProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = plugin,
UseShellExecute = false,
CreateNoWindow = true,
ErrorDialog = false,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = appPath ?? 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.Start();
_started = true;
}

return true;
}

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();
}
catch (Exception) { }

_disposed = true;
}
}
}
}

+ 10
- 2
shadowsocks-csharp/Controller/Service/TCPRelay.cs View File

@@ -570,7 +570,15 @@ namespace Shadowsocks.Controller
// Setting up proxy
IProxy remote;
EndPoint proxyEP = null;
if (_config.proxy.useProxy)
EndPoint serverEP = SocketUtil.GetEndPoint(_server.server, _server.server_port);
EndPoint pluginEP = _controller.GetPluginLocalEndPointIfConfigured(_server);
if (pluginEP != null)
{
serverEP = pluginEP;
remote = new DirectConnect();
}
else if (_config.proxy.useProxy)
{
switch (_config.proxy.proxyType)
{
@@ -607,7 +615,7 @@ namespace Shadowsocks.Controller
proxyTimer.Enabled = true;
proxyTimer.Session = session;
proxyTimer.DestEndPoint = SocketUtil.GetEndPoint(_server.server, _server.server_port);
proxyTimer.DestEndPoint = serverEP;
proxyTimer.Server = _server;
_proxyConnected = false;


+ 67
- 4
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
@@ -14,6 +15,8 @@ using Shadowsocks.Model;
using Shadowsocks.Properties;
using Shadowsocks.Util;
using System.Linq;
using Shadowsocks.Controller.Service;
using Shadowsocks.Proxy;
namespace Shadowsocks.Controller
{
@@ -33,6 +36,8 @@ namespace Shadowsocks.Controller
private StrategyManager _strategyManager;
private PrivoxyRunner privoxyRunner;
private GFWListUpdater gfwListUpdater;
private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer;
public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance;
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; }
@@ -79,6 +84,7 @@ namespace Shadowsocks.Controller
_config = Configuration.Load();
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
_strategyManager = new StrategyManager(this);
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>();
StartReleasingMemory();
StartTrafficStatistics(61);
}
@@ -144,6 +150,23 @@ namespace Shadowsocks.Controller
return GetCurrentServer();
}
public EndPoint GetPluginLocalEndPointIfConfigured(Server server)
{
var plugin = _pluginsByServer.GetOrAdd(server, Sip003Plugin.CreateIfConfigured);
if (plugin == null)
{
return null;
}
if (plugin.StartIfNeeded())
{
Logging.Info(
$"Started SIP003 plugin for {server.Identifier()} on {plugin.LocalEndPoint} - PID: {plugin.ProcessId}");
}
return plugin.LocalEndPoint;
}
public void SaveServers(List<Server> servers, int localPort)
{
_config.configs = servers;
@@ -259,6 +282,7 @@ namespace Shadowsocks.Controller
{
_listener.Stop();
}
StopPlugins();
if (privoxyRunner != null)
{
privoxyRunner.Stop();
@@ -270,6 +294,15 @@ namespace Shadowsocks.Controller
Encryption.RNG.Close();
}
private void StopPlugins()
{
foreach (var serverAndPlugin in _pluginsByServer)
{
serverAndPlugin.Value?.Dispose();
}
_pluginsByServer.Clear();
}
public void TouchPACFile()
{
string pacFilename = _pacServer.TouchPACFile();
@@ -297,13 +330,41 @@ namespace Shadowsocks.Controller
public static string GetQRCode(Server server)
{
string tag = string.Empty;
string parts = $"{server.method}:{server.password}@{server.server}:{server.server_port}";
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts));
if(!server.remarks.IsNullOrEmpty())
string url = string.Empty;
if (string.IsNullOrWhiteSpace(server.plugin))
{
// For backwards compatiblity, if no plugin, use old url format
string parts = $"{server.method}:{server.password}@{server.server}:{server.server_port}";
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts));
url = base64;
}
else
{
// SIP002
string parts = $"{server.method}:{server.password}";
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts));
string websafeBase64 = base64.Replace('+', '-').Replace('/', '_').TrimEnd('=');
string pluginPart = server.plugin;
if (!string.IsNullOrWhiteSpace(server.plugin_opts))
{
pluginPart += ";" + server.plugin_opts;
}
url = string.Format(
"{0}@{1}:{2}/?plugin={3}",
websafeBase64,
HttpUtility.UrlEncode(server.server, Encoding.UTF8),
server.server_port,
HttpUtility.UrlEncode(pluginPart, Encoding.UTF8));
}
if (!server.remarks.IsNullOrEmpty())
{
tag = $"#{HttpUtility.UrlEncode(server.remarks, Encoding.UTF8)}";
}
return $"ss://{base64}{tag}";
return $"ss://{url}{tag}";
}
public void UpdatePACFromGFWList()
@@ -421,6 +482,8 @@ namespace Shadowsocks.Controller
protected void Reload()
{
StopPlugins();
Encryption.RNG.Reload();
// some logic in configuration updated the config when saving, we need to read it again
_config = Configuration.Load();


+ 89
- 20
shadowsocks-csharp/Model/Server.cs View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using Shadowsocks.Controller;
@@ -10,10 +10,6 @@ namespace Shadowsocks.Model
[Serializable]
public class Server
{
public static readonly Regex
UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase),
DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase);
private const int DefaultServerTimeoutSec = 5;
public const int MaxServerTimeoutSec = 20;
@@ -21,6 +17,8 @@ namespace Shadowsocks.Model
public int server_port;
public string password;
public string method;
public string plugin;
public string plugin_opts;
public string remarks;
public int timeout;
@@ -65,6 +63,8 @@ namespace Shadowsocks.Model
server = "";
server_port = 8388;
method = "aes-256-cfb";
plugin = "";
plugin_opts = "";
password = "";
remarks = "";
timeout = DefaultServerTimeoutSec;
@@ -72,26 +72,95 @@ namespace Shadowsocks.Model
public static List<Server> GetServers(string ssURL)
{
var matches = UrlFinder.Matches(ssURL);
if (matches.Count <= 0) return null;
var serverUrls = ssURL.Split('\r', '\n');
List<Server> servers = new List<Server>();
foreach (Match match in matches)
foreach (string serverUrl in serverUrls)
{
Server tmp = new Server();
var base64 = match.Groups["base64"].Value;
var tag = match.Groups["tag"].Value;
if (!tag.IsNullOrEmpty())
if (string.IsNullOrWhiteSpace(serverUrl))
{
continue;
}
Uri parsedUrl;
try
{
parsedUrl = new Uri(serverUrl);
}
catch (UriFormatException)
{
continue;
}
Server tmp = new Server
{
remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped)
};
string possiblyUnpaddedBase64 = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
bool isOldFormatUrl = possiblyUnpaddedBase64.Length == 0;
if (isOldFormatUrl)
{
int prefixLength = "ss://".Length;
int indexOfHashOrSlash = serverUrl.LastIndexOfAny(
new[] { '/', '#' },
serverUrl.Length - 1,
serverUrl.Length - prefixLength);
int substringLength = serverUrl.Length - prefixLength;
if (indexOfHashOrSlash >= 0)
{
substringLength = indexOfHashOrSlash - prefixLength;
}
possiblyUnpaddedBase64 = serverUrl.Substring(prefixLength, substringLength).TrimEnd('/');
}
else
{
// Web-safe base64 to normal base64
possiblyUnpaddedBase64 = possiblyUnpaddedBase64.Replace('-', '+').Replace('_', '/');
}
string base64 = possiblyUnpaddedBase64.PadRight(
possiblyUnpaddedBase64.Length + (4 - possiblyUnpaddedBase64.Length % 4) % 4,
'=');
string innerUserInfoOrUrl = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
string userInfo;
if (isOldFormatUrl)
{
tmp.remarks = HttpUtility.UrlDecode(tag, Encoding.UTF8);
Uri innerUrl = new Uri("inner://" + innerUserInfoOrUrl);
userInfo = innerUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped);
tmp.server = innerUrl.GetComponents(UriComponents.Host, UriFormat.Unescaped);
tmp.server_port = innerUrl.Port;
}
Match details = DetailsParser.Match(Encoding.UTF8.GetString(Convert.FromBase64String(
base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))));
if (!details.Success)
else
{
userInfo = innerUserInfoOrUrl;
tmp.server = parsedUrl.GetComponents(UriComponents.Host, UriFormat.Unescaped);
tmp.server_port = parsedUrl.Port;
}
string[] userInfoParts = userInfo.Split(new[] { ':' }, 2);
if (userInfoParts.Length != 2)
{
continue;
tmp.method = details.Groups["method"].Value;
tmp.password = details.Groups["password"].Value;
tmp.server = details.Groups["hostname"].Value;
tmp.server_port = int.Parse(details.Groups["port"].Value);
}
tmp.method = userInfoParts[0];
tmp.password = userInfoParts[1];
NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query);
string[] pluginParts = HttpUtility.UrlDecode(queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2);
if (pluginParts.Length > 0)
{
tmp.plugin = pluginParts[0] ?? "";
}
if (pluginParts.Length > 1)
{
tmp.plugin_opts = pluginParts[1] ?? "";
}
servers.Add(tmp);
}


+ 107
- 54
shadowsocks-csharp/View/ConfigForm.Designer.cs View File

@@ -29,6 +29,8 @@
private void InitializeComponent()
{
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.PluginOptionsLabel = new System.Windows.Forms.Label();
this.PluginTextBox = new System.Windows.Forms.TextBox();
this.RemarksTextBox = new System.Windows.Forms.TextBox();
this.RemarksLabel = new System.Windows.Forms.Label();
this.IPLabel = new System.Windows.Forms.Label();
@@ -41,6 +43,8 @@
this.EncryptionSelect = new System.Windows.Forms.ComboBox();
this.TimeoutLabel = new System.Windows.Forms.Label();
this.TimeoutTextBox = new System.Windows.Forms.TextBox();
this.PluginLabel = new System.Windows.Forms.Label();
this.PluginOptionsTextBox = new System.Windows.Forms.TextBox();
this.panel2 = new System.Windows.Forms.Panel();
this.OKButton = new System.Windows.Forms.Button();
this.MyCancelButton = new System.Windows.Forms.Button();
@@ -74,8 +78,10 @@
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.Controls.Add(this.RemarksTextBox, 1, 5);
this.tableLayoutPanel1.Controls.Add(this.RemarksLabel, 0, 5);
this.tableLayoutPanel1.Controls.Add(this.PluginOptionsLabel, 0, 5);
this.tableLayoutPanel1.Controls.Add(this.PluginTextBox, 1, 4);
this.tableLayoutPanel1.Controls.Add(this.RemarksTextBox, 1, 7);
this.tableLayoutPanel1.Controls.Add(this.RemarksLabel, 0, 7);
this.tableLayoutPanel1.Controls.Add(this.IPLabel, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.ServerPortLabel, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.PasswordLabel, 0, 2);
@@ -84,13 +90,15 @@
this.tableLayoutPanel1.Controls.Add(this.PasswordTextBox, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.EncryptionLabel, 0, 3);
this.tableLayoutPanel1.Controls.Add(this.EncryptionSelect, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.TimeoutLabel, 0, 6);
this.tableLayoutPanel1.Controls.Add(this.TimeoutTextBox, 1, 6);
this.tableLayoutPanel1.Controls.Add(this.TimeoutLabel, 0, 8);
this.tableLayoutPanel1.Controls.Add(this.TimeoutTextBox, 1, 8);
this.tableLayoutPanel1.Controls.Add(this.PluginLabel, 0, 4);
this.tableLayoutPanel1.Controls.Add(this.PluginOptionsTextBox, 1, 5);
this.tableLayoutPanel1.Location = new System.Drawing.Point(8, 21);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(0);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(3);
this.tableLayoutPanel1.RowCount = 7;
this.tableLayoutPanel1.RowCount = 9;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
@@ -98,26 +106,48 @@
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.Size = new System.Drawing.Size(255, 167);
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.Size = new System.Drawing.Size(253, 215);
this.tableLayoutPanel1.TabIndex = 0;
//
// PluginOptionsLabel
//
this.PluginOptionsLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.PluginOptionsLabel.AutoSize = true;
this.PluginOptionsLabel.Location = new System.Drawing.Point(6, 140);
this.PluginOptionsLabel.Name = "PluginOptionsLabel";
this.PluginOptionsLabel.Size = new System.Drawing.Size(75, 13);
this.PluginOptionsLabel.TabIndex = 15;
this.PluginOptionsLabel.Text = "Plugin Options";
//
// PluginTextBox
//
this.PluginTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.PluginTextBox.Location = new System.Drawing.Point(87, 111);
this.PluginTextBox.MaxLength = 256;
this.PluginTextBox.Name = "PluginTextBox";
this.PluginTextBox.Size = new System.Drawing.Size(160, 20);
this.PluginTextBox.TabIndex = 4;
this.PluginTextBox.WordWrap = false;
//
// RemarksTextBox
//
this.RemarksTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.RemarksTextBox.Location = new System.Drawing.Point(89, 113);
this.RemarksTextBox.Location = new System.Drawing.Point(87, 163);
this.RemarksTextBox.MaxLength = 32;
this.RemarksTextBox.Name = "RemarksTextBox";
this.RemarksTextBox.Size = new System.Drawing.Size(160, 21);
this.RemarksTextBox.TabIndex = 4;
this.RemarksTextBox.Size = new System.Drawing.Size(160, 20);
this.RemarksTextBox.TabIndex = 6;
this.RemarksTextBox.WordWrap = false;
//
// RemarksLabel
//
this.RemarksLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.RemarksLabel.AutoSize = true;
this.RemarksLabel.Location = new System.Drawing.Point(36, 117);
this.RemarksLabel.Location = new System.Drawing.Point(32, 166);
this.RemarksLabel.Name = "RemarksLabel";
this.RemarksLabel.Size = new System.Drawing.Size(47, 12);
this.RemarksLabel.Size = new System.Drawing.Size(49, 13);
this.RemarksLabel.TabIndex = 9;
this.RemarksLabel.Text = "Remarks";
//
@@ -125,9 +155,9 @@
//
this.IPLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.IPLabel.AutoSize = true;
this.IPLabel.Location = new System.Drawing.Point(24, 10);
this.IPLabel.Location = new System.Drawing.Point(30, 9);
this.IPLabel.Name = "IPLabel";
this.IPLabel.Size = new System.Drawing.Size(59, 12);
this.IPLabel.Size = new System.Drawing.Size(51, 13);
this.IPLabel.TabIndex = 0;
this.IPLabel.Text = "Server IP";
//
@@ -135,9 +165,9 @@
//
this.ServerPortLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.ServerPortLabel.AutoSize = true;
this.ServerPortLabel.Location = new System.Drawing.Point(12, 37);
this.ServerPortLabel.Location = new System.Drawing.Point(21, 35);
this.ServerPortLabel.Name = "ServerPortLabel";
this.ServerPortLabel.Size = new System.Drawing.Size(71, 12);
this.ServerPortLabel.Size = new System.Drawing.Size(60, 13);
this.ServerPortLabel.TabIndex = 1;
this.ServerPortLabel.Text = "Server Port";
//
@@ -145,39 +175,39 @@
//
this.PasswordLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.PasswordLabel.AutoSize = true;
this.PasswordLabel.Location = new System.Drawing.Point(30, 64);
this.PasswordLabel.Location = new System.Drawing.Point(28, 61);
this.PasswordLabel.Name = "PasswordLabel";
this.PasswordLabel.Size = new System.Drawing.Size(53, 12);
this.PasswordLabel.Size = new System.Drawing.Size(53, 13);
this.PasswordLabel.TabIndex = 2;
this.PasswordLabel.Text = "Password";
//
// IPTextBox
//
this.IPTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.IPTextBox.Location = new System.Drawing.Point(89, 6);
this.IPTextBox.Location = new System.Drawing.Point(87, 6);
this.IPTextBox.MaxLength = 512;
this.IPTextBox.Name = "IPTextBox";
this.IPTextBox.Size = new System.Drawing.Size(160, 21);
this.IPTextBox.Size = new System.Drawing.Size(160, 20);
this.IPTextBox.TabIndex = 0;
this.IPTextBox.WordWrap = false;
//
// ServerPortTextBox
//
this.ServerPortTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.ServerPortTextBox.Location = new System.Drawing.Point(89, 33);
this.ServerPortTextBox.Location = new System.Drawing.Point(87, 32);
this.ServerPortTextBox.MaxLength = 10;
this.ServerPortTextBox.Name = "ServerPortTextBox";
this.ServerPortTextBox.Size = new System.Drawing.Size(160, 21);
this.ServerPortTextBox.Size = new System.Drawing.Size(160, 20);
this.ServerPortTextBox.TabIndex = 1;
this.ServerPortTextBox.WordWrap = false;
//
// PasswordTextBox
//
this.PasswordTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.PasswordTextBox.Location = new System.Drawing.Point(89, 60);
this.PasswordTextBox.Location = new System.Drawing.Point(87, 58);
this.PasswordTextBox.MaxLength = 256;
this.PasswordTextBox.Name = "PasswordTextBox";
this.PasswordTextBox.Size = new System.Drawing.Size(160, 21);
this.PasswordTextBox.Size = new System.Drawing.Size(160, 20);
this.PasswordTextBox.TabIndex = 2;
this.PasswordTextBox.UseSystemPasswordChar = true;
this.PasswordTextBox.WordWrap = false;
@@ -186,9 +216,9 @@
//
this.EncryptionLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.EncryptionLabel.AutoSize = true;
this.EncryptionLabel.Location = new System.Drawing.Point(18, 91);
this.EncryptionLabel.Location = new System.Drawing.Point(24, 88);
this.EncryptionLabel.Name = "EncryptionLabel";
this.EncryptionLabel.Size = new System.Drawing.Size(65, 12);
this.EncryptionLabel.Size = new System.Drawing.Size(57, 13);
this.EncryptionLabel.TabIndex = 8;
this.EncryptionLabel.Text = "Encryption";
//
@@ -199,7 +229,7 @@
this.EncryptionSelect.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.EncryptionSelect.FormattingEnabled = true;
this.EncryptionSelect.ImeMode = System.Windows.Forms.ImeMode.NoControl;
this.EncryptionSelect.ItemHeight = 12;
this.EncryptionSelect.ItemHeight = 13;
this.EncryptionSelect.Items.AddRange(new object[] {
"rc4-md5",
"salsa20",
@@ -219,30 +249,50 @@
"aes-192-gcm",
"aes-256-gcm",
"chacha20-ietf-poly1305"});
this.EncryptionSelect.Location = new System.Drawing.Point(89, 87);
this.EncryptionSelect.Location = new System.Drawing.Point(87, 84);
this.EncryptionSelect.Name = "EncryptionSelect";
this.EncryptionSelect.Size = new System.Drawing.Size(160, 20);
this.EncryptionSelect.Size = new System.Drawing.Size(160, 21);
this.EncryptionSelect.TabIndex = 3;
//
// TimeoutLabel
//
this.TimeoutLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.TimeoutLabel.AutoSize = true;
this.TimeoutLabel.Location = new System.Drawing.Point(6, 144);
this.TimeoutLabel.Location = new System.Drawing.Point(11, 192);
this.TimeoutLabel.Name = "TimeoutLabel";
this.TimeoutLabel.RightToLeft = System.Windows.Forms.RightToLeft.No;
this.TimeoutLabel.Size = new System.Drawing.Size(77, 12);
this.TimeoutLabel.Size = new System.Drawing.Size(70, 13);
this.TimeoutLabel.TabIndex = 10;
this.TimeoutLabel.Text = "Timeout(Sec)";
//
// TimeoutTextBox
//
this.TimeoutTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.TimeoutTextBox.Location = new System.Drawing.Point(89, 140);
this.TimeoutTextBox.Location = new System.Drawing.Point(87, 189);
this.TimeoutTextBox.MaxLength = 5;
this.TimeoutTextBox.Name = "TimeoutTextBox";
this.TimeoutTextBox.Size = new System.Drawing.Size(160, 21);
this.TimeoutTextBox.TabIndex = 11;
this.TimeoutTextBox.Size = new System.Drawing.Size(160, 20);
this.TimeoutTextBox.TabIndex = 7;
//
// PluginLabel
//
this.PluginLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.PluginLabel.AutoSize = true;
this.PluginLabel.Location = new System.Drawing.Point(45, 114);
this.PluginLabel.Name = "PluginLabel";
this.PluginLabel.Size = new System.Drawing.Size(36, 13);
this.PluginLabel.TabIndex = 12;
this.PluginLabel.Text = "Plugin";
//
// PluginOptionsTextBox
//
this.PluginOptionsTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.PluginOptionsTextBox.Location = new System.Drawing.Point(87, 137);
this.PluginOptionsTextBox.MaxLength = 256;
this.PluginOptionsTextBox.Name = "PluginOptionsTextBox";
this.PluginOptionsTextBox.Size = new System.Drawing.Size(160, 20);
this.PluginOptionsTextBox.TabIndex = 5;
this.PluginOptionsTextBox.WordWrap = false;
//
// panel2
//
@@ -262,7 +312,7 @@
this.OKButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0);
this.OKButton.Name = "OKButton";
this.OKButton.Size = new System.Drawing.Size(75, 23);
this.OKButton.TabIndex = 12;
this.OKButton.TabIndex = 15;
this.OKButton.Text = "OK";
this.OKButton.UseVisualStyleBackColor = true;
this.OKButton.Click += new System.EventHandler(this.OKButton_Click);
@@ -275,7 +325,7 @@
this.MyCancelButton.Margin = new System.Windows.Forms.Padding(3, 3, 0, 0);
this.MyCancelButton.Name = "MyCancelButton";
this.MyCancelButton.Size = new System.Drawing.Size(75, 23);
this.MyCancelButton.TabIndex = 13;
this.MyCancelButton.TabIndex = 16;
this.MyCancelButton.Text = "Cancel";
this.MyCancelButton.UseVisualStyleBackColor = true;
this.MyCancelButton.Click += new System.EventHandler(this.CancelButton_Click);
@@ -287,7 +337,7 @@
this.DeleteButton.Margin = new System.Windows.Forms.Padding(3, 6, 0, 3);
this.DeleteButton.Name = "DeleteButton";
this.DeleteButton.Size = new System.Drawing.Size(80, 23);
this.DeleteButton.TabIndex = 9;
this.DeleteButton.TabIndex = 11;
this.DeleteButton.Text = "&Delete";
this.DeleteButton.UseVisualStyleBackColor = true;
this.DeleteButton.Click += new System.EventHandler(this.DeleteButton_Click);
@@ -299,7 +349,7 @@
this.AddButton.Margin = new System.Windows.Forms.Padding(0, 6, 3, 3);
this.AddButton.Name = "AddButton";
this.AddButton.Size = new System.Drawing.Size(80, 23);
this.AddButton.TabIndex = 8;
this.AddButton.TabIndex = 10;
this.AddButton.Text = "&Add";
this.AddButton.UseVisualStyleBackColor = true;
this.AddButton.Click += new System.EventHandler(this.AddButton_Click);
@@ -312,7 +362,7 @@
this.ServerGroupBox.Location = new System.Drawing.Point(178, 0);
this.ServerGroupBox.Margin = new System.Windows.Forms.Padding(12, 0, 0, 0);
this.ServerGroupBox.Name = "ServerGroupBox";
this.ServerGroupBox.Size = new System.Drawing.Size(266, 205);
this.ServerGroupBox.Size = new System.Drawing.Size(264, 252);
this.ServerGroupBox.TabIndex = 0;
this.ServerGroupBox.TabStop = false;
this.ServerGroupBox.Text = "Server";
@@ -321,12 +371,11 @@
//
this.ServersListBox.FormattingEnabled = true;
this.ServersListBox.IntegralHeight = false;
this.ServersListBox.ItemHeight = 12;
this.ServersListBox.Location = new System.Drawing.Point(0, 0);
this.ServersListBox.Margin = new System.Windows.Forms.Padding(0);
this.ServersListBox.Name = "ServersListBox";
this.ServersListBox.Size = new System.Drawing.Size(166, 148);
this.ServersListBox.TabIndex = 7;
this.ServersListBox.TabIndex = 9;
this.ServersListBox.SelectedIndexChanged += new System.EventHandler(this.ServersListBox_SelectedIndexChanged);
//
// tableLayoutPanel2
@@ -349,7 +398,7 @@
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel2.Size = new System.Drawing.Size(444, 301);
this.tableLayoutPanel2.Size = new System.Drawing.Size(442, 348);
this.tableLayoutPanel2.TabIndex = 7;
//
// tableLayoutPanel6
@@ -362,7 +411,7 @@
this.tableLayoutPanel6.Controls.Add(this.MoveDownButton, 1, 0);
this.tableLayoutPanel6.Controls.Add(this.MoveUpButton, 0, 0);
this.tableLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Top;
this.tableLayoutPanel6.Location = new System.Drawing.Point(0, 269);
this.tableLayoutPanel6.Location = new System.Drawing.Point(0, 316);
this.tableLayoutPanel6.Margin = new System.Windows.Forms.Padding(0);
this.tableLayoutPanel6.Name = "tableLayoutPanel6";
this.tableLayoutPanel6.RowCount = 1;
@@ -377,7 +426,7 @@
this.MoveDownButton.Margin = new System.Windows.Forms.Padding(3, 6, 0, 3);
this.MoveDownButton.Name = "MoveDownButton";
this.MoveDownButton.Size = new System.Drawing.Size(80, 23);
this.MoveDownButton.TabIndex = 11;
this.MoveDownButton.TabIndex = 14;
this.MoveDownButton.Text = "Move D&own";
this.MoveDownButton.UseVisualStyleBackColor = true;
this.MoveDownButton.Click += new System.EventHandler(this.MoveDownButton_Click);
@@ -389,7 +438,7 @@
this.MoveUpButton.Margin = new System.Windows.Forms.Padding(0, 6, 3, 3);
this.MoveUpButton.Name = "MoveUpButton";
this.MoveUpButton.Size = new System.Drawing.Size(80, 23);
this.MoveUpButton.TabIndex = 10;
this.MoveUpButton.TabIndex = 13;
this.MoveUpButton.Text = "Move &Up";
this.MoveUpButton.UseVisualStyleBackColor = true;
this.MoveUpButton.Click += new System.EventHandler(this.MoveUpButton_Click);
@@ -405,7 +454,7 @@
this.tableLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel5.Controls.Add(this.ProxyPortTextBox, 1, 0);
this.tableLayoutPanel5.Controls.Add(this.ProxyPortLabel, 0, 0);
this.tableLayoutPanel5.Location = new System.Drawing.Point(248, 205);
this.tableLayoutPanel5.Location = new System.Drawing.Point(256, 252);
this.tableLayoutPanel5.Margin = new System.Windows.Forms.Padding(0);
this.tableLayoutPanel5.Name = "tableLayoutPanel5";
this.tableLayoutPanel5.Padding = new System.Windows.Forms.Padding(3);
@@ -415,26 +464,26 @@
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 58F));
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 58F));
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 58F));
this.tableLayoutPanel5.Size = new System.Drawing.Size(196, 64);
this.tableLayoutPanel5.Size = new System.Drawing.Size(186, 64);
this.tableLayoutPanel5.TabIndex = 9;
//
// ProxyPortTextBox
//
this.ProxyPortTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.ProxyPortTextBox.Location = new System.Drawing.Point(77, 21);
this.ProxyPortTextBox.Location = new System.Drawing.Point(67, 22);
this.ProxyPortTextBox.MaxLength = 10;
this.ProxyPortTextBox.Name = "ProxyPortTextBox";
this.ProxyPortTextBox.Size = new System.Drawing.Size(113, 21);
this.ProxyPortTextBox.TabIndex = 6;
this.ProxyPortTextBox.Size = new System.Drawing.Size(113, 20);
this.ProxyPortTextBox.TabIndex = 8;
this.ProxyPortTextBox.WordWrap = false;
//
// ProxyPortLabel
//
this.ProxyPortLabel.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.ProxyPortLabel.AutoSize = true;
this.ProxyPortLabel.Location = new System.Drawing.Point(6, 26);
this.ProxyPortLabel.Location = new System.Drawing.Point(6, 25);
this.ProxyPortLabel.Name = "ProxyPortLabel";
this.ProxyPortLabel.Size = new System.Drawing.Size(65, 12);
this.ProxyPortLabel.Size = new System.Drawing.Size(55, 13);
this.ProxyPortLabel.TabIndex = 3;
this.ProxyPortLabel.Text = "Proxy Port";
//
@@ -449,7 +498,7 @@
this.tableLayoutPanel3.Controls.Add(this.MyCancelButton, 1, 0);
this.tableLayoutPanel3.Controls.Add(this.OKButton, 0, 0);
this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Right;
this.tableLayoutPanel3.Location = new System.Drawing.Point(285, 272);
this.tableLayoutPanel3.Location = new System.Drawing.Point(283, 319);
this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3);
this.tableLayoutPanel3.Name = "tableLayoutPanel3";
this.tableLayoutPanel3.RowCount = 1;
@@ -468,7 +517,7 @@
this.tableLayoutPanel4.Controls.Add(this.DeleteButton, 1, 0);
this.tableLayoutPanel4.Controls.Add(this.AddButton, 0, 0);
this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Top;
this.tableLayoutPanel4.Location = new System.Drawing.Point(0, 205);
this.tableLayoutPanel4.Location = new System.Drawing.Point(0, 252);
this.tableLayoutPanel4.Margin = new System.Windows.Forms.Padding(0);
this.tableLayoutPanel4.Name = "tableLayoutPanel4";
this.tableLayoutPanel4.RowCount = 2;
@@ -484,7 +533,7 @@
this.DuplicateButton.Margin = new System.Windows.Forms.Padding(0, 6, 3, 3);
this.DuplicateButton.Name = "DuplicateButton";
this.DuplicateButton.Size = new System.Drawing.Size(80, 23);
this.DuplicateButton.TabIndex = 10;
this.DuplicateButton.TabIndex = 12;
this.DuplicateButton.Text = "Dupli&cate";
this.DuplicateButton.UseVisualStyleBackColor = true;
this.DuplicateButton.Click += new System.EventHandler(this.DuplicateButton_Click);
@@ -559,6 +608,10 @@
private System.Windows.Forms.Button DuplicateButton;
private System.Windows.Forms.Label TimeoutLabel;
private System.Windows.Forms.TextBox TimeoutTextBox;
private System.Windows.Forms.Label PluginOptionsLabel;
private System.Windows.Forms.TextBox PluginTextBox;
private System.Windows.Forms.Label PluginLabel;
private System.Windows.Forms.TextBox PluginOptionsTextBox;
}
}

+ 6
- 0
shadowsocks-csharp/View/ConfigForm.cs View File

@@ -47,6 +47,8 @@ namespace Shadowsocks.View
ServerPortLabel.Text = I18N.GetString("Server Port");
PasswordLabel.Text = I18N.GetString("Password");
EncryptionLabel.Text = I18N.GetString("Encryption");
PluginLabel.Text = I18N.GetString("Plugin");
PluginOptionsLabel.Text = I18N.GetString("Plugin Options");
ProxyPortLabel.Text = I18N.GetString("Proxy Port");
RemarksLabel.Text = I18N.GetString("Remarks");
TimeoutLabel.Text = I18N.GetString("Timeout(Sec)");
@@ -94,6 +96,8 @@ namespace Shadowsocks.View
}
server.password = PasswordTextBox.Text;
server.method = EncryptionSelect.Text;
server.plugin = PluginTextBox.Text;
server.plugin_opts = PluginOptionsTextBox.Text;
server.remarks = RemarksTextBox.Text;
if (!int.TryParse(TimeoutTextBox.Text, out server.timeout))
{
@@ -127,6 +131,8 @@ namespace Shadowsocks.View
PasswordTextBox.Text = server.password;
ProxyPortTextBox.Text = _modifiedConfiguration.localPort.ToString();
EncryptionSelect.Text = server.method ?? "aes-256-cfb";
PluginTextBox.Text = server.plugin;
PluginOptionsTextBox.Text = server.plugin_opts;
RemarksTextBox.Text = server.remarks;
TimeoutTextBox.Text = server.timeout.ToString();
}


+ 1
- 0
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -145,6 +145,7 @@
<Compile Include="Controller\Strategy\BalancingStrategy.cs" />
<Compile Include="Controller\Strategy\StrategyManager.cs" />
<Compile Include="Controller\Strategy\IStrategy.cs" />
<Compile Include="Controller\Service\Sip003Plugin.cs" />
<Compile Include="Proxy\Socks5Proxy.cs" />
<Compile Include="Settings.cs" />
<Compile Include="StringEx.cs" />


+ 132
- 0
test/UnitTest.cs View File

@@ -8,6 +8,7 @@ using System.Threading;
using System.Collections.Generic;
using Shadowsocks.Controller.Hotkeys;
using Shadowsocks.Encryption.Stream;
using Shadowsocks.Model;
namespace test
{
@@ -224,5 +225,136 @@ namespace test
throw;
}
}
[TestMethod]
public void ParseAndGenerateShadowsocksUrl()
{
var server = new Server
{
server = "192.168.100.1",
server_port = 8888,
password = "test",
method = "bf-cfb"
};
var serverCanonUrl = "ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4";
var serverWithRemark = new Server
{
server = server.server,
server_port = server.server_port,
password = server.password,
method = server.method,
remarks = "example-server"
};
var serverWithRemarkCanonUrl = "ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4#example-server";
var serverWithPlugin = new Server
{
server = server.server,
server_port = server.server_port,
password = server.password,
method = server.method,
plugin = "obfs-local",
plugin_opts = "obfs=http;obfs-host=google.com"
};
var serverWithPluginCanonUrl =
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888/?plugin=obfs-local%3bobfs%3dhttp%3bobfs-host%3dgoogle.com";
var serverWithPluginAndRemark = new Server
{
server = server.server,
server_port = server.server_port,
password = server.password,
method = server.method,
plugin = serverWithPlugin.plugin,
plugin_opts = serverWithPlugin.plugin_opts,
remarks = serverWithRemark.remarks
};
var serverWithPluginAndRemarkCanonUrl =
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888/?plugin=obfs-local%3bobfs%3dhttp%3bobfs-host%3dgoogle.com#example-server";
RunParseShadowsocksUrlTest(
string.Join(
"\r\n",
serverCanonUrl,
"\r\n",
"ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4/",
serverWithRemarkCanonUrl,
"ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4/#example-server"),
new[]
{
server,
server,
serverWithRemark,
serverWithRemark
});
RunParseShadowsocksUrlTest(
string.Join(
"\r\n",
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888",
"\r\n",
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888/",
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888#example-server",
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888/#example-server",
serverWithPluginCanonUrl,
serverWithPluginAndRemarkCanonUrl,
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888/?plugin=obfs-local%3bobfs%3dhttp%3bobfs-host%3dgoogle.com&unsupported=1#example-server"),
new[]
{
server,
server,
serverWithRemark,
serverWithRemark,
serverWithPlugin,
serverWithPluginAndRemark,
serverWithPluginAndRemark
});
var generateUrlCases = new Dictionary<string, Server>
{
[serverCanonUrl] = server,
[serverWithRemarkCanonUrl] = serverWithRemark,
[serverWithPluginCanonUrl] = serverWithPlugin,
[serverWithPluginAndRemarkCanonUrl] = serverWithPluginAndRemark
};
RunGenerateShadowsocksUrlTest(generateUrlCases);
}
private static void RunParseShadowsocksUrlTest(string testCase, IReadOnlyList<Server> expected)
{
var actual = Server.GetServers(testCase);
if (actual.Count != expected.Count)
{
Assert.Fail("Wrong number of configs. Expected: {0}. Actual: {1}", expected.Count, actual.Count);
}
for (int i = 0; i < expected.Count; i++)
{
var expectedServer = expected[i];
var actualServer = actual[i];
Assert.AreEqual(expectedServer.server, actualServer.server);
Assert.AreEqual(expectedServer.server_port, actualServer.server_port);
Assert.AreEqual(expectedServer.password, actualServer.password);
Assert.AreEqual(expectedServer.method, actualServer.method);
Assert.AreEqual(expectedServer.plugin, actualServer.plugin);
Assert.AreEqual(expectedServer.plugin_opts, actualServer.plugin_opts);
Assert.AreEqual(expectedServer.remarks, actualServer.remarks);
Assert.AreEqual(expectedServer.timeout, actualServer.timeout);
}
}
private static void RunGenerateShadowsocksUrlTest(IReadOnlyDictionary<string, Server> testCases)
{
foreach (var testCase in testCases)
{
string expected = testCase.Key;
Server config = testCase.Value;
var actual = ShadowsocksController.GetQRCode(config);
Assert.AreEqual(expected, actual);
}
}
}
}

Loading…
Cancel
Save