Expand Shadowsocks runtime compatibility
This commit is contained in:
parent
d1638726ca
commit
4d1f589280
167 changed files with 57173 additions and 1640 deletions
|
|
@ -183,15 +183,20 @@ private struct StoredProxySubscriptionNodeConfig: Decodable {
|
|||
var network: StoredProxySubscriptionNetworkTransport?
|
||||
var cipher: String?
|
||||
var plugin: StoredJSONValue?
|
||||
var smux: StoredJSONValue?
|
||||
var udp: Bool?
|
||||
var udpOverTCP: Bool?
|
||||
var clientFingerprint: String?
|
||||
|
||||
var runtimeSupported: Bool {
|
||||
switch type {
|
||||
case "trojan":
|
||||
return network?.isTrojanTCP ?? true
|
||||
case "shadowsocks":
|
||||
return plugin == nil
|
||||
&& udpOverTCP != true
|
||||
guard Self.supportedShadowsocksSmux(smux) else {
|
||||
return false
|
||||
}
|
||||
return Self.supportedShadowsocksPlugin(plugin, clientFingerprint: clientFingerprint)
|
||||
&& Self.supportedShadowsocksCiphers.contains(cipher?.lowercased() ?? "")
|
||||
default:
|
||||
return false
|
||||
|
|
@ -203,18 +208,215 @@ private struct StoredProxySubscriptionNodeConfig: Decodable {
|
|||
case network
|
||||
case cipher
|
||||
case plugin
|
||||
case smux
|
||||
case udp
|
||||
case udpOverTCP = "udp_over_tcp"
|
||||
case clientFingerprint = "client_fingerprint"
|
||||
}
|
||||
|
||||
private static let supportedShadowsocksCiphers: Set<String> = [
|
||||
"none",
|
||||
"plain",
|
||||
"table",
|
||||
"rc4-md5",
|
||||
"rc4",
|
||||
"aes-128-ctr",
|
||||
"aes-192-ctr",
|
||||
"aes-256-ctr",
|
||||
"aes-128-cfb",
|
||||
"aes-128-cfb1",
|
||||
"aes-128-cfb8",
|
||||
"aes-128-cfb128",
|
||||
"aes-192-cfb",
|
||||
"aes-192-cfb1",
|
||||
"aes-192-cfb8",
|
||||
"aes-192-cfb128",
|
||||
"aes-256-cfb",
|
||||
"aes-256-cfb1",
|
||||
"aes-256-cfb8",
|
||||
"aes-256-cfb128",
|
||||
"aes-128-ofb",
|
||||
"aes-192-ofb",
|
||||
"aes-256-ofb",
|
||||
"camellia-128-ctr",
|
||||
"camellia-192-ctr",
|
||||
"camellia-256-ctr",
|
||||
"camellia-128-cfb",
|
||||
"camellia-128-cfb1",
|
||||
"camellia-128-cfb8",
|
||||
"camellia-128-cfb128",
|
||||
"camellia-192-cfb",
|
||||
"camellia-192-cfb1",
|
||||
"camellia-192-cfb8",
|
||||
"camellia-192-cfb128",
|
||||
"camellia-256-cfb",
|
||||
"camellia-256-cfb1",
|
||||
"camellia-256-cfb8",
|
||||
"camellia-256-cfb128",
|
||||
"camellia-128-ofb",
|
||||
"camellia-192-ofb",
|
||||
"camellia-256-ofb",
|
||||
"chacha20",
|
||||
"chacha20-ietf",
|
||||
"xchacha20",
|
||||
"aes-128-gcm",
|
||||
"aead_aes_128_gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"aead_aes_256_gcm",
|
||||
"aes-128-ccm",
|
||||
"aes-192-ccm",
|
||||
"aes-256-ccm",
|
||||
"aes-128-gcm-siv",
|
||||
"aes-256-gcm-siv",
|
||||
"chacha8-ietf-poly1305",
|
||||
"chacha20-ietf-poly1305",
|
||||
"chacha20-poly1305",
|
||||
"aead_chacha20_poly1305",
|
||||
"rabbit128-poly1305",
|
||||
"xchacha8-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305",
|
||||
"sm4-gcm",
|
||||
"sm4-ccm",
|
||||
"aegis-128l",
|
||||
"aegis-256",
|
||||
"aez-384",
|
||||
"deoxys-ii-256-128",
|
||||
"ascon128",
|
||||
"ascon128a",
|
||||
"lea-128-gcm",
|
||||
"lea-192-gcm",
|
||||
"lea-256-gcm",
|
||||
"2022-blake3-aes-128-gcm",
|
||||
"2022-blake3-aes-256-gcm",
|
||||
"2022-blake3-aes-128-ccm",
|
||||
"2022-blake3-aes-256-ccm",
|
||||
"2022-blake3-chacha20-poly1305",
|
||||
"2022-blake3-chacha8-poly1305",
|
||||
]
|
||||
|
||||
private static func supportedShadowsocksPlugin(
|
||||
_ plugin: StoredJSONValue?,
|
||||
clientFingerprint: String?
|
||||
) -> Bool {
|
||||
guard let plugin else {
|
||||
return true
|
||||
}
|
||||
guard case .object(let object) = plugin,
|
||||
case .string(let name)? = object["name"]
|
||||
else {
|
||||
return false
|
||||
}
|
||||
let normalizedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let opts: [String: StoredJSONValue]
|
||||
if case .object(let decodedOpts)? = object["opts"] {
|
||||
opts = decodedOpts
|
||||
} else {
|
||||
opts = [:]
|
||||
}
|
||||
if normalizedName == "restls",
|
||||
let clientFingerprint,
|
||||
Self.shadowsocksClientFingerprintIsActive(clientFingerprint)
|
||||
{
|
||||
return false
|
||||
}
|
||||
if normalizedName == "v2ray-plugin" || normalizedName == "gost-plugin" {
|
||||
guard let mode = opts["mode"]?.stringValue ?? opts["obfs"]?.stringValue else {
|
||||
return false
|
||||
}
|
||||
guard mode.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == "websocket" else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if normalizedName == "shadow-tls" {
|
||||
let version = opts["version"]?.stringValue ?? "2"
|
||||
let normalizedVersion = version.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
switch normalizedVersion {
|
||||
case "1":
|
||||
return true
|
||||
case "2", "3":
|
||||
if let clientFingerprint,
|
||||
Self.shadowsocksClientFingerprintIsActive(clientFingerprint)
|
||||
{
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
if normalizedName == "kcptun" {
|
||||
guard let smuxVersion = opts["smuxver"]?.stringValue else {
|
||||
return true
|
||||
}
|
||||
guard let version = Double(smuxVersion.trimmingCharacters(in: .whitespacesAndNewlines)) else {
|
||||
return false
|
||||
}
|
||||
return version >= 0
|
||||
}
|
||||
if normalizedName == "restls" {
|
||||
let versionHint = opts["version-hint"]?.stringValue ?? ""
|
||||
switch versionHint.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
|
||||
case "tls12", "tls13":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
guard normalizedName == "obfs" else {
|
||||
return true
|
||||
}
|
||||
guard let mode = opts["mode"]?.stringValue ?? opts["obfs"]?.stringValue else {
|
||||
return false
|
||||
}
|
||||
switch mode.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
|
||||
case "http", "tls":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private static func shadowsocksClientFingerprintIsActive(_ value: String) -> Bool {
|
||||
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||
return [
|
||||
"chrome",
|
||||
"firefox",
|
||||
"safari",
|
||||
"ios",
|
||||
"android",
|
||||
"edge",
|
||||
"360",
|
||||
"qq",
|
||||
"random",
|
||||
"chrome120",
|
||||
"firefox120",
|
||||
"safari16",
|
||||
"chrome_psk",
|
||||
"chrome_psk_shuffle",
|
||||
"chrome_padding_psk_shuffle",
|
||||
"chrome_pq",
|
||||
"chrome_pq_psk",
|
||||
"randomized",
|
||||
].contains(normalized)
|
||||
}
|
||||
|
||||
private static func supportedShadowsocksSmux(_ smux: StoredJSONValue?) -> Bool {
|
||||
guard case .object(let object)? = smux else {
|
||||
return true
|
||||
}
|
||||
guard object["enabled"]?.boolValue ?? false else {
|
||||
return true
|
||||
}
|
||||
let protocolName = object["protocol"]?.stringValue?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.lowercased() ?? "h2mux"
|
||||
guard protocolName == "h2mux" || protocolName == "smux" || protocolName == "yamux" else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private enum StoredProxySubscriptionNetworkTransport: Decodable, Hashable {
|
||||
|
|
@ -247,6 +449,42 @@ private enum StoredJSONValue: Decodable, Hashable {
|
|||
case array([StoredJSONValue])
|
||||
case null
|
||||
|
||||
var stringValue: String? {
|
||||
switch self {
|
||||
case .string(let value):
|
||||
return value
|
||||
case .number(let value):
|
||||
if value.rounded(.towardZero) == value {
|
||||
return String(Int(value))
|
||||
}
|
||||
return String(value)
|
||||
case .bool(let value):
|
||||
return String(value)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var boolValue: Bool? {
|
||||
switch self {
|
||||
case .bool(let value):
|
||||
return value
|
||||
case .string(let value):
|
||||
switch value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
|
||||
case "1", "true", "yes", "y":
|
||||
return true
|
||||
case "0", "false", "no", "n":
|
||||
return false
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
case .number(let value):
|
||||
return value != 0
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
init(from decoder: Swift.Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if container.decodeNil() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue