-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathsocks5.lua
155 lines (145 loc) · 4.53 KB
/
socks5.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
local ngx = require('ngx')
local socks5 = {}
local char = string.char
-- magic numbers
local SOCKS5 = 0x05
local NUMBER_OF_AUTH_METHODS = 0x01
local NO_AUTHENTICATION = 0x00
local TCP_CONNECTION = 0x01
local RESERVED = 0x00
local IPv4 = 0x01
local DOMAIN_NAME = 0x03
local IPv6 = 0x04
local REQUEST_GRANTED = 0x00
local CONN_ERRORS = {
[0x01] = 'general failure',
[0x02] = 'connection not allowed by ruleset',
[0x03] = 'network unreachable',
[0x04] = 'host unreachable',
[0x05] = 'connection refused by destination host',
[0x06] = 'TTL expired',
[0x07] = 'command not supported / protocol error',
[0x08] = 'address type not supported',
}
local CHUNK_SIZE = 1024
-- authentication to socks5 server
socks5.auth = function(cosocket)
cosocket:send(char(SOCKS5, NUMBER_OF_AUTH_METHODS,
NO_AUTHENTICATION))
local auth_response = cosocket:receive(2)
if auth_response ~= char(SOCKS5, NO_AUTHENTICATION) then
return nil, "Socks5 authentification failed"
end
return true
end
-- connection request
socks5.connect = function(cosocket, host, port)
local host_length = #host
local port_big_endian = char(
math.floor(port / 256), port % 256)
cosocket:send(char(SOCKS5, TCP_CONNECTION, RESERVED,
DOMAIN_NAME, host_length) .. host .. port_big_endian)
local conn_response = cosocket:receive(3)
if conn_response ~=
char(SOCKS5, REQUEST_GRANTED, RESERVED) then
local status = conn_response:byte(2)
local message = CONN_ERRORS[status] or 'Unknown error'
return nil, message
end
-- pop address
local addr_type = cosocket:receive(1)
if addr_type == char(DOMAIN_NAME) then
local addr_length = addr_type:byte(1)
cosocket:receive(addr_length)
elseif addr_type == char(IPv4) then
cosocket:receive(4)
elseif addr_type == char(IPv6) then
cosocket:receive(16)
else
return nil, 'Bad address type: ' .. string.byte(addr_type)
end
-- pop port
cosocket:receive(2)
return true
end
socks5.handle_request = function(socks5host, socks5port,
request_changer, response_changer, change_only_html)
local sosocket = ngx.socket.connect(socks5host, socks5port)
do
local status, message = socks5.auth(sosocket)
if not status then
ngx.say('Error: ' .. message)
return
end
end
local target_host = ngx.req.get_headers()['Host']
if request_changer then
target_host = request_changer(target_host)
end
local target_port = 80
do
local status, message = socks5.connect(sosocket,
target_host, target_port)
if not status then
ngx.say('Error: ' .. message)
return
end
end
-- read request
local clheader = ngx.req.raw_header()
if request_changer then
clheader = request_changer(clheader)
end
sosocket:send(clheader)
ngx.req.read_body()
local clbody = ngx.req.get_body_data()
if clbody then
if request_changer then
clbody = request_changer(clbody)
end
sosocket:send(clbody)
end
-- read response
local soheader, message =
sosocket:receiveuntil('\r\n\r\n')()
if not soheader then
ngx.say('No headers received from target: ' .. message)
return
end
local sobody_length = soheader:match(
'Content%-Length%: (%d+)')
local is_html = soheader:match('Content%-Type: text/html')
local change = is_html or not change_only_html
local clsocket = ngx.req.socket(true)
if response_changer and change then
-- read whole body
local sobody = sosocket:receive(sobody_length or '*a') or ''
sobody = response_changer(sobody)
soheader = response_changer(soheader)
if soheader:find('Content%-Length%:') then
soheader = soheader:gsub('Content%-Length%: %d+',
'Content-Length: ' .. #sobody)
else
soheader = soheader ..
'\r\nContent-Length: ' .. #sobody
end
clsocket:send(soheader .. '\r\n\r\n' .. sobody)
else
-- stream
clsocket:send(soheader .. '\r\n\r\n')
while true do
local sobody, _, partial = sosocket:receive(CHUNK_SIZE)
if not sobody then
clsocket:send(partial)
break
end
local bytes = clsocket:send(sobody)
if not bytes then
break
end
end
end
-- close
sosocket:close()
end
return socks5