├── .gitattributes ├── Makefile ├── dist.ini ├── .gitignore ├── LICENSE ├── t └── 00-socks5.t ├── README.md ├── valgrind.suppress └── lib └── resty └── socks5 └── server.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | test: 3 | @WORKDIR=$(shell pwd) /usr/bin/prove 4 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-socks5-server 2 | abstract = socks5 server on OpenResty 3 | version = 0.1.2 4 | author = Bingwu Yang (detailyang) 5 | license = mit 6 | requires = 7 | repo_link = https://github.com/detailyang/lua-resty-socks5-server 8 | is_original = yes 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | t/servroot 43 | resty_modules 44 | lua-resty-socks5-server-* 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 detailyang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /t/00-socks5.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket::Lua::Stream; 2 | 3 | my $workdir = $ENV{WORKDIR}; 4 | 5 | #worker_connections(1014); 6 | #master_on(); 7 | #workers(2); 8 | #log_level('warn'); 9 | 10 | #no_diff(); 11 | #no_long_string(); 12 | repeat_each(2); 13 | plan tests => repeat_each() * 2 * blocks(); 14 | 15 | no_shuffle(); 16 | run_tests(); 17 | 18 | our $stream_config = <<"_EOS_"; 19 | lua_resolver 114.114.114.114; 20 | lua_code_cache off; 21 | lua_package_path '$workdir/?.lua;;'; 22 | _EOS_ 23 | 24 | __DATA__ 25 | 26 | === TEST 1: noauth method 27 | --- stream_config eval: $::http_config 28 | --- stream_server_config 29 | content_by_lua_block { 30 | local socks5_server = require "lib.resty.socks5.server" 31 | 32 | socks5_server.run(2000) 33 | } 34 | 35 | --- stream_request eval 36 | "\x05\x01\x00" 37 | 38 | --- stream_response eval 39 | "\x05\x00" 40 | 41 | === TEST 2: auth method 42 | --- stream_config eval: $::http_config 43 | --- stream_server_config 44 | content_by_lua_block { 45 | local socks5_server = require "lib.resty.socks5.server" 46 | 47 | socks5_server.run(2000, 'user', 'password') 48 | } 49 | 50 | --- stream_request eval 51 | "\x05\x02\x00\x02" 52 | 53 | --- stream_response eval 54 | "\x05\x02" 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | # lua-resty-socks5-server 5 | This is an implementation of the SOCKS v5 [RFC1928](https://www.ietf.org/rfc/rfc1928.txt) server in the OpenResty and It's based on the stream-lua-ningx-module under the hood. 6 | 7 | Table of Contents 8 | ----------------- 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Usage](#usage) 12 | * [API](#api) 13 | * [Contributing](#contributing) 14 | * [Author](#author) 15 | * [License](#license) 16 | 17 | Status 18 | ==== 19 | Experimental (works well on personal server) 20 | 21 | Usage 22 | ==== 23 | Make sure your stream_lua_nginx's cosocket support the API `tcpsock:receive('*b')`, we are rely on it to implementation full duplex between upstream and downstream. 24 | 25 | ```bash 26 | server { 27 | listen 1234; 28 | 29 | content_by_lua_block { 30 | local socks5_server = require "lib.resty.socks5.server" 31 | 32 | socks5_server.run(3000) 33 | -- or if you want to enable authentication 34 | -- socks5_server.run(3000, "username", "password") 35 | } 36 | } 37 | ``` 38 | 39 | API 40 | ==== 41 | 42 | run 43 | --- 44 | `syntax: module.run(timeout[,username, password])` 45 | 46 | run a socks5 server. 47 | 48 | * `timeout` 49 | The socket timeout (default 1000 ms) include connect、read、write between upstream and downstream. 50 | 51 | * `username` 52 | The socks5 authentication username. 53 | 54 | * `password` 55 | The socks5 authentication username. 56 | 57 | 58 | Contributing 59 | ------------ 60 | 61 | To contribute to lua-resty-socks5-server, clone this repo locally and commit your code on a separate branch. 62 | 63 | PS: PR Welcome :rocket: :rocket: :rocket: :rocket: 64 | 65 | 66 | Author 67 | ------ 68 | 69 | > GitHub [@detailyang](https://github.com/detailyang) 70 | 71 | 72 | License 73 | ------- 74 | lua-resty-socks5-server is licensed under the [MIT] license. 75 | 76 | [MIT]: https://github.com/detailyang/ybw/blob/master/licenses/MIT 77 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Error: Parse error on line 1: 4 | { 8 | Memcheck:Param 9 | write(buf) 10 | fun:__write_nocancel 11 | fun:ngx_log_error_core 12 | fun:ngx_resolver_read_response 13 | } 14 | { 15 | 16 | Memcheck:Cond 17 | fun:ngx_sprintf_num 18 | fun:ngx_vslprintf 19 | fun:ngx_log_error_core 20 | fun:ngx_resolver_read_response 21 | fun:ngx_epoll_process_events 22 | fun:ngx_process_events_and_timers 23 | fun:ngx_single_process_cycle 24 | fun:main 25 | } 26 | { 27 | 28 | Memcheck:Addr1 29 | fun:ngx_vslprintf 30 | fun:ngx_snprintf 31 | fun:ngx_sock_ntop 32 | fun:ngx_event_accept 33 | } 34 | { 35 | 36 | Memcheck:Param 37 | write(buf) 38 | fun:__write_nocancel 39 | fun:ngx_log_error_core 40 | fun:ngx_resolver_read_response 41 | fun:ngx_event_process_posted 42 | fun:ngx_process_events_and_timers 43 | fun:ngx_single_process_cycle 44 | fun:main 45 | } 46 | { 47 | 48 | Memcheck:Cond 49 | fun:ngx_sprintf_num 50 | fun:ngx_vslprintf 51 | fun:ngx_log_error_core 52 | fun:ngx_resolver_read_response 53 | fun:ngx_event_process_posted 54 | fun:ngx_process_events_and_timers 55 | fun:ngx_single_process_cycle 56 | fun:main 57 | } 58 | { 59 | 60 | Memcheck:Leak 61 | fun:malloc 62 | fun:ngx_alloc 63 | obj:* 64 | } 65 | { 66 | 67 | exp-sgcheck:SorG 68 | fun:ngx_http_lua_ndk_set_var_get 69 | } 70 | { 71 | 72 | exp-sgcheck:SorG 73 | fun:ngx_http_variables_init_vars 74 | fun:ngx_http_block 75 | } 76 | { 77 | 78 | exp-sgcheck:SorG 79 | fun:ngx_conf_parse 80 | } 81 | { 82 | 83 | exp-sgcheck:SorG 84 | fun:ngx_vslprintf 85 | fun:ngx_log_error_core 86 | } 87 | { 88 | 89 | Memcheck:Leak 90 | fun:malloc 91 | fun:ngx_alloc 92 | fun:ngx_calloc 93 | fun:ngx_event_process_init 94 | } 95 | { 96 | 97 | Memcheck:Leak 98 | fun:malloc 99 | fun:ngx_alloc 100 | fun:ngx_malloc 101 | fun:ngx_pcalloc 102 | } 103 | { 104 | 105 | Memcheck:Leak 106 | fun:malloc 107 | fun:ngx_alloc 108 | fun:(below main) 109 | } 110 | { 111 | 112 | Memcheck:Param 113 | epoll_ctl(event) 114 | fun:epoll_ctl 115 | } 116 | { 117 | 118 | Memcheck:Leak 119 | fun:malloc 120 | fun:ngx_alloc 121 | fun:ngx_event_process_init 122 | } 123 | { 124 | 125 | Memcheck:Cond 126 | fun:ngx_conf_flush_files 127 | fun:ngx_single_process_cycle 128 | } 129 | { 130 | 131 | Memcheck:Cond 132 | fun:memcpy 133 | fun:ngx_vslprintf 134 | fun:ngx_log_error_core 135 | fun:ngx_http_charset_header_filter 136 | } 137 | { 138 | 139 | Memcheck:Leak 140 | fun:memalign 141 | fun:posix_memalign 142 | fun:ngx_memalign 143 | fun:ngx_pcalloc 144 | } 145 | { 146 | 147 | Memcheck:Param 148 | socketcall.setsockopt(optval) 149 | fun:setsockopt 150 | fun:drizzle_state_connect 151 | } 152 | { 153 | 154 | Memcheck:Leak 155 | fun:malloc 156 | fun:ngx_alloc 157 | fun:ngx_palloc_large 158 | } 159 | { 160 | 161 | Memcheck:Leak 162 | fun:malloc 163 | fun:ngx_alloc 164 | fun:ngx_pool_cleanup_add 165 | } 166 | { 167 | 168 | Memcheck:Leak 169 | fun:malloc 170 | fun:ngx_alloc 171 | fun:ngx_pnalloc 172 | } 173 | { 174 | 175 | Memcheck:Cond 176 | fun:ngx_conf_flush_files 177 | fun:ngx_single_process_cycle 178 | fun:main 179 | } 180 | 181 | { 182 | 183 | Memcheck:Leak 184 | fun:malloc 185 | fun:ngx_alloc 186 | fun:ngx_palloc 187 | } 188 | { 189 | 190 | Memcheck:Leak 191 | fun:malloc 192 | fun:ngx_alloc 193 | fun:ngx_pcalloc 194 | } 195 | { 196 | 197 | Memcheck:Leak 198 | fun:malloc 199 | fun:ngx_alloc 200 | fun:ngx_malloc 201 | fun:ngx_palloc_large 202 | } 203 | { 204 | 205 | Memcheck:Leak 206 | fun:malloc 207 | fun:ngx_alloc 208 | fun:ngx_create_pool 209 | } 210 | { 211 | 212 | Memcheck:Leak 213 | fun:malloc 214 | fun:ngx_alloc 215 | fun:ngx_malloc 216 | fun:ngx_palloc 217 | } 218 | { 219 | 220 | Memcheck:Leak 221 | fun:malloc 222 | fun:ngx_alloc 223 | fun:ngx_malloc 224 | fun:ngx_pnalloc 225 | } 226 | 227 | { 228 | 229 | Memcheck:Leak 230 | fun:malloc 231 | fun:ngx_alloc 232 | fun:ngx_palloc_large 233 | fun:ngx_palloc 234 | fun:ngx_array_push 235 | fun:ngx_http_get_variable_index 236 | fun:ngx_http_memc_add_variable 237 | fun:ngx_http_memc_init 238 | fun:ngx_http_block 239 | fun:ngx_conf_parse 240 | fun:ngx_init_cycle 241 | fun:main 242 | } 243 | 244 | { 245 | 246 | Memcheck:Leak 247 | fun:malloc 248 | fun:ngx_alloc 249 | fun:ngx_event_process_init 250 | fun:ngx_single_process_cycle 251 | fun:main 252 | } 253 | { 254 | 255 | Memcheck:Leak 256 | fun:malloc 257 | fun:ngx_alloc 258 | fun:ngx_crc32_table_init 259 | fun:main 260 | } 261 | { 262 | 263 | Memcheck:Leak 264 | fun:malloc 265 | fun:ngx_alloc 266 | fun:ngx_event_process_init 267 | fun:ngx_worker_process_init 268 | fun:ngx_worker_process_cycle 269 | fun:ngx_spawn_process 270 | fun:ngx_start_worker_processes 271 | fun:ngx_master_process_cycle 272 | fun:main 273 | } 274 | { 275 | 276 | Memcheck:Leak 277 | fun:malloc 278 | fun:ngx_alloc 279 | fun:ngx_palloc_large 280 | fun:ngx_palloc 281 | fun:ngx_pcalloc 282 | fun:ngx_hash_init 283 | fun:ngx_http_variables_init_vars 284 | fun:ngx_http_block 285 | fun:ngx_conf_parse 286 | fun:ngx_init_cycle 287 | fun:main 288 | } 289 | { 290 | 291 | Memcheck:Leak 292 | fun:malloc 293 | fun:ngx_alloc 294 | fun:ngx_palloc_large 295 | fun:ngx_palloc 296 | fun:ngx_pcalloc 297 | fun:ngx_http_upstream_drizzle_create_srv_conf 298 | fun:ngx_http_upstream 299 | fun:ngx_conf_parse 300 | fun:ngx_http_block 301 | fun:ngx_conf_parse 302 | fun:ngx_init_cycle 303 | fun:main 304 | } 305 | { 306 | 307 | Memcheck:Leak 308 | fun:malloc 309 | fun:ngx_alloc 310 | fun:ngx_palloc_large 311 | fun:ngx_palloc 312 | fun:ngx_pcalloc 313 | fun:ngx_hash_keys_array_init 314 | fun:ngx_http_variables_add_core_vars 315 | fun:ngx_http_core_preconfiguration 316 | fun:ngx_http_block 317 | fun:ngx_conf_parse 318 | fun:ngx_init_cycle 319 | fun:main 320 | } 321 | { 322 | 323 | Memcheck:Leak 324 | fun:malloc 325 | fun:ngx_alloc 326 | fun:ngx_palloc_large 327 | fun:ngx_palloc 328 | fun:ngx_array_push 329 | fun:ngx_hash_add_key 330 | fun:ngx_http_add_variable 331 | fun:ngx_http_echo_add_variables 332 | fun:ngx_http_echo_handler_init 333 | fun:ngx_http_block 334 | fun:ngx_conf_parse 335 | fun:ngx_init_cycle 336 | } 337 | { 338 | 339 | Memcheck:Leak 340 | fun:malloc 341 | fun:ngx_alloc 342 | fun:ngx_palloc_large 343 | fun:ngx_palloc 344 | fun:ngx_pcalloc 345 | fun:ngx_http_upstream_drizzle_create_srv_conf 346 | fun:ngx_http_core_server 347 | fun:ngx_conf_parse 348 | fun:ngx_http_block 349 | fun:ngx_conf_parse 350 | fun:ngx_init_cycle 351 | fun:main 352 | } 353 | { 354 | 355 | Memcheck:Leak 356 | fun:malloc 357 | fun:ngx_alloc 358 | fun:ngx_palloc_large 359 | fun:ngx_palloc 360 | fun:ngx_pcalloc 361 | fun:ngx_http_upstream_drizzle_create_srv_conf 362 | fun:ngx_http_block 363 | fun:ngx_conf_parse 364 | fun:ngx_init_cycle 365 | fun:main 366 | } 367 | { 368 | 369 | Memcheck:Leak 370 | fun:malloc 371 | fun:ngx_alloc 372 | fun:ngx_palloc_large 373 | fun:ngx_palloc 374 | fun:ngx_array_push 375 | fun:ngx_hash_add_key 376 | fun:ngx_http_variables_add_core_vars 377 | fun:ngx_http_core_preconfiguration 378 | fun:ngx_http_block 379 | fun:ngx_conf_parse 380 | fun:ngx_init_cycle 381 | fun:main 382 | } 383 | { 384 | 385 | Memcheck:Leak 386 | fun:malloc 387 | fun:ngx_alloc 388 | fun:ngx_palloc_large 389 | fun:ngx_palloc 390 | fun:ngx_pcalloc 391 | fun:ngx_init_cycle 392 | fun:main 393 | } 394 | { 395 | 396 | Memcheck:Leak 397 | fun:malloc 398 | fun:ngx_alloc 399 | fun:ngx_palloc_large 400 | fun:ngx_palloc 401 | fun:ngx_hash_init 402 | fun:ngx_http_upstream_init_main_conf 403 | fun:ngx_http_block 404 | fun:ngx_conf_parse 405 | fun:ngx_init_cycle 406 | fun:main 407 | } 408 | { 409 | 410 | Memcheck:Leak 411 | fun:malloc 412 | fun:ngx_alloc 413 | fun:ngx_palloc_large 414 | fun:ngx_palloc 415 | fun:ngx_pcalloc 416 | fun:ngx_http_drizzle_keepalive_init 417 | fun:ngx_http_upstream_drizzle_init 418 | fun:ngx_http_upstream_init_main_conf 419 | fun:ngx_http_block 420 | fun:ngx_conf_parse 421 | fun:ngx_init_cycle 422 | fun:main 423 | } 424 | { 425 | 426 | Memcheck:Leak 427 | fun:malloc 428 | fun:ngx_alloc 429 | fun:ngx_palloc_large 430 | fun:ngx_palloc 431 | fun:ngx_hash_init 432 | fun:ngx_http_variables_init_vars 433 | fun:ngx_http_block 434 | fun:ngx_conf_parse 435 | fun:ngx_init_cycle 436 | fun:main 437 | } 438 | { 439 | 440 | Memcheck:Leak 441 | fun:memalign 442 | fun:posix_memalign 443 | fun:ngx_memalign 444 | fun:ngx_create_pool 445 | } 446 | { 447 | 448 | Memcheck:Leak 449 | fun:memalign 450 | fun:posix_memalign 451 | fun:ngx_memalign 452 | fun:ngx_palloc_block 453 | fun:ngx_palloc 454 | } 455 | { 456 | 457 | Memcheck:Cond 458 | fun:index 459 | fun:expand_dynamic_string_token 460 | fun:_dl_map_object 461 | fun:map_doit 462 | fun:_dl_catch_error 463 | fun:do_preload 464 | fun:dl_main 465 | fun:_dl_sysdep_start 466 | fun:_dl_start 467 | } 468 | -------------------------------------------------------------------------------- /lib/resty/socks5/server.lua: -------------------------------------------------------------------------------- 1 | local _M = { _VERSION = '0.1.2' } 2 | 3 | local bit = require "bit" 4 | local byte = string.byte 5 | local char = string.char 6 | local sub = string.sub 7 | local ngx_log = ngx.log 8 | local ngx_exit = ngx.exit 9 | 10 | local DEBUG = ngx.DEBUG 11 | local ERR = ngx.ERR 12 | local ERROR = ngx.ERROR 13 | local OK = ngx.OK 14 | 15 | local SUB_AUTH_VERSION = 0x01 16 | local RSV = 0x00 17 | local NOAUTH = 0x00 18 | local GSSAPI = 0x01 19 | local AUTH = 0x02 20 | local IANA = 0x03 21 | local RESERVED = 0x80 22 | local NOMETHODS = 0xFF 23 | local VERSION = 0x05 24 | local IPV4 = 0x01 25 | local DOMAIN_NAME = 0x03 26 | local IPV6 = 0x04 27 | local CONNECT = 0x01 28 | local BIND = 0x02 29 | local UDP = 0x03 30 | local SUCCEEDED = 0x00 31 | local FAILURE = 0x01 32 | local RULESET = 0x02 33 | local NETWORK_UNREACHABLE = 0x03 34 | local HOST_UNREACHABLE = 0x04 35 | local CONNECTION_REFUSED = 0x05 36 | local TTL_EXPIRED = 0x06 37 | local COMMAND_NOT_SUPORTED = 0x07 38 | local ADDRESS_TYPE_NOT_SUPPORTED = 0x08 39 | local UNASSIGNED = 0x09 40 | local support_methods = { 41 | [NOAUTH] = true, 42 | [AUTH] = true 43 | } 44 | 45 | local function send_method(sock, method) 46 | -- 47 | --+----+--------+ 48 | --|VER | METHOD | 49 | --+----+--------+ 50 | --| 1 | 1 | 51 | --+----+--------+ 52 | -- 53 | 54 | local data = char(VERSION, method) 55 | 56 | return sock:send(data) 57 | end 58 | 59 | local function receive_methods(sock) 60 | -- 61 | -- +----+----------+----------+ 62 | -- |VER | NMETHODS | METHODS | 63 | -- +----+----------+----------+ 64 | -- | 1 | 1 | 1 to 255 | 65 | -- +----+----------+----------+ 66 | -- 67 | 68 | local data, err = sock:receive(2) 69 | if not data then 70 | ngx_exit(ERROR) 71 | 72 | return nil, err 73 | end 74 | 75 | local ver = byte(data, 1) 76 | local nmethods = byte(data, 2) 77 | 78 | local methods, err = sock:receive(nmethods) 79 | if not methods then 80 | ngx_exit(ERROR) 81 | 82 | return nil, err 83 | end 84 | 85 | return { 86 | ver= ver, 87 | nmethods = nmethods, 88 | methods = methods 89 | }, nil 90 | end 91 | 92 | local function send_replies(sock, rep, atyp, addr, port) 93 | -- 94 | --+----+-----+-------+------+----------+----------+ 95 | --|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 96 | --+----+-----+-------+------+----------+----------+ 97 | --| 1 | 1 | X'00' | 1 | Variable | 2 | 98 | --+----+-----+-------+------+----------+----------+ 99 | -- 100 | 101 | local data = {} 102 | data[1] = char(VERSION) 103 | data[2] = char(rep) 104 | data[3] = char(RSV) 105 | 106 | if atyp then 107 | data[4] = atyp 108 | data[5] = addr 109 | data[6] = port 110 | else 111 | data[4] = char(IPV4) 112 | data[5] = "\x00\x00\x00\x00" 113 | data[6] = "\x00\x00" 114 | end 115 | 116 | 117 | return sock:send(data) 118 | end 119 | 120 | local function receive_requests(sock) 121 | -- 122 | -- +----+-----+-------+------+----------+----------+ 123 | -- |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 124 | -- +----+-----+-------+------+----------+----------+ 125 | -- | 1 | 1 | X'00' | 1 | Variable | 2 | 126 | -- +----+-----+-------+------+----------+----------+ 127 | -- 128 | 129 | local data, err = sock:receive(4) 130 | if not data then 131 | ngx_log(ERR, "failed to receive requests: ", err) 132 | 133 | return nil, err 134 | end 135 | 136 | local ver = byte(data, 1) 137 | local cmd = byte(data, 2) 138 | local rsv = byte(data, 3) 139 | local atyp = byte(data, 4) 140 | 141 | local dst_len = 0 142 | if atyp == IPV4 then 143 | dst_len = 4 144 | elseif atyp == DOMAIN_NAME then 145 | local data, err = sock:receive(1) 146 | if not data then 147 | ngx_log(ERR, "failed to receive domain name len: ", err) 148 | 149 | return nil, err 150 | end 151 | dst_len = byte(data, 1) 152 | elseif atyp == IPV6 then 153 | dst_len = 16 154 | else 155 | return nil, "unknow atyp " .. atyp 156 | end 157 | 158 | local data, err = sock:receive(dst_len + 2) -- port 159 | if err then 160 | ngx_log(ERR, "failed to receive DST.ADDR: ", err) 161 | 162 | return nil, err 163 | end 164 | 165 | local dst = sub(data, 1, dst_len) 166 | local port_2 = byte(data, dst_len + 1) 167 | local port_1 = byte(data, dst_len + 2) 168 | local port = port_1 + port_2 * 256 169 | 170 | return { 171 | ver = ver, 172 | cmd = cmd, 173 | rsv = rsv, 174 | atyp = atyp, 175 | addr = dst, 176 | port = port, 177 | }, nil 178 | end 179 | 180 | local function receive_auth(sock) 181 | -- 182 | --+----+------+----------+------+----------+ 183 | --|VER | ULEN | UNAME | PLEN | PASSWD | 184 | --+----+------+----------+------+----------+ 185 | --| 1 | 1 | 1 to 255 | 1 | 1 to 255 | 186 | --+----+------+----------+------+----------+ 187 | -- 188 | 189 | local data, err = sock:receive(2) 190 | if err then 191 | return nil, err 192 | end 193 | 194 | local ver = byte(data, 1) 195 | local ulen = byte(data, 2) 196 | 197 | local data, err = sock:receive(ulen) 198 | if err then 199 | return nil, err 200 | end 201 | 202 | local uname = data 203 | 204 | local data, err = sock:receive(1) 205 | if err then 206 | return nil, err 207 | end 208 | 209 | local plen = byte(data, 1) 210 | 211 | local data, err = sock:receive(plen) 212 | if err then 213 | return nil, err 214 | end 215 | 216 | local passwd = data 217 | 218 | return { 219 | username = uname, 220 | password = passwd 221 | }, nil 222 | end 223 | 224 | local function send_auth_status(sock, status) 225 | -- 226 | --+----+--------+ 227 | --|VER | STATUS | 228 | --+----+--------+ 229 | --| 1 | 1 | 230 | --+----+--------+ 231 | -- 232 | 233 | local data = {} 234 | 235 | data[1] = char(SUB_AUTH_VERSION) 236 | data[2] = char(status) 237 | 238 | return sock:send(data) 239 | end 240 | 241 | local function stringify_addr(atyp, addr) 242 | if atyp == IPV4 then 243 | dst = string.format("%d.%d.%d.%d", 244 | byte(data, 1), 245 | byte(data, 2), 246 | byte(data, 3), 247 | byte(data, 4) 248 | ) 249 | elseif atyp == IPV6 then 250 | dst = string.format("[%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X]", 251 | byte(dst, 1), byte(dst, 2), 252 | byte(dst, 3), byte(dst, 4), 253 | byte(dst, 5), byte(dst, 6), 254 | byte(dst, 7), byte(dst, 8), 255 | byte(dst, 9), byte(dst, 10), 256 | byte(dst, 11), byte(dst, 12), 257 | byte(dst, 13), byte(dst, 14), 258 | byte(dst, 15), byte(dst, 16) 259 | ) 260 | else 261 | return addr 262 | end 263 | end 264 | 265 | 266 | function _M.run(timeout, username, password) 267 | local downsock, err = assert(ngx.req.socket(true)) 268 | if not downsock then 269 | ngx_log(ERR, "failed to get the request socket: ", err) 270 | return ngx.exit(ERROR) 271 | end 272 | 273 | timeout = timeout or 1000 274 | downsock:settimeout(timeout) 275 | 276 | local negotiation, err = receive_methods(downsock) 277 | if err then 278 | ngx_log(ERR, "receive methods error: ", err) 279 | ngx_exit(ERROR) 280 | 281 | return 282 | end 283 | 284 | if negotiation.ver ~= VERSION then 285 | ngx_log(DEBUG, "only support version: ", VERSION) 286 | return ngx_exit(OK) 287 | end 288 | 289 | -- ignore client supported methods, we only support AUTH and NOAUTH 290 | -- for #i = 1, negotiation.methods + 1 then 291 | -- local method = byte(negotiation.methods, i) 292 | -- end 293 | 294 | local method = NOAUTH 295 | if username then 296 | method = AUTH 297 | end 298 | 299 | local ok, err = send_method(downsock, method) 300 | if err then 301 | ngx_log(ERR, "send method error: ", err) 302 | ngx_exit(ERROR) 303 | 304 | return 305 | end 306 | 307 | if username then 308 | local auth, err = receive_auth(downsock) 309 | if err then 310 | ngx_log(ERR, "send method error: ", err) 311 | ngx_exit(ERROR) 312 | 313 | return 314 | end 315 | 316 | local status = FAILURE 317 | if auth.username == username and auth.password == password then 318 | status = SUCCEEDED 319 | end 320 | 321 | local ok, err = send_auth_status(downsock, status) 322 | if err then 323 | ngx_log(ERR, "send auth status error: ", err) 324 | ngx_exit(ERROR) 325 | 326 | return 327 | end 328 | 329 | if status == FAILURE then 330 | return 331 | end 332 | end 333 | 334 | local requests, err = receive_requests(downsock) 335 | if err then 336 | ngx_log(ERR, "send request error: ", err) 337 | ngx_exit(ERROR) 338 | 339 | return 340 | end 341 | 342 | if requests.cmd ~= CONNECT then 343 | local ok, err = send_replies(downsock, COMMAND_NOT_SUPORTED) 344 | if err then 345 | ngx_log(ERR, "send replies error: ", err) 346 | ngx_exit(ERROR) 347 | 348 | end 349 | 350 | return 351 | end 352 | 353 | local upsock = ngx.socket.tcp() 354 | upsock:settimeout(timeout) 355 | 356 | local addr = stringify_addr(requests.atyp, requests.addr) 357 | local ok, err = upsock:connect(addr, requests.port) 358 | if err then 359 | ngx_log(ERR, "connect request " .. requests.addr .. 360 | ":" .. requests.port .. " error: ", err) 361 | ngx_exit(ERROR) 362 | 363 | return 364 | end 365 | 366 | local ok, err = send_replies(downsock, SUCCEEDED) 367 | if err then 368 | ngx_log(ERR, "send replies error: ", err) 369 | ngx_exit(ERROR) 370 | 371 | return 372 | end 373 | 374 | local pipe = function(src, dst) 375 | while true do 376 | local data, err, partial = src:receive('*b') 377 | if not data then 378 | if partial then 379 | dst:send(partial) 380 | end 381 | 382 | if err ~= 'closed' then 383 | ngx_log(ERR, "pipe receive the src get error: ", err) 384 | end 385 | 386 | break 387 | end 388 | 389 | local ok, err = dst:send(data) 390 | if err then 391 | ngx_log(ERR, "pipe send the dst get error: ", err) 392 | 393 | return 394 | end 395 | end 396 | end 397 | 398 | local co_updown = ngx.thread.spawn(pipe, upsock, downsock) 399 | local co_downup = ngx.thread.spawn(pipe, downsock, upsock) 400 | 401 | ngx.thread.wait(co_updown) 402 | ngx.thread.wait(co_downup) 403 | end 404 | 405 | return _M 406 | --------------------------------------------------------------------------------