127 lines
3.4 KiB
Lua
127 lines
3.4 KiB
Lua
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
|