/// <summary> /// Follow scheme https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html /// to parse a given shadowsocks server uri. /// </summary> internal ShadowsocksServer ParseShadowsocksURI(string ssURI) { var u = new Uri(ssURI); var server = new ShadowsocksServer(); // UserInfo: WebSafe Base64(method:password) var plainUserInfo = Encoding.UTF8.GetString(WebSafeBase64Decode(HttpUtility.UrlDecode(u.UserInfo))).Split(':', 2); server.Method = plainUserInfo[0]; server.Password = plainUserInfo.ElementAtOrDefault(1); // Host & Port server.Host = u.Host; server.Port = u.Port; // Name if (u.Fragment.StartsWith("#")) { server.Name = HttpUtility.UrlDecode(u.Fragment.Substring(1)); } // Plugin var otherParams = HttpUtility.ParseQueryString(u.Query); var plugin = (otherParams.Get("plugin") ?? "").Trim(); var pluginInfos = plugin.Split(";"); switch (pluginInfos[0].Trim()) { case "simple-obfs": case "obfs-local": var options = new SimpleObfsPluginOptions(); server.PluginOptions = options; foreach (var info in pluginInfos) { var trimedInfo = info.Trim(); if (trimedInfo.StartsWith("obfs=")) { if (SimpleObfsPluginOptions.TryParseMode(trimedInfo.Substring("obfs=".Length).Trim(), out SimpleObfsPluginMode mode)) { options.Mode = mode; } } else if (string.IsNullOrEmpty(options.Host) && trimedInfo.StartsWith("obfs-host=")) { options.Host = trimedInfo.Substring("obfs-host=".Length); } else { // TODO: log } } break; } return(server); }
/// <summary> /// Parse QuantumultX shadowsocks proxy setting /// Sample: /// shadowsocks=example.com:80, method=chacha20, password=pwd, obfs=http, obfs-host=bing.com, obfs-uri=/resource/file, fast-open=false, udp-relay=false, server_check_url=http://www.apple.com/generate_204, tag=ss-01 /// shadowsocks=example.com:443, method=chacha20, password=pwd, ssr-protocol=auth_chain_b, ssr-protocol-param=def, obfs=tls1.2_ticket_fastauth, obfs-host=bing.com, tag=ssr /// </summary> /// <param name="line">A string starts with `shadowsocks=`</param> /// <returns>Could be an instance of <see cref="ShadowsocksRServer"> or <see cref="ShadowsocksServer"></returns> public Server ParseShadowsocksLine(string line) { var properties = new Dictionary <string, string>(); foreach (var kvPair in line.Split(",")) { var parts = kvPair.Split("=", 2); if (parts.Length != 2) { this.logger.LogWarning($"[{{ComponentName}}] Invaild shadowsocks setting: {line}", this.GetType()); return(null); } properties.TryAdd(parts[0].Trim().ToLower(), parts[1].Trim()); } var host = default(string); var port = default(int); var method = properties.GetValueOrDefault("method"); var password = properties.GetValueOrDefault("password"); if (!Misc.SplitHostAndPort(properties.GetValueOrDefault("shadowsocks"), out host, out port)) { this.logger.LogWarning($"[{{ComponentName}}] Host and port are invalid and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } if (string.IsNullOrEmpty(method)) { this.logger.LogWarning($"[{{ComponentName}}] Encryption method is missing and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } if (string.IsNullOrEmpty(password)) { this.logger.LogWarning($"[{{ComponentName}}] Password is missing and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } Server server; if (this.IsShadowsocksRServer(properties)) { var ssrServer = new ShadowsocksRServer { Method = method, }; ssrServer.Obfuscation = properties.GetValueOrDefault("obfs"); ssrServer.ObfuscationParameter = properties.GetValueOrDefault("obfs-host"); ssrServer.Protocol = properties.GetValueOrDefault("ssr-protocol"); ssrServer.ProtocolParameter = properties.GetValueOrDefault("ssr-protocol-param"); // QuantumultX doesn't support following parameter yet // * UDP Port // * UDP over TCP server = ssrServer; } else { var ssServer = new ShadowsocksServer { Method = method, }; if (bool.TryParse(properties.GetValueOrDefault("udp-relay", "false"), out bool udpRelay)) { ssServer.UDPRelay = udpRelay; } if (bool.TryParse(properties.GetValueOrDefault("fast-open", "false"), out bool fastOpen)) { ssServer.FastOpen = fastOpen; } var obfs = properties.GetValueOrDefault("obfs", "").ToLower(); switch (obfs) { case "tls": case "http": { if (!SimpleObfsPluginOptions.TryParseMode(obfs, out SimpleObfsPluginMode mode)) { this.logger.LogWarning($"[{{ComponentName}}] Simple-obfs mode `{obfs}` is not supported and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } var options = new SimpleObfsPluginOptions() { Host = properties.GetValueOrDefault("obfs-host"), Mode = mode, }; if (obfs != "tls") { options.Uri = properties.GetValueOrDefault("obfs-uri"); } ssServer.PluginOptions = options; } break; case "wss": case "ws": { if (!V2RayPluginOptions.TryParseMode(obfs, out V2RayPluginMode mode)) { this.logger.LogWarning($"[{{ComponentName}}] Simple-obfs mode `{obfs}` is not supported and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } var options = new V2RayPluginOptions() { Mode = mode, Host = properties.GetValueOrDefault("obfs-host"), Path = properties.GetValueOrDefault("obfs-uri"), EnableTLS = obfs == "wss", }; ssServer.PluginOptions = options; } break; default: this.logger.LogWarning($"[{{ComponentName}}] Obfuscation `{obfs}` is not supported and this proxy setting is ignored. Invaild shadowsocks setting : {line}", this.GetType()); return(null); } server = ssServer; } server.Host = host; server.Port = port; server.Name = properties.GetValueOrDefault("tag", $"{server.Host}:{server.Port}"); return(server); }
public Server ParseShadowsocksServer(YamlMappingNode proxy) { string portString = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "port", "0"); int port; if (!int.TryParse(portString, out port)) { this.logger.LogError($"Invalid port: {port}."); return(null); } var server = new ShadowsocksServer() { Port = port, Name = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "name"), Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "server"), Password = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "password"), Method = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "cipher"), UDPRelay = Yaml.GetTruthFromYamlChildrenNode(proxy, "udp"), }; YamlNode pluginOptionsNode; // refer to offical clash to parse plugin options // https://github.com/Dreamacro/clash/blob/34338e7107c1868124f8aab2446f6b71c9b0640f/adapters/outbound/shadowsocks.go#L135 if (proxy.Children.TryGetValue("plugin-opts", out pluginOptionsNode) && pluginOptionsNode.NodeType == YamlNodeType.Mapping) { switch (Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "plugin").ToLower()) { case "obfs": var simpleObfsModeString = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "mode"); var simpleObfsOptions = new SimpleObfsPluginOptions(); if (SimpleObfsPluginOptions.TryParseMode(simpleObfsModeString, out SimpleObfsPluginMode simpleObfsMode)) { simpleObfsOptions.Mode = simpleObfsMode; } else if (!string.IsNullOrWhiteSpace(simpleObfsModeString)) { this.logger.LogError($"Unsupported simple-obfs mode: {simpleObfsModeString}. This server will be ignored."); return(null); } simpleObfsOptions.Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "host"); server.PluginOptions = simpleObfsOptions; break; case "v2ray-plugin": // also refer to official v2ray-plugin to parse v2ray-plugin options // https://github.com/shadowsocks/v2ray-plugin/blob/c7017f45bb1e12cf1e4b739bcb8f42f3eb8b22cd/main.go#L126 var v2rayModeString = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "mode"); var options = new V2RayPluginOptions(); server.PluginOptions = options; if (V2RayPluginOptions.TryParseMode(v2rayModeString, out V2RayPluginMode v2rayMode)) { options.Mode = v2rayMode; } else { this.logger.LogError($"Unsupported v2ray-plugin mode: {v2rayModeString}. This server will be ignored."); return(null); } options.Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "host"); options.Path = Yaml.GetStringOrDefaultFromYamlChildrenNode(pluginOptionsNode, "path"); options.EnableTLS = Yaml.GetTruthFromYamlChildrenNode(pluginOptionsNode, "tls"); options.SkipCertVerification = Yaml.GetTruthFromYamlChildrenNode(pluginOptionsNode, "skip-cert-verify"); options.Headers = new Dictionary <string, string>(); YamlNode headersNode; if (!(pluginOptionsNode as YamlMappingNode).Children.TryGetValue("headers", out headersNode)) { break; } if (headersNode.NodeType != YamlNodeType.Mapping) { break; } foreach (var header in (headersNode as YamlMappingNode)) { if (header.Value.NodeType != YamlNodeType.Scalar) { continue; } options.Headers.Add((header.Key as YamlScalarNode).Value, (header.Value as YamlScalarNode).Value); } break; } } if (server.PluginOptions == null) { var simpleObfsModeString = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "obfs"); if (SimpleObfsPluginOptions.TryParseMode(simpleObfsModeString, out SimpleObfsPluginMode simpleObfsMode)) { server.PluginOptions = new SimpleObfsPluginOptions() { Mode = simpleObfsMode, Host = Yaml.GetStringOrDefaultFromYamlChildrenNode(proxy, "obfs-host") }; } else if (!string.IsNullOrWhiteSpace(simpleObfsModeString)) { this.logger.LogError($"Unsupported simple-obfs mode: {simpleObfsModeString}"); return(null); } } return(server); }