├── HTTP.lua ├── LICENSE ├── README.md ├── Shadowsocks.lua ├── gfwhosts.txt └── win-runtime.zip /HTTP.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- ZyWebD Under-construction Branch Version - simplified 3 | -- (c) 2015 zyxwvu 4 | -- 5 | local uv = require "xuv" 6 | local lfsattr, band = require "lfs".attributes, (bit32 or bit).band 7 | local crunning, cresume, cyield = coroutine.running, coroutine.resume, coroutine.yield 8 | local schar, sformat, tconcat, mmin = string.char, string.format, table.concat, math.min 9 | local HTTP = { Reader = {}, Response = {}, Backend = uv, Timeout = 10000 } 10 | 11 | local function SafeResume(co, ...) 12 | local s, err = cresume(co, ...) 13 | if not s then print(debug.traceback(co, err)) end 14 | end 15 | 16 | HTTP.ServerVer = "ZyWebD/15.02; " .. _VERSION 17 | HTTP.PathSep = package.config:sub(1, 1) 18 | function HTTP.UrlDecode(is) 19 | return is:gsub("%%([A-Fa-f0-9][A-Fa-f0-9])", function(m) return schar(tonumber(m, 16)) end) 20 | end 21 | 22 | HTTP.Decoders = {} 23 | 24 | HTTP.Decoders["HTTP Request"] = function(buffer) 25 | local l, r = buffer:find("\r?\n\r?\n") 26 | if l and r then 27 | assert(l - 1 > 1, "empty request") 28 | local head = buffer:sub(1, l - 1) 29 | local result, firstLine = {}, true 30 | for l in head:gmatch("([^\r\n]+)") do 31 | if firstLine then 32 | local verb, resource = l:match("^([A-Z]+) ([^%s]+) HTTP/1%.[01]$") 33 | assert(verb and resource, "bad request") 34 | result.method, result.resource_orig = verb, resource 35 | local resource2, querystr = resource:match("^([^%?]+)%??(.*)") 36 | result.headers = {} 37 | result.resource, result.query = HTTP.UrlDecode(resource2), querystr 38 | firstLine = false 39 | else 40 | local k, v = l:match("^([A-Za-z0-9%-]+):%s?(.+)$") 41 | assert(k and v, "bad request") 42 | result.headers[k:lower()] = v 43 | end 44 | end 45 | return result, buffer:sub(r + 1, -1) 46 | elseif #buffer > 0x10000 then -- impossible for a header to be larger than 64K 47 | error "header too long" -- notify the reader to stop reading from the stream 48 | end 49 | end 50 | 51 | function HTTP.Decoders.Line(buffer) 52 | local l, r = buffer:find("\r?\n") 53 | if l and r then 54 | if r < #buffer then -- in case there's something left 55 | return buffer:sub(1, l - 1), buffer:sub(r + 1, -1) 56 | else return buffer:sub(1, l - 1) end 57 | end 58 | end 59 | 60 | HTTP.SafeResume = SafeResume 61 | HTTP.Reader_MT = { __index = HTTP.Reader } 62 | HTTP.Response_MT = { __index = HTTP.Response } 63 | 64 | function HTTP.Reader:Push(str, err) 65 | if not str then 66 | self.stopped = true 67 | if self.decoder then 68 | SafeResume(self.readco, nil, err or "stopped") 69 | end 70 | if self.watchdog then uv.close(self.watchdog) end 71 | elseif self.buffer then 72 | self.buffer = self.buffer .. str 73 | if self.decoder then 74 | local s, result, rest = pcall(self.decoder, self.buffer) 75 | if not s then 76 | SafeResume(self.readco, nil, result) 77 | elseif result then 78 | if rest and #rest > 0 then 79 | self.buffer = rest 80 | else 81 | self.buffer = nil 82 | end 83 | SafeResume(self.readco, result) 84 | end 85 | end 86 | else 87 | if self.decoder then 88 | local s, result, rest = pcall(self.decoder, str) 89 | if not s then 90 | self.buffer = str 91 | SafeResume(self.readco, nil, result) 92 | elseif result then 93 | if rest and #rest > 0 then self.buffer = rest end 94 | SafeResume(self.readco, result) 95 | else self.buffer = str end 96 | else self.buffer = str end 97 | end 98 | end 99 | 100 | function HTTP.Reader:Decode(decoder_name) 101 | assert(not self.decoder, "already reading") 102 | local decoder = HTTP.Decoders[decoder_name] or decoder_name 103 | if self.buffer then 104 | local s, result, rest = pcall(decoder, self.buffer) 105 | if not s then 106 | return nil, result 107 | elseif result then 108 | if rest and #rest > 0 then 109 | self.buffer = rest 110 | else 111 | self.buffer = nil 112 | end 113 | return result 114 | end 115 | end 116 | if self.stopped then return nil, "stopped" end 117 | self.readco, self.decoder = crunning(), decoder 118 | if self.watchdog then 119 | uv.timer_start(self.watchdog, function() 120 | uv.timer_stop(self.watchdog) 121 | SafeResume(self.readco, nil, "timeout") 122 | end, self.decode_timeout) 123 | local result, err = cyield() 124 | self.readco, self.decoder = nil, nil 125 | uv.timer_stop(self.watchdog) 126 | return result, err 127 | else 128 | local result, err = cyield() 129 | self.readco, self.decoder = nil, nil 130 | return result, err 131 | end 132 | end 133 | 134 | function HTTP.Reader:Read(len) 135 | local readSome = function(buffer) 136 | if #buffer <= len then return buffer elseif #buffer > len then 137 | return buffer:sub(1, len), buffer:sub(len + 1, -1) 138 | end 139 | end 140 | local cache = {} 141 | while len > 0 do 142 | local block, err = self:Decode(readSome) 143 | if block then 144 | cache[#cache + 1] = block 145 | len = len - #block 146 | else 147 | cache[#cache + 1] = self.buffer 148 | self.buffer = tconcat(cache) 149 | return nil, err 150 | end 151 | end 152 | return tconcat(cache) 153 | end 154 | 155 | function HTTP.Reader:Peek() 156 | assert(not self.decoder, "already reading") 157 | if self.buffer then 158 | local buffer = self.buffer 159 | self.buffer = nil 160 | return self.buffer 161 | end 162 | if self.stopped then return nil, "stopped" end 163 | self.readco, self.decoder = crunning(), function(buffer) 164 | return buffer 165 | end 166 | local result, err = cyield() 167 | self.readco, self.decoder = nil, nil 168 | return result, err 169 | end 170 | 171 | function HTTP.NewReader(timeout) 172 | if timeout then assert(timeout >= 100, "too short timeout") end 173 | local reader = { decode_timeout = timeout } 174 | if reader.decode_timeout then reader.watchdog = uv.new_timer() end 175 | return setmetatable(reader, HTTP.Reader_MT) 176 | end 177 | 178 | local statuscodes = { 179 | [100] = 'Continue', [101] = 'Switching Protocols', 180 | [200] = 'OK', [201] = 'Created', [202] = 'Accepted', 181 | [203] = 'Non-Authoritative Information', 182 | [204] = 'No Content', [205] = 'Reset Content', [206] = 'Partial Content', 183 | [300] = 'Multiple Choices', [301] = 'Moved Permanently', [302] = 'Found', 184 | [303] = 'See Other', [304] = 'Not Modified', 185 | [400] = 'Bad Request', [401] = 'Unauthorized', 186 | [403] = 'Forbidden', [404] = 'Not Found', 187 | [405] = 'Method Not Allowed', [406] = 'Not Acceptable', 188 | [408] = 'Request Time-out', [409] = 'Conflict', [410] = 'Gone', 189 | [411] = 'Length Required', [412] = 'Precondition Failed', 190 | [413] = 'Request Entity Too Large', [415] = 'Unsupported Media Type', 191 | [416] = 'Requested Range Not Satisfiable', [417] = 'Expectation Failed', 192 | [418] = 'I\'m a teapot', -- RFC 2324 193 | [500] = 'Internal Server Error', [501] = 'Not Implemented', 194 | [502] = 'Bad Gateway', [503] = 'Service Unavailable', 195 | } 196 | 197 | function HTTP.Response:RawWrite(chunk) 198 | if self.disabled then return nil, "disabled" end 199 | local s, err = self.coreWrite(self.stream, chunk, function(err) 200 | HTTP.SafeResume(self.thread, err) 201 | end) 202 | if s then 203 | if err ~= "done nothing" then -- tweak for SSL 204 | err = cyield() 205 | if err then return nil, err end 206 | end 207 | self.tx = self.tx + #chunk 208 | return self 209 | else return nil, err end 210 | end 211 | 212 | function HTTP.Response:Disable() 213 | if not self.disabled then 214 | self.disabled = true 215 | if self.on_close then self.on_close() end 216 | end 217 | end 218 | 219 | function HTTP.Response:Handled() 220 | return self.headerSent or self.disabled 221 | end 222 | 223 | function HTTP.Response:Close() 224 | if not self.disabled then 225 | self.coreClose(self.stream) 226 | self.disabled = true 227 | end 228 | end 229 | 230 | function HTTP.Response:WriteHeader(code, headers) 231 | assert(not self.headerSent, "header already sent") 232 | assert(statuscodes[code], "bad status code") 233 | local head = { 234 | ("HTTP/1.1 %d %s"):format(code, statuscodes[code]), 235 | "Server: " .. HTTP.ServerVer } 236 | for k, v in pairs(headers) do 237 | if type(v) == "table" then 238 | for i, vv in ipairs(v) do head[#head + 1] = k .. ": " .. vv end 239 | else head[#head + 1] = k .. ": " .. v end 240 | end 241 | head[#head + 1] = "\r\n" 242 | assert(self:RawWrite(tconcat(head, "\r\n"))) 243 | self.headerSent = code 244 | return self 245 | end 246 | 247 | function HTTP.Response:DisplayError(state, content) 248 | if not self:Handled() then 249 | self:WriteHeader(state, { ["Content-Length"] = #content, ["Content-Type"] = "text/html" }) 250 | self:RawWrite(content) 251 | end 252 | self:Close() 253 | end 254 | 255 | function HTTP.Response:RedirectTo(resource) 256 | return self:WriteHeader(302, { ["Content-Length"] = 0, ["Location"] = resource }) 257 | end 258 | 259 | function HTTP.ServiceLoop(sync) 260 | while true do 261 | local request = sync.reader:Decode "HTTP Request" 262 | if request then 263 | request.peername, request.ssl_port = sync.peername, sync.ssl_port 264 | request.reader = sync.reader 265 | local res = setmetatable({ 266 | coreWrite = sync.write, coreClose = sync.close, thread = sync.thread, 267 | stream = sync.stream, tx = 0 }, HTTP.Response_MT) 268 | sync.response = res 269 | local s, err = pcall(sync.callback, request, res) 270 | if res.disabled then return end -- We can't do anything... 271 | if s then 272 | if not res.headerSent then 273 | local result = "Request not caught by any handler." 274 | res:WriteHeader(500, { ["Content-Type"] = "text/plain", ["Content-Length"] = #result }) 275 | res:RawWrite(result) 276 | end 277 | else 278 | if res.headerSent then print(err) return res:Close() else 279 | res:DisplayError(500, ([[ 280 | HTTP Error 500

