245 lines
8.1 KiB
Swift
Executable File
245 lines
8.1 KiB
Swift
Executable File
import Foundation
|
|
|
|
public class SOCKS5ProxySocket: ProxySocket {
|
|
enum SOCKS5ProxyReadStatus: CustomStringConvertible {
|
|
case invalid,
|
|
readingVersionIdentifierAndNumberOfMethods,
|
|
readingMethods,
|
|
readingConnectHeader,
|
|
readingIPv4Address,
|
|
readingDomainLength,
|
|
readingDomain,
|
|
readingIPv6Address,
|
|
readingPort,
|
|
forwarding,
|
|
stopped
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .invalid:
|
|
return "invalid"
|
|
case .readingVersionIdentifierAndNumberOfMethods:
|
|
return "reading version and methods"
|
|
case .readingMethods:
|
|
return "reading methods"
|
|
case .readingConnectHeader:
|
|
return "reading connect header"
|
|
case .readingIPv4Address:
|
|
return "IPv4 address"
|
|
case .readingDomainLength:
|
|
return "domain length"
|
|
case .readingDomain:
|
|
return "domain"
|
|
case .readingIPv6Address:
|
|
return "IPv6 address"
|
|
case .readingPort:
|
|
return "reading port"
|
|
case .forwarding:
|
|
return "forwarding"
|
|
case .stopped:
|
|
return "stopped"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum SOCKS5ProxyWriteStatus: CustomStringConvertible {
|
|
case invalid,
|
|
sendingResponse,
|
|
forwarding,
|
|
stopped
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .invalid:
|
|
return "invalid"
|
|
case .sendingResponse:
|
|
return "sending response"
|
|
case .forwarding:
|
|
return "forwarding"
|
|
case .stopped:
|
|
return "stopped"
|
|
}
|
|
}
|
|
}
|
|
/// The remote host to connect to.
|
|
public var destinationHost: String!
|
|
|
|
/// The remote port to connect to.
|
|
public var destinationPort: Int!
|
|
|
|
private var readStatus: SOCKS5ProxyReadStatus = .invalid
|
|
private var writeStatus: SOCKS5ProxyWriteStatus = .invalid
|
|
|
|
public var readStatusDescription: String {
|
|
return readStatus.description
|
|
}
|
|
|
|
public var writeStatusDescription: String {
|
|
return writeStatus.description
|
|
}
|
|
|
|
/**
|
|
Begin reading and processing data from the socket.
|
|
*/
|
|
override public func openSocket() {
|
|
super.openSocket()
|
|
|
|
guard !isCancelled else {
|
|
return
|
|
}
|
|
|
|
readStatus = .readingVersionIdentifierAndNumberOfMethods
|
|
socket.readDataTo(length: 2)
|
|
}
|
|
|
|
// swiftlint:disable function_body_length
|
|
// swiftlint:disable cyclomatic_complexity
|
|
/**
|
|
The socket did read some data.
|
|
|
|
- parameter data: The data read from the socket.
|
|
- parameter from: The socket where the data is read from.
|
|
*/
|
|
override public func didRead(data: Data, from: RawTCPSocketProtocol) {
|
|
super.didRead(data: data, from: from)
|
|
|
|
switch readStatus {
|
|
case .forwarding:
|
|
delegate?.didRead(data: data, from: self)
|
|
case .readingVersionIdentifierAndNumberOfMethods:
|
|
data.withUnsafeBytes { pointer in
|
|
let p = pointer.bindMemory(to: Int8.self)
|
|
|
|
guard p.baseAddress!.pointee == 5 else {
|
|
// TODO: notify observer
|
|
self.disconnect()
|
|
return
|
|
}
|
|
|
|
guard p.baseAddress!.successor().pointee > 0 else {
|
|
// TODO: notify observer
|
|
self.disconnect()
|
|
return
|
|
}
|
|
|
|
self.readStatus = .readingMethods
|
|
self.socket.readDataTo(length: Int(p.baseAddress!.successor().pointee))
|
|
}
|
|
case .readingMethods:
|
|
// TODO: check for 0x00 in read data
|
|
|
|
let response = Data([0x05, 0x00])
|
|
// we would not be able to read anything before the data is written out, so no need to handle the dataWrote event.
|
|
write(data: response)
|
|
readStatus = .readingConnectHeader
|
|
socket.readDataTo(length: 4)
|
|
case .readingConnectHeader:
|
|
data.withUnsafeBytes { pointer in
|
|
let p = pointer.bindMemory(to: Int8.self)
|
|
|
|
guard p.baseAddress!.pointee == 5 && p.baseAddress!.successor().pointee == 1 else {
|
|
// TODO: notify observer
|
|
self.disconnect()
|
|
return
|
|
}
|
|
switch p.baseAddress!.advanced(by: 3).pointee {
|
|
case 1:
|
|
readStatus = .readingIPv4Address
|
|
socket.readDataTo(length: 4)
|
|
case 3:
|
|
readStatus = .readingDomainLength
|
|
socket.readDataTo(length: 1)
|
|
case 4:
|
|
readStatus = .readingIPv6Address
|
|
socket.readDataTo(length: 16)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
case .readingIPv4Address:
|
|
var address = Data(count: Int(INET_ADDRSTRLEN))
|
|
_ = data.withUnsafeBytes { data_ptr in
|
|
address.withUnsafeMutableBytes { addr_ptr in
|
|
inet_ntop(AF_INET, data_ptr.baseAddress!, addr_ptr.bindMemory(to: Int8.self).baseAddress!, socklen_t(INET_ADDRSTRLEN))
|
|
}
|
|
}
|
|
|
|
destinationHost = String(data: address, encoding: .utf8)
|
|
|
|
readStatus = .readingPort
|
|
socket.readDataTo(length: 2)
|
|
case .readingIPv6Address:
|
|
var address = Data(count: Int(INET6_ADDRSTRLEN))
|
|
_ = data.withUnsafeBytes { data_ptr in
|
|
address.withUnsafeMutableBytes { addr_ptr in
|
|
inet_ntop(AF_INET6, data_ptr.baseAddress!, addr_ptr.bindMemory(to: Int8.self).baseAddress!, socklen_t(INET6_ADDRSTRLEN))
|
|
}
|
|
}
|
|
|
|
destinationHost = String(data: address, encoding: .utf8)
|
|
|
|
readStatus = .readingPort
|
|
socket.readDataTo(length: 2)
|
|
case .readingDomainLength:
|
|
readStatus = .readingDomain
|
|
socket.readDataTo(length: Int(data.first!))
|
|
case .readingDomain:
|
|
destinationHost = String(data: data, encoding: .utf8)
|
|
readStatus = .readingPort
|
|
socket.readDataTo(length: 2)
|
|
case .readingPort:
|
|
data.withUnsafeBytes {
|
|
destinationPort = Int($0.load(as: UInt16.self).bigEndian)
|
|
}
|
|
|
|
readStatus = .forwarding
|
|
session = ConnectSession(host: destinationHost, port: destinationPort)
|
|
observer?.signal(.receivedRequest(session!, on: self))
|
|
delegate?.didReceive(session: session!, from: self)
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
|
|
/**
|
|
The socket did send some data.
|
|
|
|
- parameter data: The data which have been sent to remote (acknowledged). Note this may not be available since the data may be released to save memory.
|
|
- parameter from: The socket where the data is sent out.
|
|
*/
|
|
override public func didWrite(data: Data?, by: RawTCPSocketProtocol) {
|
|
super.didWrite(data: data, by: by)
|
|
|
|
switch writeStatus {
|
|
case .forwarding:
|
|
delegate?.didWrite(data: data, by: self)
|
|
case .sendingResponse:
|
|
writeStatus = .forwarding
|
|
observer?.signal(.readyForForward(self))
|
|
delegate?.didBecomeReadyToForwardWith(socket: self)
|
|
default:
|
|
return
|
|
}
|
|
}
|
|
|
|
/**
|
|
Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server.
|
|
|
|
- parameter adapter: The `AdapterSocket`.
|
|
*/
|
|
override public func respondTo(adapter: AdapterSocket) {
|
|
super.respondTo(adapter: adapter)
|
|
|
|
guard !isCancelled else {
|
|
return
|
|
}
|
|
|
|
var responseBytes = [UInt8](repeating: 0, count: 10)
|
|
responseBytes[0...3] = [0x05, 0x00, 0x00, 0x01]
|
|
let responseData = Data(responseBytes)
|
|
|
|
writeStatus = .sendingResponse
|
|
write(data: responseData)
|
|
}
|
|
}
|