mirror of
https://github.com/SoftEtherVPN/SoftEtherVPN.git
synced 2025-06-27 19:35:09 +03:00
Add iOS client implementation with SoftEther protocol handshake support
This commit is contained in:
parent
e1ec3d42e5
commit
62c71ebe5c
@ -0,0 +1,118 @@
|
|||||||
|
import Foundation
|
||||||
|
import Network
|
||||||
|
import Security
|
||||||
|
|
||||||
|
/// SecureConnection handles the TLS connection with the SoftEther VPN server
|
||||||
|
class SecureConnection {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var connection: NWConnection?
|
||||||
|
private let host: String
|
||||||
|
private let port: UInt16
|
||||||
|
private let queue = DispatchQueue(label: "com.softether.connection", qos: .userInitiated)
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
/// Initialize a secure connection
|
||||||
|
/// - Parameters:
|
||||||
|
/// - host: Server hostname or IP address
|
||||||
|
/// - port: Server port number
|
||||||
|
init(host: String, port: UInt16) {
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public Methods
|
||||||
|
|
||||||
|
/// Connect to the server using TLS
|
||||||
|
/// - Parameter completion: Callback with connection result
|
||||||
|
func connect(completion: @escaping (Bool, Error?) -> Void) {
|
||||||
|
let hostEndpoint = NWEndpoint.Host(host)
|
||||||
|
let portEndpoint = NWEndpoint.Port(rawValue: port)!
|
||||||
|
|
||||||
|
// Create TLS parameters
|
||||||
|
let tlsOptions = NWProtocolTLS.Options()
|
||||||
|
|
||||||
|
// Configure TLS for maximum compatibility with SoftEther
|
||||||
|
let securityOptions = tlsOptions.securityProtocolOptions
|
||||||
|
sec_protocol_options_set_tls_min_version(securityOptions, .TLSv12)
|
||||||
|
sec_protocol_options_set_tls_max_version(securityOptions, .TLSv13)
|
||||||
|
|
||||||
|
// Allow all cipher suites for compatibility
|
||||||
|
sec_protocol_options_set_cipher_suites(securityOptions, nil, 0)
|
||||||
|
|
||||||
|
// Disable certificate validation for initial development (ENABLE IN PRODUCTION)
|
||||||
|
sec_protocol_options_set_verify_block(securityOptions, { (_, _, trustResult, _) in
|
||||||
|
return true // Accept all certificates for testing
|
||||||
|
}, queue)
|
||||||
|
|
||||||
|
// Create TCP options with TLS
|
||||||
|
let tcpOptions = NWProtocolTCP.Options()
|
||||||
|
tcpOptions.enableKeepalive = true
|
||||||
|
tcpOptions.keepaliveIdle = 30
|
||||||
|
|
||||||
|
// Create connection parameters
|
||||||
|
let parameters = NWParameters(tls: tlsOptions, tcp: tcpOptions)
|
||||||
|
|
||||||
|
// Create the connection
|
||||||
|
connection = NWConnection(host: hostEndpoint, port: portEndpoint, using: parameters)
|
||||||
|
|
||||||
|
// Set up state handling
|
||||||
|
connection?.stateUpdateHandler = { [weak self] state in
|
||||||
|
switch state {
|
||||||
|
case .ready:
|
||||||
|
completion(true, nil)
|
||||||
|
case .failed(let error):
|
||||||
|
self?.disconnect()
|
||||||
|
completion(false, error)
|
||||||
|
case .cancelled:
|
||||||
|
completion(false, NSError(domain: "SoftEtherError", code: 1000, userInfo: [NSLocalizedDescriptionKey: "Connection cancelled"]))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the connection
|
||||||
|
connection?.start(queue: queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnect from the server
|
||||||
|
func disconnect() {
|
||||||
|
connection?.cancel()
|
||||||
|
connection = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send data to the server
|
||||||
|
/// - Parameters:
|
||||||
|
/// - data: Data to send
|
||||||
|
/// - completion: Callback with error if any
|
||||||
|
func send(data: Data, completion: @escaping (Error?) -> Void) {
|
||||||
|
guard let connection = connection, connection.state == .ready else {
|
||||||
|
completion(NSError(domain: "SoftEtherError", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Connection not ready"]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.send(content: data, completion: .contentProcessed { error in
|
||||||
|
completion(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive data from the server
|
||||||
|
/// - Parameter completion: Callback with received data and error if any
|
||||||
|
func receive(completion: @escaping (Data?, Error?) -> Void) {
|
||||||
|
guard let connection = connection, connection.state == .ready else {
|
||||||
|
completion(nil, NSError(domain: "SoftEtherError", code: 1001, userInfo: [NSLocalizedDescriptionKey: "Connection not ready"]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { data, _, isComplete, error in
|
||||||
|
completion(data, error)
|
||||||
|
|
||||||
|
if isComplete {
|
||||||
|
// Connection was closed by the peer
|
||||||
|
self.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Handles the specific client signature format that SoftEther expects
|
||||||
|
class SoftEtherClientSignature {
|
||||||
|
|
||||||
|
// MARK: - Constants
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let clientBuildNumber: UInt32 = 5187
|
||||||
|
static let clientVersion: UInt32 = 5_02_0000 + clientBuildNumber
|
||||||
|
static let clientString = "SoftEther VPN Client"
|
||||||
|
static let softEtherMagic: [UInt8] = [0x5E, 0x68] // 'Se' in hex
|
||||||
|
|
||||||
|
// Protocol identification constants from SoftEther source
|
||||||
|
static let cedar = "CEDAR"
|
||||||
|
static let sessionKey = "sessionkey"
|
||||||
|
static let protocol1 = "PROTOCOL"
|
||||||
|
static let protocol2 = "PROTOCOL2"
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public Methods
|
||||||
|
|
||||||
|
/// Generate the client signature packet that identifies this client as a legitimate SoftEther VPN client
|
||||||
|
/// - Returns: Data containing the formatted client signature
|
||||||
|
static func generateSignature() -> Data {
|
||||||
|
var data = Data()
|
||||||
|
|
||||||
|
// 1. Add SoftEther magic bytes
|
||||||
|
data.append(contentsOf: Constants.softEtherMagic)
|
||||||
|
|
||||||
|
// 2. Add client version in network byte order (big endian)
|
||||||
|
data.appendUInt32(Constants.clientVersion)
|
||||||
|
|
||||||
|
// 3. Add client build number in network byte order
|
||||||
|
data.appendUInt32(Constants.clientBuildNumber)
|
||||||
|
|
||||||
|
// 4. Add cedar protocol identifier
|
||||||
|
if let cedarData = Constants.cedar.data(using: .ascii) {
|
||||||
|
data.append(cedarData)
|
||||||
|
data.append(0) // null terminator
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Add client string with null terminator
|
||||||
|
if let clientString = (Constants.clientString + "\0").data(using: .ascii) {
|
||||||
|
data.append(clientString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Add protocol identifiers
|
||||||
|
if let protocolData = (Constants.protocol1 + "\0").data(using: .ascii) {
|
||||||
|
data.append(protocolData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let protocol2Data = (Constants.protocol2 + "\0").data(using: .ascii) {
|
||||||
|
data.append(protocol2Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Add session key marker
|
||||||
|
if let sessionKeyData = (Constants.sessionKey + "\0").data(using: .ascii) {
|
||||||
|
data.append(sessionKeyData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Add random data for session key (typically 20 bytes)
|
||||||
|
let randomSessionKey = SoftEtherCrypto.randomBytes(count: 20)
|
||||||
|
data.append(randomSessionKey)
|
||||||
|
|
||||||
|
// 9. Calculate and append SHA-1 hash of the entire data for integrity verification
|
||||||
|
let hash = SoftEtherCrypto.sha1(data)
|
||||||
|
data.append(hash)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify a server response to the client signature
|
||||||
|
/// - Parameter data: Response data from server
|
||||||
|
/// - Returns: True if valid response, false otherwise
|
||||||
|
static func verifyServerResponse(_ data: Data) -> Bool {
|
||||||
|
// Basic validation - a real implementation would parse and validate the server response format
|
||||||
|
// This is a minimal check to see if we have enough data and it starts with the magic bytes
|
||||||
|
guard data.count >= 8 else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if response starts with SoftEther magic bytes
|
||||||
|
if data[0] == Constants.softEtherMagic[0] && data[1] == Constants.softEtherMagic[1] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
/// Handles encryption operations for SoftEther protocol
|
||||||
|
class SoftEtherCrypto {
|
||||||
|
|
||||||
|
// MARK: - Constants
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let sha1Size = 20
|
||||||
|
static let md5Size = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public Methods
|
||||||
|
|
||||||
|
/// Generate secure random bytes
|
||||||
|
/// - Parameter count: Number of random bytes to generate
|
||||||
|
/// - Returns: Data containing random bytes
|
||||||
|
static func randomBytes(count: Int) -> Data {
|
||||||
|
var data = Data(count: count)
|
||||||
|
_ = data.withUnsafeMutableBytes {
|
||||||
|
SecRandomCopyBytes(kSecRandomDefault, count, $0.baseAddress!)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate SHA-1 hash
|
||||||
|
/// - Parameter data: Input data
|
||||||
|
/// - Returns: SHA-1 hash of the input data
|
||||||
|
static func sha1(_ data: Data) -> Data {
|
||||||
|
let digest = SHA1.hash(data: data)
|
||||||
|
return Data(digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate MD5 hash
|
||||||
|
/// - Parameter data: Input data
|
||||||
|
/// - Returns: MD5 hash of the input data
|
||||||
|
static func md5(_ data: Data) -> Data {
|
||||||
|
let digest = Insecure.MD5.hash(data: data)
|
||||||
|
return Data(digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt data using RC4 algorithm (for SoftEther compatibility)
|
||||||
|
/// - Parameters:
|
||||||
|
/// - data: Data to encrypt
|
||||||
|
/// - key: Encryption key
|
||||||
|
/// - Returns: Encrypted data
|
||||||
|
static func rc4Encrypt(data: Data, key: Data) -> Data {
|
||||||
|
let rc4 = RC4(key: key)
|
||||||
|
return rc4.process(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt data using RC4 algorithm (for SoftEther compatibility)
|
||||||
|
/// - Parameters:
|
||||||
|
/// - data: Data to decrypt
|
||||||
|
/// - key: Decryption key
|
||||||
|
/// - Returns: Decrypted data
|
||||||
|
static func rc4Decrypt(data: Data, key: Data) -> Data {
|
||||||
|
// RC4 is symmetric, so encryption and decryption are the same operation
|
||||||
|
return rc4Encrypt(data: data, key: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple RC4 implementation for SoftEther compatibility
|
||||||
|
/// Note: RC4 is considered insecure, but SoftEther uses it in parts of its protocol
|
||||||
|
private class RC4 {
|
||||||
|
private var state: [UInt8]
|
||||||
|
|
||||||
|
init(key: Data) {
|
||||||
|
state = Array(0...255)
|
||||||
|
var j: Int = 0
|
||||||
|
|
||||||
|
// Key scheduling algorithm
|
||||||
|
for i in 0..<256 {
|
||||||
|
let keyByte = key[i % key.count]
|
||||||
|
j = (j + Int(state[i]) + Int(keyByte)) & 0xFF
|
||||||
|
state.swapAt(i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(_ data: Data) -> Data {
|
||||||
|
var result = Data(count: data.count)
|
||||||
|
var i: Int = 0
|
||||||
|
var j: Int = 0
|
||||||
|
|
||||||
|
// Generate keystream and XOR with plaintext
|
||||||
|
for k in 0..<data.count {
|
||||||
|
i = (i + 1) & 0xFF
|
||||||
|
j = (j + Int(state[i])) & 0xFF
|
||||||
|
state.swapAt(i, j)
|
||||||
|
let keyStreamByte = state[(Int(state[i]) + Int(state[j])) & 0xFF]
|
||||||
|
result[k] = data[k] ^ keyStreamByte
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
123
SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherPacket.swift
Normal file
123
SoftEtherVPN-iOS/SoftEtherVPN-iOS/Protocol/SoftEtherPacket.swift
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Handles the SoftEther packet structure for communication
|
||||||
|
class SoftEtherPacket {
|
||||||
|
|
||||||
|
// MARK: - Constants
|
||||||
|
|
||||||
|
private enum PacketType: UInt32 {
|
||||||
|
case clientSignature = 0x01
|
||||||
|
case serverResponse = 0x02
|
||||||
|
case sessionRequest = 0x03
|
||||||
|
case sessionResponse = 0x04
|
||||||
|
case data = 0x05
|
||||||
|
case keepAlive = 0x06
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum Constants {
|
||||||
|
static let headerSize: UInt32 = 16
|
||||||
|
static let maxPacketSize: UInt32 = 1024 * 1024 // 1MB
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var packetType: PacketType
|
||||||
|
private var packetId: UInt32
|
||||||
|
private var packetData: Data
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
/// Initialize a packet with type, ID and data
|
||||||
|
/// - Parameters:
|
||||||
|
/// - type: Packet type
|
||||||
|
/// - id: Packet ID
|
||||||
|
/// - data: Packet payload
|
||||||
|
init(type: UInt32, id: UInt32, data: Data) {
|
||||||
|
self.packetType = PacketType(rawValue: type) ?? .data
|
||||||
|
self.packetId = id
|
||||||
|
self.packetData = data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a packet from raw data
|
||||||
|
/// - Parameter data: Raw packet data
|
||||||
|
init?(fromData data: Data) {
|
||||||
|
guard data.count >= Int(Constants.headerSize) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse header
|
||||||
|
let typeValue = data.readUInt32(at: 0)
|
||||||
|
self.packetId = data.readUInt32(at: 4)
|
||||||
|
let dataSize = data.readUInt32(at: 8)
|
||||||
|
|
||||||
|
// Validate packet
|
||||||
|
guard let type = PacketType(rawValue: typeValue),
|
||||||
|
dataSize <= Constants.maxPacketSize,
|
||||||
|
data.count >= Int(Constants.headerSize + dataSize) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.packetType = type
|
||||||
|
|
||||||
|
// Extract payload
|
||||||
|
let startIndex = Int(Constants.headerSize)
|
||||||
|
let endIndex = startIndex + Int(dataSize)
|
||||||
|
self.packetData = data.subdata(in: startIndex..<endIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public Methods
|
||||||
|
|
||||||
|
/// Serialize the packet to binary data format
|
||||||
|
/// - Returns: Serialized packet data
|
||||||
|
func serialize() -> Data {
|
||||||
|
var result = Data(capacity: Int(Constants.headerSize) + packetData.count)
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
result.appendUInt32(packetType.rawValue)
|
||||||
|
result.appendUInt32(packetId)
|
||||||
|
result.appendUInt32(UInt32(packetData.count))
|
||||||
|
result.appendUInt32(0) // Reserved
|
||||||
|
|
||||||
|
// Write payload
|
||||||
|
result.append(packetData)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the packet type
|
||||||
|
/// - Returns: Packet type
|
||||||
|
func getType() -> UInt32 {
|
||||||
|
return packetType.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the packet ID
|
||||||
|
/// - Returns: Packet ID
|
||||||
|
func getId() -> UInt32 {
|
||||||
|
return packetId
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the packet payload
|
||||||
|
/// - Returns: Packet payload data
|
||||||
|
func getData() -> Data {
|
||||||
|
return packetData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Extensions
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
/// Read a UInt32 value from the data at specified offset
|
||||||
|
/// - Parameter offset: Offset to read from
|
||||||
|
/// - Returns: UInt32 value in big-endian order
|
||||||
|
func readUInt32(at offset: Int) -> UInt32 {
|
||||||
|
let slice = self.subdata(in: offset..<(offset + 4))
|
||||||
|
return slice.withUnsafeBytes { $0.load(as: UInt32.self).bigEndian }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a UInt32 value to the data in big-endian order
|
||||||
|
/// - Parameter value: UInt32 value to append
|
||||||
|
mutating func appendUInt32(_ value: UInt32) {
|
||||||
|
var bigEndian = value.bigEndian
|
||||||
|
append(UnsafeBufferPointer(start: &bigEndian, count: 1))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
import Foundation
|
||||||
|
import Network
|
||||||
|
import Security
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
/// SoftEtherProtocol manages the communication between iOS client and SoftEther VPN server
|
||||||
|
class SoftEtherProtocol {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private var secureConnection: SecureConnection?
|
||||||
|
private var isConnected = false
|
||||||
|
private var host: String = ""
|
||||||
|
private var port: UInt16 = 443
|
||||||
|
private var nextPacketId: UInt32 = 1
|
||||||
|
|
||||||
|
// MARK: - Public Methods
|
||||||
|
|
||||||
|
/// Connect to a SoftEther VPN server
|
||||||
|
/// - Parameters:
|
||||||
|
/// - host: The server hostname or IP address
|
||||||
|
/// - port: The server port (default: 443)
|
||||||
|
/// - completion: Callback with connection result
|
||||||
|
public func connect(to host: String, port: UInt16 = 443, completion: @escaping (Bool, Error?) -> Void) {
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
// Create a secure connection
|
||||||
|
secureConnection = SecureConnection(host: host, port: port)
|
||||||
|
|
||||||
|
// Connect using TLS
|
||||||
|
secureConnection?.connect { [weak self] success, error in
|
||||||
|
guard let self = self, success else {
|
||||||
|
completion(false, error ?? NSError(domain: "SoftEtherError", code: 1, userInfo: [NSLocalizedDescriptionKey: "TLS connection failed"]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// After successful TLS connection, send the client signature
|
||||||
|
self.sendClientSignature { success, error in
|
||||||
|
if success {
|
||||||
|
self.isConnected = true
|
||||||
|
}
|
||||||
|
completion(success, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnect from the server
|
||||||
|
public func disconnect() {
|
||||||
|
secureConnection?.disconnect()
|
||||||
|
isConnected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private Methods
|
||||||
|
|
||||||
|
/// Send the SoftEther client signature to identify as a legitimate client
|
||||||
|
/// - Parameter completion: Callback with result
|
||||||
|
private func sendClientSignature(completion: @escaping (Bool, Error?) -> Void) {
|
||||||
|
// Generate client signature using our specialized class
|
||||||
|
let signatureData = SoftEtherClientSignature.generateSignature()
|
||||||
|
|
||||||
|
// Create a packet with the signature data
|
||||||
|
let packetId = self.nextPacketId
|
||||||
|
self.nextPacketId += 1
|
||||||
|
|
||||||
|
let packet = SoftEtherPacket(type: 0x01, id: packetId, data: signatureData)
|
||||||
|
let packetData = packet.serialize()
|
||||||
|
|
||||||
|
print("Sending client signature packet: \(packetData.count) bytes")
|
||||||
|
|
||||||
|
// Send the packet
|
||||||
|
secureConnection?.send(data: packetData) { [weak self] error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
print("Error sending client signature: \(error)")
|
||||||
|
completion(false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// After sending signature, wait for server response
|
||||||
|
self.receiveServerResponse { success, error in
|
||||||
|
completion(success, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive and process server response after sending signature
|
||||||
|
/// - Parameter completion: Callback with result
|
||||||
|
private func receiveServerResponse(completion: @escaping (Bool, Error?) -> Void) {
|
||||||
|
secureConnection?.receive { data, error in
|
||||||
|
if let error = error {
|
||||||
|
print("Error receiving server response: \(error)")
|
||||||
|
completion(false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = data, data.count > 4 else {
|
||||||
|
let error = NSError(domain: "SoftEtherError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid server response"])
|
||||||
|
print("Invalid server response: insufficient data")
|
||||||
|
completion(false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Received server response: \(data.count) bytes")
|
||||||
|
|
||||||
|
// Parse the response packet
|
||||||
|
guard let packet = SoftEtherPacket(fromData: data) else {
|
||||||
|
let error = NSError(domain: "SoftEtherError", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid packet format"])
|
||||||
|
print("Could not parse server response packet")
|
||||||
|
completion(false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the response
|
||||||
|
let packetData = packet.getData()
|
||||||
|
let isValid = SoftEtherClientSignature.verifyServerResponse(packetData)
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
print("Server accepted our client signature")
|
||||||
|
completion(true, nil)
|
||||||
|
} else {
|
||||||
|
print("Server rejected our client signature")
|
||||||
|
let error = NSError(domain: "SoftEtherError", code: 4, userInfo: [NSLocalizedDescriptionKey: "Server rejected client signature"])
|
||||||
|
completion(false, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a data packet to the server
|
||||||
|
/// - Parameters:
|
||||||
|
/// - data: Data to send
|
||||||
|
/// - completion: Callback with result
|
||||||
|
func sendData(data: Data, completion: @escaping (Bool, Error?) -> Void) {
|
||||||
|
guard isConnected else {
|
||||||
|
completion(false, NSError(domain: "SoftEtherError", code: 5, userInfo: [NSLocalizedDescriptionKey: "Not connected to server"]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let packetId = self.nextPacketId
|
||||||
|
self.nextPacketId += 1
|
||||||
|
|
||||||
|
let packet = SoftEtherPacket(type: 0x05, id: packetId, data: data)
|
||||||
|
let packetData = packet.serialize()
|
||||||
|
|
||||||
|
secureConnection?.send(data: packetData) { error in
|
||||||
|
if let error = error {
|
||||||
|
completion(false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(true, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive data from the server
|
||||||
|
/// - Parameter completion: Callback with received data and result
|
||||||
|
func receiveData(completion: @escaping (Data?, Bool, Error?) -> Void) {
|
||||||
|
guard isConnected else {
|
||||||
|
completion(nil, false, NSError(domain: "SoftEtherError", code: 5, userInfo: [NSLocalizedDescriptionKey: "Not connected to server"]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secureConnection?.receive { data, error in
|
||||||
|
if let error = error {
|
||||||
|
completion(nil, false, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = data, data.count > 4 else {
|
||||||
|
completion(nil, false, NSError(domain: "SoftEtherError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid server response"]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the packet
|
||||||
|
guard let packet = SoftEtherPacket(fromData: data) else {
|
||||||
|
completion(nil, false, NSError(domain: "SoftEtherError", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid packet format"]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(packet.getData(), true, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
149
SoftEtherVPN-iOS/SoftEtherVPN-iOS/SoftEtherVPNClient.swift
Normal file
149
SoftEtherVPN-iOS/SoftEtherVPN-iOS/SoftEtherVPNClient.swift
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
/// SoftEtherVPNClient provides a simple interface for connecting to SoftEther VPN servers
|
||||||
|
public class SoftEtherVPNClient {
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
|
||||||
|
private let protocol: SoftEtherProtocol
|
||||||
|
private var connectionState: ConnectionState = .disconnected
|
||||||
|
|
||||||
|
// MARK: - Public Types
|
||||||
|
|
||||||
|
/// Connection states for the VPN client
|
||||||
|
public enum ConnectionState {
|
||||||
|
case disconnected
|
||||||
|
case connecting
|
||||||
|
case connected
|
||||||
|
case disconnecting
|
||||||
|
case error(Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection delegate to receive state updates
|
||||||
|
public protocol ConnectionDelegate: AnyObject {
|
||||||
|
func connectionStateDidChange(_ state: ConnectionState)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Weak reference to the delegate
|
||||||
|
public weak var delegate: ConnectionDelegate?
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
self.protocol = SoftEtherProtocol()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public Methods
|
||||||
|
|
||||||
|
/// Connect to a SoftEther VPN server
|
||||||
|
/// - Parameters:
|
||||||
|
/// - host: Server hostname or IP address
|
||||||
|
/// - port: Server port (default: 443)
|
||||||
|
/// - completion: Optional completion handler
|
||||||
|
public func connect(to host: String, port: UInt16 = 443, completion: ((Bool, Error?) -> Void)? = nil) {
|
||||||
|
// Update state
|
||||||
|
connectionState = .connecting
|
||||||
|
delegate?.connectionStateDidChange(connectionState)
|
||||||
|
|
||||||
|
// Connect using the protocol implementation
|
||||||
|
protocol.connect(to: host, port: port) { [weak self] success, error in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
if success {
|
||||||
|
self.connectionState = .connected
|
||||||
|
} else if let error = error {
|
||||||
|
self.connectionState = .error(error)
|
||||||
|
} else {
|
||||||
|
self.connectionState = .disconnected
|
||||||
|
}
|
||||||
|
|
||||||
|
self.delegate?.connectionStateDidChange(self.connectionState)
|
||||||
|
completion?(success, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disconnect from the server
|
||||||
|
/// - Parameter completion: Optional completion handler
|
||||||
|
public func disconnect(completion: (() -> Void)? = nil) {
|
||||||
|
// Update state
|
||||||
|
connectionState = .disconnecting
|
||||||
|
delegate?.connectionStateDidChange(connectionState)
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
protocol.disconnect()
|
||||||
|
|
||||||
|
// Update state again
|
||||||
|
connectionState = .disconnected
|
||||||
|
delegate?.connectionStateDidChange(connectionState)
|
||||||
|
|
||||||
|
completion?()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current connection state
|
||||||
|
/// - Returns: Current ConnectionState
|
||||||
|
public func getConnectionState() -> ConnectionState {
|
||||||
|
return connectionState
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if currently connected
|
||||||
|
/// - Returns: True if connected, false otherwise
|
||||||
|
public func isConnected() -> Bool {
|
||||||
|
if case .connected = connectionState {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Example Usage
|
||||||
|
|
||||||
|
/// Example showing how to use this class in a view controller
|
||||||
|
public static func exampleUsage() -> String {
|
||||||
|
return """
|
||||||
|
// In your view controller:
|
||||||
|
|
||||||
|
private let vpnClient = SoftEtherVPNClient()
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// Set delegate
|
||||||
|
vpnClient.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func connectButtonTapped(_ sender: UIButton) {
|
||||||
|
if vpnClient.isConnected() {
|
||||||
|
vpnClient.disconnect()
|
||||||
|
} else {
|
||||||
|
vpnClient.connect(to: "vpn.example.com") { success, error in
|
||||||
|
if !success {
|
||||||
|
print("Failed to connect: \\(error?.localizedDescription ?? "Unknown error")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - ConnectionDelegate
|
||||||
|
|
||||||
|
extension YourViewController: SoftEtherVPNClient.ConnectionDelegate {
|
||||||
|
func connectionStateDidChange(_ state: SoftEtherVPNClient.ConnectionState) {
|
||||||
|
switch state {
|
||||||
|
case .connected:
|
||||||
|
connectButton.setTitle("Disconnect", for: .normal)
|
||||||
|
statusLabel.text = "Connected"
|
||||||
|
case .connecting:
|
||||||
|
statusLabel.text = "Connecting..."
|
||||||
|
case .disconnecting:
|
||||||
|
statusLabel.text = "Disconnecting..."
|
||||||
|
case .disconnected:
|
||||||
|
connectButton.setTitle("Connect", for: .normal)
|
||||||
|
statusLabel.text = "Disconnected"
|
||||||
|
case .error(let error):
|
||||||
|
statusLabel.text = "Error: \\(error.localizedDescription)"
|
||||||
|
connectButton.setTitle("Connect", for: .normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user