Replace NEKit dependency with reduced subset

This commit is contained in:
relikd
2020-03-24 21:12:58 +01:00
parent 2473e77519
commit cbec3981bb
103 changed files with 24996 additions and 264 deletions

View File

@@ -0,0 +1,113 @@
import Foundation
/// This class just forwards data directly.
/// - note: It is designed to work with tun2socks only.
public class DirectProxySocket: ProxySocket {
enum DirectProxyReadStatus: CustomStringConvertible {
case invalid,
forwarding,
stopped
var description: String {
switch self {
case .invalid:
return "invalid"
case .forwarding:
return "forwarding"
case .stopped:
return "stopped"
}
}
}
enum DirectProxyWriteStatus {
case invalid,
forwarding,
stopped
var description: String {
switch self {
case .invalid:
return "invalid"
case .forwarding:
return "forwarding"
case .stopped:
return "stopped"
}
}
}
private var readStatus: DirectProxyReadStatus = .invalid
private var writeStatus: DirectProxyWriteStatus = .invalid
public var readStatusDescription: String {
return readStatus.description
}
public var writeStatusDescription: String {
return writeStatus.description
}
/**
Begin reading and processing data from the socket.
- note: Since there is nothing to read and process before forwarding data, this just calls `delegate?.didReceiveRequest`.
*/
override public func openSocket() {
super.openSocket()
guard !isCancelled else {
return
}
if let address = socket.destinationIPAddress, let port = socket.destinationPort {
session = ConnectSession(host: address.presentation, port: Int(port.value))
observer?.signal(.receivedRequest(session!, on: self))
delegate?.didReceive(session: session!, from: self)
} else {
forceDisconnect()
}
}
/**
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
}
readStatus = .forwarding
writeStatus = .forwarding
observer?.signal(.readyForForward(self))
delegate?.didBecomeReadyToForwardWith(socket: self)
}
/**
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 open func didRead(data: Data, from: RawTCPSocketProtocol) {
super.didRead(data: data, from: from)
delegate?.didRead(data: data, from: self)
}
/**
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 by: The socket where the data is sent out.
*/
override open func didWrite(data: Data?, by: RawTCPSocketProtocol) {
super.didWrite(data: data, by: by)
delegate?.didWrite(data: data, by: self)
}
}

View File

