Skip to content

Commit

Permalink
SIP002 and SIP003 support
Browse files Browse the repository at this point in the history
  • Loading branch information
rwasef1830 committed Aug 14, 2017
1 parent ed31a3c commit f04b497
Show file tree
Hide file tree
Showing 8 changed files with 545 additions and 80 deletions.
12 changes: 10 additions & 2 deletions shadowsocks-csharp/Controller/Service/TCPRelay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,15 @@ private void StartConnect()
// 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)
{
Expand Down Expand Up @@ -607,7 +615,7 @@ private void StartConnect()
proxyTimer.Enabled = true;

proxyTimer.Session = session;
proxyTimer.DestEndPoint = SocketUtil.GetEndPoint(_server.server, _server.server_port);
proxyTimer.DestEndPoint = serverEP;
proxyTimer.Server = _server;

_proxyConnected = false;
Expand Down
70 changes: 66 additions & 4 deletions shadowsocks-csharp/Controller/ShadowsocksController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
Expand All @@ -14,6 +15,7 @@
using Shadowsocks.Properties;
using Shadowsocks.Util;
using System.Linq;
using Shadowsocks.Proxy;

namespace Shadowsocks.Controller
{
Expand All @@ -33,6 +35,8 @@ public class ShadowsocksController
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; }

Expand Down Expand Up @@ -79,6 +83,7 @@ public ShadowsocksController()
_config = Configuration.Load();
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
_strategyManager = new StrategyManager(this);
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>();
StartReleasingMemory();
StartTrafficStatistics(61);
}
Expand Down Expand Up @@ -144,6 +149,23 @@ public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, E
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;
Expand Down Expand Up @@ -259,6 +281,7 @@ public void Stop()
{
_listener.Stop();
}
StopPlugins();
if (privoxyRunner != null)
{
privoxyRunner.Stop();
Expand All @@ -270,6 +293,15 @@ public void Stop()
Encryption.RNG.Close();
}

private void StopPlugins()
{
foreach (var serverAndPlugin in _pluginsByServer)
{
serverAndPlugin.Value?.Dispose();
}
_pluginsByServer.Clear();
}

public void TouchPACFile()
{
string pacFilename = _pacServer.TouchPACFile();
Expand Down Expand Up @@ -297,13 +329,41 @@ public string GetQRCodeForCurrentServer()
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()
Expand Down Expand Up @@ -421,6 +481,8 @@ public void UpdateOutboundCounter(Server server, long n)

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();
Expand Down
109 changes: 89 additions & 20 deletions shadowsocks-csharp/Model/Server.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -10,17 +10,15 @@ 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;

public string server;
public int server_port;
public string password;
public string method;
public string plugin;
public string plugin_opts;
public string remarks;
public int timeout;

Expand Down Expand Up @@ -65,33 +63,104 @@ public Server()
server = "";
server_port = 8388;
method = "aes-256-cfb";
plugin = "";
plugin_opts = "";
password = "";
remarks = "";
timeout = DefaultServerTimeoutSec;
}

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);
}
Expand Down
Loading

0 comments on commit f04b497

Please sign in to comment.