[WIP] shadowsocks implementation added

This commit is contained in:
Alex Kh 2021-12-21 23:17:22 +04:00
parent 40996888c9
commit 6583090d4f
2259 changed files with 417734 additions and 92 deletions

View file

@ -29,6 +29,8 @@ public class IOSVpnProtocolImpl : NSObject {
private var privateKey : PrivateKey? = nil
private var deviceIpv4Address: String? = nil
private var deviceIpv6Address: String? = nil
private var openVPNConfig: String? = nil
private var shadowSocksConfig: String? = nil
@objc enum ConnectionState: Int { case Error, Connected, Disconnected }
@ -45,6 +47,8 @@ public class IOSVpnProtocolImpl : NSObject {
precondition(!vpnBundleID.isEmpty)
stateChangeCallback = callback
self.openVPNConfig = config
self.shadowSocksConfig = nil
NotificationCenter.default.removeObserver(self)
NotificationCenter.default.addObserver(self,
@ -117,6 +121,8 @@ public class IOSVpnProtocolImpl : NSObject {
self.privateKey = PrivateKey(rawValue: privateKey)
self.deviceIpv4Address = deviceIpv4Address
self.deviceIpv6Address = deviceIpv6Address
self.openVPNConfig = nil
self.shadowSocksConfig = nil
NotificationCenter.default.removeObserver(self)
@ -170,6 +176,63 @@ public class IOSVpnProtocolImpl : NSObject {
}
}
}
@objc init(bundleID: String,
tunnelConfig: String,
ssConfig: String,
closure: @escaping (ConnectionState, Date?) -> Void,
callback: @escaping (Bool) -> Void) {
super.init()
vpnBundleID = bundleID;
precondition(!vpnBundleID.isEmpty)
stateChangeCallback = callback
self.openVPNConfig = tunnelConfig
self.shadowSocksConfig = ssConfig
NotificationCenter.default.removeObserver(self)
NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil)
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
if let error = error {
Logger.global?.log(message: "Loading from preference failed: \(error)")
closure(ConnectionState.Error, nil)
return
}
if self == nil {
Logger.global?.log(message: "We are shutting down.")
return
}
let nsManagers = managers ?? []
Logger.global?.log(message: "We have received \(nsManagers.count) managers.")
print("We have received \(nsManagers.count) managers.")
let tunnel = nsManagers.first(where: IOSVpnProtocolImpl.isOurManager(_:))
if tunnel == nil {
Logger.global?.log(message: "Creating the tunnel via shadowsocks")
print("Creating the tunnel via SS")
self!.tunnel = NETunnelProviderManager()
closure(ConnectionState.Disconnected, nil)
return
}
Logger.global?.log(message: "Tunnel already exists")
print("SS Tunnel already exists")
self!.tunnel = tunnel
if tunnel?.connection.status == .connected {
closure(ConnectionState.Connected, tunnel?.connection.connectedDate)
} else {
closure(ConnectionState.Disconnected, nil)
}
}
}
@objc private func vpnStatusDidChange(notification: Notification) {
guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return }
@ -230,6 +293,27 @@ public class IOSVpnProtocolImpl : NSObject {
return true
}
@objc func connect(ssConfig: String,
ovpnConfig: String,
failureCallback: @escaping () -> Void) {
Logger.global?.log(message: "Connecting")
assert(tunnel != nil)
self.openVPNConfig = ovpnConfig
self.shadowSocksConfig = ssConfig
let addr: String = ovpnConfig
.splitToArray(separator: "\n", trimmingCharacters: nil)
.first { $0.starts(with: "remote ") }
.splitToArray(separator: " ", trimmingCharacters: nil)[1]
print("server: \(addr)")
// Let's remove the previous config if it exists.
(tunnel?.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
self.configureTunnel(withShadowSocks: self.shadowSocksConfig, serverAddress: addr, config: self.openVPNConfig, failureCallback: failureCallback)
}
@objc func connect(ovpnConfig: String, failureCallback: @escaping () -> Void) {
Logger.global?.log(message: "Connecting")
assert(tunnel != nil)
@ -299,6 +383,8 @@ public class IOSVpnProtocolImpl : NSObject {
failureCallback()
return
}
guard tunnel != nil else { failureCallback(); return }
proto.providerBundleIdentifier = vpnBundleID
tunnel!.protocolConfiguration = proto
@ -351,6 +437,47 @@ public class IOSVpnProtocolImpl : NSObject {
}
}
func configureTunnel(withShadowSocks ssConfig: String?, serverAddress: String, config: String?, failureCallback: @escaping () -> Void) {
guard let ss = ssConfig, let ovpn = config else { failureCallback(); return }
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = vpnBundleID
tunnelProtocol.providerConfiguration = ["ovpn": Data(ovpn.utf8), "ss": Data(ss.utf8)]
tunnel?.protocolConfiguration = tunnelProtocol
tunnel?.localizedDescription = "Amnezia ShadowSocks"
tunnel?.isEnabled = true
tunnel?.saveToPreferences { [unowned self] saveError in
if let error = saveError {
Logger.global?.log(message: "Connect ShadowSocks Tunnel Save Error: \(error)")
failureCallback()
return
}
Logger.global?.log(message: "Saving ShadowSocks tunnel succeeded")
self.tunnel?.loadFromPreferences { error in
if let error = error {
Logger.global?.log(message: "Connect ShadowSocks Tunnel Load Error: \(error)")
failureCallback()
return
}
Logger.global?.log(message: "Loading the ShadowSocks tunnel succeeded")
print("Loading the ss tunnel succeeded")
do {
print("starting ss tunnel")
try self.tunnel?.connection.startVPNTunnel()
} catch let error {
Logger.global?.log(message: "Something went wrong: \(error)")
failureCallback()
return
}
}
}
}
func configureOpenVPNTunnel(serverAddress: String, config: String, failureCallback: @escaping () -> Void) {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.serverAddress = serverAddress
@ -393,7 +520,7 @@ public class IOSVpnProtocolImpl : NSObject {
@objc func disconnect() {
Logger.global?.log(message: "Disconnecting")
assert(tunnel != nil)
guard tunnel != nil else { return }
(tunnel!.connection as? NETunnelProviderSession)?.stopTunnel()
}
@ -408,12 +535,60 @@ public class IOSVpnProtocolImpl : NSObject {
checkWireguardStatus(callback: callback)
case .openvpn:
checkOVPNStatus(callback: callback)
case .shadowsocks:
checkShadowSocksStatus(callback: callback)
case .empty:
break
}
}
private func checkShadowSocksStatus(callback: @escaping (String, String, String) -> Void) {
Logger.global?.log(message: "Check ShadowSocks")
guard let proto = tunnel?.protocolConfiguration as? NETunnelProviderProtocol else {
callback("", "", "")
return
}
guard let ssData = proto.providerConfiguration?["ss"] as? Data,
let ssConfig = try? JSONSerialization.jsonObject(with: ssData, options: []) as? [String: Any],
let serverIpv4Gateway = ssConfig["remote_host"] as? String else {
callback("", "", "")
return
}
print("server IP: \(serverIpv4Gateway)")
let deviceIpv4Address = getTunIPAddress()
print("device IP: \(serverIpv4Gateway)")
if deviceIpv4Address == nil {
callback("", "", "")
return
}
guard let session = tunnel?.connection as? NETunnelProviderSession else {
callback("", "", "")
return
}
do {
try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in
guard let data = data,
let configString = String(data: data, encoding: .utf8)
else {
Logger.global?.log(message: "Failed to convert data to string")
callback("", "", "")
return
}
callback("\(serverIpv4Gateway)", "\(deviceIpv4Address!)", configString)
}
} catch {
Logger.global?.log(message: "Failed to retrieve data from session")
callback("", "", "")
}
}
private func checkOVPNStatus(callback: @escaping (String, String, String) -> Void) {
Logger.global?.log(message: "Check OpenVPN")
guard let proto = tunnel?.protocolConfiguration as? NETunnelProviderProtocol else {
@ -463,7 +638,6 @@ public class IOSVpnProtocolImpl : NSObject {
Logger.global?.log(message: "Failed to retrieve data from session")
callback("", "", "")
}
}
private func checkWireguardStatus(callback: @escaping (String, String, String) -> Void) {
@ -549,7 +723,7 @@ public class IOSVpnProtocolImpl : NSObject {
}
enum TunnelType: String {
case wireguard, openvpn, empty
case wireguard, openvpn, shadowsocks, empty
}
extension String {
@ -557,6 +731,7 @@ extension String {
switch self {
case "wireguard": return .wireguard
case "openvpn": return .openvpn
case "shadowsocks": return .shadowsocks
default:
return .empty
}