<commit_before>//
//  This file is part of Blokada.
//
//  This Source Code Form is subject to the terms of the Mozilla Public
//  License, v. 2.0. If a copy of the MPL was not distributed with this
//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
//  Copyright © 2020 Blocka AB. All rights reserved.
//
//  @author <NAME>
//

import Network
import NetworkExtension
import os.log

struct TunnelConfig {
    var privateKey: String
    var gatewayId: String
    var gatewayIpv4: String
    var gatewayIpv6: String
    var gatewayPort: String
    var vip4: String
    var vip6: String
    var dns: String
}

enum PacketTunnelProviderError: String, Error {
    case couldNotStartBackend
    case couldNotDetermineFileDescriptor
    case couldNotSetNetworkSettings
}

enum IPStack {
    case IPv4
    case IPv6
}

class PacketTunnelProvider: NEPacketTunnelProvider, TunnelSessionDelegate {
    var tunnelStarted = false
    private var dnsHandle: OpaquePointer?
    private var apiHandle: OpaquePointer?
    private var device = PacketLoop()
    private var deviceStack: IPStack = .IPv4
    private var tunnelConfig: TunnelConfig?
    private var previousConfig: TunnelConfig?
    private var networkMonitor: NWPathMonitor?
    private var currentGatewayStack: IPStack?
    private var pauseTimer: Timer?

    deinit {
        networkMonitor?.cancel()
    }