@@ -0,0 +1,207 @@
import Foundation
public class HTTPProxySocket: ProxySocket {
enum HTTPProxyReadStatus: CustomStringConvertible {
case invalid,
readingFirstHeader,
pendingFirstHeader,
readingHeader,
readingContent,
stopped
var description: String {
switch self {
case .invalid:
return "invalid"
case .readingFirstHeader:
return "reading first header"
case .pendingFirstHeader:
return "waiting to send first header"
case .readingHeader:
return "reading header (forwarding)"
case .readingContent:
return "reading content (forwarding)"
case .stopped:
return "stopped"
}
}
}
enum HTTPProxyWriteStatus: CustomStringConvertible {
case invalid,
sendingConnectResponse,
forwarding,
stopped
var description: String {
switch self {
case .invalid:
return "invalid"
case .sendingConnectResponse:
return "sending response header for CONNECT"
case .forwarding:
return "waiting to begin forwarding data"
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 currentHeader: HTTPHeader!
private let scanner: HTTPStreamScanner = HTTPStreamScanner()
private var readStatus: HTTPProxyReadStatus = .invalid
private var writeStatus: HTTPProxyWriteStatus = .invalid
public var isConnectCommand = false
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 = .readingFirstHeader
socket.readDataTo(data: Utils.HTTPData.DoubleCRLF)
}
override public func readData() {
guard !isCancelled else {
return
}
// Return the first header we read when the socket was opened if the proxy command is not CONNECT.
if readStatus == .pendingFirstHeader {
delegate?.didRead(data: currentHeader.toData(), from: self)
readStatus = .readingContent
return
}
switch scanner.nextAction {
case .readContent(let length):
readStatus = .readingContent
if length > 0 {
socket.readDataTo(length: length)
} else {
socket.readData()
}
case .readHeader:
readStatus = .readingHeader
socket.readDataTo(data: Utils.HTTPData.DoubleCRLF)
case .stop:
readStatus = .stopped
disconnect()
}
}
// 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)
let result: HTTPStreamScanner.Result
do {
result = try scanner.input(data)
} catch let error {
disconnect(becauseOf: error)
return
}
switch (readStatus, result) {
case (.readingFirstHeader, .header(let header)):
currentHeader = header
currentHeader.removeProxyHeader()
currentHeader.rewriteToRelativePath()
destinationHost = currentHeader.host
destinationPort = currentHeader.port
isConnectCommand = currentHeader.isConnect
if !isConnectCommand {
readStatus = .pendingFirstHeader
} else {
readStatus = .readingContent
}
session = ConnectSession(host: destinationHost!, port: destinationPort!)
observer?.signal(.receivedRequest(session!, on: self))
delegate?.didReceive(session: session!, from: self)
case (.readingHeader, .header(let header)):
currentHeader = header
currentHeader.removeProxyHeader()
currentHeader.rewriteToRelativePath()
delegate?.didRead(data: currentHeader.toData(), from: self)
case (.readingContent, .content(let content)):
delegate?.didRead(data: content, 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 by: 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 .sendingConnectResponse:
writeStatus = .forwarding
observer?.signal(.readyForForward(self))
delegate?.didBecomeReadyToForwardWith(socket: self)
default:
delegate?.didWrite(data: data, by: self)
}
}
/**
Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server.
- parameter adapter: The `AdapterSocket`.
*/
public override func respondTo(adapter: AdapterSocket) {
super.respondTo(adapter: adapter)
guard !isCancelled else {
return
}
if isConnectCommand {
writeStatus = .sendingConnectResponse
write(data: Utils.HTTPData.ConnectSuccessResponse)
} else {
writeStatus = .forwarding
observer?.signal(.readyForForward(self))
delegate?.didBecomeReadyToForwardWith(socket: self)
}
}
}

View File

@@ -0,0 +1,178 @@
import Foundation
/// The socket which encapsulates the logic to handle connection to proxies.
open class ProxySocket: NSObject, SocketProtocol, RawTCPSocketDelegate {
/// Received `ConnectSession`.
public var session: ConnectSession?
public var observer: Observer<ProxySocketEvent>?
private var _cancelled = false
var isCancelled: Bool {
return _cancelled
}
open override var description: String {
if let session = session {
return "<\(typeName) host:\(session.host) port: \(session.port))>"
} else {
return "<\(typeName)>"
}
}
/**
Init a `ProxySocket` with a raw TCP socket.
- parameter socket: The raw TCP socket.
*/
public init(socket: RawTCPSocketProtocol, observe: Bool = true) {
self.socket = socket
super.init()
self.socket.delegate = self
if observe {
observer = ObserverFactory.currentFactory?.getObserverForProxySocket(self)
}
}
/**
Begin reading and processing data from the socket.
*/
open func openSocket() {
guard !isCancelled else {
return
}
observer?.signal(.socketOpened(self))
}
/**
Response to the `AdapterSocket` on the other side of the `Tunnel` which has succefully connected to the remote server.
- parameter adapter: The `AdapterSocket`.
*/
open func respondTo(adapter: AdapterSocket) {
guard !isCancelled else {
return
}
observer?.signal(.askedToResponseTo(adapter, on: self))
}
/**
Read data from the socket.
- warning: This should only be called after the last read is finished, i.e., `delegate?.didReadData()` is called.
*/
open func readData() {
guard !isCancelled else {
return
}
socket.readData()
}
/**
Send data to remote.
- parameter data: Data to send.
- warning: This should only be called after the last write is finished, i.e., `delegate?.didWriteData()` is called.
*/
open func write(data: Data) {
guard !isCancelled else {
return
}
socket.write(data: data)
}
/**
Disconnect the socket elegantly.
*/
open func disconnect(becauseOf error: Error? = nil) {
guard !isCancelled else {
return
}
_status = .disconnecting
_cancelled = true
session?.disconnected(becauseOf: error, by: .proxy)
socket.disconnect()
observer?.signal(.disconnectCalled(self))
}
/**
Disconnect the socket immediately.
*/
open func forceDisconnect(becauseOf error: Error? = nil) {
guard !isCancelled else {
return
}
_status = .disconnecting
_cancelled = true
session?.disconnected(becauseOf: error, by: .proxy)
socket.forceDisconnect()
observer?.signal(.forceDisconnectCalled(self))
}
// MARK: SocketProtocol Implementation
/// The underlying TCP socket transmitting data.
public var socket: RawTCPSocketProtocol!
/// The delegate instance.
weak public var delegate: SocketDelegate?
var _status: SocketStatus = .established
/// The current connection status of the socket.
public var status: SocketStatus {
return _status
}
// MARK: RawTCPSocketDelegate Protocol Implementation
/**
The socket did disconnect.
- parameter socket: The socket which did disconnect.
*/
open func didDisconnectWith(socket: RawTCPSocketProtocol) {
_status = .closed
observer?.signal(.disconnected(self))
delegate?.didDisconnectWith(socket: self)
}
/**
The socket did read some data.
- parameter data: The data read from the socket.
- parameter withTag: The tag given when calling the `readData` method.
- parameter from: The socket where the data is read from.
*/
open func didRead(data: Data, from: RawTCPSocketProtocol) {
observer?.signal(.readData(data, on: self))
}
/**
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.
*/
open func didWrite(data: Data?, by: RawTCPSocketProtocol) {
observer?.signal(.wroteData(data, on: self))
}
/**
The socket did connect to remote.
- note: This never happens for `ProxySocket`.
- parameter socket: The connected socket.
*/
open func didConnectWith(socket: RawTCPSocketProtocol) {
}
}

View File

@@ -0,0 +1,244 @@
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)
}
}