├── .gitignore ├── .swift-format ├── .swiftpm └── xcode │ ├── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ ├── http.xcscheme │ ├── proxy-nio-Package.xcscheme │ └── proxy-nio.xcscheme ├── Package.resolved ├── Package.swift ├── README.md ├── Scripts └── udpchk.py ├── Sources ├── Run │ └── main.swift ├── http │ ├── ConnectHandler.swift │ ├── GlueHandler.swift │ └── HttpServer.swift └── proxy-nio │ ├── shadowsocks │ ├── Data+Byte.swift │ ├── SSRelayHandler.swift │ ├── ShadowsocksHandler.swift │ ├── ShadowsocksServer.swift │ └── cryptor │ │ ├── AEADCryptor.swift │ │ ├── Cryptor.swift │ │ ├── HKDF.swift │ │ └── Nonce.swift │ └── socks5 │ ├── ByteBuffer+Util.swift │ ├── Channel+Relay.swift │ ├── GlueHandler.swift │ ├── Socks.swift │ ├── Socks5Server.swift │ ├── SocksAddress.swift │ ├── SocksDecoder.swift │ ├── SocksEncoder.swift │ ├── SocksHandler.swift │ ├── SocksRequest.swift │ ├── SocksResponse.swift │ ├── UDPHandler.swift │ └── UDPHeader.swift └── Tests ├── LinuxMain.swift └── proxy-nioTests ├── XCTestManifests.swift └── proxy_nioTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | *.xcscheme 7 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "lineLength": 100, 4 | "indentation": { 5 | "spaces": 4 6 | }, 7 | "maximumBlankLines": 1, 8 | "respectsExistingLineBreaks": true, 9 | "lineBreakBeforeControlFlowKeywords": true, 10 | "lineBreakBeforeEachArgument": true 11 | } 12 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/http.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/proxy-nio-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 57 | 63 | 64 | 65 | 66 | 67 | 72 | 73 | 75 | 81 | 82 | 83 | 84 | 85 | 95 | 96 | 102 | 103 | 104 | 105 | 111 | 112 | 118 | 119 | 120 | 121 | 123 | 124 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/proxy-nio.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-crypto", 6 | "repositoryURL": "https://github.com/apple/swift-crypto", 7 | "state": { 8 | "branch": null, 9 | "revision": "9680b7251cd2be22caaed8f1468bd9e8915a62fb", 10 | "version": "1.1.2" 11 | } 12 | }, 13 | { 14 | "package": "swift-log", 15 | "repositoryURL": "https://github.com/apple/swift-log", 16 | "state": { 17 | "branch": null, 18 | "revision": "173f567a2dfec11d74588eea82cecea555bdc0bc", 19 | "version": "1.4.0" 20 | } 21 | }, 22 | { 23 | "package": "swift-nio", 24 | "repositoryURL": "https://github.com/apple/swift-nio", 25 | "state": { 26 | "branch": null, 27 | "revision": "43931b7a7daf8120a487601530c8bc03ce711992", 28 | "version": "2.25.1" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | var targets: [PackageDescription.Target] = [ 7 | .target(name: "proxy-nio", 8 | dependencies: [ 9 | .product(name: "NIO", package: "swift-nio"), 10 | .product(name: "NIOHTTP1", package: "swift-nio"), 11 | .product(name: "Logging", package: "swift-log")]), 12 | .target(name: "http", 13 | dependencies: [ 14 | .product(name: "NIO", package: "swift-nio"), 15 | .product(name: "Logging", package: "swift-log"), 16 | .product(name: "NIOHTTP1", package: "swift-nio"),]), 17 | .target(name: "Run", dependencies: [ 18 | "proxy-nio", 19 | "http", 20 | .product(name: "Crypto", package: "swift-crypto"),]), 21 | .testTarget(name: "proxy-nioTests", dependencies: ["proxy-nio"]), 22 | ] 23 | 24 | let package = Package( 25 | name: "proxy-nio", 26 | platforms: [.macOS("10.15")], 27 | products: [ 28 | .library(name: "proxy-nio", targets: ["proxy-nio"]), 29 | .library(name: "http", targets: ["http"]), 30 | .executable(name: "Run", targets: ["Run"]), 31 | ], 32 | dependencies: [ 33 | // Dependencies declare other packages that this package depends on. 34 | // .package(url: /* package url */, from: "1.0.0"), 35 | .package(url: "https://github.com/apple/swift-nio", .upToNextMajor(from: "2.25.0")), 36 | .package(url: "https://github.com/apple/swift-log", from: "1.4.0"), 37 | .package(url: "https://github.com/apple/swift-crypto", from: "1.1.2"), 38 | ], 39 | targets: targets 40 | ) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # proxy-nio 2 | 3 | NIO proxy 4 | 5 | ## Feature | Plan 6 | 7 | - [x] socks5 proxy with auth 8 | - [x] shadowsocks protocol(udp is in plan) 9 | - [x] socsk5 udp relay 10 | - [ ] socks5 forward 11 | - [ ] http(s) proxy 12 | - [ ] sniffer http(s) traffic 13 | - [ ] support iOS platform 14 | 15 | ## Requirement 16 | 17 | * macOS 10.15 18 | * swift 5.3 19 | 20 | ## Check UDP 21 | 22 | ```shell 23 | python udpchk.py -p localhost -P 1080 24 | ``` 25 | 26 | ## RFC Documents 27 | 28 | 1. [SOCKS Protocol Version 5](https://tools.ietf.org/html/rfc1928) 29 | 2. [Username/Password Authentication for SOCKS V5](https://tools.ietf.org/html/rfc1929) 30 | 31 | -------------------------------------------------------------------------------- /Scripts/udpchk.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | if sys.platform == 'win32' and (sys.version_info.major < 3 4 | or (sys.version_info.major == 3 and sys.version_info.minor < 4)): 5 | # inet_pton is only supported on Windows since Python 3.4 6 | import win_inet_pton 7 | import socket 8 | import socks 9 | 10 | def test_udp(typ, addr, port, user=None, pwd=None): 11 | s = socks.socksocket(socket.AF_INET, socket.SOCK_DGRAM) # Same API as socket.socket in the standard lib 12 | try: 13 | s.set_proxy(socks.SOCKS5, addr, port, False, user, pwd) # SOCKS4 and SOCKS5 use port 1080 by default 14 | # Can be treated identical to a regular socket object 15 | # Raw DNS request 16 | req = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x05\x62\x61\x69\x64\x75\x03\x63\x6f\x6d\x00\x00\x01\x00\x01" 17 | s.sendto(req, ("8.8.8.8", 53)) 18 | (rsp, address)= s.recvfrom(4096) 19 | if rsp[0] == req[0] and rsp[1] == req[1]: 20 | print("UDP check passed") 21 | else: 22 | print("Invalid response") 23 | except socket.error as e: 24 | print(repr(e)) 25 | except socks.ProxyError as e: 26 | print(e.msg) 27 | 28 | 29 | def main(): 30 | import os 31 | import argparse 32 | def ip_port(string): 33 | value = int(string) 34 | if value <= 0 or value > 65535: 35 | msg = "%r is not valid port number" % string 36 | raise argparse.ArgumentTypeError(msg) 37 | return value 38 | 39 | parser = argparse.ArgumentParser(prog=os.path.basename(__file__), 40 | description='Test SOCKS5 UDP support by sending DNS request to 8.8.8.8:53 and receive response.') 41 | parser.add_argument('--proxy', "-p", metavar="PROXY", dest='proxy', required=True, 42 | help='IP or domain name of proxy to be tested against UDP support.') 43 | parser.add_argument('--port', "-P", metavar="PORT", dest='port', type=ip_port, default=1080, 44 | help='Port of proxy to be tested against UDP support.') 45 | parser.add_argument('--user', "-u", metavar="username", dest="user", default=None, 46 | help='Specify username to be used for proxy authentication.') 47 | parser.add_argument('--pwd', "-k", metavar="password", dest="pwd", default=None, 48 | help='Specify password to be used for proxy authentication.') 49 | args = parser.parse_args() 50 | test_udp(None, args.proxy, args.port, args.user, args.pwd) 51 | 52 | 53 | if __name__ == "__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /Sources/Run/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/9. 6 | // 7 | 8 | import Foundation 9 | import proxy_nio 10 | import http 11 | import Dispatch 12 | 13 | func testSocks() { 14 | let server: Socks5Server = Socks5Server() 15 | server.start(config: .default) 16 | } 17 | 18 | func testSocksWithAuth() { 19 | // curl -x socks5://admin:password@localhost:1080 baidu.com 20 | let server: Socks5Server = Socks5Server() 21 | server.start(config: SocksConfiguration(auth: .pass(username: "admin", password: "password"), port: 1080)) 22 | } 23 | 24 | func testShadowsocks() { 25 | let server: ShadowsocksServer = ShadowsocksServer() 26 | // Config your shadowsocks server 27 | let config = ShadowsocksConfiguration(host: "your host", port: 1080, password: "your password") 28 | server.start(config: config) 29 | } 30 | 31 | func testHttp() { 32 | // curl -x localhost:1080 baidu.com 33 | let server = HttpServer() 34 | server.start(port: 1080) 35 | } 36 | 37 | testHttp() 38 | -------------------------------------------------------------------------------- /Sources/http/ConnectHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectHandler.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/28. 6 | // 7 | 8 | import NIO 9 | import NIOHTTP1 10 | import Logging 11 | 12 | 13 | final class ConnectHandler { 14 | private var upgradeState: State 15 | 16 | private var logger: Logger 17 | 18 | init(logger: Logger) { 19 | self.upgradeState = .idle 20 | self.logger = logger 21 | } 22 | } 23 | 24 | 25 | extension ConnectHandler { 26 | fileprivate enum State { 27 | case idle 28 | case beganConnecting 29 | case awaitingEnd(connectResult: Channel) 30 | case awaitingConnection(pendingBytes: [NIOAny]) 31 | case upgradeComplete(pendingBytes: [NIOAny]) 32 | case upgradeFailed 33 | } 34 | } 35 | 36 | 37 | extension ConnectHandler: ChannelInboundHandler { 38 | typealias InboundIn = HTTPServerRequestPart 39 | typealias OutboundOut = HTTPServerResponsePart 40 | 41 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 42 | switch self.upgradeState { 43 | case .idle: 44 | self.handleInitialMessage(context: context, data: self.unwrapInboundIn(data)) 45 | 46 | case .beganConnecting: 47 | // We got .end, we're still waiting on the connection 48 | if case .end = self.unwrapInboundIn(data) { 49 | self.upgradeState = .awaitingConnection(pendingBytes: []) 50 | self.removeDecoder(context: context) 51 | } 52 | 53 | case .awaitingEnd(let peerChannel): 54 | if case .end = self.unwrapInboundIn(data) { 55 | // Upgrade has completed! 56 | self.upgradeState = .upgradeComplete(pendingBytes: []) 57 | self.removeDecoder(context: context) 58 | self.glue(peerChannel, context: context) 59 | } 60 | 61 | case .awaitingConnection(var pendingBytes): 62 | // We've seen end, this must not be HTTP anymore. Danger, Will Robinson! Do not unwrap. 63 | self.upgradeState = .awaitingConnection(pendingBytes: []) 64 | pendingBytes.append(data) 65 | self.upgradeState = .awaitingConnection(pendingBytes: pendingBytes) 66 | 67 | case .upgradeComplete(pendingBytes: var pendingBytes): 68 | // We're currently delivering data, keep doing so. 69 | self.upgradeState = .upgradeComplete(pendingBytes: []) 70 | pendingBytes.append(data) 71 | self.upgradeState = .upgradeComplete(pendingBytes: pendingBytes) 72 | 73 | case .upgradeFailed: 74 | break 75 | } 76 | } 77 | 78 | func handlerAdded(context: ChannelHandlerContext) { 79 | // Add logger metadata. 80 | self.logger[metadataKey: "localAddress"] = "\(String(describing: context.channel.localAddress))" 81 | self.logger[metadataKey: "remoteAddress"] = "\(String(describing: context.channel.remoteAddress))" 82 | self.logger[metadataKey: "channel"] = "\(ObjectIdentifier(context.channel))" 83 | } 84 | } 85 | 86 | 87 | extension ConnectHandler: RemovableChannelHandler { 88 | func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) { 89 | var didRead = false 90 | 91 | // We are being removed, and need to deliver any pending bytes we may have if we're upgrading. 92 | while case .upgradeComplete(var pendingBytes) = self.upgradeState, pendingBytes.count > 0 { 93 | // Avoid a CoW while we pull some data out. 94 | self.upgradeState = .upgradeComplete(pendingBytes: []) 95 | let nextRead = pendingBytes.removeFirst() 96 | self.upgradeState = .upgradeComplete(pendingBytes: pendingBytes) 97 | 98 | context.fireChannelRead(nextRead) 99 | didRead = true 100 | } 101 | 102 | if didRead { 103 | context.fireChannelReadComplete() 104 | } 105 | 106 | self.logger.debug("Removing \(self) from pipeline") 107 | context.leavePipeline(removalToken: removalToken) 108 | } 109 | } 110 | 111 | extension ConnectHandler { 112 | private func handleInitialMessage(context: ChannelHandlerContext, data: InboundIn) { 113 | guard case .head(let head) = data else { 114 | self.logger.error("Invalid HTTP message type \(data)") 115 | self.httpErrorAndClose(context: context) 116 | return 117 | } 118 | 119 | self.logger.info("\(head.method) \(head.uri) \(head.version)") 120 | 121 | guard head.method == .CONNECT else { 122 | self.logger.error("Invalid HTTP method: \(head.method)") 123 | self.httpErrorAndClose(context: context) 124 | return 125 | } 126 | 127 | let components = head.uri.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false) 128 | let host = components.first! // There will always be a first. 129 | let port = components.last.flatMap { Int($0, radix: 10) } ?? 80 // Port 80 if not specified 130 | self.upgradeState = .beganConnecting 131 | self.connectTo(host: String(host), port: port, context: context) 132 | } 133 | 134 | private func connectTo(host: String, port: Int, context: ChannelHandlerContext) { 135 | self.logger.info("Connecting to \(host):\(port)") 136 | 137 | let channelFuture = ClientBootstrap(group: context.eventLoop) 138 | .connect(host: String(host), port: port) 139 | 140 | channelFuture.whenSuccess { channel in 141 | self.connectSucceeded(channel: channel, context: context) 142 | } 143 | channelFuture.whenFailure { error in 144 | self.connectFailed(error: error, context: context) 145 | } 146 | } 147 | 148 | private func connectSucceeded(channel: Channel, context: ChannelHandlerContext) { 149 | self.logger.info("Connected to \(String(describing: channel.remoteAddress))") 150 | 151 | switch self.upgradeState { 152 | case .beganConnecting: 153 | // Ok, we have a channel, let's wait for end. 154 | self.upgradeState = .awaitingEnd(connectResult: channel) 155 | 156 | case .awaitingConnection(pendingBytes: let pendingBytes): 157 | // Upgrade complete! Begin gluing the connection together. 158 | self.upgradeState = .upgradeComplete(pendingBytes: pendingBytes) 159 | self.glue(channel, context: context) 160 | 161 | case .awaitingEnd(let peerChannel): 162 | // This case is a logic error, close already connected peer channel. 163 | peerChannel.close(mode: .all, promise: nil) 164 | context.close(promise: nil) 165 | 166 | case .idle, .upgradeFailed, .upgradeComplete: 167 | // These cases are logic errors, but let's be careful and just shut the connection. 168 | context.close(promise: nil) 169 | } 170 | } 171 | 172 | private func connectFailed(error: Error, context: ChannelHandlerContext) { 173 | self.logger.error("Connect failed: \(error)") 174 | 175 | switch self.upgradeState { 176 | case .beganConnecting, .awaitingConnection: 177 | // We still have a somewhat active connection here in HTTP mode, and can report failure. 178 | self.httpErrorAndClose(context: context) 179 | 180 | case .awaitingEnd(let peerChannel): 181 | // This case is a logic error, close already connected peer channel. 182 | peerChannel.close(mode: .all, promise: nil) 183 | context.close(promise: nil) 184 | 185 | case .idle, .upgradeFailed, .upgradeComplete: 186 | // Most of these cases are logic errors, but let's be careful and just shut the connection. 187 | context.close(promise: nil) 188 | } 189 | 190 | context.fireErrorCaught(error) 191 | } 192 | 193 | private func glue(_ peerChannel: Channel, context: ChannelHandlerContext) { 194 | self.logger.debug("Gluing together \(ObjectIdentifier(context.channel)) and \(ObjectIdentifier(peerChannel))") 195 | 196 | // Ok, upgrade has completed! We now need to begin the upgrade process. 197 | // First, send the 200 message. 198 | // This content-length header is MUST NOT, but we need to workaround NIO's insistence that we set one. 199 | let headers = HTTPHeaders([("Content-Length", "0")]) 200 | let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .ok, headers: headers) 201 | context.write(self.wrapOutboundOut(.head(head)), promise: nil) 202 | context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) 203 | 204 | // Now remove the HTTP encoder. 205 | self.removeEncoder(context: context) 206 | 207 | // Now we need to glue our channel and the peer channel together. 208 | let (localGlue, peerGlue) = GlueHandler.matchedPair() 209 | context.channel.pipeline.addHandler(localGlue).and(peerChannel.pipeline.addHandler(peerGlue)).whenComplete { result in 210 | switch result { 211 | case .success(_): 212 | context.pipeline.removeHandler(self, promise: nil) 213 | case .failure(_): 214 | // Close connected peer channel before closing our channel. 215 | peerChannel.close(mode: .all, promise: nil) 216 | context.close(promise: nil) 217 | } 218 | } 219 | } 220 | 221 | private func httpErrorAndClose(context: ChannelHandlerContext) { 222 | self.upgradeState = .upgradeFailed 223 | 224 | let headers = HTTPHeaders([("Content-Length", "0"), ("Connection", "close")]) 225 | let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .badRequest, headers: headers) 226 | context.write(self.wrapOutboundOut(.head(head)), promise: nil) 227 | context.writeAndFlush(self.wrapOutboundOut(.end(nil))).whenComplete { (_: Result) in 228 | context.close(mode: .output, promise: nil) 229 | } 230 | } 231 | 232 | private func removeDecoder(context: ChannelHandlerContext) { 233 | // We drop the future on the floor here as these handlers must all be in our own pipeline, and this should 234 | // therefore succeed fast. 235 | context.pipeline.context(handlerType: ByteToMessageHandler.self).whenSuccess { 236 | context.pipeline.removeHandler(context: $0, promise: nil) 237 | } 238 | } 239 | 240 | private func removeEncoder(context: ChannelHandlerContext) { 241 | context.pipeline.context(handlerType: HTTPResponseEncoder.self).whenSuccess { 242 | context.pipeline.removeHandler(context: $0, promise: nil) 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /Sources/http/GlueHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlueHandler.swift 3 | // Socks5Server 4 | // 5 | // Created by Purkylin King on 2020/9/30. 6 | // 7 | 8 | import NIO 9 | 10 | final class GlueHandler { 11 | private var partner: GlueHandler? 12 | 13 | private var context: ChannelHandlerContext? 14 | 15 | private var pendingRead: Bool = false 16 | 17 | private init() { } 18 | } 19 | 20 | 21 | extension GlueHandler { 22 | static func matchedPair() -> (GlueHandler, GlueHandler) { 23 | let first = GlueHandler() 24 | let second = GlueHandler() 25 | 26 | first.partner = second 27 | second.partner = first 28 | 29 | return (first, second) 30 | } 31 | } 32 | 33 | 34 | extension GlueHandler { 35 | private func partnerWrite(_ data: NIOAny) { 36 | self.context?.write(data, promise: nil) 37 | } 38 | 39 | private func partnerFlush() { 40 | self.context?.flush() 41 | } 42 | 43 | private func partnerWriteEOF() { 44 | self.context?.close(mode: .output, promise: nil) 45 | } 46 | 47 | private func partnerCloseFull() { 48 | self.context?.close(promise: nil) 49 | } 50 | 51 | private func partnerBecameWritable() { 52 | if self.pendingRead { 53 | self.pendingRead = false 54 | self.context?.read() 55 | } 56 | } 57 | 58 | private var partnerWritable: Bool { 59 | return self.context?.channel.isWritable ?? false 60 | } 61 | } 62 | 63 | extension GlueHandler: ChannelDuplexHandler { 64 | typealias InboundIn = NIOAny 65 | typealias OutboundIn = NIOAny 66 | typealias OutboundOut = NIOAny 67 | 68 | func handlerAdded(context: ChannelHandlerContext) { 69 | self.context = context 70 | } 71 | 72 | func handlerRemoved(context: ChannelHandlerContext) { 73 | self.context = nil 74 | self.partner = nil 75 | } 76 | 77 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 78 | self.partner?.partnerWrite(data) 79 | } 80 | 81 | func channelReadComplete(context: ChannelHandlerContext) { 82 | self.partner?.partnerFlush() 83 | } 84 | 85 | func channelInactive(context: ChannelHandlerContext) { 86 | self.partner?.partnerCloseFull() 87 | } 88 | 89 | func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { 90 | if let event = event as? ChannelEvent, case .inputClosed = event { 91 | // We have read EOF. 92 | self.partner?.partnerWriteEOF() 93 | } 94 | } 95 | 96 | func errorCaught(context: ChannelHandlerContext, error: Error) { 97 | self.partner?.partnerCloseFull() 98 | } 99 | 100 | func channelWritabilityChanged(context: ChannelHandlerContext) { 101 | if context.channel.isWritable { 102 | self.partner?.partnerBecameWritable() 103 | } 104 | } 105 | 106 | func read(context: ChannelHandlerContext) { 107 | if let partner = self.partner, partner.partnerWritable { 108 | context.read() 109 | } else { 110 | self.pendingRead = true 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Sources/http/HttpServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HttpServer.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/10. 6 | // 7 | 8 | import NIO 9 | import Logging 10 | import NIOHTTP1 11 | import Dispatch 12 | 13 | public final class HttpServer { 14 | private let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 15 | public private(set) var isRunning: Bool = false 16 | 17 | public init() { } 18 | 19 | public func start(port: Int = 1080) { 20 | let logger = Logger(label: "http_server") 21 | 22 | let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 23 | let bootstrap = ServerBootstrap(group: group) 24 | .serverChannelOption(ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 1) 25 | .childChannelOption(ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 1) 26 | .childChannelInitializer { channel in 27 | channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes))).flatMap { 28 | channel.pipeline.addHandler(HTTPResponseEncoder()).flatMap { 29 | channel.pipeline.addHandler(ConnectHandler(logger: Logger(label: "com.apple.nio-connect-proxy.ConnectHandler"))) 30 | } 31 | } 32 | } 33 | 34 | do { 35 | let channel = try bootstrap.bind(host: "::0", port: port).wait() 36 | logger.info("start http server on port \(port) success") 37 | isRunning = true 38 | try channel.closeFuture.wait() 39 | } catch { 40 | logger.error("start http server on port \(port) failed") 41 | } 42 | } 43 | 44 | public func stop() { 45 | try? group.syncShutdownGracefully() 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/Data+Byte.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Data+Byte.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/15. 6 | // 7 | 8 | import Foundation 9 | import CryptoKit 10 | 11 | extension Data { 12 | init?(hex: String) { 13 | guard hex.count.isMultiple(of: 2) else { 14 | return nil 15 | } 16 | 17 | let chars = hex.map { $0 } 18 | let bytes = stride(from: 0, to: chars.count, by: 2) 19 | .map { String(chars[$0]) + String(chars[$0 + 1]) } 20 | .compactMap { UInt8($0, radix: 16) } 21 | 22 | guard hex.count / bytes.count == 2 else { return nil } 23 | self.init(bytes) 24 | } 25 | 26 | var bytes:[UInt8] { 27 | return [UInt8](self) 28 | } 29 | 30 | func hexString() -> String { 31 | return self.map { String(format:"%02x", $0) }.joined() 32 | } 33 | 34 | func md5() -> Data { 35 | var md5 = Insecure.MD5() 36 | md5.update(data: self) 37 | let digest = Data(md5.finalize()) 38 | return digest 39 | } 40 | 41 | static func random(length: Int) -> Data { 42 | var data = Data(count: length) 43 | _ = data.withUnsafeMutableBytes { 44 | SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!) 45 | } 46 | return data 47 | } 48 | } 49 | 50 | extension DataProtocol { 51 | func toInteger(as: T.Type = T.self, bigEndian: Bool = true) -> T? { 52 | let size = MemoryLayout.size 53 | guard self.count >= size else { 54 | return nil 55 | } 56 | 57 | var bytes = Array(self.prefix(size)) 58 | 59 | if !bigEndian { 60 | bytes = bytes.reversed() 61 | } 62 | 63 | let value = bytes.reduce(0) { soFar, byte in 64 | return soFar << 8 | T(byte) 65 | } 66 | 67 | return value 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/SSRelayHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSRelayHandler.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/15. 6 | // 7 | 8 | import Foundation 9 | import NIO 10 | import Logging 11 | 12 | class SSRelayHandler { 13 | private var partner: SSRelayHandler? 14 | 15 | private var context: ChannelHandlerContext? 16 | 17 | private var pendingRead: Bool = false 18 | 19 | var isLocal: Bool = false 20 | var cryptor: Cryptor 21 | 22 | private init(cryptor: Cryptor) { 23 | self.cryptor = cryptor 24 | } 25 | } 26 | 27 | extension SSRelayHandler { 28 | static func matchedPair(cryptor: Cryptor) -> (SSRelayHandler, SSRelayHandler) { 29 | let first = SSRelayHandler(cryptor: cryptor) 30 | let second = SSRelayHandler(cryptor: cryptor) 31 | 32 | first.partner = second 33 | second.partner = first 34 | 35 | return (first, second) 36 | } 37 | } 38 | 39 | extension SSRelayHandler { 40 | private func partnerWrite(_ data: NIOAny, promise: EventLoopPromise? = nil) { 41 | guard let context = self.context else { return } 42 | do { 43 | if isLocal { 44 | var buffer = self.unwrapInboundIn(data) 45 | if let bytes = buffer.readBytes(length: buffer.readableBytes) { 46 | let ciphertext = try cryptor.decrypt(payload: bytes) 47 | 48 | var outBuffer = context.channel.allocator.buffer(capacity: ciphertext.count) 49 | outBuffer.writeBytes(ciphertext) 50 | self.context?.write(self.wrapOutboundOut(outBuffer), promise: nil) 51 | self.context?.flush() 52 | } 53 | } else { 54 | var buffer = self.unwrapInboundIn(data) 55 | if let bytes = buffer.readBytes(length: buffer.readableBytes) { 56 | let ciphertext = try cryptor.encrypt(payload: bytes) 57 | 58 | var outBuffer = context.channel.allocator.buffer(capacity: ciphertext.count) 59 | outBuffer.writeBytes(ciphertext) 60 | self.context?.write(self.wrapOutboundOut(outBuffer), promise: nil) 61 | } 62 | } 63 | } catch { 64 | logger.error("crypto failed once") 65 | partnerCloseFull() 66 | } 67 | } 68 | 69 | private func partnerFlush() { 70 | self.context?.flush() 71 | } 72 | 73 | private func partnerWriteEOF() { 74 | self.context?.close(mode: .output, promise: nil) 75 | } 76 | 77 | private func partnerCloseFull() { 78 | self.context?.close(promise: nil) 79 | } 80 | 81 | private func partnerBecameWritable() { 82 | if self.pendingRead { 83 | self.pendingRead = false 84 | self.context?.read() 85 | } 86 | } 87 | 88 | private var partnerWritable: Bool { 89 | return self.context?.channel.isWritable ?? false 90 | } 91 | } 92 | 93 | extension SSRelayHandler: ChannelDuplexHandler { 94 | typealias InboundIn = ByteBuffer 95 | typealias OutboundIn = ByteBuffer 96 | typealias OutboundOut = ByteBuffer 97 | 98 | func handlerAdded(context: ChannelHandlerContext) { 99 | self.context = context 100 | } 101 | 102 | func handlerRemoved(context: ChannelHandlerContext) { 103 | self.context = nil 104 | self.partner = nil 105 | } 106 | 107 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 108 | self.partner?.partnerWrite(data) 109 | } 110 | 111 | func channelReadComplete(context: ChannelHandlerContext) { 112 | self.partner?.partnerFlush() 113 | } 114 | 115 | func channelInactive(context: ChannelHandlerContext) { 116 | self.partner?.partnerCloseFull() 117 | } 118 | 119 | func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { 120 | if let event = event as? ChannelEvent, case .inputClosed = event { 121 | // We have read EOF. 122 | self.partner?.partnerWriteEOF() 123 | } 124 | } 125 | 126 | func errorCaught(context: ChannelHandlerContext, error: Error) { 127 | self.partner?.partnerCloseFull() 128 | } 129 | 130 | func channelWritabilityChanged(context: ChannelHandlerContext) { 131 | if context.channel.isWritable { 132 | self.partner?.partnerBecameWritable() 133 | } 134 | } 135 | 136 | func read(context: ChannelHandlerContext) { 137 | if let partner = self.partner, partner.partnerWritable { 138 | context.read() 139 | } else { 140 | self.pendingRead = true 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/ShadowsocksHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShadowsocksHandler.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/15. 6 | // 7 | 8 | import Foundation 9 | import NIO 10 | import Logging 11 | 12 | class ShadowsocksHandler: ChannelInboundHandler { 13 | typealias InboundIn = SocksRequest 14 | typealias OutboundOut = SocksResponse 15 | 16 | private let config: ShadowsocksConfiguration 17 | private var requestCommand: RequestCommand! 18 | 19 | private unowned let decoder: SocksDecoder 20 | 21 | init(config: ShadowsocksConfiguration, decoder: SocksDecoder) { 22 | self.config = config 23 | self.decoder = decoder 24 | } 25 | 26 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 27 | let req = self.unwrapInboundIn(data) 28 | 29 | switch req { 30 | case .initial: 31 | logger.debug("receive initial") 32 | let response = SocksResponse.initial(method: .none) 33 | decoder.state = .cmd 34 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 35 | case .command(let request): 36 | logger.debug("receive command") 37 | 38 | self.requestCommand = request 39 | 40 | switch request.cmd { 41 | case .connect: 42 | connectTo(host: config.host, port: UInt16(config.port), context: context) 43 | case .bind: 44 | fatalError("invalid command") 45 | case .udp: 46 | // TODO: crypto part 47 | beginUDP(context: context) 48 | } 49 | default: 50 | break 51 | } 52 | } 53 | 54 | func errorCaught(context: ChannelHandlerContext, error: Error) { 55 | logger.error(.init(stringLiteral: error.localizedDescription)) 56 | context.channel.close(mode: .all, promise: nil) 57 | } 58 | 59 | func connectTo(host: String, port: UInt16, context: ChannelHandlerContext) { 60 | let future = ClientBootstrap(group: context.eventLoop).connect(host: host, port: Int(port)) 61 | 62 | future.whenComplete { result in 63 | switch result { 64 | case .success(let channel): 65 | logger.debug("connect host success") 66 | let response = SocksResponse.command(type: .success, addr: .zero(for: .v4), port: 0) 67 | context.writeAndFlush(self.wrapOutboundOut(response)).whenComplete { [unowned self] result in 68 | context.pipeline.removeHandler(handlerType: MessageToByteHandler.self) 69 | context.pipeline.removeHandler(handlerType: ByteToMessageHandler.self) 70 | 71 | let cryptor = AEADCryptor(password: config.password, keyLen: config.method.keyLen, saltLen: config.method.saltLen) 72 | context.channel.relayWithCryptor(peerChannel: channel, cryptor: cryptor).and(context.pipeline.removeHandler(self)).whenComplete { result in 73 | let bytes: [UInt8] = requestCommand.addr.bytes + requestCommand.port.bytes 74 | let info = try! cryptor.encrypt(payload: bytes) 75 | var buffer = channel.allocator.buffer(capacity: info.count) 76 | buffer.writeBytes(info) 77 | channel.write(NIOAny(buffer), promise: nil) 78 | 79 | } 80 | } 81 | case .failure(let error): 82 | logger.error("connect host failed, \(error.localizedDescription)") 83 | let response = SocksResponse.command(type: .unreachable, addr: .zero(for: .v4), port: 0) 84 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 85 | context.close(mode: .output, promise: nil) 86 | } 87 | } 88 | } 89 | 90 | func beginUDP(context: ChannelHandlerContext) { 91 | // TODO 92 | let bootstrap = DatagramBootstrap(group: context.eventLoop) 93 | .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) 94 | .channelOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 30 * 2048)) 95 | .channelInitializer { channel in 96 | channel.pipeline.addHandler(UDPHandler()) 97 | } 98 | 99 | let future = bootstrap.bind(host: "0.0.0.0", port: 0) 100 | future.whenComplete { result in 101 | switch result { 102 | case .success(let channel): 103 | guard let address = channel.localAddress, let port = address.port else { 104 | fatalError("bind udp failed") 105 | } 106 | 107 | logger.debug(.init(stringLiteral: "bind udp on: \(port)")) 108 | 109 | let response = SocksResponse.command(type: .success, addr: .zero(for: .v4), port: UInt16(port)) 110 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 111 | case .failure(let error): 112 | logger.error(.init(stringLiteral: error.localizedDescription)) 113 | let response = SocksResponse.command(type: .connectFailed, addr: .zero(for: .v4), port: 0) 114 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 115 | context.close(mode: .output, promise: nil) 116 | } 117 | } 118 | } 119 | } 120 | 121 | extension ShadowsocksHandler: RemovableChannelHandler { } 122 | 123 | extension Channel { 124 | func relayWithCryptor(peerChannel: Channel, cryptor: Cryptor) -> EventLoopFuture<(Void)> { 125 | let (localGlue, peerGlue) = SSRelayHandler.matchedPair(cryptor: cryptor) 126 | localGlue.isLocal = true 127 | 128 | return self.pipeline.addHandler(localGlue).and(peerChannel.pipeline.addHandler(peerGlue)).map { _ in 129 | return 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/ShadowsocksServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShadowsocksServer.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/26. 6 | // 7 | 8 | import NIO 9 | 10 | public struct ShadowsocksConfiguration { 11 | public enum Method { 12 | case aes_128_gcm 13 | case aes_192_gcm 14 | case aes_256_gcm 15 | 16 | var keyLen: Int { 17 | switch self { 18 | case .aes_128_gcm: 19 | return 16 20 | case .aes_192_gcm: 21 | return 24 22 | case .aes_256_gcm: 23 | return 32 24 | } 25 | } 26 | 27 | var saltLen: Int { 28 | switch self { 29 | case .aes_128_gcm: 30 | return 16 31 | case .aes_192_gcm: 32 | return 24 33 | case .aes_256_gcm: 34 | return 32 35 | } 36 | } 37 | } 38 | 39 | let localPort: Int 40 | 41 | let host: String 42 | let port: Int 43 | let method: Method 44 | let password: String 45 | 46 | public init(host: String, port: Int, password: String, method: Method = .aes_256_gcm, localPort: Int = 1080) { 47 | self.host = host 48 | self.port = port 49 | self.password = password 50 | self.method = method 51 | self.localPort = localPort 52 | } 53 | } 54 | 55 | public final class ShadowsocksServer { 56 | private let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 57 | public private(set) var isRunning: Bool = false 58 | 59 | public init() { } 60 | 61 | public func start(config: ShadowsocksConfiguration) { 62 | if isRunning { 63 | logger.warning("shadowsocks server has started") 64 | return 65 | } 66 | 67 | let bootstrap = ServerBootstrap(group: group) 68 | // Specify backlog and enable SO_REUSEADDR for the server itself 69 | .serverChannelOption(ChannelOptions.backlog, value: 256) 70 | .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) 71 | // Set the handlers that are appled to the accepted Channels 72 | .childChannelInitializer { channel in 73 | // Ensure we don't read faster than we can write by adding the BackPressureHandler into the pipeline. 74 | channel.pipeline.addHandler(BackPressureHandler()).flatMap { v in 75 | channel.pipeline.configShadowsocks(config: config) 76 | } 77 | } 78 | // Enable SO_REUSEADDR for the accepted Channels 79 | .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) 80 | .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16) 81 | .childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator()) 82 | do { 83 | let channel = try bootstrap.bind(host: "::0", port: config.localPort).wait() 84 | logger.debug("start ss server on port \(config.port) success") 85 | isRunning = true 86 | 87 | try channel.closeFuture.wait() 88 | } catch { 89 | logger.error("start ss server on port \(config.port) failed") 90 | } 91 | 92 | logger.debug("ss server has stopped") 93 | isRunning = false 94 | } 95 | 96 | public func stop() { 97 | try? group.syncShutdownGracefully() 98 | } 99 | } 100 | 101 | extension ChannelPipeline { 102 | func configShadowsocks(config: ShadowsocksConfiguration) -> EventLoopFuture { 103 | let encoderHandler = MessageToByteHandler(SocksEncoder()) 104 | let decoder = SocksDecoder() 105 | let decoderHandler = ByteToMessageHandler(decoder) 106 | let handler = ShadowsocksHandler(config: config, decoder: decoder) 107 | 108 | return self.addHandlers(encoderHandler, decoderHandler, handler) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/cryptor/AEADCryptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AEADCryptor.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/15. 6 | // 7 | 8 | import Foundation 9 | import Crypto 10 | import struct NIO.ByteBuffer 11 | 12 | public class AEADCryptor: Cryptor { 13 | private let password: String 14 | private let keyLen: Int 15 | private let saltLen: Int 16 | 17 | private lazy var encryptor: AEADCryptor.Encryptor = { 18 | let salt = Data.random(length: saltLen) 19 | return Encryptor(password: password, salt: salt, keyLen: keyLen) 20 | }() 21 | 22 | private var decryptor: AEADCryptor.Decryptor? 23 | 24 | required init(password: String, keyLen: Int = 32, saltLen: Int = 32) { 25 | self.password = password 26 | self.keyLen = keyLen 27 | self.saltLen = saltLen 28 | } 29 | 30 | func encrypt(payload: [UInt8]) throws -> [UInt8] { 31 | try encryptor.encrypt(bytes: payload) 32 | } 33 | 34 | func decrypt(payload: [UInt8]) throws -> [UInt8] { 35 | let ciphertext: [UInt8] 36 | 37 | if decryptor == nil { 38 | guard payload.count >= saltLen else { 39 | logger.warning("The salt length is not enough") 40 | return [] 41 | } 42 | 43 | let salt = payload[0.. [UInt8] { 73 | guard bytes.count > 0 else { 74 | return [] 75 | } 76 | 77 | var output = [UInt8]() 78 | var chunk = ArraySlice(bytes) 79 | var idx: Int = 0 80 | 81 | while true { 82 | let validLength = min(maxChunkSize, bytes.count - idx) 83 | chunk = bytes[idx..) throws -> [UInt8] { 96 | func combine(box: AES.GCM.SealedBox) -> Data { 97 | return box.ciphertext + box.tag 98 | } 99 | 100 | let length: UInt16 = UInt16(chunk.count) 101 | let payload = chunk 102 | 103 | var output = [UInt8]() 104 | 105 | let lengthResult = try AES.GCM.seal(length.bytes, using: key, nonce: AES.GCM.Nonce(data: nonce.bytes)) 106 | output += combine(box: lengthResult) 107 | 108 | nonce.increment() 109 | 110 | let payloadResult = try AES.GCM.seal(payload, using: key, nonce: AES.GCM.Nonce(data: nonce.bytes)) 111 | output += combine(box: payloadResult) 112 | 113 | nonce.increment() 114 | 115 | assert(output.count == chunk.count + 34) 116 | 117 | if !hasSendSalt { 118 | hasSendSalt = true 119 | output = salt.bytes + output 120 | } 121 | 122 | logger.debug("encrypt chunk once") 123 | 124 | return output 125 | } 126 | } 127 | 128 | private class Decryptor { 129 | private let key: SymmetricKey 130 | private let salt: Data? 131 | private var nonce = Nonce() 132 | private let keyLen: Int 133 | 134 | private var buffer = ByteBuffer(bytes: []) 135 | 136 | init(password: String, salt: Data, keyLen: Int) { 137 | self.salt = salt 138 | self.keyLen = keyLen 139 | 140 | let derivedKey = evpBytesToKey(password: password, keyLen: keyLen) 141 | let subkey = hkdf_sha1(Data(derivedKey), salt: salt, info: ssInfo, outputSize: keyLen)! 142 | self.key = SymmetricKey(data: subkey) 143 | } 144 | 145 | func decrypt(bytes: [UInt8]) throws -> [UInt8] { 146 | guard bytes.count > 0 else { 147 | return [] 148 | } 149 | 150 | buffer.writeBytes(bytes) 151 | 152 | var output = [UInt8]() 153 | 154 | while buffer.readableBytes > 0 { 155 | let backNonce = nonce 156 | var peekBuffer = buffer 157 | 158 | guard let data1 = peekBuffer.readBytes(length: 2) else { 159 | self.nonce = backNonce 160 | break 161 | } 162 | guard let tag1 = peekBuffer.readBytes(length: 16) else { 163 | self.nonce = backNonce 164 | break 165 | } 166 | 167 | let lengthData = try decrypt(bytes: data1, tag: tag1) 168 | let length: UInt16 = lengthData.toInteger()! 169 | nonce.increment() 170 | 171 | guard let data2 = peekBuffer.readBytes(length: Int(length)) else { 172 | self.nonce = backNonce 173 | break 174 | } 175 | 176 | guard let tag2 = peekBuffer.readBytes(length: 16) else { 177 | self.nonce = backNonce 178 | break 179 | } 180 | output += try decrypt(bytes: data2, tag: tag2) 181 | nonce.increment() 182 | buffer = peekBuffer 183 | 184 | logger.debug("decrypt success once") 185 | } 186 | 187 | return output 188 | } 189 | 190 | private func decrypt(bytes: [UInt8], tag: [UInt8]) throws -> [UInt8] { 191 | let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: nonce.bytes), ciphertext: bytes, tag: tag) 192 | return try AES.GCM.open(sealedBox, using: key).bytes 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/cryptor/Cryptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cryptor.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/27. 6 | // 7 | 8 | import Foundation 9 | 10 | // ss max tcp chunk size 11 | let maxChunkSize = 0x3FFF 12 | 13 | // ss derived key info 14 | let ssInfo = "ss-subkey".data(using: .utf8)! 15 | 16 | protocol Cryptor { 17 | init(password: String, keyLen: Int, saltLen: Int) 18 | 19 | func encrypt(payload: [UInt8]) throws -> [UInt8] 20 | func decrypt(payload: [UInt8]) throws -> [UInt8] 21 | } 22 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/cryptor/HKDF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/15. 6 | // 7 | 8 | import Foundation 9 | import Crypto 10 | 11 | func hkdf_sha1(_ key: Data, salt: Data, info: Data, outputSize: Int = 20) -> Data? { 12 | // It would be nice to make this generic over if HashFunction had byteCount instead of each hash 13 | // individually implementing it. 14 | let iterations = UInt8(ceil(Double(outputSize) / Double(Insecure.SHA1.byteCount))) 15 | guard iterations <= 255 else { 16 | return nil 17 | } 18 | 19 | let prk = HMAC.authenticationCode(for: key, using: SymmetricKey(data: salt)) 20 | let key = SymmetricKey(data: prk) 21 | var hkdf = Data() 22 | var value = Data() 23 | 24 | for i in 1...iterations { 25 | value.append(info) 26 | value.append(i) 27 | 28 | let code = HMAC.authenticationCode(for: value, using: key) 29 | hkdf.append(contentsOf: code) 30 | 31 | value = Data(code) 32 | } 33 | 34 | return hkdf.prefix(outputSize) 35 | } 36 | 37 | func evpBytesToKey(password:String, keyLen:Int) -> [UInt8] { 38 | var m = [Data]() 39 | guard let passwd = password.data(using: String.Encoding.utf8) else { 40 | return [] 41 | } 42 | 43 | while m.reduce(0, {$0 + $1.count}) < keyLen { 44 | let data = m.count > 0 ? m.last! + passwd : passwd 45 | m.append(data.md5()) 46 | } 47 | let final = m.reduce(Data(), +) 48 | return Array(final.bytes[0...(keyLen - 1)]) 49 | } 50 | -------------------------------------------------------------------------------- /Sources/proxy-nio/shadowsocks/cryptor/Nonce.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/27. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Nonce { 11 | private let length: Int 12 | private var storage: [UInt8] 13 | 14 | // Little endian 15 | var bytes: [UInt8] { 16 | return storage 17 | } 18 | 19 | init(length: Int = 12) { 20 | self.length = length 21 | self.storage = Array(repeating: 0, count: length) 22 | } 23 | 24 | mutating func increment() { 25 | for (idx, v) in storage.enumerated() { 26 | let overflow = v == UInt8.max 27 | storage[idx] = v &+ 1 28 | 29 | if !overflow { 30 | break 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/ByteBuffer+Util.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ByteBuffer+Util.swift 3 | // Socks5Server 4 | // 5 | // Created by Purkylin King on 2020/9/25. 6 | // 7 | 8 | import NIO 9 | 10 | extension ByteBuffer { 11 | @discardableResult 12 | mutating func skipBytes(_ len: Int) -> Bool { 13 | if self.readableBytes >= len { 14 | self.moveReaderIndex(forwardBy: len) 15 | return true 16 | } else { 17 | return false 18 | } 19 | } 20 | 21 | func peekInteger(as: T.Type = T.self) -> T? { 22 | let size = MemoryLayout.size 23 | guard self.readableBytes >= size else { return nil } 24 | return getInteger(at: self.readerIndex, endianness: .big, as: `as`) 25 | } 26 | 27 | /// Read a fix length string, first byte represent length 28 | mutating func readString() -> String? { 29 | guard self.readableBytes >= 1 else { return nil } 30 | 31 | guard let peekLen = self.getInteger(at: self.readerIndex, as: UInt8.self) else { return nil } 32 | guard self.readableBytes >= 1 + peekLen else { return nil } 33 | self.skipBytes(1) 34 | return self.readString(length: Int(peekLen)) 35 | } 36 | 37 | /// Read all available bytes 38 | mutating func readAll() -> [UInt8] { 39 | guard self.readableBytes > 0 else { return [] } 40 | return self.readBytes(length: self.readableBytes) ?? [] 41 | } 42 | } 43 | 44 | extension FixedWidthInteger { 45 | var bytes: [UInt8] { 46 | withUnsafeBytes(of: self.bigEndian, Array.init) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/Channel+Relay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/23. 6 | // 7 | 8 | import NIO 9 | 10 | extension Channel { 11 | func relay(peerChannel: Channel) -> EventLoopFuture<(Void)> { 12 | let (localGlue, peerGlue) = GlueHandler.matchedPair() 13 | return self.pipeline.addHandler(localGlue).and(peerChannel.pipeline.addHandler(peerGlue)).map { _ in 14 | return 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/GlueHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlueHandler.swift 3 | // Socks5Server 4 | // 5 | // Created by Purkylin King on 2020/9/30. 6 | // 7 | 8 | import NIO 9 | 10 | class GlueHandler { 11 | private var partner: GlueHandler? 12 | 13 | private var context: ChannelHandlerContext? 14 | 15 | private var pendingRead: Bool = false 16 | 17 | private init() { } 18 | } 19 | 20 | extension GlueHandler { 21 | static func matchedPair() -> (GlueHandler, GlueHandler) { 22 | let first = GlueHandler() 23 | let second = GlueHandler() 24 | 25 | first.partner = second 26 | second.partner = first 27 | 28 | return (first, second) 29 | } 30 | } 31 | 32 | extension GlueHandler { 33 | private func partnerWrite(_ data: NIOAny) { 34 | self.context?.write(data, promise: nil) 35 | } 36 | 37 | private func partnerFlush() { 38 | self.context?.flush() 39 | } 40 | 41 | private func partnerWriteEOF() { 42 | self.context?.close(mode: .output, promise: nil) 43 | } 44 | 45 | private func partnerCloseFull() { 46 | self.context?.close(promise: nil) 47 | } 48 | 49 | private func partnerBecameWritable() { 50 | if self.pendingRead { 51 | self.pendingRead = false 52 | self.context?.read() 53 | } 54 | } 55 | 56 | private var partnerWritable: Bool { 57 | return self.context?.channel.isWritable ?? false 58 | } 59 | } 60 | 61 | extension GlueHandler: ChannelDuplexHandler { 62 | typealias InboundIn = NIOAny 63 | typealias OutboundIn = NIOAny 64 | typealias OutboundOut = NIOAny 65 | 66 | func handlerAdded(context: ChannelHandlerContext) { 67 | self.context = context 68 | } 69 | 70 | func handlerRemoved(context: ChannelHandlerContext) { 71 | self.context = nil 72 | self.partner = nil 73 | } 74 | 75 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 76 | self.partner?.partnerWrite(data) 77 | } 78 | 79 | func channelReadComplete(context: ChannelHandlerContext) { 80 | self.partner?.partnerFlush() 81 | } 82 | 83 | func channelInactive(context: ChannelHandlerContext) { 84 | self.partner?.partnerCloseFull() 85 | } 86 | 87 | func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { 88 | if let event = event as? ChannelEvent, case .inputClosed = event { 89 | // We have read EOF. 90 | self.partner?.partnerWriteEOF() 91 | } 92 | } 93 | 94 | func errorCaught(context: ChannelHandlerContext, error: Error) { 95 | self.partner?.partnerCloseFull() 96 | } 97 | 98 | func channelWritabilityChanged(context: ChannelHandlerContext) { 99 | if context.channel.isWritable { 100 | self.partner?.partnerBecameWritable() 101 | } 102 | } 103 | 104 | func read(context: ChannelHandlerContext) { 105 | if let partner = self.partner, partner.partnerWritable { 106 | context.read() 107 | } else { 108 | self.pendingRead = true 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/Socks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Socks.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/28. 6 | // 7 | 8 | import Foundation 9 | import Logging 10 | 11 | let logger: Logger = { 12 | var obj = Logger(label: "proxy-nio") 13 | obj.logLevel = .debug 14 | return obj 15 | }() 16 | 17 | struct Socks { 18 | static let version: UInt8 = 5 19 | 20 | enum Atyp: UInt8 { 21 | case v4 = 1 22 | case domain = 3 23 | case v6 = 4 24 | } 25 | 26 | enum AuthType: UInt8 { 27 | case none = 0x00 28 | case gssapi = 0x01 29 | case password = 0x02 30 | case unsupported = 0xFF 31 | } 32 | 33 | enum RequestType: UInt8 { 34 | case connect = 1 35 | case bind = 2 36 | case udp = 3 37 | } 38 | 39 | enum ResponseType: UInt8 { 40 | case success = 0 41 | case connectFailed 42 | case ruleNotAllowed 43 | case badNetwork 44 | case unreachable 45 | case connectRejected 46 | case timeout 47 | case unsupported 48 | case unsupportedAddressType 49 | case undefined 50 | } 51 | } 52 | 53 | enum SocksError: Error { 54 | case readFailed 55 | case invalidRequest 56 | case invalidVersion 57 | } 58 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/Socks5Server.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Socks5Server.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/9. 6 | // 7 | 8 | import NIO 9 | import Logging 10 | 11 | public struct SocksConfiguration { 12 | public enum Auth { 13 | case none 14 | case pass(username: String, password: String) 15 | } 16 | 17 | var auth: Auth 18 | var port: Int 19 | 20 | public init(auth: Auth, port: Int) { 21 | self.auth = auth 22 | self.port = port 23 | } 24 | 25 | public static let `default` = SocksConfiguration(auth: .none, port: 1080) 26 | } 27 | 28 | public final class Socks5Server { 29 | private let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 30 | public private(set) var isRunning: Bool = false 31 | 32 | public init() { } 33 | 34 | public func start(config: SocksConfiguration = .default) { 35 | if isRunning { 36 | logger.warning("socks5 server has started") 37 | return 38 | } 39 | 40 | let bootstrap = ServerBootstrap(group: group) 41 | // Specify backlog and enable SO_REUSEADDR for the server itself 42 | .serverChannelOption(ChannelOptions.backlog, value: 256) 43 | .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) 44 | // Set the handlers that are appled to the accepted Channels 45 | .childChannelInitializer { channel in 46 | // Ensure we don't read faster than we can write by adding the BackPressureHandler into the pipeline. 47 | channel.pipeline.addHandler(BackPressureHandler()).flatMap { v in 48 | channel.pipeline.configSocks(config: config) 49 | } 50 | } 51 | // Enable SO_REUSEADDR for the accepted Channels 52 | .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) 53 | .childChannelOption(ChannelOptions.maxMessagesPerRead, value: 16) 54 | .childChannelOption(ChannelOptions.recvAllocator, value: AdaptiveRecvByteBufferAllocator()) 55 | do { 56 | let channel = try bootstrap.bind(host: "::0", port: config.port).wait() 57 | logger.debug("start socks server on port \(config.port) success") 58 | isRunning = true 59 | 60 | try channel.closeFuture.wait() 61 | } catch { 62 | logger.error("start socks server on port \(config.port) failed") 63 | } 64 | 65 | logger.debug("socks5 server has stopped") 66 | isRunning = false 67 | } 68 | 69 | public func stop() { 70 | try? group.syncShutdownGracefully() 71 | } 72 | } 73 | 74 | extension ChannelPipeline { 75 | func configSocks(config: SocksConfiguration) -> EventLoopFuture { 76 | let encoderHandler = MessageToByteHandler(SocksEncoder()) 77 | let decoder = SocksDecoder() 78 | let decoderHandler = ByteToMessageHandler(decoder) 79 | let handler = SocksHandler(config: config, decoder: decoder) 80 | return self.addHandlers(encoderHandler, decoderHandler, handler) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/SocksAddress.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocksAddress.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/25. 6 | // 7 | 8 | import NIO 9 | 10 | struct SocksAddress { 11 | let atyp: Socks.Atyp 12 | private let storage: [UInt8] 13 | 14 | var bytes: [UInt8] { 15 | return [atyp.rawValue] + storage 16 | } 17 | 18 | init?(from buffer: inout ByteBuffer) throws { 19 | guard let num = buffer.readInteger(as: UInt8.self) else { return nil } 20 | guard let type = Socks.Atyp(rawValue: num) else { throw SocksError.invalidRequest } 21 | 22 | self.atyp = type 23 | 24 | switch atyp { 25 | case .v4: 26 | guard let bytes = buffer.readBytes(length: 4) else { return nil } 27 | self.storage = bytes 28 | case .v6: 29 | guard let bytes = buffer.readBytes(length: 14) else { return nil } 30 | self.storage = bytes 31 | case .domain: 32 | guard let len = buffer.readInteger(as: UInt8.self) else { return nil } 33 | guard let bytes = buffer.readBytes(length: Int(len)) else { return nil } 34 | self.storage = bytes 35 | } 36 | } 37 | 38 | private init(atyp: Socks.Atyp, bytes: [UInt8]) { 39 | self.atyp = atyp 40 | self.storage = bytes 41 | } 42 | 43 | var host: String? { 44 | switch atyp { 45 | case .v4: 46 | let value = storage[0..<4].withUnsafeBytes { $0.load(as: UInt32.self) } 47 | let addr = in_addr(s_addr: value) 48 | 49 | var buffer = [Int8](repeating: 0, count: Int(INET_ADDRSTRLEN)) 50 | _ = withUnsafePointer(to: addr) { pointer in 51 | inet_ntop(AF_INET, pointer, &buffer, UInt32(INET_ADDRSTRLEN)) 52 | } 53 | 54 | return String(cString: buffer) 55 | case .v6: 56 | var addr = sockaddr_in6() 57 | addr.sin6_family = sa_family_t(AF_INET6) 58 | 59 | var buffer = [Int8](repeating: 0, count: Int(INET6_ADDRSTRLEN)) 60 | withUnsafeMutableBytes(of: &addr.sin6_addr) { $0.copyBytes(from: storage[..<16]) } 61 | inet_ntop(AF_INET6, &addr.sin6_addr, &buffer, UInt32(INET6_ADDRSTRLEN)) 62 | 63 | return String(cString: buffer) 64 | case .domain: 65 | return String(bytes: storage, encoding: .utf8) 66 | } 67 | } 68 | 69 | static func zero(for type: Socks.Atyp) -> Self { 70 | switch type { 71 | case .v4: 72 | return SocksAddress(atyp: .v4, bytes: Array(repeating: 0, count: 4)) 73 | case .v6: 74 | return SocksAddress(atyp: .v4, bytes: Array(repeating: 0, count: 16)) 75 | case .domain: 76 | fatalError("Shouldn't call this function") 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/SocksDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocksDecoder.swift 3 | // Socks5Server 4 | // 5 | // Created by Purkylin King on 2020/9/25. 6 | // 7 | 8 | import NIO 9 | 10 | class SocksDecoder: ByteToMessageDecoder { 11 | typealias InboundOut = SocksRequest 12 | 13 | enum State { 14 | case initial 15 | case auth 16 | case cmd 17 | case udp 18 | case waiting 19 | } 20 | 21 | var state: State = .waiting 22 | 23 | func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState { 24 | switch state { 25 | case .initial: 26 | var peekBuffer = buffer 27 | guard let request = try RequestInitial(from: &peekBuffer) else { return .needMoreData } 28 | let output = self.wrapInboundOut(SocksRequest.initial(req: request)) 29 | buffer = peekBuffer 30 | state = .waiting 31 | context.fireChannelRead(output) 32 | case .auth: 33 | var peekBuffer = buffer 34 | guard let request = try RequestAuth(from: &peekBuffer) else { return .needMoreData } 35 | let output = self.wrapInboundOut(SocksRequest.auth(req: request)) 36 | buffer = peekBuffer 37 | state = .waiting 38 | context.fireChannelRead(output) 39 | case .cmd: 40 | var peekBuffer = buffer 41 | guard let request = try RequestCommand(from: &peekBuffer) else { return .needMoreData } 42 | let output = self.wrapInboundOut(SocksRequest.command(req: request)) 43 | buffer = peekBuffer 44 | state = .waiting 45 | context.fireChannelRead(output) 46 | default: 47 | break 48 | } 49 | 50 | return .continue 51 | } 52 | 53 | func decodeLast(context: ChannelHandlerContext, buffer: inout ByteBuffer, seenEOF: Bool) throws -> DecodingState { 54 | // This method is needed, otherwise will infinite loop 55 | assert(state == .waiting) 56 | return .needMoreData 57 | } 58 | 59 | func decoderAdded(context: ChannelHandlerContext) { 60 | logger.debug("has added decoder") 61 | state = .initial 62 | } 63 | 64 | func decoderRemoved(context: ChannelHandlerContext) { 65 | logger.debug("has remove decoder") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/SocksEncoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocksEncoder.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/23. 6 | // 7 | 8 | import NIO 9 | 10 | class SocksEncoder: MessageToByteEncoder, RemovableChannelHandler { 11 | typealias OutboundIn = SocksResponse 12 | 13 | func encode(data: SocksResponse, out: inout ByteBuffer) throws { 14 | logger.debug("out: \(data.toBytes())") 15 | out.writeBytes(data.toBytes()) 16 | } 17 | } 18 | 19 | extension SocksResponse { 20 | func toBytes() -> [UInt8] { 21 | switch self { 22 | case .initial(let method): 23 | return [Socks.version, method.rawValue] 24 | case .auth(let success): 25 | return [0x01, success ? 0 : 1] 26 | case let .command(type, addr, port): 27 | return [Socks.version, type.rawValue, 0x0] + addr.bytes + port.bytes 28 | } 29 | } 30 | } 31 | 32 | extension MessageToByteHandler: RemovableChannelHandler { } 33 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/SocksHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocksHandler.swift 3 | // Socks5Server 4 | // 5 | // Created by Purkylin King on 2020/9/25. 6 | // 7 | 8 | import NIO 9 | 10 | class SocksHandler: ChannelInboundHandler, RemovableChannelHandler { 11 | typealias InboundIn = SocksRequest 12 | typealias OutboundOut = SocksResponse 13 | 14 | private unowned let decoder: SocksDecoder 15 | 16 | private let serverPort: Int 17 | private let auth: SocksConfiguration.Auth 18 | 19 | init(config: SocksConfiguration, decoder: SocksDecoder) { 20 | self.serverPort = config.port 21 | self.auth = config.auth 22 | self.decoder = decoder 23 | } 24 | 25 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 26 | let req = self.unwrapInboundIn(data) 27 | 28 | switch req { 29 | case .initial(let request): 30 | logger.debug("receive initial") 31 | let authMethod = getAuthMethod(supportMethods: request.methods) 32 | let response = SocksResponse.initial(method: authMethod) 33 | context.write(self.wrapOutboundOut(response), promise: nil) 34 | 35 | switch authMethod { 36 | case .none: 37 | decoder.state = .cmd 38 | context.flush() 39 | case .password: 40 | decoder.state = .auth 41 | context.flush() 42 | default: 43 | context.channel.close(mode: .output).cascade(to: nil) 44 | } 45 | case .auth(let request): 46 | logger.debug("receive auth") 47 | let success = checkAuth(username: request.username, password: request.password) 48 | let response = SocksResponse.auth(success: success) 49 | decoder.state = .cmd 50 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 51 | case .command(let request): 52 | logger.debug("receive command") 53 | switch request.cmd { 54 | case .connect: 55 | connectTo(host: request.addr.host!, port: request.port, context: context) 56 | case .bind: 57 | // Current bind command is not supported 58 | let response = SocksResponse.command(type: .unsupported, addr: SocksAddress.zero(for: .v4), port: 0) 59 | context.write(self.wrapOutboundOut(response), promise: nil) 60 | case .udp: 61 | beginUDP(context: context) 62 | } 63 | default: 64 | break 65 | } 66 | } 67 | 68 | func errorCaught(context: ChannelHandlerContext, error: Error) { 69 | logger.error(.init(stringLiteral: error.localizedDescription)) 70 | context.channel.close(mode: .all, promise: nil) 71 | } 72 | 73 | func connectTo(host: String, port: UInt16, context: ChannelHandlerContext) { 74 | let future = ClientBootstrap(group: context.eventLoop).connect(host: host, port: Int(port)) 75 | 76 | future.whenComplete { result in 77 | switch result { 78 | case .success(let channel): 79 | logger.debug("connect host success") 80 | let response = SocksResponse.command(type: .success, addr: .zero(for: .v4), port: 0) 81 | context.writeAndFlush(self.wrapOutboundOut(response)).whenComplete { [unowned self] result in 82 | context.pipeline.removeHandler(handlerType: MessageToByteHandler.self) 83 | context.pipeline.removeHandler(handlerType: ByteToMessageHandler.self) 84 | context.channel.relay(peerChannel: channel).and(context.pipeline.removeHandler(self)).cascade(to: nil) 85 | } 86 | case .failure(let error): 87 | logger.error("connect host failed, \(error.localizedDescription)") 88 | let response = SocksResponse.command(type: .unreachable, addr: .zero(for: .v4), port: 0) 89 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 90 | context.close(mode: .output, promise: nil) 91 | } 92 | } 93 | } 94 | 95 | func beginUDP(context: ChannelHandlerContext) { 96 | let bootstrap = DatagramBootstrap(group: context.eventLoop) 97 | .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) 98 | .channelOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 30 * 2048)) 99 | .channelInitializer { channel in 100 | channel.pipeline.addHandler(UDPHandler()) 101 | } 102 | 103 | let future = bootstrap.bind(host: "0.0.0.0", port: 0) 104 | future.whenComplete { result in 105 | switch result { 106 | case .success(let channel): 107 | guard let address = channel.localAddress, let port = address.port else { 108 | fatalError("bind udp failed") 109 | } 110 | 111 | logger.debug(.init(stringLiteral: "bind udp on: \(port)")) 112 | 113 | let response = SocksResponse.command(type: .success, addr: .zero(for: .v4), port: UInt16(port)) 114 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 115 | case .failure(let error): 116 | logger.error(.init(stringLiteral: error.localizedDescription)) 117 | let response = SocksResponse.command(type: .connectFailed, addr: .zero(for: .v4), port: 0) 118 | context.writeAndFlush(self.wrapOutboundOut(response), promise: nil) 119 | context.close(mode: .output, promise: nil) 120 | } 121 | } 122 | } 123 | 124 | func handlerRemoved(context: ChannelHandlerContext) { 125 | logger.debug("remove socks handler") 126 | } 127 | 128 | private func getAuthMethod(supportMethods: [Socks.AuthType]) -> Socks.AuthType { 129 | guard needAuth else { 130 | return .none 131 | } 132 | 133 | if supportMethods.contains(.password) { 134 | return .password 135 | } else { 136 | return .unsupported 137 | } 138 | } 139 | 140 | private func checkAuth(username: String, password: String) -> Bool { 141 | if case let .pass(rightUsername, rightPassword) = auth { 142 | if username == rightUsername && password == rightPassword { 143 | return true 144 | } 145 | } 146 | return false 147 | } 148 | 149 | private var needAuth: Bool { 150 | if case .pass = auth { 151 | return true 152 | } else { 153 | return false 154 | } 155 | } 156 | } 157 | 158 | extension ChannelPipeline { 159 | func removeHandler(handlerType: T.Type) { 160 | self.context(handlerType: handlerType).whenSuccess { 161 | self.removeHandler(context: $0).whenFailure { error in 162 | fatalError(error.localizedDescription) 163 | } 164 | } 165 | } 166 | } 167 | 168 | 169 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/SocksRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocksRequest.swift 3 | // Socks5Server 4 | // 5 | // Created by Purkylin King on 2020/9/25. 6 | // 7 | 8 | import NIO 9 | 10 | enum SocksRequest { 11 | case initial(req: RequestInitial) 12 | case command(req: RequestCommand) 13 | case auth(req: RequestAuth) 14 | case relay(bytes: [UInt8]) 15 | } 16 | 17 | /** 18 | +----+----------+----------+ 19 | |VER | NMETHODS | METHODS | 20 | +----+----------+----------+ 21 | | 1 | 1 | 1 to 255 | 22 | +----+----------+----------+ 23 | */ 24 | struct RequestInitial { 25 | let version: UInt8 26 | let methods: [Socks.AuthType] 27 | 28 | init?(from buffer: inout ByteBuffer) throws { 29 | guard let version = buffer.readInteger(as: UInt8.self) else { return nil } 30 | guard version == Socks.version else { throw SocksError.invalidVersion } 31 | 32 | guard let count = buffer.readInteger(as: UInt8.self), let methodBytes = buffer.readBytes(length: Int(count)) else { return nil } 33 | 34 | self.version = version 35 | self.methods = methodBytes.compactMap { Socks.AuthType.init(rawValue: $0) } 36 | } 37 | } 38 | 39 | /** 40 | +----+------+----------+------+----------+ 41 | |VER | ULEN | UNAME | PLEN | PASSWD | 42 | +----+------+----------+------+----------+ 43 | | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 44 | +----+------+----------+------+----------+ 45 | */ 46 | 47 | struct RequestAuth { 48 | let version: UInt8 49 | let username: String 50 | let password: String 51 | 52 | init?(from buffer: inout ByteBuffer) throws { 53 | guard let version = buffer.readInteger(as: UInt8.self) else { return nil } 54 | guard version == 0x1 else { throw SocksError.invalidRequest } 55 | self.version = version 56 | 57 | guard let username = buffer.readString() else { return nil } 58 | self.username = username 59 | 60 | guard let password = buffer.readString() else { return nil } 61 | self.password = password 62 | } 63 | } 64 | 65 | /** 66 | +----+-----+-------+------+----------+----------+ 67 | |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 68 | +----+-----+-------+------+----------+----------+ 69 | | 1 | 1 | X'00' | 1 | Variable | 2 | 70 | +----+-----+-------+------+----------+----------+ 71 | */ 72 | struct RequestCommand { 73 | let version: UInt8 74 | let cmd: Socks.RequestType 75 | let addr: SocksAddress 76 | let port: UInt16 77 | 78 | init?(from buffer: inout ByteBuffer) throws { 79 | guard let version = buffer.readInteger(as: UInt8.self) else { return nil } 80 | self.version = version 81 | 82 | guard let cmdValue = buffer.readInteger(as: UInt8.self) else { return nil } 83 | guard let cmd = Socks.RequestType(rawValue: cmdValue) else { 84 | throw SocksError.invalidRequest 85 | } 86 | self.cmd = cmd 87 | 88 | guard buffer.skipBytes(1) else { return nil } 89 | 90 | guard let addr = try SocksAddress(from: &buffer) else { return nil } 91 | self.addr = addr 92 | guard let port = buffer.readInteger(as: UInt16.self) else { return nil } 93 | self.port = port 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/SocksResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocksResponse.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2020/10/9. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SocksResponse { 11 | case initial(method: Socks.AuthType) 12 | case auth(success: Bool) 13 | case command(type: Socks.ResponseType, addr: SocksAddress, port: UInt16) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/UDPHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UDPHandler.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/25. 6 | // 7 | 8 | import NIO 9 | 10 | class UDPHandler: ChannelInboundHandler { 11 | public typealias InboundIn = AddressedEnvelope 12 | public typealias OutboundOut = AddressedEnvelope 13 | 14 | var source: SocketAddress? = nil 15 | 16 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) { 17 | let envelope = self.unwrapInboundIn(data) 18 | var buffer = envelope.data 19 | 20 | if source == nil { 21 | source = envelope.remoteAddress 22 | } 23 | 24 | let isLocal = source == envelope.remoteAddress 25 | 26 | if isLocal { 27 | guard let header = try? UDPHeader(from: &buffer) else { return } 28 | guard header.frag == 0 else { 29 | fatalError("not support fragment") 30 | } 31 | 32 | guard let host = header.addr.host, let remote = try? SocketAddress.makeAddressResolvingHost(host, port: Int(header.port)) else { 33 | fatalError("invalid address") 34 | } 35 | 36 | let outBuffer = context.channel.allocator.buffer(bytes: header.data) 37 | let outEnvolope = AddressedEnvelope(remoteAddress: remote, data: outBuffer, metadata: nil) 38 | context.writeAndFlush(self.wrapOutboundOut(outEnvolope), promise: nil) 39 | } else { 40 | guard let data = buffer.readBytes(length: buffer.readableBytes) else { return } 41 | let header = UDPHeader(addr: .zero(for: .v4), port: 0, data: data) 42 | let outbuffer = context.channel.allocator.buffer(bytes: header.bytes) 43 | // logger.debug(.init(stringLiteral: "out: \(header.bytes)")) 44 | let envolope = AddressedEnvelope(remoteAddress: source!, data: outbuffer, metadata: nil) 45 | context.writeAndFlush(self.wrapOutboundOut(envolope), promise: nil) 46 | } 47 | } 48 | 49 | public func channelReadComplete(context: ChannelHandlerContext) { 50 | // As we are not really interested getting notified on success or failure we just pass nil as promise to 51 | // reduce allocations. 52 | context.flush() 53 | } 54 | 55 | public func errorCaught(context: ChannelHandlerContext, error: Error) { 56 | // As we are not really interested getting notified on success or failure we just pass nil as promise to 57 | // reduce allocations. 58 | context.close(promise: nil) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Sources/proxy-nio/socks5/UDPHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UDPHeader.swift 3 | // 4 | // 5 | // Created by Purkylin King on 2021/1/25. 6 | // 7 | 8 | import NIO 9 | 10 | /* 11 | +----+------+------+----------+----------+----------+ 12 | |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | 13 | +----+------+------+----------+----------+----------+ 14 | | 2 | 1 | 1 | Variable | 2 | Variable | 15 | +----+------+------+----------+----------+----------+ 16 | */ 17 | struct UDPHeader { 18 | let frag: UInt8 19 | let addr: SocksAddress 20 | let port: UInt16 21 | let data: [UInt8] 22 | 23 | init?(from buffer: inout ByteBuffer) throws { 24 | guard buffer.skipBytes(2) else { return nil } 25 | guard let frag = buffer.readInteger(as: UInt8.self) else { return nil } 26 | guard let addr = try SocksAddress(from: &buffer) else { return nil } 27 | guard let port = buffer.readInteger(as: UInt16.self) else { return nil } 28 | 29 | self.frag = frag 30 | self.addr = addr 31 | self.port = port 32 | self.data = buffer.readAll() 33 | } 34 | 35 | init(addr: SocksAddress, port: UInt16, data: [UInt8] = []) { 36 | self.frag = 0 37 | self.addr = addr 38 | self.port = port 39 | self.data = data 40 | } 41 | 42 | var bytes: [UInt8] { 43 | return [0x0, 0x0, frag] + addr.bytes + port.bytes + data 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import proxy_nioTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += proxy_nioTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/proxy-nioTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(proxy_nioTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/proxy-nioTests/proxy_nioTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import proxy_nio 3 | 4 | final class proxy_nioTests: XCTestCase { 5 | func testPort() { 6 | let port: UInt16 = 1080 7 | XCTAssertEqual(port.bytes, [0x04, 0x38]) 8 | } 9 | 10 | // func testIP1() { 11 | // let v4 = SocksV4Address(host: "192.168.1.12", port: 80)! 12 | // XCTAssertEqual(v4.bytes, [0xc0, 0xa8, 0x01, 0x0c, 0x00, 0x50]) 13 | // 14 | // let v6 = SocksV6Address(host: "::ffff:c0a8:0102", port: 80)! 15 | // 16 | // let v6Bytes: [UInt8] = [ 17 | // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | // 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x01, 0x02, 19 | // 0x00, 0x50 20 | // ] 21 | // XCTAssertEqual(v6.bytes, v6Bytes) 22 | // } 23 | // 24 | // func testIP2() { 25 | // let v4 = SocksV4Address(bytes: [0xc0, 0xa8, 0x01, 0x0c, 0x00, 0x50])! 26 | // XCTAssertEqual(v4.description, "192.168.1.12:80") 27 | // 28 | // let v6Bytes: [UInt8] = [ 29 | // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 30 | // 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x01, 0x02, 31 | // 0x00, 0x50 32 | // ] 33 | // 34 | // let v6 = SocksV6Address(bytes: v6Bytes)! 35 | // XCTAssertEqual(v6.description, "::ffff:192.168.1.2:80") 36 | // } 37 | 38 | static var allTests = [ 39 | ("testExample", testPort), 40 | ] 41 | } 42 | --------------------------------------------------------------------------------