500 Internal Server Error

%s Error: %s

281 |

* This response is generated by %s

]]):format(_VERSION, err, HTTP.ServerVer)) 282 | end 283 | end 284 | if request.headers.connection then 285 | request.headers.connection = request.headers.connection:lower() 286 | if request.headers.connection ~= "keep-alive" then return res:Close() end 287 | else return res:Close() end 288 | else sync.close(sync.stream) break end 289 | end 290 | end 291 | 292 | function HTTP.HandleStream(self, rest, callback) 293 | local reader = HTTP.NewReader(HTTP.Timeout) 294 | if rest then reader:Push(rest) end 295 | local sync = { stream = self, reader = reader, callback = callback } 296 | sync.peername = self:getpeername() 297 | if not sync.peername then self:close(); return end 298 | function self.on_close(note) 299 | reader:Push(nil, note) 300 | if sync.response then sync.response:Disable() end 301 | end 302 | sync.write, sync.close = self.write, self.close 303 | function self.on_data(chunk) reader:Push(chunk) end 304 | self:read_start() 305 | sync.thread = coroutine.create(HTTP.ServiceLoop) 306 | HTTP.SafeResume(sync.thread, sync) 307 | end 308 | 309 | return HTTP -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-shadowsocks 2 | 3 | A shadowsocks client in Lua, based on libuv through my binding lua-xuv. 4 | 5 | ## Features 6 | 7 | * It's written in Lua so it is tiny. 8 | * Automated direct or shadowsocks proxying... 9 | * Builtin transparent proxying (only on Linux, 127.0.0.1:2336) 10 | * HTTP CONNECT proxy (to be finished) 11 | * Detects GFW behaviour and let the user decide if we need to proxy it. 12 | * 内置绿爸,为绿色上网保驾护航! 13 | 14 | ## How to deploy? 15 | 16 | ### Windows 17 | 18 | Extract the win-runtime.zip and in command prompt execute "luajit Shadowsocks.lua server_ip [server_port]". 19 | 20 | ### Linux 21 | 22 | Install LuaJIT (download it from luajit.org, tar xf then make, sudo make install) 23 | Build LuaCrypto and lua-xuv (https://github.com/imzyxwvu/lua-xuv) 24 | Open a screen session and "luajit Shadowsocks.lua server_ip [server_port]" 25 | -------------------------------------------------------------------------------- /Shadowsocks.lua: -------------------------------------------------------------------------------- 1 | PREFETCHED_DNS = { 2 | ["twitter.com"] = "199.59.150.7"; 3 | ["pbs.twimg.com"] = "199.96.57.7"; 4 | ["abs.twimg.com"] = "199.96.57.7"; 5 | ["www.google.com"] = "173.194.72.105"; 6 | ["www.google.com.hk"] = "173.194.72.94"; 7 | ["www.google.co.jp"] = "173.194.72.94"; 8 | } 9 | 10 | -- 11 | -- Shadowsocks Implement in Lua 12 | -- This program can help you cross your company's firewall. 13 | -- by zyxwvu 14 | -- 15 | 16 | uv = require "xuv" 17 | local bxor, band, schar = (bit or bit32).bxor, (bit or bit32).band, string.char 18 | 19 | if jit and jit.os == "Linux" then 20 | ffi = require "ffi" 21 | ffi.cdef[[void (*signal(int signum,void(* handler)(int)))(int);]] 22 | ffi.C.signal(13, function() end) -- Ignore SIGPIPE 23 | end 24 | 25 | HTTP = require "HTTP" 26 | 27 | -------------------------------------------------------------------------------- 28 | ------------------------------ Name Resolving ------------------------------ 29 | -------------------------------------------------------------------------------- 30 | 31 | DNS = { 32 | A = 0x01, CNAME = 0x05, MX = 0x0F, TXT = 0x10, Cache = PREFETCHED_DNS 33 | } 34 | 35 | function DNS.PackHost(host) 36 | local result = {} 37 | for p in host:gmatch("([^%.]+)") do 38 | assert(#p < 128, "part of hostname too long") 39 | result[#result + 1] = schar(#p) .. p 40 | end 41 | result[#result + 1] = "\0" 42 | return table.concat(result) 43 | end 44 | 45 | function DNS.MakeReq(query) 46 | local header = schar( 47 | 0, query.id or 0, -- Query ID 48 | query.rc and 1 or 0, 0, 0, 1, 0, 0, 0, 0, 0, 0) 49 | return header .. DNS.PackHost(query[1]) .. 50 | schar(0, query.type or 1, 0, 1) 51 | end 52 | 53 | function DNS.ParseRes(resp) 54 | if #resp < 12 then error"header not complete" end 55 | local result = { } 56 | local b1, b2, b3, b4 = resp:byte(5, 8) 57 | local nres, nref, nar = b4, resp:byte(10, 10), resp:byte(12, 12) 58 | result.id = resp:byte(2, 2) 59 | result.rcode = band(resp:byte(4, 4), 0xF) 60 | local ptr = 13 61 | function extractName() 62 | local name = {} 63 | local ptrBackup 64 | while true do 65 | local l = resp:byte(ptr, ptr) 66 | ptr = ptr + 1 67 | if l == 0xC0 then 68 | if not ptrBackup then ptrBackup = ptr + 1 end 69 | ptr = resp:byte(ptr, ptr) + 1 70 | elseif l == 0 then break 71 | elseif not l then error"name not complete" 72 | else 73 | name[#name + 1] = resp:sub(ptr, ptr + l - 1) 74 | ptr = ptr + l 75 | end 76 | end 77 | if ptrBackup then ptr = ptrBackup end 78 | return table.concat(name, ".") 79 | end 80 | result.name = extractName() 81 | ptr = ptr + 1 82 | result.type = resp:byte(ptr) 83 | ptr = ptr + 3 84 | function extractRecord() 85 | local result = {} 86 | result.rname = extractName() 87 | ptr = ptr + 1 88 | result.rtype = resp:byte(ptr) 89 | ptr = ptr + 3 90 | b1, b2, b3, b4 = resp:byte(ptr, ptr + 3) 91 | result.rttl = b1 * 0x1000000 + b2 * 0x10000 + b3 * 0x100 + b4 92 | ptr = ptr + 4 93 | b1, b2 = resp:byte(ptr, ptr + 1) 94 | local len, ptrBackup = b1 * 0x100 + b2, ptr 95 | if result.rtype == 1 and len == 4 then 96 | result.data = ("%d.%d.%d.%d"):format(resp:byte(ptr + 2, ptr + 5)) 97 | elseif result.rtype == 5 or result.rtype == 2 or result.rtype == 6 then 98 | ptr = ptr + 2; result.data = extractName(); 99 | else 100 | result.data = resp:sub(ptr + 2, ptr + len + 1) 101 | end 102 | ptr = ptrBackup + len + 2 103 | return result 104 | end 105 | if nres > 0 then 106 | result.records = {} 107 | for i = 1, nres do result.records[i] = extractRecord() end 108 | elseif nref > 0 then 109 | result.aa, result.records = true, {} 110 | for i = 1, nref do result.records[i] = extractRecord() end 111 | for i = 1, nar do 112 | local append = extractRecord() 113 | for i, v in ipairs(result.records) do 114 | if append.rtype == 1 and append.rname == v.data then 115 | v.addr = append.data 116 | end 117 | end 118 | end 119 | end 120 | return result 121 | end 122 | 123 | function DNS.ToIP(name, callback) 124 | if DNS.Cache[name] then 125 | callback(DNS.Cache[name]) 126 | else 127 | local handle = uv.udp_new() 128 | local dnsQuery = DNS.MakeReq{name, type = 1, rc = true} 129 | local s_time = os.clock() 130 | uv.udp_send(handle, dnsQuery, "8.8.8.8", 53) 131 | uv.udp_send(handle, dnsQuery, "114.114.114.114", 53) 132 | local watchdog = uv.timer_new() 133 | uv.timer_start(watchdog, function() 134 | uv.close(handle) 135 | uv.close(watchdog) 136 | callback(nil, "timeout") 137 | end, 1500) 138 | uv.udp_recv_start(handle, function(blk, peer) 139 | local s, result = pcall(DNS.ParseRes, blk) 140 | if s then 141 | uv.close(watchdog) 142 | uv.close(handle) 143 | if result.rcode == 3 or not result.records then 144 | callback(nil, "bad name") 145 | return 146 | end 147 | local cname 148 | for i, v in ipairs(result.records) do 149 | if v.rtype == 1 then 150 | result = v.data 151 | print((" * state: name %s resolved: %s"): 152 | format(name, result)) 153 | if cname then 154 | print((" ( CNAME = %s ) "): 155 | format(cname)) 156 | end 157 | print((" * statistic: by %s in %.1f secs"): 158 | format(peer, os.clock() - s_time)) 159 | DNS.Cache[name] = result 160 | callback(result) 161 | return 162 | elseif v.rtype == 5 then cname = v.data end 163 | end 164 | callback(nil, "no A result") 165 | else 166 | print((" * invaild DNS packet by: %s"):format(peer)) 167 | end 168 | end) 169 | end 170 | end 171 | 172 | -------------------------------------------------------------------------------- 173 | ------------------------------ Proxy Controlling ------------------------------ 174 | -------------------------------------------------------------------------------- 175 | 176 | GFWTree, GFWList = {}, {} 177 | 178 | function ReadGFWHosts(gfwhosts) 179 | local fp = io.open(gfwhosts or "gfwhosts.txt", "r") 180 | if fp then 181 | GFWTree, GFWList = {}, {} 182 | for l in fp:lines() do 183 | if l:sub(1, 1) ~= "#" and l:find("[^%s\t]+") then 184 | l = l:match("^[%s\t]*([^%s\t]+)[%s\t]*$") 185 | if l:find("^%d+%.%d+%.%d+%.%d+$") then 186 | GFWList[l] = true 187 | else 188 | local name = {} 189 | for p in l:gmatch "[^%.]+" do 190 | name[#name + 1] = p 191 | end 192 | local nftree = GFWTree 193 | for i = #name, 1, -1 do 194 | if not nftree[name[i]] then 195 | nftree[name[i]] = {} 196 | end 197 | nftree = nftree[name[i]] 198 | end 199 | nftree[1], nftree[2] = true, l:sub(1, 1) == "." 200 | end 201 | end 202 | end 203 | fp:close() 204 | end 205 | end 206 | 207 | function NeedForward(ipaddr, name_str) 208 | if GFWList[ipaddr] then return true end 209 | if not name_str then return false end 210 | local name = {} 211 | for p in name_str:gmatch "[^%.]+" do 212 | name[#name + 1] = p 213 | end 214 | local nftree = GFWTree 215 | for i = #name, 1, -1 do 216 | nftree = nftree[name[i]] 217 | if not nftree then break end 218 | if nftree[2] then break end 219 | end 220 | if nftree then 221 | if nftree[1] then 222 | if ipaddr then GFWList[ipaddr] = name_str end 223 | return true 224 | end 225 | end 226 | return false 227 | end 228 | 229 | ReadGFWHosts() 230 | 231 | -------------------------------------------------------------------------------- 232 | ------------------------------ Web Service ------------------------------ 233 | -------------------------------------------------------------------------------- 234 | 235 | function web_service_connect(res, host, port) 236 | local co = coroutine.running() 237 | local address 238 | if host:find("^%d+%.%d+%.%d+%.%d+$") then 239 | address = host 240 | else 241 | if DNS.Cache[host] then -- coroutine issue 242 | address = DNS.Cache[host] 243 | else 244 | DNS.ToIP(host, function(...) 245 | HTTP.SafeResume(co, ...) 246 | end) 247 | local result = coroutine.yield() 248 | if result then 249 | address = result 250 | else 251 | return res:DisplayError(502, "UNKNOWN DNS NAME") 252 | end 253 | end 254 | end 255 | if false and NeedForward(address, host) then 256 | -- Coming later... 257 | else 258 | uv.connect(address, port, function(...) 259 | HTTP.SafeResume(co, ...) 260 | end) 261 | local stream, err = coroutine.yield() 262 | if stream then 263 | print(" * HTTP: plain connected to: " .. host) 264 | res:WriteHeader(200, {}) 265 | -- downgrade the stream 266 | res.disabled = true 267 | res.reader:Push(nil, "downgraded") 268 | -- pipe the streams 269 | function stream.on_close() res.stream:close() end 270 | function stream.on_data(chunk) 271 | stream:read_stop() 272 | res.stream:write(chunk, function(err) 273 | if not err and stream() then 274 | stream:read_start() 275 | end 276 | end) 277 | end 278 | if res.reader.buffer then 279 | stream:write(res.reader.buffer) 280 | res.reader.buffer = nil 281 | end 282 | function res.stream.on_close(err) stream:close() end 283 | function res.stream.on_data(chunk) 284 | res.stream:read_stop() 285 | stream:write(chunk, function(err) 286 | if not err and res.stream() then 287 | res.stream:read_start() 288 | end 289 | end) 290 | end 291 | stream:read_start() 292 | if res.reader.paused then res.stream:read_start() end 293 | else 294 | return res:DisplayError(502, err or "BAD PROXY") 295 | end 296 | end 297 | 298 | end 299 | 300 | function web_service(req, res) 301 | print((" * Web Service: %s %s %s"):format(req.peername, req.method, req.resource_orig)) 302 | if req.method == "CONNECT" then 303 | local host, port = req.resource_orig:match("^([A-Za-z0-9%-%.]+):(%d+)$") 304 | if host and port then 305 | res.reader = req.reader 306 | return web_service_connect(res, host, assert(tonumber(port))) 307 | else 308 | res:DisplayError(400, "BAD REQUEST") 309 | end 310 | elseif req.method == "BREW" or req.method == "WHEN" then 311 | res:DisplayError(418, [[