    override func startTunnel(options: [String : NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
        self.persistedStats = Stats.load()

        engine_logger("info")
        freopen(LoggerSaver.logFile?.path.cString(using: .ascii)!, "a+", stderr)
        freopen(LoggerSaver.logFile?.path.cString(using: .ascii)!, "a+", stdout)

        panic_hook { (msg) in
            let str = String(cString: msg!)
            NELogger.e("PacketTunnelProvider: Blocka Engine: \(str)")
        }

        NELogger.v("PacketTunnelProvider: startTunnel")
        if (tunnelStarted) {
            NELogger.v("PacketTunnelProvider: tunnel already started, ignoring")
            return startTunnelCompletionHandler(nil)
        }
        tunnelStarted = true

        guard let config = (protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration else {
            return startTunnelCompletionHandler("No protocolConfiguration specified")
        }

        self.apiHandle = api_new(10, config["userAgent"] as! String)

        var dns: String? = nil
        if let version = config["version"] as? String, version == "6" {
            // New config, all ok.
        } else {
            if config["mode"] as? String == "plus" {
                // Old config, but plus was activated, so keep the VPN active (no adblocking) until user takes action
                dns = "8.8.8.8"
            } else {
                NELogger.w("PacketTunnelProvider: old config version, doing passs through until config update")
                return self.passThrough(completionHandler: startTunnelCompletionHandler)
            }
        }

        tunnelConfig = TunnelConfig(
            privateKey: config["privateKey"] as! String,
            gatewayId: config["gatewayId"] as! String,
            gatewayIpv4: config["ipv4"] as! String,
            //gatewayIpv6: config["ipv6"] as! String,
            gatewayIpv6: "",
            gatewayPort: config["port"] as! String,
            vip4: config["vip4"] as! String,
            vip6: config["vip6"] as! String,
            dns: dns ?? config["dns"] as! String
        )
        return self.connectVPN(completionHandler: startTunnelCompletionHandler)
    }

    private func connectVPN(completionHandler: @escaping ((Error?)) -> Void) {
        NELogger.v("PacketTunnelProvider: connectVPN")
        let settings = createTunnelSettings(gatewayIp: tunnelConfig!.gatewayIpv4, vip4: tunnelConfig!.vip4, vip6: tunnelConfig!.vip6, dns: tunnelConfig!.dns, defaultRoute: true)

        setTunnelNetworkSettings(settings) { error in
            if let error = error {
                NELogger.e("PacketTunnelProvider: failed connectVPN with tunnel network settings".cause(error))
                completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
                return
            }
            guard let config = self.tunnelConfig else { fatalError("connectVPN: no config") }

            NELogger.v("PacketTunnelProvider: connecting to: \(self.tunnelConfig!.gatewayId)")
            self.device.stop()
            self.networkMonitor = NWPathMonitor()
            self.networkMonitor?.start(queue: .global())
            self.device.start(privateKey: config.privateKey, gatewayKey: config.gatewayId, delegate: self)
            NELogger.v("PacketTunnelProvider: real VPN established")
            completionHandler(nil)
        }
    }

    override func sleep(completionHandler: @escaping () -> Void) {
        self.device.sleep()
        completionHandler()
    }

    override func wake() {
        self.device.wake()
    }<commit_msg>ios: try fix conn issues with reconnect on wake<commit_after>//
//  This file is part of Blokada.
//
//  This Source Code Form is subject to the terms of the Mozilla Public
//  License, v. 2.0. If a copy of the MPL was not distributed with this
//  file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
//  Copyright © 2020 Blocka AB. All rights reserved.
//
//  @author <NAME>
//

import Network
import NetworkExtension
import os.log

struct TunnelConfig {
    var privateKey: String
    var gatewayId: String
    var gatewayIpv4: String
    var gatewayIpv6: String
    var gatewayPort: String
    var vip4: String
    var vip6: String
    var dns: String
}

enum PacketTunnelProviderError: String, Error {
    case couldNotStartBackend
    case couldNotDetermineFileDescriptor
    case couldNotSetNetworkSettings
}

enum IPStack {
    case IPv4
    case IPv6
}

class PacketTunnelProvider: NEPacketTunnelProvider, TunnelSessionDelegate {
    var tunnelStarted = false
    private var dnsHandle: OpaquePointer?
    private var apiHandle: OpaquePointer?
    private var device = PacketLoop()
    private var deviceStack: IPStack = .IPv4
    private var tunnelConfig: TunnelConfig?
    private var previousConfig: TunnelConfig?
    private var networkMonitor: NWPathMonitor?
    private var currentGatewayStack: IPStack?
    private var pauseTimer: Timer?

    deinit {
        networkMonitor?.cancel()
    }

    override func startTunnel(options: [String : NSObject]?, completionHandler startTunnelCompletionHandler: @escaping (Error?) -> Void) {
        self.persistedStats = Stats.load()

        engine_logger("info")
        freopen(LoggerSaver.logFile?.path.cString(using: .ascii)!, "a+", stderr)
        freopen(LoggerSaver.logFile?.path.cString(using: .ascii)!, "a+", stdout)

        panic_hook { (msg) in
            let str = String(cString: msg!)
            NELogger.e("PacketTunnelProvider: Blocka Engine: \(str)")
        }

        NELogger.v("PacketTunnelProvider: startTunnel")
        if (tunnelStarted) {
            NELogger.v("PacketTunnelProvider: tunnel already started, ignoring")
            return startTunnelCompletionHandler(nil)
        }
        tunnelStarted = true

        guard let config = (protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration else {
            return startTunnelCompletionHandler("No protocolConfiguration specified")
        }

        self.apiHandle = api_new(10, config["userAgent"] as! String)

        var dns: String? = nil
        if let version = config["version"] as? String, version == "6" {
            // New config, all ok.
        } else {
            if config["mode"] as? String == "plus" {
                // Old config, but plus was activated, so keep the VPN active (no adblocking) until user takes action
                dns = "8.8.8.8"
            } else {
                NELogger.w("PacketTunnelProvider: old config version, doing passs through until config update")
                return self.passThrough(completionHandler: startTunnelCompletionHandler)
            }
        }

        tunnelConfig = TunnelConfig(
            privateKey: config["privateKey"] as! String,
            gatewayId: config["gatewayId"] as! String,
            gatewayIpv4: config["ipv4"] as! String,
            //gatewayIpv6: config["ipv6"] as! String,
            gatewayIpv6: "",
            gatewayPort: config["port"] as! String,
            vip4: config["vip4"] as! String,
            vip6: config["vip6"] as! String,
            dns: dns ?? config["dns"] as! String
        )
        return self.connectVPN(completionHandler: startTunnelCompletionHandler)
    }

    private func connectVPN(completionHandler: @escaping ((Error?)) -> Void) {
        NELogger.v("PacketTunnelProvider: connectVPN")
        let settings = createTunnelSettings(gatewayIp: tunnelConfig!.gatewayIpv4, vip4: tunnelConfig!.vip4, vip6: tunnelConfig!.vip6, dns: tunnelConfig!.dns, defaultRoute: true)

        setTunnelNetworkSettings(settings) { error in
            if let error = error {
                NELogger.e("PacketTunnelProvider: failed connectVPN with tunnel network settings".cause(error))
                completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
                return
            }
            guard let config = self.tunnelConfig else { fatalError("connectVPN: no config") }

            NELogger.v("PacketTunnelProvider: connecting to: \(self.tunnelConfig!.gatewayId)")
            self.device.stop()
            self.networkMonitor = NWPathMonitor()
            self.networkMonitor?.start(queue: .global())
            self.device.start(privateKey: config.privateKey, gatewayKey: config.gatewayId, delegate: self)
            NELogger.v("PacketTunnelProvider: real VPN established")
            completionHandler(nil)
        }
    }

    override func sleep(completionHandler: @escaping () -> Void) {
        self.device.sleep()
        completionHandler()
    }

    override func wake() {
        self.device.wake()
        // TODO: experimental
        connectVPN { error in
            NELogger.e("PacketTunnelProvider: error connecting after wake: \(error)")
        }
    }
{'fraction_non_alphanumeric': 0.09559255631733594, 'fraction_numerical': 0.007051909892262488, 'mean_word_length': 2.6247781327653534, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7301947', 'n_tokens_mistral': 2903, 'n_tokens_neox': 2811, 'n_words': 769}