├── .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 |
--------------------------------------------------------------------------------