From d4a93b1be8f225f17148a561466b9920df519c62 Mon Sep 17 00:00:00 2001 From: tokers Date: Sun, 9 Dec 2018 13:46:34 +0800 Subject: [PATCH] feature: socks5 proxy --- lib/resty/requests/adapter.lua | 14 ++- lib/resty/requests/proxies.lua | 178 +++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 lib/resty/requests/proxies.lua diff --git a/lib/resty/requests/adapter.lua b/lib/resty/requests/adapter.lua index e901e53..e2734d5 100644 --- a/lib/resty/requests/adapter.lua +++ b/lib/resty/requests/adapter.lua @@ -2,6 +2,7 @@ local util = require "resty.requests.util" local response = require "resty.requests.response" +local proxies = require "resty.requests.proxies" local check_http2, http2 = pcall(require, "resty.http2") local pairs = pairs @@ -19,7 +20,7 @@ local new_tab = util.new_tab local is_tab = util.is_tab local is_func = util.is_func -local _M = { _VERSION = "0.4" } +local _M = { _VERSION = "0.5" } local mt = { __index = _M } local DEFAULT_POOL_SIZE = 30 @@ -146,6 +147,10 @@ end local function proxy(self, request) + if self.socks5_proxy then + return proxies.socks5(self.sock, request) + end + if not self.https_proxy then return true end @@ -155,7 +160,7 @@ local function proxy(self, request) local message = new_tab(4, 0) message[1] = format("CONNECT %s HTTP/1.1\r\n", host) - message[2] = format("Host: %s\r\n", host) + message[2] = format("Host: %s:%d\r\n", request.host, request.port) message[3] = format("User-Agent: resty-requests\r\n") message[4] = format("Proxy-Connection: keep-alive\r\n\r\n") @@ -242,7 +247,9 @@ local function connect(self, request) host = proxies[scheme].host port = proxies[scheme].port if scheme == "https" then - self.https_proxy = format("%s:%d", request.host, request.port) + self.https_proxy = true + elseif scheme == "socks5" then + self.socks5_proxy = true end else host = request.host @@ -491,6 +498,7 @@ local function new(opts) h2_stream = nil, https_proxy = nil, -- https proxy + socks5_proxy = nil, -- socks5 proxy error_filter = opts.error_filter, } diff --git a/lib/resty/requests/proxies.lua b/lib/resty/requests/proxies.lua new file mode 100644 index 0000000..3bfb48f --- /dev/null +++ b/lib/resty/requests/proxies.lua @@ -0,0 +1,178 @@ +-- Copyright (C) Alex Zhang + +local bit = require "bit" +local ffi = require "ffi" + +local byte = string.byte +local char = string.char +local band = bit.band +local lshift = bit.lshift +local rshift = bit.rshift +local C = ffi.C + +local SOCKS5_REP = { + [0] = "succeeded", + [1] = "general socks server failure", + [2] = "connection not allowed by ruleset", + [3] = "network unreachable", + [4] = "host unreachable", + [5] = "connection refused", + [6] = "ttl expired", + [7] = "command not supported", + [8] = "address type not supported", + [9] = "to X'FF' unassigned", +} + +local DOT_BYTE = byte('.') +local COLON_BYTE = byte(':') +local INADDR_NONE = -1 + +local _M = { _VERSION = "0.1" } + + +ffi.cdef[[ +uint32_t htonl(uint32_t hostlong); +]] + + +local function inet_addr(host) + local addr = 0 + local octet = 0 + local n = 0 + for i = 1, #host do + local b = byte(host, i, i) + if b >= 0 and b <= 9 then + octet = octet * 10 + b + if octet > 255 then + return INADDR_NONE + end + + elseif b == DOT_BYTE then + addr = lshift(addr, 8) + octet + octet = 0 + n = n + 1 + + else + return INADDR_NONE + end + end + + if n ~= 3 then + return INADDR_NONE + end + + addr = lshift(addr, 8) + octet + return C.htonl(addr) +end + + +local function addr_type(host) + local ipv4 = inet_addr(host) + if ipv4 ~= INADDR_NONE then + return { + "1", -- type + char(band(rshift(host, 24), 0xff)), + char(band(rshift(host, 16), 0xff)), + char(band(rshift(host, 8), 0xff)), + char(band(host, 0xff)), + } + end + + local colons = 0 + for i = 1, #host do + if host:byte(i, i) == COLON_BYTE then + colons = colons + 1 + end + end + + if colons > 1 then + -- TODO support IPv6 + error("ipv6 not supported yet") + end + + return { "3", char(#host), host } +end + + +local function process_method(sock) + -- version 5, method count 1, no authentication (0) + local _, err = sock:send("510") + if err then + return nil, err + end + + local data, err = sock:receive(2) + if err then + return nil, err + end + + local ver, method = byte(data, 1, 2) + if ver ~= 5 then + return nil, "invalid socks version " .. ver + end + + if method ~= 0 then + return nil, "authentication-based socks5 not supported yet" + end + + return true +end + + +local function connect(sock, request) + local host = request.host + local port = request.port + local data = { + "510", + addr_type(host), + char(band(rshift(port, 8), 0xff)), + char(band(port, 0xff)), + } + + local _, err = sock:send(data) + if err then + return nil, err + end + + data, err = sock:receive(4) + if err then + return nil, err + end + + local ver, rep, _, atype = byte(data, 1, 4) + if ver ~= 5 then + return nil, "invalid socks version " .. ver + end + + if rep ~= 0 then + return nil, SOCKS5_REP[rep] or "unknown socks REP field" + end + + if atype ~= 1 then + return nil, "socks5 server used invalid bind address type" .. atype + end + + -- IPv4 (4 bytes) + port (2 bytes) + local _, err = sock:receive(6) + if err then + return nil, err + end + + return true +end + + +local function socks5_proxy(sock, request) + local ok, err = process_method(sock) + if not ok then + return nil, err + end + + return connect(sock, request) +end + + +_M.socks5 = socks5_proxy + + +return _M