From f7a9a08a9bf9cc1cb4824db8ddee20c1c9998122 Mon Sep 17 00:00:00 2001 From: Ycarus Date: Mon, 22 Jan 2018 11:20:49 +0100 Subject: [PATCH] Add MPTCP support to shadowsocks --- luci-app-shadowsocks-libev/Makefile | 17 + .../luasrc/controller/shadowsocks-libev.lua | 33 ++ .../shadowsocks-libev/instance-details.lua | 53 +++ .../model/cbi/shadowsocks-libev/instances.lua | 104 ++++++ .../model/cbi/shadowsocks-libev/rules.lua | 110 ++++++ .../model/cbi/shadowsocks-libev/servers.lua | 31 ++ .../luasrc/model/shadowsocks-libev.lua | 265 +++++++++++++++ .../view/shadowsocks-libev/add_instance.htm | 45 +++ .../uci-defaults/40_luci-shadowsocks-libev | 11 + shadowsocks-libev/Makefile | 128 +++++++ shadowsocks-libev/README.md | 97 ++++++ shadowsocks-libev/files/firewall.ss-rules | 2 + .../files/shadowsocks-libev.config | 33 ++ .../files/shadowsocks-libev.init | 321 ++++++++++++++++++ shadowsocks-libev/files/shadowsocks.track | 50 +++ shadowsocks-libev/files/ss-rules | 264 ++++++++++++++ shadowsocks-libev/files/ss-rules.defaults | 10 + .../patches/010-ECONNRESET.patch | 14 + 18 files changed, 1588 insertions(+) create mode 100644 luci-app-shadowsocks-libev/Makefile create mode 100644 luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua create mode 100644 luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instance-details.lua create mode 100644 luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instances.lua create mode 100644 luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/rules.lua create mode 100644 luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/servers.lua create mode 100644 luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua create mode 100644 luci-app-shadowsocks-libev/luasrc/view/shadowsocks-libev/add_instance.htm create mode 100644 luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev create mode 100644 shadowsocks-libev/Makefile create mode 100644 shadowsocks-libev/README.md create mode 100644 shadowsocks-libev/files/firewall.ss-rules create mode 100644 shadowsocks-libev/files/shadowsocks-libev.config create mode 100644 shadowsocks-libev/files/shadowsocks-libev.init create mode 100755 shadowsocks-libev/files/shadowsocks.track create mode 100755 shadowsocks-libev/files/ss-rules create mode 100755 shadowsocks-libev/files/ss-rules.defaults create mode 100644 shadowsocks-libev/patches/010-ECONNRESET.patch diff --git a/luci-app-shadowsocks-libev/Makefile b/luci-app-shadowsocks-libev/Makefile new file mode 100644 index 000000000..4ec60c4f1 --- /dev/null +++ b/luci-app-shadowsocks-libev/Makefile @@ -0,0 +1,17 @@ +# +# Copyright (C) 2017 Yousong Zhou +# Copyright (C) 2018 Ycarus (Yannick Chabanois) +# +# This is free software, licensed under the Apache License, Version 2.0 . +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=LuCI Support for shadowsocks-libev +LUCI_DEPENDS:= + +PKG_LICENSE:=Apache-2.0 + +include ../luci/luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua b/luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua new file mode 100644 index 000000000..05d12e38b --- /dev/null +++ b/luci-app-shadowsocks-libev/luasrc/controller/shadowsocks-libev.lua @@ -0,0 +1,33 @@ +-- Copyright 2017 Yousong Zhou +-- Licensed to the public under the Apache License 2.0. +-- +module("luci.controller.shadowsocks-libev", package.seeall) + +function index() + entry({"admin", "services", "shadowsocks-libev"}, + alias("admin", "services", "shadowsocks-libev", "instances"), + _("Shadowsocks-libev"), 59) + + entry({"admin", "services", "shadowsocks-libev", "instances"}, + arcombine(cbi("shadowsocks-libev/instances"), cbi("shadowsocks-libev/instance-details")), + _("Local Instances"), 10).leaf = true + + entry({"admin", "services", "shadowsocks-libev", "servers"}, + cbi("shadowsocks-libev/servers"), + _("Remote Servers"), 20).leaf = true + + entry({"admin", "services", "shadowsocks-libev", "rules"}, + cbi("shadowsocks-libev/rules"), + _("Redir Rules"), 30).leaf = true + + entry({"admin", "services", "shadowsocks-libev", "status"}, call("ss_status"), nil).leaf = true + +end + +function ss_status() + local ut = require "luci.util" + local rv = ut.ubus("service", "list", {name = "shadowsocks-libev"})["shadowsocks-libev"] or {_=0} + + luci.http.prepare_content("application/json") + luci.http.write_json(rv) +end diff --git a/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instance-details.lua b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instance-details.lua new file mode 100644 index 000000000..22f3106d0 --- /dev/null +++ b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instance-details.lua @@ -0,0 +1,53 @@ +-- Copyright 2017 Yousong Zhou +-- Licensed to the public under the Apache License 2.0. + +local ds = require "luci.dispatcher" +local ss = require "luci.model.shadowsocks-libev" + +local sname = arg[1] +local redirect_url = ds.build_url("admin/services/shadowsocks-libev/instances") +local s, o + +local m = Map("shadowsocks-libev") +local sdata = m:get(sname) +if not sdata then + luci.http.redirect(redirect_url) + return +end +local stype = sdata[".type"] +m.redirect = redirect_url +m.title = "shadowsocks-libev - %s - %s" % {stype, sname} + + +s = m:section(NamedSection, sname, stype) +s:tab("general", translate("General Settings")) +s:tab("advanced", translate("Advanced Settings")) +s:taboption("general", Flag, "disabled", translate("Disable")) +ss.option_install_package(s, "general") +ss.options_common(s, "advanced") + +if stype == "ss_server" then + ss.options_server(s, "general") + o = s:taboption("general", Value, "bind_address", + translate("Bind address"), + translate("The address ss-server will initiate connection from")) + o.datatype = "ipaddr" + o.placeholder = "0.0.0.0" + ss.values_ipaddr(o) + o = s:taboption("general", Value, "manager_address", translate("Manager address")) + o.datatype = "hostport" +else + ss.options_client(s, "general") + if stype == "ss_tunnel" then + o = s:taboption("general", Value, "tunnel_address", + translate("Tunnel address"), + translate("The address ss-tunnel will forward traffic to")) + o.datatype = "hostport" + elseif stype == "ss_redir" then + o = s:taboption("advanced", Flag, "disable_sni", + translate("Disable SNI"), + translate("Disable parsing HTTP/HTTPS payload to find then resolve hostname at remote server")) + end +end + +return m diff --git a/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instances.lua b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instances.lua new file mode 100644 index 000000000..62a90fb41 --- /dev/null +++ b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/instances.lua @@ -0,0 +1,104 @@ +-- Copyright 2017 Yousong Zhou +-- Licensed to the public under the Apache License 2.0. + +local ds = require "luci.dispatcher" +local ss = require "luci.model.shadowsocks-libev" +local ut = require "luci.util" +local m, s, o + +m = Map("shadowsocks-libev", + translate("Local Instances"), + translate("Instances of shadowsocks-libev components, e.g. ss-local, \ + ss-redir, ss-tunnel, ss-server, etc. To enable an instance it \ + is required to enable both the instance itself and the remote \ + server it refers to.")) + +local instances = {} +local cfgtypes = { "ss_local", "ss_redir", "ss_server", "ss_tunnel" } + +for sname, sdata in pairs(m:get()) do + local key, value = ss.cfgvalue_overview(sdata) + if key ~= nil then + instances[key] = value + end +end + +s = m:section(Table, instances) +s.addremove = true +s.template_addremove = "shadowsocks-libev/add_instance" +s.extedit = function(self, section) + local value = instances[section] + if type(value) == "table" then + return ds.build_url(unpack(ds.context.requestpath), + "services/shadowsocks-libev/instances", + value[".name"]) + end +end +s.parse = function(self, ...) + Table.parse(self, ...) + + local crval = REMOVE_PREFIX .. self.config + local name = self.map:formvaluetable(crval) + for k,v in pairs(name) do + local value = instances[k] + local sname = value[".name"] + if type(value) == "table" then + m:del(sname) + instances[k] = nil + for _, oname in ipairs({"redir_tcp", "redir_udp"}) do + local ovalue = m:get("ss_rules", oname) + if ovalue == sname then + m:del("ss_rules", oname) + end + end + end + end + + local stype = m:formvalue("_newinst.type") + local sname = m:formvalue("_newinst.name") + if ut.contains(cfgtypes, stype) then + local created + if sname and #sname > 0 then + created = m:set(sname, nil, stype) + else + created = m:add(stype) + sname = created + end + if created then + m.uci:save("shadowsocks-libev") + luci.http.redirect(ds.build_url( + "admin/services/shadowsocks-libev/instances", sname + )) + end + end +end + +o = s:option(DummyValue, "name", translate("Name")) +o.rawhtml = true +o = s:option(DummyValue, "overview", translate("Overview")) +o.rawhtml = true + +s:option(DummyValue, "running", translate("Running")) + +o = s:option(Button, "disabled", translate("Enable/Disable")) +o.render = function(self, section, scope) + if instances[section].disabled then + self.title = translate("Disabled") + self.inputstyle = "reset" + else + self.title = translate("Enabled") + self.inputstyle = "save" + end + Button.render(self, section, scope) +end +o.write = function(self, section) + local sdata = instances[section] + if type(sdata) == "table" then + local sname = sdata[".name"] + local disabled = not sdata["disabled"] + sdata["disabled"] = disabled + m:set(sname, "disabled", tostring(disabled)) + end +end + +return m diff --git a/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/rules.lua b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/rules.lua new file mode 100644 index 000000000..8fab133a5 --- /dev/null +++ b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/rules.lua @@ -0,0 +1,110 @@ +-- Copyright 2017 Yousong Zhou +-- Licensed to the public under the Apache License 2.0. + +local ss = require("luci.model.shadowsocks-libev") + +local m, s, o + +m = Map("shadowsocks-libev", + translate("Redir Rules"), + translate("On this page you can configure how traffics are to be \ + forwarded to ss-redir instances. \ + If enabled, packets will first have their src ip addresses checked \ + against Src ip/net bypass, Src ip/net forward, \ + Src ip/net checkdst and if none matches Src default \ + will give the default action to be taken. \ + If the prior check results in action checkdst, packets will continue \ + to have their dst addresses checked.")) + +local sdata = m:get('ss_rules') +if not sdata then + m:set('ss_rules', nil, 'ss_rules') + m:set('ss_rules', 'disabled', "1") +end + +function src_dst_option(s, ...) + local o = s:taboption(...) + --o.datatype = "or(ip4addr,cidr4)" + o.datatype = "ip4addr" +end + +s = m:section(NamedSection, "ss_rules", "ss_rules") +s:tab("general", translate("General Settings")) +s:tab("src", translate("Source Settings")) +s:tab("dst", translate("Destination Settings")) + +s:taboption('general', Flag, "disabled", translate("Disable")) +ss.option_install_package(s, 'general') + +o = s:taboption('general', ListValue, "redir_tcp", + translate("ss-redir for TCP")) +ss.values_redir(o, 'tcp') +o = s:taboption('general', ListValue, "redir_udp", + translate("ss-redir for UDP")) +ss.values_redir(o, 'udp') + +o = s:taboption('general', ListValue, "local_default", + translate("Local-out default"), + translate("Default action for locally generated TCP packets")) +ss.values_actions(o) +o = s:taboption('general', DynamicList, "ifnames", + translate("Ingress interfaces"), + translate("Only apply rules on packets from these network interfaces")) +ss.values_ifnames(o) +s:taboption('general', Value, "ipt_args", + translate("Extra arguments"), + translate("Passes additional arguments to iptables. Use with care!")) + +src_dst_option(s, 'src', DynamicList, "src_ips_bypass", + translate("Src ip/net bypass"), + translate("Bypass ss-redir for packets with src address in this list")) +src_dst_option(s, 'src', DynamicList, "src_ips_forward", + translate("Src ip/net forward"), + translate("Forward through ss-redir for packets with src address in this list")) +src_dst_option(s, 'src', DynamicList, "src_ips_checkdst", + translate("Src ip/net checkdst"), + translate("Continue to have dst address checked for packets with src address in this list")) +o = s:taboption('src', ListValue, "src_default", + translate("Src default"), + translate("Default action for packets whose src address do not match any of the src ip/net list")) +ss.values_actions(o) + +src_dst_option(s, 'dst', DynamicList, "dst_ips_bypass", + translate("Dst ip/net bypass"), + translate("Bypass ss-redir for packets with dst address in this list")) +src_dst_option(s, 'dst', DynamicList, "dst_ips_forward", + translate("Dst ip/net forward"), + translate("Forward through ss-redir for packets with dst address in this list")) + +o = s:taboption('dst', FileBrowser, "dst_ips_bypass_file", + translate("Dst ip/net bypass file"), + translate("File containing ip/net for the purposes as with Dst ip/net bypass")) +o.datatype = "file" +s:taboption('dst', FileBrowser, "dst_ips_forward_file", + translate("Dst ip/net forward file"), + translate("File containing ip/net for the purposes as with Dst ip/net forward")) +o.datatype = "file" +o = s:taboption('dst', ListValue, "dst_default", + translate("Dst default"), + translate("Default action for packets whose dst address do not match any of the dst ip list")) +ss.values_actions(o) + +local installed = os.execute("iptables -m recent -h &>/dev/null") == 0 +if installed then + o = s:taboption('dst', Flag, "dst_forward_recentrst") +else + m:set('ss_rules', 'dst_forward_recentrst', "0") + o = s:taboption("dst", Button, "_install") + o.inputtitle = translate("Install package iptables-mod-conntrack-extra") + o.inputstyle = "apply" + o.write = function() + return luci.http.redirect( + luci.dispatcher.build_url("admin/system/packages") .. + "?submit=1&install=iptables-mod-conntrack-extra" + ) + end +end +o.title = translate("Forward recentrst") +o.description = translate("Forward those packets whose dst have recently sent to us multiple tcp-rst") + +return m diff --git a/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/servers.lua b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/servers.lua new file mode 100644 index 000000000..71c66562e --- /dev/null +++ b/luci-app-shadowsocks-libev/luasrc/model/cbi/shadowsocks-libev/servers.lua @@ -0,0 +1,31 @@ +-- Copyright 2017 Yousong Zhou +-- Licensed to the public under the Apache License 2.0. + +local ds = require "luci.dispatcher" +local ss = require("luci.model.shadowsocks-libev") + +local m, s + +m = Map("shadowsocks-libev", + translate("Remote Servers"), + translate("Definition of remote shadowsocks servers. \ + Disable any of them will also disable instances refering to it.")) + +local sname = arg[1] +if sname then + if not m:get(sname) then + luci.http.redirect(ds.build_url("admin/services/shadowsocks-libev/servers")) + return + end + s = m:section(NamedSection, sname, "server") + m.title = m.title .. ' - ' .. sname +else + s = m:section(TypedSection, "server") + s.template = 'cbi/tblsection' + s.addremove = true +end + +s:option(Flag, "disabled", translate("Disable")) +ss.options_server(s) + +return m diff --git a/luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua b/luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua new file mode 100644 index 000000000..e8a0cf03e --- /dev/null +++ b/luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua @@ -0,0 +1,265 @@ +-- Copyright 2017 Yousong Zhou +-- Licensed to the public under the Apache License 2.0. + +local _up = getfenv(3) +local ut = require("luci.util") +local sys = require("luci.sys") +local ds = require("luci.dispatcher") +local nw = require("luci.model.network") +nw.init() +module("luci.model.shadowsocks-libev", function(m) + setmetatable(m, {__index=function (self, k) + local tb = _up + return rawget(self, k) or _up[k] + end}) +end) + +function values_actions(o) + o:value("bypass") + o:value("forward") + if o.option ~= "dst_default" then + o:value("checkdst") + end +end + +function values_redir(o, xmode) + o.map.uci.foreach("shadowsocks-libev", "ss_redir", function(sdata) + local disabled = ucival_to_bool(sdata["disabled"]) + local sname = sdata[".name"] + local mode = sdata["mode"] or "tcp_only" + if not disabled and mode:find(xmode) then + local desc = "%s - %s" % {sname, mode} + o:value(sname, desc) + end + end) + o:value("", "") + o.default = "" +end + +function values_serverlist(o) + o.map.uci.foreach("shadowsocks-libev", "server", function(sdata) + local sname = sdata[".name"] + local server = sdata["server"] + local server_port = sdata["server_port"] + if server and server_port then + local desc = "%s - %s:%s" % {sname, sdata["server"], sdata["server_port"]} + o:value(sname, desc) + end + end) +end + +function values_ipaddr(o) + for _, v in ipairs(nw:get_interfaces()) do + for _, a in ipairs(v:ipaddrs()) do + o:value(a:host():string(), '%s (%s)' %{ a:host(), v:shortname() }) + end + end +end + +function values_ifnames(o) + for _, v in ipairs(sys.net.devices()) do + o:value(v) + end +end + +function options_client(s, tab) + local o + + o = s:taboption(tab, ListValue, "server", translate("Remote server")) + values_serverlist(o) + o = s:taboption(tab, Value, "local_address", translate("Local address")) + o.datatype = "ipaddr" + o.placeholder = "0.0.0.0" + values_ipaddr(o) + o = s:taboption(tab, Value, "local_port", translate("Local port")) + o.datatype = "port" +end + +function options_server(s, tab) + local o + local optfunc + + if tab == nil then + optfunc = function(...) return s:option(...) end + else + optfunc = function(...) return s:taboption(tab, ...) end + end + + o = optfunc(Value, "server", translate("Server")) + o.datatype = "host" + o.size = 16 + o = optfunc(Value, "server_port", translate("Server port")) + o.datatype = "port" + o.size = 5 + o = optfunc(ListValue, "method", translate("Method")) + for _, m in ipairs(methods) do + o:value(m) + end + o = optfunc(Value, "key", translate("Key (base64 encoding)")) + o.datatype = "base64" + o.password = true + o.size = 12 + o = optfunc(Value, "password", translate("Password")) + o.password = true + o.size = 12 +end + +function options_common(s, tab) + local o + + o = s:taboption(tab, ListValue, "mode", translate("Mode of operation")) + for _, m in ipairs(modes) do + o:value(m) + end + o.default = "tcp_and_udp" + o = s:taboption(tab, Value, "mtu", translate("MTU")) + o.datatype = "uinteger" + o = s:taboption(tab, Value, "timeout", translate("Timeout (sec)")) + o.datatype = "uinteger" + s:taboption(tab, Value, "user", translate("Run as")) + + s:taboption(tab, Flag, "verbose", translate("Verbose")) + s:taboption(tab, Flag, "ipv6_first", translate("IPv6 First"), translate("Prefer IPv6 addresses when resolving names")) + s:taboption(tab, Flag, "fast_open", translate("Enable TCP Fast Open")) + s:taboption(tab, Flag, "reuse_port", translate("Enable SO_REUSEPORT")) + s:taboption(tab, Flag, "mptcp", translate("Enable MPTCP")) +end + +function ucival_to_bool(val) + return val == "true" or val == "1" or val == "yes" or val == "on" +end + +function cfgvalue_overview(sdata) + local stype = sdata[".type"] + local lines = {} + + if stype == "ss_server" then + cfgvalue_overview_(sdata, lines, names_options_server) + cfgvalue_overview_(sdata, lines, names_options_common) + cfgvalue_overview_(sdata, lines, { + "bind_address", + "manager_address", + }) + elseif stype == "ss_local" or stype == "ss_redir" or stype == "ss_tunnel" then + cfgvalue_overview_(sdata, lines, names_options_client) + if stype == "ss_tunnel" then + cfgvalue_overview_(sdata, lines, {"tunnel_address"}) + elseif stype == "ss_redir" then + cfgvalue_overview_(sdata, lines, {"disable_sni"}) + end + cfgvalue_overview_(sdata, lines, names_options_common) + else + return nil, nil + end + local sname = sdata[".name"] + local key = "%s.%s" % {stype, sname} + local value = { + [".name"] = sname, + name = '%s.%s' % {stype, sname}, + overview = table.concat(lines, "
"), + disabled = ucival_to_bool(sdata["disabled"]), + } + return key, value +end + +function cfgvalue_overview_(sdata, lines, names) + local line + + for _, n in ipairs(names) do + local v = sdata[n] + if v ~= nil then + if n == "key" or n == "password" then + v = translate("") + end + local fv = "%s" % ut.pcdata(v) + if sdata[".type"] ~= "ss_server" and n == "server" then + fv = '%s' % { + ds.build_url("admin/services/shadowsocks-libev/servers", v), fv} + end + line = n .. ": " .. fv + table.insert(lines, line) + end + end +end + +function option_install_package(s, tab) + local bin = s.sectiontype:gsub("_", "-", 1) + local installed = nixio.fs.access("/usr/bin/" .. bin) + if installed then + return + end + local opkg_package = "shadowsocks-libev-" .. bin + local p_install + if tab then + p_install = s:taboption(tab, Button, "_install") + else + p_install = s:option(Button, "_install") + end + p_install.title = translate("Package is not installed") + p_install.inputtitle = translate("Install package %q" % opkg_package) + p_install.inputstyle = "apply" + + function p_install.write() + return luci.http.redirect( + luci.dispatcher.build_url("admin/system/packages") .. + "?submit=1&install=%s" % opkg_package + ) + end +end + +names_options_server = { + "server", + "server_port", + "method", + "key", + "password", +} + +names_options_client = { + "server", + "local_address", + "local_port", +} + +names_options_common = { + "verbose", + "ipv6_first", + "fast_open", + "reuse_port", + "mode", + "mtu", + "timeout", + "user", +} + +modes = { + "tcp_only", + "tcp_and_udp", + "udp_only", +} + +methods = { + -- aead + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "chacha20-ietf-poly1305", + "xchacha20-ietf-poly1305", + -- stream + "table", + "rc4", + "rc4-md5", + "aes-128-cfb", + "aes-192-cfb", + "aes-256-cfb", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "bf-cfb", + "camellia-128-cfb", + "camellia-192-cfb", + "camellia-256-cfb", + "salsa20", + "chacha20", + "chacha20-ietf", +} diff --git a/luci-app-shadowsocks-libev/luasrc/view/shadowsocks-libev/add_instance.htm b/luci-app-shadowsocks-libev/luasrc/view/shadowsocks-libev/add_instance.htm new file mode 100644 index 000000000..219d89b07 --- /dev/null +++ b/luci-app-shadowsocks-libev/luasrc/view/shadowsocks-libev/add_instance.htm @@ -0,0 +1,45 @@ +
+
+ + + + + + +
+ + + + + +
+
+ diff --git a/luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev b/luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev new file mode 100644 index 000000000..6f30fa77b --- /dev/null +++ b/luci-app-shadowsocks-libev/root/etc/uci-defaults/40_luci-shadowsocks-libev @@ -0,0 +1,11 @@ +#!/bin/sh + +uci -q batch <<-EOF >/dev/null + delete ucitrack.@shadowsocks-libev[-1] + add ucitrack shadowsocks-libev + set ucitrack.@shadowsocks-libev[-1].init=shadowsocks-libev + commit ucitrack +EOF + +rm -f /tmp/luci-indexcache +exit 0 diff --git a/shadowsocks-libev/Makefile b/shadowsocks-libev/Makefile new file mode 100644 index 000000000..cd8b9fdca --- /dev/null +++ b/shadowsocks-libev/Makefile @@ -0,0 +1,128 @@ +# +# Copyright (C) 2017-2018 Yousong Zhou +# Copyright (C) 2018 Ycarus (Yannick Chabanois) +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +# Checklist when bumping versions +# +# - update cipher list by checking src/crypto.c:crypto_init() +# - check if default mode has changed from being tcp_only +# +PKG_NAME:=shadowsocks-libev +PKG_VERSION:=3.1.2 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=https://github.com/shadowsocks/shadowsocks-libev/releases/download/v$(PKG_VERSION) +PKG_HASH:=3611f09793af923d988ecbd15ad89fb66839e51a0059685d8f88c0131658e4a7 + +PKG_MAINTAINER:=Yousong Zhou + +PKG_LICENSE:=GPL-3.0+ +PKG_LICENSE_FILES:=LICENSE + +PKG_FIXUP:=autoreconf +PKG_INSTALL:=1 +PKG_USE_MIPS16:=0 +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + + +define Package/shadowsocks-libev-config + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + TITLE:=shadowsocks-libev config scripts + URL:=https://github.com/shadowsocks/shadowsocks-libev +endef + +define Package/shadowsocks-libev-config/install + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_DATA) ./files/shadowsocks-libev.config $(1)/etc/config/shadowsocks-libev + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/shadowsocks-libev.init $(1)/etc/init.d/shadowsocks-libev + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./files/shadowsocks.track $(1)/usr/bin/ss-redir-track +endef + + +define Package/shadowsocks-libev/Default + define Package/shadowsocks-libev-$(1) + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + TITLE:=shadowsocks-libev $(1) + URL:=https://github.com/shadowsocks/shadowsocks-libev + DEPENDS:=+libcares +libev +libmbedtls +libpcre +libpthread +libsodium +shadowsocks-libev-config +zlib + endef + + define Package/shadowsocks-libev-$(1)/install + $$(INSTALL_DIR) $$(1)/usr/bin + $$(INSTALL_BIN) $$(PKG_INSTALL_DIR)/usr/bin/$(1) $$(1)/usr/bin + endef + +endef + +SHADOWSOCKS_COMPONENTS:=ss-local ss-redir ss-tunnel ss-server +define shadowsocks-libev/templates + $(foreach component,$(SHADOWSOCKS_COMPONENTS), + $(call Package/shadowsocks-libev/Default,$(component)) + ) +endef +$(eval $(call shadowsocks-libev/templates)) + + +define Package/shadowsocks-libev-ss-rules + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + TITLE:=shadowsocks-libev ss-rules + URL:=https://github.com/shadowsocks/shadowsocks-libev + DEPENDS:=+ip +ipset +iptables-mod-tproxy +resolveip +shadowsocks-libev-ss-redir +shadowsocks-libev-config +endef + +define Package/shadowsocks-libev-ss-rules/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./files/ss-rules $(1)/usr/bin + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_DATA) ./files/firewall.ss-rules $(1)/etc + $(INSTALL_BIN) ./files/ss-rules.defaults $(1)/etc/uci-defaults +endef + +define Package/shadowsocks-libev-ss-rules/prerm +#!/bin/sh +s=firewall.ss_rules +uci get "$$s" >/dev/null || exit 0 +uci batch <<-EOF + delete $$s + commit firewall +EOF +endef + +define Build/Prepare + $(call Build/Prepare/Default) + $(FIND) $(PKG_BUILD_DIR) \ + -name '*.o' \ + -o -name '*.lo' \ + -o -name '.deps' \ + -o -name '.libs' \ + | $(XARGS) rm -rvf +endef + +CONFIGURE_ARGS += \ + --disable-documentation \ + --disable-silent-rules \ + --disable-assert \ + --disable-ssp \ + +$(eval $(call BuildPackage,shadowsocks-libev-config)) +$(eval $(call BuildPackage,shadowsocks-libev-ss-rules)) +$(foreach component,$(SHADOWSOCKS_COMPONENTS), \ + $(eval $(call BuildPackage,shadowsocks-libev-$(component))) \ +) diff --git a/shadowsocks-libev/README.md b/shadowsocks-libev/README.md new file mode 100644 index 000000000..ada71208b --- /dev/null +++ b/shadowsocks-libev/README.md @@ -0,0 +1,97 @@ +## components + +`ss-local` provides SOCKS5 proxy with UDP associate support. + + socks5 ss plain + --------> tcp:local_address:local_port ----> ss server -------> dest + +`ss-redir`. The REDIRECT and TPROXY part are to be provided by `ss-rules` script. REDIRECT only works for tcp traffic (see also darkk/redsocks). TPROXY is used to proxy udp messages, but it's only available in the PREROUTING chain and as such cannot proxy local out traffic. + + plain plain ss plain + ---------> REDIRECT ------> tcp:local_address:local_port ----> ss server -----> original dest + + plain plain ss plain + ---------> TPROXY -------> udp:local_address:local_port -----> ss server -----> original dest + +`ss-tunnel` provides ssh `-L` local-forwarding-like tunnel. Typically it's used to tunnel DNS traffic to the remote. + + plain ss plain + ---------> tcp|udp:local_address:local_port ------> ss server -------> tunnel_address + +`ss-server`, the "ss server" in the above diagram + +## uci + +Option names are the same as those used in json config files. Check `validate_xxx` func definition of the [service script](files/shadowsocks-libev.init) and shadowsocks-libev's own documentation for supported options and expected value types. A [sample config file](files/shadowsocks-libev.config) is also provided for reference. + +Every section have a `disabled` option to temporarily turn off the component instance or component instances referring to it. + +Section type `server` is for definition of remote shadowsocks servers. They will be referred to from other component sections and as such should be named (as compared to anonymous section). + +Section type `ss_local`, `ss_redir`, `ss_tunnel` are for specification of shadowsocks-libev components. They share mostly a common set of options like `local_port`, `verbose`, `fast_open`, `timeout`, etc. + +We can have multiple instances of component and `server` sections. The relationship between them is many-to-one. This will have the following implications + + - It's possible to have both `ss_local` and `ss_redir` referring to the same `server` definition + - It's possible to have multiple instances of `ss_redir` listening on the same address:port with `reuse_port` enabled referring to the same or different `server` sections + +`ss_rules` section is for configuring the behaviour of `ss-rules` script. There can only exist at most one such section with the name also being `ss_rules` + + redir_tcp name of ss_redir section with mode tcp_only or tcp_and_udp + redir_udp name of ss_redir section with mode udp_only or tcp_and_udp + ifnames only apply rules on packets from these ifnames + + --- for incoming packets having source address in + + src_ips_bypass will bypass the redir chain + src_ips_forward will always go through the redir chain + src_ips_checkdst will continue to have their destination addresses checked + + --- otherwise, the default action can be specified with + + src_default bypass, forward, [checkdst] + + --- if the previous check result is checkdst, + --- then packets having destination address in + + dst_ips_bypass_file + dst_ips_bypass will bypass the redir chain + dst_ips_forward_file + dst_ips_forward will go through the redir chain + + --- otherwise, the default action can be specified with + + dst_default [bypass], forward + + --- for local out tcp packets, the default action can be specified with + + local_default [bypass], forward, checkdst + +Bool option `dst_forward_recentrst` requires iptables/netfilter `recent` match module (`opkg install iptables-mod-conntrack-extra`). When enabled, `ss-rules` will setup iptables rules to forward through `ss-redir` those packets whose destination have recently sent to us multiple tcp-rst. + +ss-rules uses kernel ipset mechanism for storing addresses/networks. Those ipsets are also part of the API and can be populated by other programs, e.g. dnsmasq with builtin ipset support. For more details please read output of `ss-rules --help` + +Note also that `src_ips_xx` and `dst_ips_xx` actually also accepts cidr network representation. Option names are retained in its current form for backward compatibility coniderations + +## notes and faq + +Useful paths and commands for debugging + + # check current running status + ubus call service list '{"name": "shadowsocks-libev"}' + ubus call service list '{"name": "shadowsocks-libev", "verbose": true}' + + # dump validate definition + ubus call service validate '{"package": "shadowsocks-libev"}' + ubus call service validate '{"package": "shadowsocks-libev"}' \ + | jsonfilter -e '$["shadowsocks-libev"]["ss_tunnel"]' + + # check json config + ls -l /var/etc/shadowsocks-libev/ + + # set uci config option verbose to 1, restart the service and follow the log + logread -f + +ss-redir needs to open a new socket and setsockopt IP_TRANSPARENT when sending udp reply to client. This requires `CAP_NET_ADMIN` and as such the process cannot run as `nobody` + +ss-local, ss-redir, etc. supports specifying an array of remote ss server, but supporting this in uci seems to be overkill. The workaround can be defining multiple `server` sections and multiple `ss-redir` instances with `reuse_port` enabled diff --git a/shadowsocks-libev/files/firewall.ss-rules b/shadowsocks-libev/files/firewall.ss-rules new file mode 100644 index 000000000..3a1d32cdc --- /dev/null +++ b/shadowsocks-libev/files/firewall.ss-rules @@ -0,0 +1,2 @@ +#!/bin/sh +/etc/init.d/shadowsocks-libev reload diff --git a/shadowsocks-libev/files/shadowsocks-libev.config b/shadowsocks-libev/files/shadowsocks-libev.config new file mode 100644 index 000000000..cdb1dac11 --- /dev/null +++ b/shadowsocks-libev/files/shadowsocks-libev.config @@ -0,0 +1,33 @@ +config ss_redir hi + option disabled 1 + option server 'sss0' + option local_address '0.0.0.0' + option local_port '1100' + option mode 'tcp_and_udp' + option timeout '60' + option fast_open 1 + option verbose 1 + option reuse_port 1 + option mptcp 1 + +config ss_rules 'ss_rules' + option disabled 1 + option redir_tcp 'hi' + option redir_udp 'hi' + option src_default 'forward' + option dst_default 'forward' + option local_default 'forward' + list dst_ips_forward '8.8.8.8' + +config server 'sss0' + option disabled 1 + option server '192.168.1.3' + option server_port '65101' + option password '********' + option method 'aes-256-cfb' + +config ss_tunnel 'dns' + option mode 'tcp_and_udp' + option server 'sss0' + option local_port '5353' + option tunnel_address '8.8.8.8:53' \ No newline at end of file diff --git a/shadowsocks-libev/files/shadowsocks-libev.init b/shadowsocks-libev/files/shadowsocks-libev.init new file mode 100644 index 000000000..5ffb7ee2b --- /dev/null +++ b/shadowsocks-libev/files/shadowsocks-libev.init @@ -0,0 +1,321 @@ +#!/bin/sh /etc/rc.common +# +# Copyright (C) 2017 Yousong Zhou +# +# This is free software, licensed under the GNU General Public License v3. +# See /LICENSE for more information. +# + +USE_PROCD=1 +START=99 + +ss_confdir=/var/etc/shadowsocks-libev +ss_bindir=/usr/bin +q='"' + +ss_mkjson() { + echo "{" >"$confjson" + if ss_mkjson_ "$@" >>$confjson; then + sed -i -e '/^\s*$/d' -e '2,$s/^/\t/' -e '$s/,$//' "$confjson" + echo "}" >>"$confjson" + else + rm -f "$confjson" + return 1 + fi +} + +ss_mkjson_() { + local func + + for func in "$@"; do + "$func" || return 1 + done +} + +ss_mkjson_server_conf() { + local cfgserver + + config_get cfgserver "$cfg" server + [ -n "$cfgserver" ] || return 1 + eval "$(validate_server_section "$cfg" ss_validate_mklocal)" + validate_server_section "$cfgserver" || return 1 + [ "$disabled" = 0 ] || return 1 + ss_mkjson_server_conf_ "$cfgserver" +} + +ss_mkjson_server_conf_() { + [ -n "$server_port" ] || return 1 + password="${password//\"/\\\"}" + cat <<-EOF + ${server:+${q}server${q}: ${q}$server${q},} + "server_port": $server_port, + ${method:+${q}method${q}: ${q}$method${q},} + ${key:+${q}key${q}: ${q}$key${q},} + ${password:+${q}password${q}: ${q}$password${q},} + EOF +} + +ss_mkjson_common_conf() { + [ "$ipv6_first" = 0 ] && ipv6_first=false || ipv6_first=true + [ "$fast_open" = 0 ] && fast_open=false || fast_open=true + [ "$reuse_port" = 0 ] && reuse_port=false || reuse_port=true + [ "$mptcp" = 0 ] && mptcp=false || mptcp=true + cat <<-EOF + "use_syslog": true, + "ipv6_first": $ipv6_first, + "fast_open": $fast_open, + "mptcp": $mptcp, + "reuse_port": $reuse_port, + ${local_address:+${q}local_address${q}: ${q}$local_address${q},} + ${local_port:+${q}local_port${q}: $local_port,} + ${mode:+${q}mode${q}: ${q}$mode${q},} + ${mtu:+${q}mtu${q}: $mtu,} + ${timeout:+${q}timeout${q}: $timeout,} + ${user:+${q}user${q}: ${q}$user${q},} + EOF +} + +ss_mkjson_ss_local_conf() { + ss_mkjson_server_conf +} + +ss_mkjson_ss_redir_conf() { + ss_mkjson_server_conf || return 1 + [ "$disable_sni" = 0 ] && disable_sni=false || disable_sni=true + cat <<-EOF + "disable_sni": $disable_sni, + EOF +} + +ss_mkjson_ss_server_conf() { + ss_mkjson_server_conf_ +} + +ss_mkjson_ss_tunnel_conf() { + ss_mkjson_server_conf || return 1 + [ -n "$tunnel_address" ] || return 1 + cat <<-EOF + ${tunnel_address:+${q}tunnel_address${q}: ${q}$tunnel_address${q},} + EOF +} + +ss_xxx() { + local cfg="$1" + local cfgtype="$2" + local bin="$ss_bindir/${cfgtype/_/-}" + local confjson="$ss_confdir/$cfgtype.$cfg.json" + + [ -x "$bin" ] || return + eval "$("validate_${cfgtype}_section" "$cfg" ss_validate_mklocal)" + "validate_${cfgtype}_section" "$cfg" || return 1 + [ "$disabled" = 0 ] || return + + if ss_mkjson \ + ss_mkjson_common_conf \ + ss_mkjson_${cfgtype}_conf \ + ; then + procd_open_instance "$cfgtype.$cfg" + procd_set_param command "$bin" -c "$confjson" + [ "$verbose" = 0 ] || procd_append_param command -v + [ "$no_delay" = 0 ] || procd_append_param command --no-delay + [ -z "$bind_address" ] || procd_append_param command -b "$bind_address" + [ -z "$manager_address" ] || procd_append_param command --manager-address "$manager_address" + procd_set_param file "$confjson" + procd_set_param limits nofile="51200 51200" + procd_set_param respawn + procd_close_instance + ss_rules_cb + fi +} + +ss_rules_cb() { + local cfgserver server + + if [ "$cfgtype" = ss_redir ]; then + config_get cfgserver "$cfg" server + config_get server "$cfgserver" server + ss_redir_servers="$ss_redir_servers $server" + if [ "$mode" = tcp_only -o "$mode" = "tcp_and_udp" ]; then + eval "ss_rules_redir_tcp_$cfg=$local_port" + fi + if [ "$mode" = udp_only -o "$mode" = "tcp_and_udp" ]; then + eval "ss_rules_redir_udp_$cfg=$local_port" + fi + fi +} + +ss_rules() { + local cfg="ss_rules" + local bin="$ss_bindir/ss-rules" + local cfgtype + local local_port_tcp local_port_udp + local args + + [ -x "$bin" ] || return 1 + config_get cfgtype "$cfg" TYPE + [ "$cfgtype" = ss_rules ] || return 1 + + eval "$(validate_ss_rules_section "$cfg" ss_validate_mklocal)" + validate_ss_rules_section "$cfg" || return 1 + [ "$disabled" = 0 ] || return 1 + + eval local_port_tcp="\$ss_rules_redir_tcp_$redir_tcp" + eval local_port_udp="\$ss_rules_redir_udp_$redir_udp" + [ -n "$local_port_tcp" -o -n "$local_port_udp" ] || return 1 + ss_redir_servers="$(echo "$ss_redir_servers" | tr ' ' '\n' | sort -u)" + [ "$dst_forward_recentrst" = 0 ] || args="$args --dst-forward-recentrst" + + "$bin" \ + -s "$ss_redir_servers" \ + -l "$local_port_tcp" \ + -L "$local_port_udp" \ + --src-default "$src_default" \ + --dst-default "$dst_default" \ + --local-default "$local_default" \ + --dst-bypass-file "$dst_ips_bypass_file" \ + --dst-forward-file "$dst_ips_forward_file" \ + --dst-bypass "$dst_ips_bypass" \ + --dst-forward "$dst_ips_forward" \ + --src-bypass "$src_ips_bypass" \ + --src-forward "$src_ips_forward" \ + --src-checkdst "$src_ips_checkdst" \ + --ifnames "$ifnames" \ + --ipt-extra "$ipt_args" \ + $args \ + || "$bin" -f +} + +start_service() { + local cfgtype + + mkdir -p "$ss_confdir" + config_load shadowsocks-libev + for cfgtype in ss_local ss_redir ss_server ss_tunnel; do + config_foreach ss_xxx "$cfgtype" "$cfgtype" + done + ss_rules +} + +stop_service() { + local bin="$ss_bindir/ss-rules" + + [ -x "$bin" ] && "$bin" -f + rm -rf "$ss_confdir" +} + +service_triggers() { + procd_add_reload_interface_trigger wan + procd_add_reload_trigger shadowsocks-libev + procd_open_validate + validate_server_section + validate_ss_local_section + validate_ss_redir_section + validate_ss_rules_section + validate_ss_server_section + validate_ss_tunnel_section + procd_close_validate +} + +ss_validate_mklocal() { + local tuple opts + + shift 2 + for tuple in "$@"; do + opts="${tuple%%:*} $opts" + done + [ -z "$opts" ] || echo "local $opts" +} + +ss_validate() { + uci_validate_section shadowsocks-libev "$@" +} + +validate_common_server_options_() { + local cfgtype="$1"; shift + local cfg="$1"; shift + local func="$1"; shift + local stream_methods='"table", "rc4", "rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "salsa20", "chacha20", "chacha20-ietf"' + local aead_methods='"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305"' + + "${func:-ss_validate}" "$cfgtype" "$cfg" "$@" \ + 'disabled:bool:0' \ + 'server:host' \ + 'server_port:port' \ + 'password:string' \ + 'key:string' \ + "method:or($stream_methods, $aead_methods)" +} + +validate_common_client_options_() { + validate_common_options_ "$@" \ + 'server:uci("shadowsocks-libev", "@server")' \ + 'local_address:host:0.0.0.0' \ + 'local_port:port' +} + +validate_common_options_() { + local cfgtype="$1"; shift + local cfg="$1"; shift + local func="$1"; shift + + "${func:-ss_validate}" "$cfgtype" "$cfg" "$@" \ + 'disabled:bool:0' \ + 'fast_open:bool:0' \ + 'ipv6_first:bool:0' \ + 'no_delay:bool:0' \ + 'reuse_port:bool:0' \ + 'mptcp:bool:0' \ + 'verbose:bool:0' \ + 'mode:or("tcp_only", "udp_only", "tcp_and_udp"):tcp_only' \ + 'mtu:uinteger' \ + 'timeout:uinteger' \ + 'user:string' +} + +validate_server_section() { + validate_common_server_options_ server "$1" "${2}" +} + +validate_ss_local_section() { + validate_common_client_options_ ss_local "$1" "${2}" +} + +validate_ss_redir_section() { + validate_common_client_options_ ss_redir "$1" \ + "${2}" \ + 'disable_sni:bool:0' +} + +validate_ss_rules_section() { + "${2:-ss_validate}" ss_rules "$1" \ + 'disabled:bool:0' \ + 'redir_tcp:uci("shadowsocks-libev", "@ss_redir")' \ + 'redir_udp:uci("shadowsocks-libev", "@ss_redir")' \ + 'src_ips_bypass:or(ip4addr,cidr4)' \ + 'src_ips_forward:or(ip4addr,cidr4)' \ + 'src_ips_checkdst:or(ip4addr,cidr4)' \ + 'dst_ips_bypass_file:file' \ + 'dst_ips_bypass:or(ip4addr,cidr4)' \ + 'dst_ips_forward_file:file' \ + 'dst_ips_forward:or(ip4addr,cidr4)' \ + 'src_default:or("bypass", "forward", "checkdst"):checkdst' \ + 'dst_default:or("bypass", "forward"):bypass' \ + 'local_default:or("bypass", "forward", "checkdst"):bypass' \ + 'dst_forward_recentrst:bool:0' \ + 'ifnames:maxlength(15)' \ + 'ipt_args:string' +} + +validate_ss_server_section() { + validate_common_server_options_ ss_server "$1" \ + validate_common_options_ \ + "${2}" \ + 'bind_address:ipaddr' \ + 'manager_address:host' +} + +validate_ss_tunnel_section() { + validate_common_client_options_ ss_tunnel "$1" \ + "${2}" \ + 'tunnel_address:regex(".+\:[0-9]+")' +} diff --git a/shadowsocks-libev/files/shadowsocks.track b/shadowsocks-libev/files/shadowsocks.track new file mode 100755 index 000000000..90ea901ca --- /dev/null +++ b/shadowsocks-libev/files/shadowsocks.track @@ -0,0 +1,50 @@ +#!/bin/sh +# vim: set noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 : + +name=$0 +basename="$(basename $0)" + +usage() { + printf "Usage: %s: [-t TIMEOUT] [-v INTERVAL] [ -r RETRY ] HOST\n" "${name}" + exit 2 +} + +log() { + logger -p daemon.info -t "${basename}" "$@" +} + +uri="ping" +timeout=5 +interval=1 +retry=0 + +while getopts "t:v:r:" opt; do + case $opt in + t) timeout="${OPTARG}";; + v) interval="${OPTARG}";; + r) retry="${OPTARG}";; + *) usage;; + esac +done + +shift $((OPTIND - 1)) + +[ -z "$1" ] && usage + +last=0 + +log "Starting..." + +while true; do + if curl -s --max-time "${timeout}" --retry "${retry}" "$1/${uri}" &>/dev/null ; then + [ ${last} = 0 ] && log "Shadowsocks is up" + /etc/init.d/shadowsocks ifup 2> /dev/null + last=1 + else + [ ${last} = 1 ] && log "Shadowsocks is down" + /etc/init.d/shadowsocks ifdown 2> /dev/null + last=0 + fi + + sleep "${interval}" +done diff --git a/shadowsocks-libev/files/ss-rules b/shadowsocks-libev/files/ss-rules new file mode 100755 index 000000000..b0a30606e --- /dev/null +++ b/shadowsocks-libev/files/ss-rules @@ -0,0 +1,264 @@ +#!/bin/sh -e +# +# Copyright (C) 2017 Yousong Zhou +# +# The design idea was derived from ss-rules by Jian Chang +# +# This is free software, licensed under the GNU General Public License v3. +# See /LICENSE for more information. +# + +ss_rules_usage() { + cat >&2 < Local port number of ss-redir with TCP mode + -L Local port number of ss-redir with UDP mode + -s List of ip addresses of remote shadowsocks server + --ifnames Only apply rules on packets from these ifnames + --src-bypass + --src-forward + --src-checkdst + --src-default + Packets will have their src ip checked in order against + bypass, forward, checkdst list and will bypass, forward + through, or continue to have their dst ip checked + respectively on the first match. Otherwise, --src-default + decide the default action + --dst-bypass + --dst-forward + --dst-bypass-file + --dst-forward-file + --dst-default + Same as with their --src-xx equivalent + --dst-forward-recentrst + Forward those packets whose destinations have recently + sent to us multiple tcp-rst packets + --local-default + Default action for local out TCP traffic + +The following ipsets will be created by ss-rules. They are also intended to be +populated by other programs like dnsmasq with ipset support + + ss_rules_src_bypass + ss_rules_src_forward + ss_rules_src_checkdst + ss_rules_dst_bypass + ss_rules_dst_forward +EOF +} + +o_dst_bypass_=" + 0.0.0.0/8 + 10.0.0.0/8 + 100.64.0.0/10 + 127.0.0.0/8 + 169.254.0.0/16 + 172.16.0.0/12 + 192.0.0.0/24 + 192.0.2.0/24 + 192.31.196.0/24 + 192.52.193.0/24 + 192.88.99.0/24 + 192.168.0.0/16 + 192.175.48.0/24 + 198.18.0.0/15 + 198.51.100.0/24 + 203.0.113.0/24 + 224.0.0.0/4 + 240.0.0.0/4 + 255.255.255.255 +" +o_src_default=bypass +o_dst_default=bypass +o_local_default=bypass + +__errmsg() { + echo "ss-rules: $*" >&2 +} + +ss_rules_parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) ss_rules_usage; exit 0;; + -f|--flush) ss_rules_flush; exit 0;; + -l) o_redir_tcp_port="$2"; shift 2;; + -L) o_redir_udp_port="$2"; shift 2;; + -s) o_remote_servers="$2"; shift 2;; + --ifnames) o_ifnames="$2"; shift 2;; + --ipt-extra) o_ipt_extra="$2"; shift 2;; + --src-default) o_src_default="$2"; shift 2;; + --dst-default) o_dst_default="$2"; shift 2;; + --local-default) o_local_default="$2"; shift 2;; + --src-bypass) o_src_bypass="$2"; shift 2;; + --src-forward) o_src_forward="$2"; shift 2;; + --src-checkdst) o_src_checkdst="$2"; shift 2;; + --dst-bypass) o_dst_bypass="$2"; shift 2;; + --dst-forward) o_dst_forward="$2"; shift 2;; + --dst-forward-recentrst) o_dst_forward_recentrst=1; shift 1;; + --dst-bypass-file) o_dst_bypass_file="$2"; shift 2;; + --dst-forward-file) o_dst_forward_file="$2"; shift 2;; + *) __errmsg "unknown option $1"; return 1;; + esac + done + + if [ -z "$o_redir_tcp_port" -a -z "$o_redir_udp_port" ]; then + __errmsg "Requires at least -l or -L option" + return 1 + fi + if [ -n "$o_dst_forward_recentrst" ] && ! iptables -m recent -h >/dev/null; then + __errmsg "Please install iptables-mod-conntrack-extra with opkg" + return 1 + fi + o_remote_servers="$(for s in $o_remote_servers; do resolveip -4 "$s"; done)" +} + +ss_rules_flush() { + local setname + + iptables-save --counters | grep -v ss_rules_ | iptables-restore --counters + while ip rule del fwmark 1 lookup 100 2>/dev/null; do true; done + ip route flush table 100 + for setname in $(ipset -n list | grep "ss_rules_"); do + ipset destroy "$setname" 2>/dev/null || true + done +} + +ss_rules_ipset_init() { + ipset --exist restore <<-EOF + create ss_rules_src_bypass hash:net hashsize 64 + create ss_rules_src_forward hash:net hashsize 64 + create ss_rules_src_checkdst hash:net hashsize 64 + create ss_rules_dst_bypass hash:net hashsize 64 + create ss_rules_dst_bypass_ hash:net hashsize 64 + create ss_rules_dst_forward hash:net hashsize 64 + create ss_rules_dst_forward_recentrst_ hash:ip hashsize 64 timeout 3600 + $(ss_rules_ipset_mkadd ss_rules_dst_bypass_ "$o_dst_bypass_ $o_remote_servers") + $(ss_rules_ipset_mkadd ss_rules_src_bypass "$o_src_bypass") + $(ss_rules_ipset_mkadd ss_rules_src_forward "$o_src_forward") + $(ss_rules_ipset_mkadd ss_rules_src_checkdst "$o_src_checkdst") + $(ss_rules_ipset_mkadd ss_rules_dst_bypass "$o_dst_bypass $(cat "$o_dst_bypass_file" 2>/dev/null)") + $(ss_rules_ipset_mkadd ss_rules_dst_forward "$o_dst_forward $(cat "$o_dst_forward_file" 2>/dev/null)") + EOF +} + +ss_rules_ipset_mkadd() { + local setname="$1"; shift + local i + + for i in $*; do + echo "add $setname $i" + done +} + +ss_rules_iptchains_init() { + ss_rules_iptchains_init_tcp + ss_rules_iptchains_init_udp +} + +ss_rules_iptchains_init_tcp() { + local local_target + + [ -n "$o_redir_tcp_port" ] || return 0 + + ss_rules_iptchains_init_ nat tcp + + case "$o_local_default" in + checkdst) local_target=ss_rules_dst ;; + forward) local_target=ss_rules_forward ;; + bypass|*) return 0;; + esac + + iptables-restore --noflush <<-EOF + *nat + :ss_rules_local_out - + -I OUTPUT 1 -p tcp -j ss_rules_local_out + -A ss_rules_local_out -m set --match-set ss_rules_dst_bypass_ dst -j RETURN + -A ss_rules_local_out -p tcp $o_ipt_extra -j $local_target -m comment --comment "local_default: $o_local_default" + COMMIT + EOF +} + +ss_rules_iptchains_init_udp() { + [ -n "$o_redir_udp_port" ] || return 0 + ss_rules_iptchains_init_ mangle udp +} + +ss_rules_iptchains_init_() { + local table="$1" + local proto="$2" + local forward_rules + local src_default_target dst_default_target + local recentrst_mangle_rules recentrst_addset_rules + + case "$proto" in + tcp) + forward_rules="-A ss_rules_forward -p tcp -j REDIRECT --to-ports $o_redir_tcp_port" + if [ -n "$o_dst_forward_recentrst" ]; then + recentrst_mangle_rules=" + *mangle + -I PREROUTING 1 -p tcp -m tcp --tcp-flags RST RST -m recent --name ss_rules_recentrst --set --rsource + COMMIT + " + recentrst_addset_rules=" + -A ss_rules_dst -m recent --name ss_rules_recentrst --rcheck --rdest --seconds 3 --hitcount 3 -j SET --add-set ss_rules_dst_forward_recentrst_ dst --exist + -A ss_rules_dst -m set --match-set ss_rules_dst_forward_recentrst_ dst -j ss_rules_forward + " + fi + ;; + udp) + ip rule add fwmark 1 lookup 100 + ip route add local default dev lo table 100 + forward_rules="-A ss_rules_forward -p udp -j TPROXY --on-port "$o_redir_udp_port" --tproxy-mark 0x01/0x01" + ;; + esac + case "$o_src_default" in + forward) src_default_target=ss_rules_forward ;; + checkdst) src_default_target=ss_rules_dst ;; + bypass|*) src_default_target=RETURN ;; + esac + case "$o_dst_default" in + forward) dst_default_target=ss_rules_forward ;; + bypass|*) dst_default_target=RETURN ;; + esac + sed -e '/^\s*$/d' -e 's/^\s\+//' <<-EOF | iptables-restore --noflush + *$table + :ss_rules_pre_src - + :ss_rules_src - + :ss_rules_dst - + :ss_rules_forward - + $(ss_rules_iptchains_mkprerules "$proto") + -A ss_rules_pre_src -m set --match-set ss_rules_dst_bypass_ dst -j RETURN + -A ss_rules_pre_src -p $proto $o_ipt_extra -j ss_rules_src + -A ss_rules_src -m set --match-set ss_rules_src_bypass src -j RETURN + -A ss_rules_src -m set --match-set ss_rules_src_forward src -j ss_rules_forward + -A ss_rules_src -m set --match-set ss_rules_src_checkdst src -j ss_rules_dst + -A ss_rules_src -j $src_default_target -m comment --comment "src_default: $o_src_default" + -A ss_rules_dst -m set --match-set ss_rules_dst_bypass dst -j RETURN + -A ss_rules_dst -m set --match-set ss_rules_dst_forward dst -j ss_rules_forward + $recentrst_addset_rules + -A ss_rules_dst -j $dst_default_target -m comment --comment "dst_default: $o_dst_default" + $forward_rules + COMMIT + $recentrst_mangle_rules + EOF +} + +ss_rules_iptchains_mkprerules() { + local proto="$1" + + if [ -z "$o_ifnames" ]; then + echo "-I PREROUTING 1 -p $proto -j ss_rules_pre_src" + else + echo $o_ifnames \ + | tr ' ' '\n' \ + | sed "s/.*/-I PREROUTING 1 -i \\0 -p $proto -j ss_rules_pre_src/" + fi +} + +ss_rules_parse_args "$@" +ss_rules_flush +ss_rules_ipset_init +ss_rules_iptchains_init diff --git a/shadowsocks-libev/files/ss-rules.defaults b/shadowsocks-libev/files/ss-rules.defaults new file mode 100755 index 000000000..c89e2d0b8 --- /dev/null +++ b/shadowsocks-libev/files/ss-rules.defaults @@ -0,0 +1,10 @@ +#!/bin/sh + +s=firewall.ss_rules +uci get "$s" >/dev/null && exit 0 +uci batch <<-EOF + set $s=include + set $s.path=/etc/firewall.ss-rules + set $s.reload=1 + commit firewall +EOF diff --git a/shadowsocks-libev/patches/010-ECONNRESET.patch b/shadowsocks-libev/patches/010-ECONNRESET.patch new file mode 100644 index 000000000..131181952 --- /dev/null +++ b/shadowsocks-libev/patches/010-ECONNRESET.patch @@ -0,0 +1,14 @@ +Index: shadowsocks-libev-3.1.2/src/redir.c +=================================================================== +--- shadowsocks-libev-3.1.2.orig/src/redir.c ++++ shadowsocks-libev-3.1.2/src/redir.c +@@ -212,7 +212,8 @@ server_recv_cb(EV_P_ ev_io *w, int reven + // continue to wait for recv + return; + } else { +- ERROR("server recv"); ++ if (errno != ECONNRESET) ++ ERROR("server recv"); + close_and_free_remote(EV_A_ remote); + close_and_free_server(EV_A_ server); + return;