I'm a teapot

]]) 312 | elseif req.resource == "/" then 313 | if req.headers["user-agent"] then 314 | if req.headers["user-agent"]:find("CFNetwork/", 1, true) then 315 | print(" * state: PAC file sent to - " .. req.peername) 316 | local sockname = res.stream:getsockname() 317 | local PAC = string.format([[ 318 | function FindProxyForURL(url, host) { // for iOS 319 | if(url.toLowerCase().substr(0, 8) == "https://") return "PROXY %s:2333"; 320 | else return "SOCKS %s:2333"; } ]], sockname, sockname) 321 | res:WriteHeader(200, { 322 | ["Content-Type"] = "application/x-ns-proxy-autoconfig", 323 | ["Content-Length"] = #PAC }) 324 | res:RawWrite(PAC) 325 | return 326 | end 327 | end 328 | elseif req.resource == "/pcl-add" and req.peername == "127.0.0.1" then 329 | if #req.query > 0 and req.headers.referer then 330 | GFWList[req.query] = req.peername 331 | res:RedirectTo(req.headers.referer) -- go back! 332 | else 333 | res:DisplayError(500, "

Bad usage

") 334 | end 335 | else res:RedirectTo "/" end 336 | end 337 | 338 | GREEN_WEB_TIP = [[ 339 | 绿色上网告示 340 | 341 |

绿色上网告示

342 |

什么,你还在访问这家同性交友网站?

343 |

根据工信部和文化部的联合调查报儿童告,该同性交友网站受到一个境外组织控制,站在党和人民的对立面,长期以来向中国的青少年儿童提供色情、血腥动画,蛊惑了我国大量的青少年。据统计,我国每年都有数千万青少年因为这种动画而患上一种叫做中二病的心理疾病。

344 |

如果需要观看动画,我们推荐你点击下面的链接,观看适宜青少年儿童观众的、具有社会主义特色的国产动画。

345 |

点击观看

346 | ]] 347 | 348 | function web_service_lvba(req, res) 349 | if req.resource == "/" then 350 | res:WriteHeader(200, { 351 | ["Content-Type"] = "text/html; charset=UTF-8", 352 | ["Content-Length"] = #GREEN_WEB_TIP 353 | }) 354 | res:RawWrite(GREEN_WEB_TIP) 355 | else res:RedirectTo "/" end 356 | end 357 | 358 | -------------------------------------------------------------------------------- 359 | ------------------------------ Shadowsocks ------------------------------ 360 | -------------------------------------------------------------------------------- 361 | 362 | crypto = require "crypto" 363 | 364 | Shadowsocks = { password = "12345", " * Clowwindy I love you! -- zyxwvu" } 365 | 366 | Shadowsocks.forwarder = { 367 | assert(arg[1], "usage: ShadowSocks.lua server_ip [server_port]"), 368 | tonumber(arg[2]) or 8388 369 | } 370 | 371 | if false then print(Shadowsocks[1]) end 372 | 373 | function Shadowsocks.random_string(length) 374 | local buffer = {} 375 | for i = 1, length do buffer[i] = math.random(0, 255) end 376 | return schar(unpack(buffer)) 377 | end 378 | 379 | function Shadowsocks.evp_bytestokey(password, key_len, iv_len) 380 | local key = string.format("%s-%d-%d", password, key_len, iv_len) 381 | local m, i = {}, 0 382 | while #(table.concat(m)) < key_len + iv_len do 383 | local data = password 384 | if i > 0 then data = m[i] .. password end 385 | m[#m + 1], i = crypto.digest("md5", data, true), i + 1 386 | end 387 | local ms = table.concat(m) 388 | local key = ms:sub(1, key_len) 389 | local iv = ms:sub(key_len + 1, iv_len) 390 | return key, iv 391 | end 392 | 393 | function Shadowsocks.rc4_md5(wtf, key, iv) 394 | local md5 = crypto.digest.new "md5" 395 | md5:update(key) 396 | md5:update(iv) 397 | return wtf.new("rc4", md5:final(nil, true), "") 398 | end 399 | 400 | function Shadowsocks.connect(ip, port, callback) 401 | local key_ = Shadowsocks.evp_bytestokey(Shadowsocks.password, 16, 16) 402 | local cipher_iv = Shadowsocks.random_string(16) 403 | local cipher, decipher 404 | cipher = Shadowsocks.rc4_md5(crypto.encrypt, key_, cipher_iv) 405 | uv.connect(Shadowsocks.forwarder[1], Shadowsocks.forwarder[2], function(self, err) 406 | if err then return callback(nil, err) end 407 | local a, b, c, d = ip:match "^(%d+)%.(%d+)%.(%d+)%.(%d+)$" 408 | a, b, c, d = tonumber(a), tonumber(b), tonumber(c), tonumber(d) 409 | assert(a and b and c and d) 410 | self:write(cipher_iv .. cipher:update( 411 | schar(1, a, b, c, d, band(port, 0xFF00) / 0x100, band(port, 0xFF)))) 412 | local agent = {} 413 | function self.on_data(chunk) 414 | if decipher then 415 | chunk = decipher:update(chunk) 416 | if #chunk > 0 then agent.on_data(chunk) end 417 | else 418 | if #chunk >= 16 then 419 | local key_ = Shadowsocks.evp_bytestokey(Shadowsocks.password, 16, 16) 420 | decipher = Shadowsocks.rc4_md5(crypto.decrypt,key_, chunk:sub(1, 16)) 421 | if #chunk > 16 then 422 | chunk = decipher:update(chunk:sub(17, -1)) 423 | if #chunk > 0 then agent.on_data(chunk) end 424 | end 425 | end 426 | end 427 | end 428 | function self.on_close(reason) 429 | if agent.on_close then agent.on_close(reason) end 430 | end 431 | function agent.write(_, chunk, callback) 432 | local cipherdata = cipher:update(chunk) 433 | if #cipherdata > 0 then 434 | self:write(cipherdata, callback) 435 | else 436 | return callback() 437 | end 438 | end 439 | function agent.close(reason) return self:close(reason) end 440 | function agent.read_start() return self:read_start() end 441 | function agent.read_stop() return self:read_stop() end 442 | function agent.alive() return self() end 443 | return callback(agent) 444 | end) 445 | end 446 | 447 | -------------------------------------------------------------------------------- 448 | ------------------------------ Proxy Service ------------------------------ 449 | -------------------------------------------------------------------------------- 450 | 451 | function shadow_service(stream, request, socks_rest) 452 | Shadowsocks.connect(request.address, request.r_port, function(remote, err) 453 | if err then 454 | print(" * error: failed to connect to " .. request.address) 455 | return request.fail() 456 | end 457 | print(" * shadowsocks: connected to " .. (request.host or request.address)) 458 | if socks_rest then 459 | stream:write(schar(0, 0, 1, 0, 0, 0, 0, 0x10, 0x10)) 460 | if #socks_rest > 0 then remote:write(socks_rest) end 461 | end 462 | function remote.on_close() stream:close() end 463 | function stream.on_close() remote:close() end 464 | function remote.on_data(chunk) 465 | remote:read_stop() 466 | clean_stream = false 467 | if stream() then stream:write(chunk, function() 468 | if remote:alive() then remote:read_start() end 469 | end) end 470 | end 471 | function stream.on_data(chunk) 472 | stream:read_stop() 473 | if remote:alive() then remote:write(chunk, function() 474 | if stream() then stream:read_start() end 475 | end) end 476 | end 477 | remote:read_start() 478 | stream:read_start() 479 | end) 480 | end 481 | 482 | function plain_service(stream, request, socks_rest) 483 | uv.connect(request.address, request.r_port, function(remote, err) 484 | if err then 485 | print(" * error: failed to connect to " .. request.address) 486 | return request.fail() 487 | end 488 | print(" * state: connected to " .. (request.host or request.address)) 489 | local clean_stream = request.r_port == 80 and socks_rest 490 | if not stream() then return end 491 | function stream.on_close() remote:close() end 492 | function remote.on_close(reason) 493 | if reason == "ECONNRESET" and clean_stream then 494 | local response = { 495 | "HTTP/1.1 502 GFWed", 496 | "Content-Type: text/html", 497 | "Connection: close", 498 | "", 499 | string.format([[Shadowsocks Lua 500 |

Shadowsocks Lua has Detected a GFW Behavior

501 |

The site %s seems to be GFWed. If you want to visit this page, click the following link:

502 | Add to Proxy Controlling List]], 503 | request.host or request.address, request.address) 504 | } 505 | stream:write(table.concat(response, "\r\n"), function() 506 | stream:close() -- Defered closing 507 | end) 508 | else 509 | stream:close() 510 | end 511 | end 512 | remote:nodelay(true) 513 | if socks_rest then 514 | local peername, peerport = remote:getpeername() 515 | local a, b, c, d = peername:match "^(%d+)%.(%d+)%.(%d+)%.(%d+)$" 516 | a, b, c, d = tonumber(a), tonumber(b), tonumber(c), tonumber(d) 517 | assert(a and b and c and d) 518 | stream:write(schar(0, 0, 1, a, b, c, d, 519 | band(peerport, 0xFF00) / 0x100, band(peerport, 0xFF))) 520 | if #socks_rest > 0 then remote:write(socks_rest) end 521 | end 522 | function remote.on_data(chunk) 523 | remote:read_stop() 524 | clean_stream = false 525 | if stream() then stream:write(chunk, function() 526 | if remote() then remote:read_start() end 527 | end) end 528 | end 529 | function stream.on_data(chunk) 530 | stream:read_stop() 531 | if remote() then remote:write(chunk, function() 532 | if stream() then stream:read_start() end 533 | end) end 534 | end 535 | remote:read_start() 536 | stream:read_start() 537 | end) 538 | end 539 | 540 | function service(stream, request, rest) 541 | if not request.address and request.host and rest then 542 | if request.host == "proxycontrol.arpa" then 543 | stream:write(schar(0, 0, 1, 127, 0, 0, 1, 0, 80)) 544 | return HTTP.HandleStream(stream, rest, web_service) 545 | end 546 | if request.host:find "%.bilibili%.com%.?$" and request.r_port == 80 then 547 | print " * GreenDad: crabbed connection to an R-18 website" 548 | stream:write(schar(0, 0, 1, 127, 0, 0, 1, 0, 80)) 549 | return HTTP.HandleStream(stream, rest, web_service_lvba) 550 | end 551 | DNS.ToIP(request.host, function(address) 552 | if address then 553 | request.address = address 554 | service(stream, request, rest) 555 | else 556 | return request.fail() 557 | end 558 | end) 559 | return 560 | end 561 | if NeedForward(request.address, request.host) then 562 | return shadow_service(stream, request, rest) 563 | else 564 | return plain_service(stream, request, rest) 565 | end 566 | end 567 | 568 | math.randomseed(os.time()) -- seed Lua's RNG 569 | uv.set_process_title("Shadowsocks Lua") 570 | 571 | if jit.os == "Linux" then 572 | -- Transparent Proxy for Linux 573 | uv.listen("127.0.0.1", 2336, 32, function(self) 574 | self:nodelay(true) 575 | local request = {} 576 | request.address, request.r_port = self:originaldst() 577 | if not request.address or request.address == "127.0.0.1" then 578 | self:close() 579 | return 580 | end 581 | function request.fail() self:close() end 582 | if NeedForward(request.address, request.host) then 583 | return shadow_service(self, request, rest) 584 | else 585 | return plain_service(self, request, rest) 586 | end 587 | end) 588 | end 589 | 590 | uv.listen("0.0.0.0", 2333, 32, function(self) 591 | self:nodelay(true) 592 | local buffer, nstage, state = "", 1, { stream = self } 593 | local function handshake() 594 | if nstage == 1 then 595 | if #buffer >= 2 then 596 | local a, b = buffer:byte(1, 2) 597 | if a == 5 and b > 0 then 598 | state.nmethods = b 599 | nstage = 2 600 | elseif a == 4 and b == 1 then 601 | return self:close() 602 | else 603 | self:read_stop() 604 | HTTP.HandleStream(self, buffer, web_service) 605 | buffer = nil 606 | return 607 | end 608 | buffer = buffer:sub(3, -1) 609 | end 610 | elseif nstage == 2 then 611 | if #buffer >= state.nmethods then 612 | buffer = buffer:sub(state.nmethods + 1, -1) 613 | self:write "\x05\x00" 614 | nstage = 3 615 | end 616 | elseif nstage == 3 then 617 | if #buffer >= 4 then 618 | local v, c, r, a = buffer:byte(1, 4) 619 | if v == 5 and c == 1 and (a == 1 or a == 3) then 620 | state.atype = a 621 | nstage, buffer = 4, buffer:sub(5, -1) 622 | return true 623 | else 624 | self:write( 625 | "\x05\x01\x00\x01\x00\x00\x00\x00\x00\x00", 626 | function() self:close() end) 627 | end 628 | end 629 | elseif nstage == 4 then 630 | if state.atype == 3 then 631 | if #buffer >= 1 then 632 | state.namelen = buffer:byte(1, 1) 633 | nstage, buffer = "name", buffer:sub(2, -1) 634 | end 635 | elseif state.atype == 1 then 636 | if #buffer >= 4 then 637 | local a, b, c, d = buffer:byte(1, 4) 638 | state.address = ("%d.%d.%d.%d"):format(a, b, c, d) 639 | nstage, buffer = 5, buffer:sub(5, -1) 640 | end 641 | end 642 | elseif nstage == "name" then 643 | if #buffer >= state.namelen then 644 | state.host = buffer:sub(1, state.namelen) 645 | nstage, buffer = 5, buffer:sub(state.namelen + 1, -1) 646 | if state.host:find("^%d+%.%d+%.%d+%.%d+$") then 647 | state.address = state.host 648 | end 649 | end 650 | elseif nstage == 5 then 651 | if #buffer >= 2 and self() then 652 | local a, b = buffer:byte(1, 2) 653 | state.r_port = a * 0x100 + b 654 | nstage, buffer = 8, buffer:sub(3, -1) 655 | self:write "\x05" 656 | self:read_stop() 657 | self.on_data = nil 658 | local s, err = pcall(service, self, state, buffer) 659 | if not s and err then 660 | print(" * Lua error: " .. err) 661 | end 662 | buffer = nil 663 | end 664 | else 665 | error("never reaches here: " .. tostring(nstage)) 666 | end 667 | end 668 | function state.fail() 669 | if self() then 670 | if not self:write("\x01\x00\x01\x00\x00\x00\x00\x00\x00", function() 671 | self:close() 672 | end) then self:close() end 673 | end 674 | end 675 | function self.on_data(chunk) 676 | buffer = buffer .. chunk 677 | local reference_buffer 678 | repeat 679 | reference_buffer = buffer 680 | handshake() 681 | if not buffer then return end 682 | until reference_buffer == buffer 683 | end 684 | self:read_start() 685 | end) 686 | 687 | uv.run() -------------------------------------------------------------------------------- /gfwhosts.txt: -------------------------------------------------------------------------------- 1 | # GFW Hosts ... Version: 201502 2 | 3 | # Kankore daisuki !!! 4 | .dmm.com 5 | 6 | .google.com 7 | .google.com.hk 8 | .google.co.jp 9 | .gmail.com 10 | .gstatic.com 11 | .googleapis.com 12 | .googlecode.com 13 | .googleusercontent.com 14 | .googlevideo.com 15 | .youtube.com 16 | .appspot.com 17 | .blogger.com 18 | .recaptcha.net 19 | .ytimg.com 20 | .ggpht.com 21 | .google.co.in 22 | .google-analytics.com 23 | goo.gl 24 | 25 | .twitter.com 26 | .twimg.com 27 | t.co 28 | bit.ly 29 | .facebook.com 30 | zh.wikipedia.org 31 | .blogspot.com 32 | .wordpress.com 33 | .tumblr.com 34 | .doubleclick.net 35 | .feedburner.com 36 | .edgekey.net 37 | .symcd.com 38 | .symcb.com 39 | -------------------------------------------------------------------------------- /win-runtime.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imzyxwvu/lua-shadowsocks/ea28e5b3798679ef7f5e97b70a4c92707f6d7252/win-runtime.zip --------------------------------------------------------------------------------