local _M = {} local splitn = require("kong.tools.string").splitn local header_mt = { __index = function(self, name) name = name:lower():gsub("_", "-") return rawget(self, name) end, __newindex = function(self, name, value) name = name:lower():gsub("_", "-") rawset(self, name, value) end, } local function new_headers() return setmetatable({}, header_mt) end -- This is a very naive forward proxy, which accepts a CONNECT over HTTP, and -- then starts tunnelling the bytes blind (for end-to-end SSL). function _M.connect(opts) local req_sock = ngx.req.socket(true) req_sock:settimeouts(1000, 1000, 1000) -- receive request line local req_line = req_sock:receive() ngx.log(ngx.DEBUG, "request line: ", req_line) local t = splitn(req_line, " ", 3) local method, host_port = t[1], t[2] if method ~= "CONNECT" then return ngx.exit(400) end t = splitn(host_port, ":", 3) local upstream_host, upstream_port = t[1], t[2] local headers = new_headers() -- receive headers repeat local line = req_sock:receive("*l") local name, value = line:match("^([^:]+):%s*(.+)$") if name and value then ngx.log(ngx.DEBUG, "header: ", name, " => ", value) headers[name] = value end until ngx.re.find(line, "^\\s*$", "jo") local basic_auth = opts and opts.basic_auth if basic_auth then ngx.log(ngx.DEBUG, "checking proxy-authorization...") local found = headers["proxy-authorization"] if not found then ngx.log(ngx.NOTICE, "client did not send proxy-authorization header") ngx.print("HTTP/1.1 401 Unauthorized\r\n\r\n") return ngx.exit(ngx.OK) end local auth = ngx.re.gsub(found, [[^Basic\s*]], "", "oji") if auth ~= basic_auth then ngx.log(ngx.NOTICE, "client sent incorrect proxy-authorization") ngx.print("HTTP/1.1 403 Forbidden\r\n\r\n") return ngx.exit(ngx.OK) end ngx.log(ngx.DEBUG, "accepted basic proxy-authorization") end -- Connect to requested upstream local upstream_sock = ngx.socket.tcp() upstream_sock:settimeouts(1000, 1000, 1000) local ok, err = upstream_sock:connect(upstream_host, upstream_port) if not ok then ngx.log(ngx.ERR, "connect to upstream ", upstream_host, ":", upstream_port, " failed: ", err) return ngx.exit(504) end -- Tell the client we are good to go ngx.print("HTTP/1.1 200 OK\r\n\r\n") ngx.flush() ngx.log(ngx.DEBUG, "tunneling started") -- 10Kb in either direction should be plenty local max_bytes = 10 * 1024 repeat local req_data = req_sock:receiveany(max_bytes) if req_data then ngx.log(ngx.DEBUG, "client RCV ", #req_data, " bytes") local bytes, err = upstream_sock:send(req_data) if bytes then ngx.log(ngx.DEBUG, "upstream SND ", bytes, " bytes") elseif err then ngx.log(ngx.ERR, "upstream SND failed: ", err) end end local res_data = upstream_sock:receiveany(max_bytes) if res_data then ngx.log(ngx.DEBUG, "upstream RCV ", #res_data, " bytes") local bytes, err = req_sock:send(res_data) if bytes then ngx.log(ngx.DEBUG, "client SND: ", bytes, " bytes") elseif err then ngx.log(ngx.ERR, "client SND failed: ", err) end end until not req_data and not res_data -- request socket should be closed upstream_sock:close() ngx.log(ngx.DEBUG, "tunneling ended") end return _M