package lienol: sync
This commit is contained in:
parent
9c81dd407f
commit
e08dd4b781
18
package/lienol/luci-app-ipsec-vpnserver-manyusers/Makefile
Normal file
18
package/lienol/luci-app-ipsec-vpnserver-manyusers/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2018-2020 Lienol <lawlienol@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for IPSec VPN Server
|
||||
LUCI_DEPENDS:=+strongswan +strongswan-minimal +strongswan-mod-xauth-generic
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=13-20191213
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
-- Copyright 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
module("luci.controller.ipsec-server", package.seeall)
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/ipsec") then return end
|
||||
|
||||
entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false
|
||||
entry({"admin", "vpn", "ipsec-server"},
|
||||
alias("admin", "vpn", "ipsec-server", "settings"),
|
||||
_("IPSec VPN Server"), 49).dependent = false
|
||||
entry({"admin", "vpn", "ipsec-server", "settings"},
|
||||
cbi("ipsec-server/settings"), _("General Settings"), 10).leaf = true
|
||||
entry({"admin", "vpn", "ipsec-server", "users"}, cbi("ipsec-server/users"),
|
||||
_("Users Manager"), 20).leaf = true
|
||||
entry({"admin", "vpn", "ipsec-server", "status"}, call("status")).leaf =
|
||||
true
|
||||
end
|
||||
|
||||
function status()
|
||||
local e = {}
|
||||
e.status = luci.sys.call("/usr/bin/pgrep ipsec > /dev/null") == 0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
@ -0,0 +1,121 @@
|
||||
local s = require "luci.sys"
|
||||
local net = require"luci.model.network".init()
|
||||
local ifaces = s.net:devices()
|
||||
local m, s, o
|
||||
mp = Map("ipsec", translate("IPSec VPN Server"))
|
||||
mp.description = translate(
|
||||
"IPSec VPN connectivity using the native built-in VPN Client on iOS or Andriod (IKEv1 with PSK and Xauth)")
|
||||
mp.template = "ipsec-server/index"
|
||||
|
||||
s = mp:section(TypedSection, "service")
|
||||
s.anonymous = true
|
||||
o = s:option(DummyValue, "ipsec-server_status", translate("Current Condition"))
|
||||
o.template = "ipsec-server/status"
|
||||
enabled = s:option(Flag, "enabled", translate("Enable"))
|
||||
enabled.default = 0
|
||||
enabled.rmempty = false
|
||||
|
||||
clientip = s:option(Value, "clientip", translate("VPN Client IP"))
|
||||
clientip.datatype = "ip4addr"
|
||||
clientip.description = translate(
|
||||
"VPN Client reserved started IP addresses with the same subnet mask")
|
||||
clientip.optional = false
|
||||
clientip.rmempty = false
|
||||
|
||||
clientdns = s:option(Value, "clientdns", translate("VPN Client DNS"))
|
||||
clientdns.datatype = "ip4addr"
|
||||
clientdns.description = translate("DNS using in VPN tunnel.")
|
||||
clientdns.optional = false
|
||||
clientdns.rmempty = false
|
||||
|
||||
secret = s:option(Value, "secret", translate("Secret Pre-Shared Key"))
|
||||
secret.password = true
|
||||
|
||||
o = s:option(Flag, "is_nat", translate("is_nat"))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "export_interface", translate("Interface"),
|
||||
translate("Specify interface forwarding traffic."))
|
||||
o:value("default", translate("default"))
|
||||
for _, iface in ipairs(ifaces) do
|
||||
if (iface:match("^br*") or iface:match("^eth*") or iface:match("^pppoe*") or
|
||||
iface:match("wlan*")) then
|
||||
local nets = net:get_interface(iface)
|
||||
nets = nets and nets:get_networks() or {}
|
||||
for k, v in pairs(nets) do nets[k] = nets[k].sid end
|
||||
nets = table.concat(nets, ",")
|
||||
o:value(iface, ((#nets > 0) and "%s (%s)" % {iface, nets} or iface))
|
||||
end
|
||||
end
|
||||
o:depends("is_nat", "1")
|
||||
|
||||
function mp.on_save(self)
|
||||
require "luci.model.uci"
|
||||
require "luci.sys"
|
||||
|
||||
local have_ike_rule = false
|
||||
local have_ipsec_rule = false
|
||||
local have_ah_rule = false
|
||||
local have_esp_rule = false
|
||||
|
||||
luci.model.uci.cursor():foreach('firewall', 'rule', function(section)
|
||||
if section.name == 'ike' then have_ike_rule = true end
|
||||
if section.name == 'ipsec' then have_ipsec_rule = true end
|
||||
if section.name == 'ah' then have_ah_rule = true end
|
||||
if section.name == 'esp' then have_esp_rule = true end
|
||||
end)
|
||||
|
||||
if not have_ike_rule then
|
||||
local cursor = luci.model.uci.cursor()
|
||||
local ike_rulename = cursor:add('firewall', 'rule')
|
||||
cursor:tset('firewall', ike_rulename, {
|
||||
['name'] = 'ike',
|
||||
['target'] = 'ACCEPT',
|
||||
['src'] = 'wan',
|
||||
['proto'] = 'udp',
|
||||
['dest_port'] = 500
|
||||
})
|
||||
cursor:save('firewall')
|
||||
cursor:commit('firewall')
|
||||
end
|
||||
if not have_ipsec_rule then
|
||||
local cursor = luci.model.uci.cursor()
|
||||
local ipsec_rulename = cursor:add('firewall', 'rule')
|
||||
cursor:tset('firewall', ipsec_rulename, {
|
||||
['name'] = 'ipsec',
|
||||
['target'] = 'ACCEPT',
|
||||
['src'] = 'wan',
|
||||
['proto'] = 'udp',
|
||||
['dest_port'] = 4500
|
||||
})
|
||||
cursor:save('firewall')
|
||||
cursor:commit('firewall')
|
||||
end
|
||||
if not have_ah_rule then
|
||||
local cursor = luci.model.uci.cursor()
|
||||
local ah_rulename = cursor:add('firewall', 'rule')
|
||||
cursor:tset('firewall', ah_rulename, {
|
||||
['name'] = 'ah',
|
||||
['target'] = 'ACCEPT',
|
||||
['src'] = 'wan',
|
||||
['proto'] = 'ah'
|
||||
})
|
||||
cursor:save('firewall')
|
||||
cursor:commit('firewall')
|
||||
end
|
||||
if not have_esp_rule then
|
||||
local cursor = luci.model.uci.cursor()
|
||||
local esp_rulename = cursor:add('firewall', 'rule')
|
||||
cursor:tset('firewall', esp_rulename, {
|
||||
['name'] = 'esp',
|
||||
['target'] = 'ACCEPT',
|
||||
['src'] = 'wan',
|
||||
['proto'] = 'esp'
|
||||
})
|
||||
cursor:save('firewall')
|
||||
cursor:commit('firewall')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return mp
|
||||
@ -0,0 +1,18 @@
|
||||
mp = Map("ipsec", translate("IPSec VPN Server"))
|
||||
mp.description = translate(
|
||||
"IPSec VPN connectivity using the native built-in VPN Client on iOS or Andriod (IKEv1 with PSK and Xauth)")
|
||||
|
||||
s = mp:section(TypedSection, "users", translate("Users Manager"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
s.template = "cbi/tblsection"
|
||||
|
||||
enabled = s:option(Flag, "enabled", translate("Enabled"))
|
||||
enabled.rmempty = false
|
||||
username = s:option(Value, "username", translate("User name"))
|
||||
username.placeholder = translate("User name")
|
||||
username.rmempty = true
|
||||
password = s:option(Value, "password", translate("Password"))
|
||||
password.rmempty = true
|
||||
|
||||
return mp
|
||||
@ -0,0 +1,13 @@
|
||||
<% include("cbi/map") %>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "vpn", "ipsec-server", "status")%>', null,
|
||||
function(x, result)
|
||||
{
|
||||
var status = document.getElementsByClassName('ipsec-server_status')[0];
|
||||
status.setAttribute("style","font-weight:bold;");
|
||||
status.setAttribute("color",result.status ? "green":"red");
|
||||
status.innerHTML = result.status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||
}
|
||||
)
|
||||
//]]>
|
||||
</script>
|
||||
@ -0,0 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<font class="ipsec-server_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,50 @@
|
||||
msgid "IPSec VPN Server"
|
||||
msgstr "IPSec VPN 服务器"
|
||||
|
||||
msgid "IPSec VPN connectivity using the native built-in VPN Client on iOS or Andriod (IKEv1 with PSK and Xauth)"
|
||||
msgstr "使用iOS 或者 Andriod (IKEv1 with PSK and Xauth) 原生内置 IPSec VPN 客户端进行连接"
|
||||
|
||||
msgid "IPSec VPN Server status"
|
||||
msgstr "IPSec VPN 服务器运行状态"
|
||||
|
||||
msgid "Current Condition"
|
||||
msgstr "当前状态"
|
||||
|
||||
msgid "General settings"
|
||||
msgstr "基本设置"
|
||||
|
||||
msgid "VPN Client IP"
|
||||
msgstr "VPN客户端地址段"
|
||||
|
||||
msgid "VPN Client reserved started IP addresses with the same subnet mask"
|
||||
msgstr "VPN客户端获取IP的起始地址,例如 192.168.100.10/24"
|
||||
|
||||
msgid "VPN Client DNS"
|
||||
msgstr "VPN客户端DNS服务器"
|
||||
|
||||
msgid "DNS using in VPN tunnel."
|
||||
msgstr "指定VPN客户端的DNS地址。"
|
||||
|
||||
msgid "Secret Pre-Shared Key"
|
||||
msgstr "PSK密钥"
|
||||
|
||||
msgid "is_nat"
|
||||
msgstr "NAT转发"
|
||||
|
||||
msgid "Interface"
|
||||
msgstr "接口"
|
||||
|
||||
msgid "Specify interface forwarding traffic."
|
||||
msgstr "指定接口转发流量。"
|
||||
|
||||
msgid "Disable from startup"
|
||||
msgstr "禁止开机启动"
|
||||
|
||||
msgid "Enable on startup"
|
||||
msgstr "允许开机启动"
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
@ -0,0 +1,14 @@
|
||||
|
||||
config service 'ipsec'
|
||||
option secret 'ipsec'
|
||||
option clientip '192.168.100.1/24'
|
||||
option is_nat '1'
|
||||
option export_interface 'default'
|
||||
option clientdns '208.67.222.222'
|
||||
option enabled '0'
|
||||
|
||||
config users
|
||||
option enabled '1'
|
||||
option username 'guest'
|
||||
option password '123456'
|
||||
|
||||
441
package/lienol/luci-app-ipsec-vpnserver-manyusers/root/etc/init.d/ipsecvpn
Executable file
441
package/lienol/luci-app-ipsec-vpnserver-manyusers/root/etc/init.d/ipsecvpn
Executable file
@ -0,0 +1,441 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
|
||||
START=90
|
||||
STOP=10
|
||||
|
||||
USE_PROCD=1
|
||||
PROG=/usr/lib/ipsec/starter
|
||||
|
||||
. $IPKG_INSTROOT/lib/functions.sh
|
||||
. $IPKG_INSTROOT/lib/functions/network.sh
|
||||
|
||||
IPSEC_SECRETS_FILE=/etc/ipsec.secrets
|
||||
IPSEC_CONN_FILE=/etc/ipsec.conf
|
||||
STRONGSWAN_CONF_FILE=/etc/strongswan.conf
|
||||
|
||||
IPSEC_VAR_SECRETS_FILE=/var/ipsec/ipsec.secrets
|
||||
IPSEC_VAR_CONN_FILE=/var/ipsec/ipsec.conf
|
||||
STRONGSWAN_VAR_CONF_FILE=/var/ipsec/strongswan.conf
|
||||
|
||||
WAIT_FOR_INTF=0
|
||||
|
||||
file_reset() {
|
||||
: > "$1"
|
||||
}
|
||||
|
||||
xappend() {
|
||||
local file="$1"
|
||||
shift
|
||||
|
||||
echo "${@}" >> "${file}"
|
||||
}
|
||||
|
||||
remove_include() {
|
||||
local file="$1"
|
||||
local include="$2"
|
||||
|
||||
sed -i "\_${include}_d" "${file}"
|
||||
}
|
||||
|
||||
remove_includes() {
|
||||
remove_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}"
|
||||
remove_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}"
|
||||
remove_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}"
|
||||
}
|
||||
|
||||
do_include() {
|
||||
local conf="$1"
|
||||
local uciconf="$2"
|
||||
local backup=`mktemp -t -p /tmp/ ipsec-init-XXXXXX`
|
||||
|
||||
[ ! -f "${conf}" ] && rm -rf "${conf}"
|
||||
touch "${conf}"
|
||||
|
||||
cat "${conf}" | grep -v "${uciconf}" > "${backup}"
|
||||
mv "${backup}" "${conf}"
|
||||
xappend "${conf}" "include ${uciconf}"
|
||||
file_reset "${uciconf}"
|
||||
}
|
||||
|
||||
ipsec_reset() {
|
||||
do_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}"
|
||||
}
|
||||
|
||||
ipsec_xappend() {
|
||||
xappend "${IPSEC_VAR_CONN_FILE}" "$@"
|
||||
}
|
||||
|
||||
swan_reset() {
|
||||
do_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}"
|
||||
}
|
||||
|
||||
swan_xappend() {
|
||||
xappend "${STRONGSWAN_VAR_CONF_FILE}" "$@"
|
||||
}
|
||||
|
||||
secret_reset() {
|
||||
do_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}"
|
||||
}
|
||||
|
||||
secret_xappend() {
|
||||
xappend "${IPSEC_VAR_SECRETS_FILE}" "$@"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo "WARNING: $@" >&2
|
||||
}
|
||||
|
||||
add_crypto_proposal() {
|
||||
local encryption_algorithm
|
||||
local hash_algorithm
|
||||
local dh_group
|
||||
|
||||
config_get encryption_algorithm "$1" encryption_algorithm
|
||||
config_get hash_algorithm "$1" hash_algorithm
|
||||
config_get dh_group "$1" dh_group
|
||||
|
||||
[ -n "${encryption_algorithm}" ] && \
|
||||
crypto="${crypto:+${crypto},}${encryption_algorithm}${hash_algorithm:+-${hash_algorithm}}${dh_group:+-${dh_group}}"
|
||||
}
|
||||
|
||||
set_crypto_proposal() {
|
||||
local conf="$1"
|
||||
local proposal
|
||||
|
||||
crypto=""
|
||||
|
||||
config_get crypto_proposal "$conf" crypto_proposal ""
|
||||
for proposal in $crypto_proposal; do
|
||||
add_crypto_proposal "$proposal"
|
||||
done
|
||||
|
||||
[ -n "${crypto}" ] && {
|
||||
local force_crypto_proposal
|
||||
|
||||
config_get_bool force_crypto_proposal "$conf" force_crypto_proposal
|
||||
|
||||
[ "${force_crypto_proposal}" = "1" ] && crypto="${crypto}!"
|
||||
}
|
||||
|
||||
crypto_proposal="${crypto}"
|
||||
}
|
||||
|
||||
config_conn() {
|
||||
# Generic ipsec conn section shared by tunnel and transport
|
||||
local mode
|
||||
local local_subnet
|
||||
local local_nat
|
||||
local local_sourceip
|
||||
local local_updown
|
||||
local local_firewall
|
||||
local remote_subnet
|
||||
local remote_sourceip
|
||||
local remote_updown
|
||||
local remote_firewall
|
||||
local ikelifetime
|
||||
local lifetime
|
||||
local margintime
|
||||
local keyingtries
|
||||
local dpdaction
|
||||
local dpddelay
|
||||
local inactivity
|
||||
local keyexchange
|
||||
|
||||
config_get mode "$1" mode "route"
|
||||
config_get local_subnet "$1" local_subnet ""
|
||||
config_get local_nat "$1" local_nat ""
|
||||
config_get local_sourceip "$1" local_sourceip ""
|
||||
config_get local_updown "$1" local_updown ""
|
||||
config_get local_firewall "$1" local_firewall ""
|
||||
config_get remote_subnet "$1" remote_subnet ""
|
||||
config_get remote_sourceip "$1" remote_sourceip ""
|
||||
config_get remote_updown "$1" remote_updown ""
|
||||
config_get remote_firewall "$1" remote_firewall ""
|
||||
config_get ikelifetime "$1" ikelifetime "3h"
|
||||
config_get lifetime "$1" lifetime "1h"
|
||||
config_get margintime "$1" margintime "9m"
|
||||
config_get keyingtries "$1" keyingtries "3"
|
||||
config_get dpdaction "$1" dpdaction "none"
|
||||
config_get dpddelay "$1" dpddelay "30s"
|
||||
config_get inactivity "$1" inactivity
|
||||
config_get keyexchange "$1" keyexchange "ikev2"
|
||||
|
||||
[ -n "$local_nat" ] && local_subnet=$local_nat
|
||||
|
||||
ipsec_xappend "conn $config_name-$1"
|
||||
ipsec_xappend " left=%any"
|
||||
ipsec_xappend " right=$remote_gateway"
|
||||
|
||||
[ -n "$local_sourceip" ] && ipsec_xappend " leftsourceip=$local_sourceip"
|
||||
[ -n "$local_subnet" ] && ipsec_xappend " leftsubnet=$local_subnet"
|
||||
|
||||
[ -n "$local_firewall" ] && ipsec_xappend " leftfirewall=$local_firewall"
|
||||
[ -n "$remote_firewall" ] && ipsec_xappend " rightfirewall=$remote_firewall"
|
||||
|
||||
ipsec_xappend " ikelifetime=$ikelifetime"
|
||||
ipsec_xappend " lifetime=$lifetime"
|
||||
ipsec_xappend " margintime=$margintime"
|
||||
ipsec_xappend " keyingtries=$keyingtries"
|
||||
ipsec_xappend " dpdaction=$dpdaction"
|
||||
ipsec_xappend " dpddelay=$dpddelay"
|
||||
|
||||
[ -n "$inactivity" ] && ipsec_xappend " inactivity=$inactivity"
|
||||
|
||||
if [ "$auth_method" = "psk" ]; then
|
||||
ipsec_xappend " leftauth=psk"
|
||||
ipsec_xappend " rightauth=psk"
|
||||
|
||||
[ "$remote_sourceip" != "" ] && ipsec_xappend " rightsourceip=$remote_sourceip"
|
||||
[ "$remote_subnet" != "" ] && ipsec_xappend " rightsubnet=$remote_subnet"
|
||||
|
||||
ipsec_xappend " auto=$mode"
|
||||
else
|
||||
warning "AuthenticationMethod $auth_method not supported"
|
||||
fi
|
||||
|
||||
[ -n "$local_identifier" ] && ipsec_xappend " leftid=$local_identifier"
|
||||
[ -n "$remote_identifier" ] && ipsec_xappend " rightid=$remote_identifier"
|
||||
[ -n "$local_updown" ] && ipsec_xappend " leftupdown=$local_updown"
|
||||
[ -n "$remote_updown" ] && ipsec_xappend " rightupdown=$remote_updown"
|
||||
ipsec_xappend " keyexchange=$keyexchange"
|
||||
|
||||
set_crypto_proposal "$1"
|
||||
[ -n "${crypto_proposal}" ] && ipsec_xappend " esp=$crypto_proposal"
|
||||
[ -n "${ike_proposal}" ] && ipsec_xappend " ike=$ike_proposal"
|
||||
}
|
||||
|
||||
config_tunnel() {
|
||||
config_conn "$1"
|
||||
|
||||
# Specific for the tunnel part
|
||||
ipsec_xappend " type=tunnel"
|
||||
}
|
||||
|
||||
config_transport() {
|
||||
config_conn "$1"
|
||||
|
||||
# Specific for the transport part
|
||||
ipsec_xappend " type=transport"
|
||||
}
|
||||
|
||||
config_remote() {
|
||||
local enabled
|
||||
local gateway
|
||||
local pre_shared_key
|
||||
local auth_method
|
||||
|
||||
config_name=$1
|
||||
|
||||
config_get_bool enabled "$1" enabled 0
|
||||
[ $enabled -eq 0 ] && return
|
||||
|
||||
config_get gateway "$1" gateway
|
||||
config_get pre_shared_key "$1" pre_shared_key
|
||||
config_get auth_method "$1" authentication_method
|
||||
config_get local_identifier "$1" local_identifier ""
|
||||
config_get remote_identifier "$1" remote_identifier ""
|
||||
|
||||
[ "$gateway" = "any" ] && remote_gateway="%any" || remote_gateway="$gateway"
|
||||
|
||||
[ -z "$local_identifier" ] && {
|
||||
local ipdest
|
||||
|
||||
[ "$remote_gateway" = "%any" ] && ipdest="1.1.1.1" || ipdest="$remote_gateway"
|
||||
local_gateway=`ip route get $ipdest | awk -F"src" '/src/{gsub(/ /,"");print $2}'`
|
||||
}
|
||||
|
||||
[ -n "$local_identifier" ] && secret_xappend -n "$local_identifier " || secret_xappend -n "$local_gateway "
|
||||
[ -n "$remote_identifier" ] && secret_xappend -n "$remote_identifier " || secret_xappend -n "$remote_gateway "
|
||||
|
||||
secret_xappend ": PSK \"$pre_shared_key\""
|
||||
|
||||
set_crypto_proposal "$1"
|
||||
ike_proposal="$crypto_proposal"
|
||||
|
||||
config_list_foreach "$1" tunnel config_tunnel
|
||||
|
||||
config_list_foreach "$1" transport config_transport
|
||||
|
||||
ipsec_xappend ""
|
||||
}
|
||||
|
||||
config_ipsec() {
|
||||
local debug
|
||||
local rtinstall_enabled
|
||||
local routing_tables_ignored
|
||||
local routing_table
|
||||
local routing_table_id
|
||||
local interface
|
||||
local device_list
|
||||
|
||||
ipsec_reset
|
||||
secret_reset
|
||||
swan_reset
|
||||
|
||||
ipsec_xappend "# generated by /etc/init.d/ipsecvpn"
|
||||
ipsec_xappend "version 2"
|
||||
ipsec_xappend ""
|
||||
|
||||
secret_xappend "# generated by /etc/init.d/ipsecvpn"
|
||||
|
||||
config_get debug "$1" debug 0
|
||||
config_get_bool rtinstall_enabled "$1" rtinstall_enabled 1
|
||||
[ $rtinstall_enabled -eq 1 ] && install_routes=yes || install_routes=no
|
||||
|
||||
# prepare extra charon config option ignore_routing_tables
|
||||
for routing_table in $(config_get "$1" "ignore_routing_tables"); do
|
||||
if [ "$routing_table" -ge 0 ] 2>/dev/null; then
|
||||
routing_table_id=$routing_table
|
||||
else
|
||||
routing_table_id=$(sed -n '/[ \t]*[0-9]\+[ \t]\+'$routing_table'[ \t]*$/s/[ \t]*\([0-9]\+\).*/\1/p' /etc/iproute2/rt_tables)
|
||||
fi
|
||||
|
||||
[ -n "$routing_table_id" ] && append routing_tables_ignored "$routing_table_id"
|
||||
done
|
||||
|
||||
local interface_list=$(config_get "$1" "interface")
|
||||
if [ -z "$interface_list" ]; then
|
||||
WAIT_FOR_INTF=0
|
||||
else
|
||||
for interface in $interface_list; do
|
||||
network_get_device device $interface
|
||||
[ -n "$device" ] && append device_list "$device" ","
|
||||
done
|
||||
[ -n "$device_list" ] && WAIT_FOR_INTF=0 || WAIT_FOR_INTF=1
|
||||
fi
|
||||
|
||||
swan_xappend "# generated by /etc/init.d/ipsecvpn"
|
||||
swan_xappend "charon {"
|
||||
swan_xappend " load_modular = yes"
|
||||
swan_xappend " install_routes = $install_routes"
|
||||
[ -n "$routing_tables_ignored" ] && swan_xappend " ignore_routing_tables = $routing_tables_ignored"
|
||||
[ -n "$device_list" ] && swan_xappend " interfaces_use = $device_list"
|
||||
swan_xappend " plugins {"
|
||||
swan_xappend " include /etc/strongswan.d/charon/*.conf"
|
||||
swan_xappend " }"
|
||||
swan_xappend " syslog {"
|
||||
swan_xappend " identifier = ipsec"
|
||||
swan_xappend " daemon {"
|
||||
swan_xappend " default = $debug"
|
||||
swan_xappend " }"
|
||||
swan_xappend " auth {"
|
||||
swan_xappend " default = $debug"
|
||||
swan_xappend " }"
|
||||
swan_xappend " }"
|
||||
swan_xappend "}"
|
||||
}
|
||||
|
||||
prepare_env() {
|
||||
mkdir -p /var/ipsec
|
||||
remove_includes
|
||||
config_load ipsec
|
||||
config_foreach config_ipsec ipsec
|
||||
config_foreach config_remote remote
|
||||
}
|
||||
|
||||
service_running() {
|
||||
ipsec status > /dev/null 2>&1
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
local bool vt_enabled=`uci get ipsec.@service[0].enabled 2>/dev/null`
|
||||
[ "$vt_enabled" = 0 ] && /etc/init.d/ipsecvpn stop > /dev/null 2>&1 && return
|
||||
running && {
|
||||
prepare_env
|
||||
[ $WAIT_FOR_INTF -eq 0 ] && {
|
||||
ipsec rereadall
|
||||
ipsec reload
|
||||
return
|
||||
}
|
||||
}
|
||||
[ "$vt_enabled" = 1 ] && start
|
||||
}
|
||||
|
||||
check_ipsec_interface() {
|
||||
local intf
|
||||
|
||||
for intf in $(config_get "$1" interface); do
|
||||
procd_add_interface_trigger "interface.*" "$intf" /etc/init.d/ipsecvpn reload
|
||||
done
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "ipsec"
|
||||
config load "ipsec"
|
||||
config_foreach check_ipsec_interface ipsec
|
||||
}
|
||||
|
||||
start_service() {
|
||||
fw3 reload
|
||||
local vt_enabled=`uci get ipsec.@service[0].enabled 2>/dev/null`
|
||||
local vt_clientip=`uci get ipsec.@service[0].clientip`
|
||||
local vt_clientdns=`uci get ipsec.@service[0].clientdns`
|
||||
local vt_secret=`uci get ipsec.@service[0].secret 2>/dev/null`
|
||||
local vt_is_nat=`uci get ipsec.@service[0].is_nat 2>/dev/null`
|
||||
local vt_export_interface=`uci get ipsec.@service[0].export_interface 2>/dev/null`
|
||||
|
||||
[ "$vt_enabled" = 0 ] && /etc/init.d/ipsecvpn stop > /dev/null 2>&1 && return
|
||||
|
||||
cat > /etc/ipsec.conf <<EOF
|
||||
# ipsec.conf - strongSwan IPsec configuration file
|
||||
|
||||
# basic configuration
|
||||
|
||||
config setup
|
||||
# strictcrlpolicy=yes
|
||||
uniqueids=never
|
||||
|
||||
# Add connections here.
|
||||
|
||||
conn xauth_psk
|
||||
keyexchange=ikev1
|
||||
ike=aes128-sha1-modp2048,aes128-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1536
|
||||
esp=aes128-sha1,3des-sha1
|
||||
left=%defaultroute
|
||||
leftauth=psk
|
||||
leftsubnet=0.0.0.0/0
|
||||
right=%any
|
||||
rightauth=psk
|
||||
rightauth2=xauth
|
||||
rightsourceip=$vt_clientip
|
||||
rightdns=$vt_clientdns
|
||||
auto=add
|
||||
EOF
|
||||
|
||||
cat > /etc/ipsec.secrets <<EOF
|
||||
# /etc/ipsec.secrets - strongSwan IPsec secrets file
|
||||
: PSK "$vt_secret"
|
||||
EOF
|
||||
|
||||
config_load ipsec
|
||||
config_foreach setup_login users
|
||||
|
||||
prepare_env
|
||||
|
||||
[ $WAIT_FOR_INTF -eq 1 ] && return
|
||||
|
||||
procd_open_instance
|
||||
|
||||
procd_set_param command $PROG --daemon charon --nofork
|
||||
|
||||
procd_set_param file $IPSEC_CONN_FILE
|
||||
procd_append_param file $IPSEC_SECRETS_FILE
|
||||
procd_append_param file $STRONGSWAN_CONF_FILE
|
||||
procd_append_param file /etc/strongswan.d/*.conf
|
||||
procd_append_param file /etc/strongswan.d/charon/*.conf
|
||||
|
||||
procd_set_param respawn
|
||||
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
setup_login() {
|
||||
config_get enabled $1 enabled
|
||||
[ "$enabled" -eq 0 ] && return 0
|
||||
config_get username $1 username
|
||||
config_get password $1 password
|
||||
[ -n "$username" ] || return 0
|
||||
[ -n "$password" ] || return 0
|
||||
echo "$username : XAUTH '$password'" >> /etc/ipsec.secrets
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete firewall.ipsecvpn
|
||||
set firewall.ipsecvpn=include
|
||||
set firewall.ipsecvpn.type=script
|
||||
set firewall.ipsecvpn.path=/usr/share/ipsecvpn/firewall.include
|
||||
set firewall.ipsecvpn.reload=1
|
||||
EOF
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@ipsec[-1]
|
||||
add ucitrack ipsec
|
||||
set ucitrack.@ipsec[-1].exec='/etc/init.d/ipsecvpn start'
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
/etc/init.d/ipsec disable && /etc/init.d/ipsec stop
|
||||
rm -f /etc/init.d/ipsec
|
||||
chmod a+x /usr/share/ipsecvpn/* >/dev/null 2>&1
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
iptables -D INPUT -p udp -m multiport --dports 500,4500 -m comment --comment "Rule For IPSec VPN Server" -j ACCEPT 2> /dev/null
|
||||
ipsec_nums=`iptables -t nat -L POSTROUTING 2> /dev/null|grep -c "Rule For IPSec VPN Server"`
|
||||
if [ -n "$ipsec_nums" ]; then
|
||||
until [ "$ipsec_nums" = 0 ]
|
||||
do
|
||||
rules=`iptables -t nat -L POSTROUTING --line-num 2> /dev/null|grep "Rule For IPSec VPN Server" |awk '{print $1}'`
|
||||
for rule in $rules
|
||||
do
|
||||
iptables -t nat -D POSTROUTING $rule 2> /dev/null
|
||||
break
|
||||
done
|
||||
ipsec_nums=`expr $ipsec_nums - 1`
|
||||
done
|
||||
fi
|
||||
nums=`iptables -L forwarding_rule 2> /dev/null|grep -c "Rule For IPSec VPN Server"`
|
||||
if [ -n "$nums" ]; then
|
||||
until [ "$nums" = 0 ]
|
||||
do
|
||||
rules=`iptables -L forwarding_rule --line-num 2> /dev/null|grep "Rule For IPSec VPN Server" |awk '{print $1}'`
|
||||
for rule in $rules
|
||||
do
|
||||
iptables -D forwarding_rule $rule 2> /dev/null
|
||||
break
|
||||
done
|
||||
nums=`expr $nums - 1`
|
||||
done
|
||||
fi
|
||||
|
||||
enable=$(uci get ipsec.ipsec.enabled)
|
||||
if [ $enable -eq 1 ]; then
|
||||
is_nat=$(uci get ipsec.ipsec.is_nat)
|
||||
if [ "$is_nat" -eq 1 ];then
|
||||
clientip=$(uci get ipsec.ipsec.clientip)
|
||||
export_interface=$(uci get ipsec.ipsec.export_interface)
|
||||
if [ "$export_interface" != "default" ];then
|
||||
iptables -t nat -I POSTROUTING -s ${clientip%.*}.0/24 -o ${export_interface} -m comment --comment "Rule For IPSec VPN Server" -j MASQUERADE
|
||||
else
|
||||
iptables -t nat -I POSTROUTING -s ${clientip%.*}.0/24 -m comment --comment "Rule For IPSec VPN Server" -j MASQUERADE
|
||||
fi
|
||||
iptables -I forwarding_rule -s ${clientip%.*}.0/24 -m comment --comment "Rule For IPSec VPN Server" -j ACCEPT
|
||||
fi
|
||||
iptables -I INPUT -p udp -m multiport --dports 500,4500 -m comment --comment "Rule For IPSec VPN Server" -j ACCEPT
|
||||
fi
|
||||
@ -6,9 +6,9 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall
|
||||
PKG_VERSION:=3.5
|
||||
PKG_RELEASE:=20
|
||||
PKG_DATE:=20200223
|
||||
PKG_VERSION:=3.6
|
||||
PKG_RELEASE:=1
|
||||
PKG_DATE:=20200224
|
||||
|
||||
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
|
||||
|
||||
|
||||
@ -1,98 +1,21 @@
|
||||
local ucursor = require"luci.model.uci".cursor()
|
||||
local json = require "luci.jsonc"
|
||||
local api = require "luci.model.cbi.passwall.api.api"
|
||||
local node_section = arg[1]
|
||||
local proto = arg[2]
|
||||
local redir_port = arg[3]
|
||||
local socks5_proxy_port = arg[4]
|
||||
local node = ucursor:get_all("passwall", node_section)
|
||||
local inbound_json = {}
|
||||
local inboundDetour_json = nil
|
||||
local vnext = {}
|
||||
local inbounds = {}
|
||||
local outbounds = {}
|
||||
local network = proto
|
||||
local routing = nil
|
||||
|
||||
if socks5_proxy_port ~= "nil" then
|
||||
inbound_json = {
|
||||
listen = "0.0.0.0",
|
||||
port = socks5_proxy_port,
|
||||
protocol = "socks",
|
||||
settings = {auth = "noauth", udp = true, ip = "127.0.0.1"}
|
||||
}
|
||||
end
|
||||
|
||||
if redir_port ~= "nil" then
|
||||
inbound_json = {
|
||||
port = redir_port,
|
||||
protocol = "dokodemo-door",
|
||||
settings = {network = proto, followRedirect = true},
|
||||
sniffing = {enabled = true, destOverride = {"http", "tls"}}
|
||||
}
|
||||
if proto == "tcp" and node.v2ray_tcp_socks == "1" then
|
||||
inboundDetour_json = {
|
||||
listen = "0.0.0.0",
|
||||
port = tonumber(node.v2ray_tcp_socks_port),
|
||||
protocol = "socks",
|
||||
settings = {
|
||||
auth = node.v2ray_tcp_socks_auth,
|
||||
accounts = (node.v2ray_tcp_socks_auth == "password") and {
|
||||
{
|
||||
user = node.v2ray_tcp_socks_auth_username,
|
||||
pass = node.v2ray_tcp_socks_auth_password
|
||||
}
|
||||
} or nil,
|
||||
udp = true
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if node.v2ray_balancing_node then
|
||||
local nodes = node.v2ray_balancing_node
|
||||
local length = #nodes
|
||||
for i = 1, length do
|
||||
local id = nodes[i]
|
||||
local vnext_json = {
|
||||
address = api.uci_get_type_id(id, "address"),
|
||||
port = tonumber(api.uci_get_type_id(id, "port")),
|
||||
users = {
|
||||
{
|
||||
id = api.uci_get_type_id(id, "v2ray_VMess_id"),
|
||||
alterId = tonumber(api.uci_get_type_id(id,
|
||||
"v2ray_VMess_alterId")),
|
||||
level = tonumber(
|
||||
api.uci_get_type_id(id, "v2ray_VMess_level")),
|
||||
security = api.uci_get_type_id(id, "v2ray_security")
|
||||
}
|
||||
}
|
||||
}
|
||||
vnext[i] = vnext_json
|
||||
end
|
||||
else
|
||||
vnext = {
|
||||
{
|
||||
address = node.address,
|
||||
port = tonumber(node.port),
|
||||
users = {
|
||||
{
|
||||
id = node.v2ray_VMess_id,
|
||||
alterId = tonumber(node.v2ray_VMess_alterId),
|
||||
level = tonumber(node.v2ray_VMess_level),
|
||||
security = node.v2ray_security
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local v2ray = {
|
||||
log = {
|
||||
-- error = "/var/log/v2ray.log",
|
||||
loglevel = "warning"
|
||||
},
|
||||
-- 传入连接
|
||||
inbounds = {inbound_json, inboundDetour_json},
|
||||
-- 传出连接
|
||||
outbounds = {
|
||||
{
|
||||
local function gen_outbound(node, tag)
|
||||
local result = nil
|
||||
if node then
|
||||
result = {
|
||||
tag = tag or node[".name"],
|
||||
cbi_id = node[".name"],
|
||||
protocol = node.v2ray_protocol or "vmess",
|
||||
mux = {
|
||||
enabled = (node.v2ray_mux == "1") and true or false,
|
||||
@ -146,9 +69,151 @@ local v2ray = {
|
||||
header = {type = node.v2ray_quic_guise}
|
||||
} or nil
|
||||
},
|
||||
settings = {vnext = vnext}
|
||||
}, -- 额外传出连接
|
||||
{protocol = "freedom", tag = "direct", settings = {keep = ""}}
|
||||
settings = {
|
||||
vnext = {
|
||||
{
|
||||
address = node.address,
|
||||
port = tonumber(node.port),
|
||||
users = {
|
||||
{
|
||||
id = node.v2ray_VMess_id,
|
||||
alterId = tonumber(node.v2ray_VMess_alterId),
|
||||
level = tonumber(node.v2ray_VMess_level),
|
||||
security = node.v2ray_security
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
if socks5_proxy_port ~= "nil" then
|
||||
table.insert(inbounds, {
|
||||
listen = "0.0.0.0",
|
||||
port = socks5_proxy_port,
|
||||
protocol = "socks",
|
||||
settings = {auth = "noauth", udp = true, ip = "127.0.0.1"}
|
||||
})
|
||||
network = "tcp,udp"
|
||||
end
|
||||
|
||||
if redir_port ~= "nil" then
|
||||
table.insert(inbounds, {
|
||||
port = redir_port,
|
||||
protocol = "dokodemo-door",
|
||||
settings = {network = proto, followRedirect = true},
|
||||
sniffing = {enabled = true, destOverride = {"http", "tls"}}
|
||||
})
|
||||
if proto == "tcp" and node.v2ray_tcp_socks == "1" then
|
||||
table.insert(inbounds, {
|
||||
listen = "0.0.0.0",
|
||||
port = tonumber(node.v2ray_tcp_socks_port),
|
||||
protocol = "socks",
|
||||
settings = {
|
||||
auth = node.v2ray_tcp_socks_auth,
|
||||
accounts = (node.v2ray_tcp_socks_auth == "password") and {
|
||||
{
|
||||
user = node.v2ray_tcp_socks_auth_username,
|
||||
pass = node.v2ray_tcp_socks_auth_password
|
||||
}
|
||||
} or nil,
|
||||
udp = true
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if node.type == "V2ray_balancing" and node.v2ray_balancing_node then
|
||||
local nodes = node.v2ray_balancing_node
|
||||
local length = #nodes
|
||||
for i = 1, length do
|
||||
local node = ucursor:get_all("passwall", nodes[i])
|
||||
local outbound = gen_outbound(node)
|
||||
if outbound then table.insert(outbounds, outbound) end
|
||||
end
|
||||
routing = {
|
||||
domainStrategy = "IPOnDemand",
|
||||
balancers = {{tag = "balancer", selector = nodes}},
|
||||
rules = {
|
||||
{type = "field", network = "tcp,udp", balancerTag = "balancer"}
|
||||
}
|
||||
}
|
||||
elseif node.type == "V2ray_shunt" then
|
||||
local rules = {}
|
||||
|
||||
local youtube_node = node.youtube_node or nil
|
||||
if youtube_node and youtube_node ~= "nil" then
|
||||
local node = ucursor:get_all("passwall", youtube_node)
|
||||
local youtube_outbound = gen_outbound(node, "youtube")
|
||||
if youtube_outbound then
|
||||
table.insert(outbounds, youtube_outbound)
|
||||
local rule = {
|
||||
type = "field",
|
||||
domain = {
|
||||
"youtube", "youtube.com", "youtu.be", "googlevideo.com",
|
||||
"gvt2.com"
|
||||
},
|
||||
outboundTag = "youtube"
|
||||
}
|
||||
table.insert(rules, rule)
|
||||
end
|
||||
end
|
||||
|
||||
local netflix_node = node.netflix_node or nil
|
||||
if netflix_node and netflix_node ~= "nil" then
|
||||
local node = ucursor:get_all("passwall", netflix_node)
|
||||
local netflix_outbound = gen_outbound(node, "netflix")
|
||||
if netflix_outbound then
|
||||
table.insert(outbounds, netflix_outbound)
|
||||
local rule = {
|
||||
type = "field",
|
||||
domain = {
|
||||
"netflix", "netflix.com", "nflxso.net", "nflxext.com",
|
||||
"nflximg.com", "nflximg.net", "nflxvideo.net"
|
||||
},
|
||||
outboundTag = "netflix"
|
||||
}
|
||||
table.insert(rules, rule)
|
||||
end
|
||||
end
|
||||
|
||||
local default_node = node.default_node or nil
|
||||
if default_node and default_node ~= "nil" then
|
||||
local node = ucursor:get_all("passwall", default_node)
|
||||
local default_outbound = gen_outbound(node, "default")
|
||||
if default_outbound then
|
||||
table.insert(outbounds, default_outbound)
|
||||
local rule = {
|
||||
type = "field",
|
||||
outboundTag = "default",
|
||||
network = network
|
||||
}
|
||||
table.insert(rules, rule)
|
||||
end
|
||||
end
|
||||
|
||||
routing = {domainStrategy = "IPOnDemand", rules = rules}
|
||||
else
|
||||
local outbound = gen_outbound(node)
|
||||
if outbound then table.insert(outbounds, outbound) end
|
||||
end
|
||||
-- 额外传出连接
|
||||
table.insert(outbounds,
|
||||
{protocol = "freedom", tag = "direct", settings = {keep = ""}})
|
||||
|
||||
local v2ray = {
|
||||
log = {
|
||||
-- error = "/var/log/v2ray.log",
|
||||
loglevel = "warning"
|
||||
},
|
||||
-- 传入连接
|
||||
inbounds = inbounds,
|
||||
-- 传出连接
|
||||
outbounds = outbounds,
|
||||
-- 路由
|
||||
routing = routing
|
||||
}
|
||||
print(json.stringify(v2ray, 1))
|
||||
|
||||
@ -71,6 +71,7 @@ if is_finded("ssr-redir") then type:value("SSR", translate("ShadowsocksR")) end
|
||||
if is_installed("v2ray") or is_finded("v2ray") then
|
||||
type:value("V2ray", translate("V2ray"))
|
||||
type:value("V2ray_balancing", translate("V2ray Balancing"))
|
||||
type:value("V2ray_shunt", translate("V2ray Shunt"))
|
||||
end
|
||||
if is_installed("brook") or is_finded("brook") then
|
||||
type:value("Brook", translate("Brook"))
|
||||
@ -83,7 +84,6 @@ v2ray_protocol = s:option(ListValue, "v2ray_protocol",
|
||||
translate("V2ray Protocol"))
|
||||
v2ray_protocol:value("vmess", translate("Vmess"))
|
||||
v2ray_protocol:depends("type", "V2ray")
|
||||
v2ray_protocol:depends("type", "V2ray_balancing")
|
||||
|
||||
local n = {}
|
||||
uci:foreach(appname, "nodes", function(e)
|
||||
@ -98,10 +98,25 @@ table.sort(key_table)
|
||||
|
||||
v2ray_balancing_node = s:option(DynamicList, "v2ray_balancing_node",
|
||||
translate("Load balancing node list"), translate(
|
||||
"Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/app/balance.html'>document</a>"))
|
||||
"Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>document</a>"))
|
||||
for _, key in pairs(key_table) do v2ray_balancing_node:value(key, n[key]) end
|
||||
v2ray_balancing_node:depends("type", "V2ray_balancing")
|
||||
|
||||
youtube_node = s:option(ListValue, "youtube_node", "Youtube " .. translate("Node"))
|
||||
youtube_node:value("nil", translate("Close"))
|
||||
for _, key in pairs(key_table) do youtube_node:value(key, n[key]) end
|
||||
youtube_node:depends("type", "V2ray_shunt")
|
||||
|
||||
netflix_node = s:option(ListValue, "netflix_node", "Netflix " .. translate("Node"))
|
||||
netflix_node:value("nil", translate("Close"))
|
||||
for _, key in pairs(key_table) do netflix_node:value(key, n[key]) end
|
||||
netflix_node:depends("type", "V2ray_shunt")
|
||||
|
||||
default_node = s:option(ListValue, "default_node", translate("Default") .. " " .. translate("Node"))
|
||||
default_node:value("nil", translate("Close"))
|
||||
for _, key in pairs(key_table) do default_node:value(key, n[key]) end
|
||||
default_node:depends("type", "V2ray_shunt")
|
||||
|
||||
address = s:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
address.rmempty = false
|
||||
address:depends("type", "Socks5")
|
||||
@ -243,7 +258,6 @@ v2ray_stream_security = s:option(ListValue, "v2ray_stream_security",
|
||||
v2ray_stream_security:value("none", "none")
|
||||
v2ray_stream_security:value("tls", "tls")
|
||||
v2ray_stream_security:depends("type", "V2ray")
|
||||
v2ray_stream_security:depends("type", "V2ray_balancing")
|
||||
|
||||
-- [[ TLS部分 ]] --
|
||||
tls_serverName = s:option(Value, "tls_serverName", translate("Domain"))
|
||||
@ -265,7 +279,6 @@ v2ray_transport:value("h2", "HTTP/2")
|
||||
v2ray_transport:value("ds", "DomainSocket")
|
||||
v2ray_transport:value("quic", "QUIC")
|
||||
v2ray_transport:depends("type", "V2ray")
|
||||
v2ray_transport:depends("type", "V2ray_balancing")
|
||||
|
||||
-- [[ TCP部分 ]]--
|
||||
|
||||
@ -363,7 +376,6 @@ v2ray_quic_guise:depends("v2ray_transport", "quic")
|
||||
|
||||
v2ray_mux = s:option(Flag, "v2ray_mux", translate("Mux"))
|
||||
v2ray_mux:depends("type", "V2ray")
|
||||
v2ray_mux:depends("type", "V2ray_balancing")
|
||||
|
||||
v2ray_mux_concurrency = s:option(Value, "v2ray_mux_concurrency",
|
||||
translate("Mux Concurrency"))
|
||||
@ -412,7 +424,6 @@ trojan_cert_path:depends("trojan_verify_cert", "1")
|
||||
|
||||
-- v2ray_insecure = s:option(Flag, "v2ray_insecure", translate("allowInsecure"))
|
||||
-- v2ray_insecure:depends("type", "V2ray")
|
||||
-- v2ray_insecure:depends("type", "V2ray_balancing")
|
||||
|
||||
function rmempty_restore()
|
||||
address.rmempty = true
|
||||
|
||||
@ -63,6 +63,7 @@ if api.uci_get_type("global_other", "show_group", "1") == "1" then
|
||||
end
|
||||
end
|
||||
|
||||
s.sortable = true
|
||||
-- 简洁模式
|
||||
if api.uci_get_type("global_other", "compact_display_nodes", "0") == "1" then
|
||||
if show_group then show_group.width = "25%" end
|
||||
@ -84,7 +85,6 @@ if api.uci_get_type("global_other", "compact_display_nodes", "0") == "1" then
|
||||
return str
|
||||
end
|
||||
else
|
||||
s.sortable = true
|
||||
---- Add Mode
|
||||
if api.uci_get_type("global_other", "show_add_mode", "1") == "1" then
|
||||
o = s:option(DummyValue, "add_mode", translate("Add Mode"))
|
||||
|
||||
@ -292,11 +292,17 @@ msgstr "V2ray 负载均衡"
|
||||
msgid "V2ray_balancing"
|
||||
msgstr "V2ray 负载均衡"
|
||||
|
||||
msgid "V2ray Shunt"
|
||||
msgstr "V2ray 分流"
|
||||
|
||||
msgid "V2ray_shunt"
|
||||
msgstr "V2ray 分流"
|
||||
|
||||
msgid "Load balancing node list"
|
||||
msgstr "负载均衡节点列表"
|
||||
|
||||
msgid "Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/app/balance.html'>document</a>"
|
||||
msgstr "负载均衡节点列表,<a target='_blank' href='https://toutyrater.github.io/app/balance.html'>文档原理</a>"
|
||||
msgid "Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>document</a>"
|
||||
msgstr "负载均衡节点列表,<a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>文档原理</a>"
|
||||
|
||||
msgid "Address"
|
||||
msgstr "地址"
|
||||
@ -484,6 +490,9 @@ msgstr "负载均衡设置"
|
||||
msgid "Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline!"
|
||||
msgstr "添加节点,指定出口功能是为多WAN用户准备的。负载比重范围1-256。多个主服务器可以负载均衡,备用只有在主服务器离线时才会启用!"
|
||||
|
||||
msgid "Node"
|
||||
msgstr "节点"
|
||||
|
||||
msgid "Node Address"
|
||||
msgstr "节点地址"
|
||||
|
||||
|
||||
@ -300,24 +300,7 @@ gen_start_config() {
|
||||
eval SOCKS5_NODE${5}_PORT=$port
|
||||
if [ "$type" == "socks5" ]; then
|
||||
echolog "Socks5节点不能使用Socks5代理节点!"
|
||||
elif [ "$type" == "v2ray" ]; then
|
||||
lua $API_GEN_V2RAY $node nil nil $local_port >$config_file
|
||||
ln_start_bin $(config_t_get global_app v2ray_file $(find_bin v2ray))/v2ray v2ray "-config=$config_file"
|
||||
elif [ "$type" == "v2ray_balancing" ]; then
|
||||
local balancing_node=$(config_n_get $node v2ray_balancing_node)
|
||||
balancing_node_address=""
|
||||
for node_id in $balancing_node
|
||||
do
|
||||
local address=$(config_n_get $node_id address)
|
||||
local port=$(config_n_get $node_id port)
|
||||
local temp=""
|
||||
if [ -z "$balancing_node_address" ]; then
|
||||
temp="${address}:${port}"
|
||||
else
|
||||
temp="${balancing_node_address}\n${address}:${port}"
|
||||
fi
|
||||
balancing_node_address="$temp"
|
||||
done
|
||||
elif [ "$type" == "v2ray" -o "$type" == "v2ray_balancing" -o "$type" == "v2ray_shunt" ]; then
|
||||
lua $API_GEN_V2RAY $node nil nil $local_port >$config_file
|
||||
ln_start_bin $(config_t_get global_app v2ray_file $(find_bin v2ray))/v2ray v2ray "-config=$config_file"
|
||||
elif [ "$type" == "trojan" ]; then
|
||||
@ -361,24 +344,7 @@ gen_start_config() {
|
||||
# local redsocks_config_file=$CONFIG_PATH/UDP_$i.conf
|
||||
# gen_redsocks_config $redsocks_config_file udp $port $node_address $node_port $server_username $server_password
|
||||
# ln_start_bin $(find_bin redsocks2) redsocks2 "-c $redsocks_config_file"
|
||||
elif [ "$type" == "v2ray" ]; then
|
||||
lua $API_GEN_V2RAY $node udp $local_port nil >$config_file
|
||||
ln_start_bin $(config_t_get global_app v2ray_file $(find_bin v2ray))/v2ray v2ray "-config=$config_file"
|
||||
elif [ "$type" == "v2ray_balancing" ]; then
|
||||
local balancing_node=$(config_n_get $node v2ray_balancing_node)
|
||||
balancing_node_address=""
|
||||
for node_id in $balancing_node
|
||||
do
|
||||
local address=$(config_n_get $node_id address)
|
||||
local port=$(config_n_get $node_id port)
|
||||
local temp=""
|
||||
if [ -z "$balancing_node_address" ]; then
|
||||
temp="${address}:${port}"
|
||||
else
|
||||
temp="${balancing_node_address}\n${address}:${port}"
|
||||
fi
|
||||
balancing_node_address="$temp"
|
||||
done
|
||||
elif [ "$type" == "v2ray" -o "$type" == "v2ray_balancing" -o "$type" == "v2ray_shunt" ]; then
|
||||
lua $API_GEN_V2RAY $node udp $local_port nil >$config_file
|
||||
ln_start_bin $(config_t_get global_app v2ray_file $(find_bin v2ray))/v2ray v2ray "-config=$config_file"
|
||||
elif [ "$type" == "trojan" ]; then
|
||||
@ -436,24 +402,7 @@ gen_start_config() {
|
||||
# local redsocks_config_file=$CONFIG_PATH/TCP_$i.conf
|
||||
# gen_redsocks_config $redsocks_config_file tcp $port $node_address $socks5_port $server_username $server_password
|
||||
# ln_start_bin $(find_bin redsocks2) redsocks2 "-c $redsocks_config_file"
|
||||
elif [ "$type" == "v2ray" ]; then
|
||||
lua $API_GEN_V2RAY $node tcp $local_port nil >$config_file
|
||||
ln_start_bin $(config_t_get global_app v2ray_file $(find_bin v2ray))/v2ray v2ray "-config=$config_file"
|
||||
elif [ "$type" == "v2ray_balancing" ]; then
|
||||
local balancing_node=$(config_n_get $node v2ray_balancing_node)
|
||||
balancing_node_address=""
|
||||
for node_id in $balancing_node
|
||||
do
|
||||
local address=$(config_n_get $node_id address)
|
||||
local port=$(config_n_get $node_id port)
|
||||
local temp=""
|
||||
if [ -z "$balancing_node_address" ]; then
|
||||
temp="${address}:${port}"
|
||||
else
|
||||
temp="${balancing_node_address}\n${address}:${port}"
|
||||
fi
|
||||
balancing_node_address="$temp"
|
||||
done
|
||||
elif [ "$type" == "v2ray" -o "$type" == "v2ray_balancing" -o "$type" == "v2ray_shunt" ]; then
|
||||
lua $API_GEN_V2RAY $node tcp $local_port nil >$config_file
|
||||
ln_start_bin $(config_t_get global_app v2ray_file $(find_bin v2ray))/v2ray v2ray "-config=$config_file"
|
||||
elif [ "$type" == "trojan" ]; then
|
||||
@ -675,6 +624,11 @@ add_dnsmasq() {
|
||||
mkdir -p $TMP_DNSMASQ_PATH $DNSMASQ_PATH /var/dnsmasq.d
|
||||
cat $RULE_PATH/whitelist_host | sed -e "/^$/d" | sed "s/^/ipset=&\/./g" | sed "s/$/\/&whitelist/g" | sort | awk '{if ($0!=line) print;line=$0}' > $TMP_DNSMASQ_PATH/whitelist_host.conf
|
||||
|
||||
local adblock=$(config_t_get global_rules adblock 0)
|
||||
[ "$adblock" == "1" ] && {
|
||||
[ -f "$RULE_PATH/adblock.conf" -a -s "$RULE_PATH/adblock.conf" ] && ln -s $RULE_PATH/adblock.conf $TMP_DNSMASQ_PATH/adblock.conf
|
||||
}
|
||||
|
||||
[ "$DNS_MODE" != "nonuse" ] && {
|
||||
[ -f "$RULE_PATH/blacklist_host" -a -s "$RULE_PATH/blacklist_host" ] && cat $RULE_PATH/blacklist_host | sed -e "/^$/d" | awk '{print "server=/."$1"/127.0.0.1#'$DNS_PORT'\nipset=/."$1"/blacklist"}' > $TMP_DNSMASQ_PATH/blacklist_host.conf
|
||||
[ -f "$RULE_PATH/router" -a -s "$RULE_PATH/router" ] && cat $RULE_PATH/router | sed -e "/^$/d" | awk '{print "server=/."$1"/127.0.0.1#'$DNS_PORT'\nipset=/."$1"/router"}' > $TMP_DNSMASQ_PATH/router.conf
|
||||
|
||||
@ -11,7 +11,6 @@ IPSET_WHITELIST="whitelist"
|
||||
ipt_n="iptables -t nat"
|
||||
ipt_m="iptables -t mangle"
|
||||
ip6t_n="ip6tables -t nat"
|
||||
ipt_comment="-m comment --comment PassWall"
|
||||
|
||||
factor() {
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
@ -44,6 +43,9 @@ dst() {
|
||||
echo "-m set --match-set $1 dst"
|
||||
}
|
||||
|
||||
comment() {
|
||||
echo "-m comment --comment '$1'"
|
||||
}
|
||||
|
||||
get_action_chain() {
|
||||
case "$1" in
|
||||
@ -146,31 +148,31 @@ load_acl() {
|
||||
fi
|
||||
|
||||
if [ "$proxy_mode" == "disable" ]; then
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp -m comment --comment "$remarks" -j RETURN
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp -m comment --comment "$remarks" -j RETURN
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(comment "$remarks") -j RETURN
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp $(comment "$remarks") -j RETURN
|
||||
else
|
||||
[ "$TCP_NODE" != "nil" ] && {
|
||||
eval TCP_NODE_TYPE=$(echo $(config_get $node type) | tr 'A-Z' 'a-z')
|
||||
if [ "$TCP_NODE_TYPE" == "brook" ]; then
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && $ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -j RETURN
|
||||
eval tcp_redir_port=\$TCP_REDIR_PORT$tcp_node
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(dst $IPSET_BLACKLIST) $(factor $tcp_redir_ports "-m multiport --dport") -m comment --comment "$remarks" -j TPROXY --tproxy-mark 0x1/0x1 --on-port $tcp_redir_port
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(factor $tcp_redir_ports "-m multiport --dport") -m comment --comment "$remarks" -$(get_jump_mode $proxy_mode) $(get_action_chain $proxy_mode)$tcp_node
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp -m comment --comment "$remarks" -j RETURN
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(dst $IPSET_BLACKLIST) $(factor $tcp_redir_ports "-m multiport --dport") $(comment "$remarks") -j TPROXY --tproxy-mark 0x1/0x1 --on-port $tcp_redir_port
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(factor $tcp_redir_ports "-m multiport --dport") $(comment "$remarks") -$(get_jump_mode $proxy_mode) $(get_action_chain $proxy_mode)$tcp_node
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(comment "$remarks") -j RETURN
|
||||
else
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && $ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -j RETURN
|
||||
eval tcp_redir_port=\$TCP_REDIR_PORT$tcp_node
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(dst $IPSET_BLACKLIST) $(factor $tcp_redir_ports "-m multiport --dport") -m comment --comment "$remarks" -j REDIRECT --to-ports $tcp_redir_port
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(factor $tcp_redir_ports "-m multiport --dport") -m comment --comment "$remarks" -$(get_jump_mode $proxy_mode) $(get_action_chain $proxy_mode)$tcp_node
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp -m comment --comment "$remarks" -j RETURN
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(dst $IPSET_BLACKLIST) $(factor $tcp_redir_ports "-m multiport --dport") $(comment "$remarks") -j REDIRECT --to-ports $tcp_redir_port
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(factor $tcp_redir_ports "-m multiport --dport") $(comment "$remarks") -$(get_jump_mode $proxy_mode) $(get_action_chain $proxy_mode)$tcp_node
|
||||
$ipt_n -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p tcp $(comment "$remarks") -j RETURN
|
||||
fi
|
||||
}
|
||||
[ "$UDP_NODE" != "nil" ] && {
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && $ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp -m multiport --dport $TCP_NO_REDIR_PORTS -j RETURN
|
||||
eval udp_redir_port=\$UDP_REDIR_PORT$udp_node
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp $(dst $IPSET_BLACKLIST) -m comment --comment "$remarks" -j TPROXY --on-port $udp_redir_port --tproxy-mark 0x1/0x1
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp $(factor $udp_redir_ports "-m multiport --dport") -m comment --comment "$remarks" -$(get_jump_mode $proxy_mode) $(get_action_chain $proxy_mode)$udp_node
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp -m comment --comment "$remarks" -j RETURN
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp $(dst $IPSET_BLACKLIST) $(comment "$remarks") -j TPROXY --on-port $udp_redir_port --tproxy-mark 0x1/0x1
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp $(factor $udp_redir_ports "-m multiport --dport") $(comment "$remarks") -$(get_jump_mode $proxy_mode) $(get_action_chain $proxy_mode)$udp_node
|
||||
$ipt_m -A PSW_ACL $(factor $ip "-s") $(factor $mac "-m mac --mac-source") -p udp $(comment "$remarks") -j RETURN
|
||||
}
|
||||
fi
|
||||
[ -z "$ip" ] && {
|
||||
@ -198,13 +200,43 @@ filter_vpsip() {
|
||||
if [ -n "$isip" ]; then
|
||||
ipset -! add $IPSET_VPSIPLIST $isip >/dev/null 2>&1 &
|
||||
else
|
||||
has=$(cat $TMP_DNSMASQ_PATH/vpsiplist_host.conf | grep "$server")
|
||||
has=$([ -f "$TMP_DNSMASQ_PATH/vpsiplist_host.conf" ] && cat $TMP_DNSMASQ_PATH/vpsiplist_host.conf | grep "$server")
|
||||
[ -z "$has" ] && echo "$server" | sed -e "/^$/d" | sed "s/^/ipset=&\//g" | sed "s/$/\/&vpsiplist/g" | sort | awk '{if ($0!=line) print;line=$0}' >> $TMP_DNSMASQ_PATH/vpsiplist_host.conf
|
||||
fi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter_node() {
|
||||
filter_rules() {
|
||||
[ -n "$1" -a "$1" != "nil" ] && {
|
||||
local type=$(echo $(config_get $1 type) | tr 'A-Z' 'a-z')
|
||||
local i=$ipt_n
|
||||
[ "$type" == "brook" ] && i=$ipt_m
|
||||
local address=$(config_get $1 address)
|
||||
local port=$(config_get $1 port)
|
||||
is_exist=$($i -L PSW 2>/dev/null | grep -c "$address:$port")
|
||||
[ "$is_exist" == 0 ] && $i -A PSW -p tcp -d $address --dport $port $(comment "$address:$port") -j RETURN
|
||||
is_exist=$($i -L PSW_OUTPUT 2>/dev/null | grep -c "$address:$port")
|
||||
[ "$is_exist" == 0 ] && $i -A PSW_OUTPUT -p tcp -d $address --dport $port $(comment "$address:$port") -j RETURN
|
||||
}
|
||||
}
|
||||
local tmp_type=$(echo $(config_get $1 type) | tr 'A-Z' 'a-z')
|
||||
if [ "$tmp_type" == "v2ray_shunt" ]; then
|
||||
filter_rules $(config_get $node youtube_node)
|
||||
filter_rules $(config_get $node netflix_node)
|
||||
filter_rules $(config_get $node default_node)
|
||||
elif [ "$tmp_type" == "v2ray_balancing" ]; then
|
||||
local balancing_node=$(config_get $node v2ray_balancing_node)
|
||||
for node_id in $balancing_node
|
||||
do
|
||||
filter_rules $node_id
|
||||
done
|
||||
else
|
||||
filter_rules $node
|
||||
fi
|
||||
}
|
||||
|
||||
dns_hijack() {
|
||||
dnshijack=$(config_t_get global dns_53)
|
||||
if [ "$dnshijack" = "1" -o "$1" = "force" ]; then
|
||||
@ -294,14 +326,7 @@ add_firewall_rule() {
|
||||
for i in $(seq 1 $SOCKS5_NODE_NUM); do
|
||||
local k=$i
|
||||
eval node=\$SOCKS5_NODE$k
|
||||
if [ "$node" != "nil" ]; then
|
||||
local SOCKS5_NODE_PORT=$(config_get $node port)
|
||||
local SOCKS5_NODE_IP=$(get_node_host_ip $node)
|
||||
[ -n "$SOCKS5_NODE_IP" -a -n "$SOCKS5_NODE_PORT" ] && {
|
||||
$ipt_n -A PSW -p tcp -d $SOCKS5_NODE_IP --dport $SOCKS5_NODE_PORT -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT -p tcp -d $SOCKS5_NODE_IP --dport $SOCKS5_NODE_PORT -j RETURN
|
||||
}
|
||||
fi
|
||||
[ "$node" != "nil" ] && filter_node $node
|
||||
done
|
||||
fi
|
||||
|
||||
@ -313,8 +338,7 @@ add_firewall_rule() {
|
||||
eval local_port=\$TCP_REDIR_PORT$k
|
||||
# 生成TCP转发规则
|
||||
if [ "$node" != "nil" ]; then
|
||||
local TCP_NODE_PORT=$(config_get $node port)
|
||||
local TCP_NODE_IP=$(get_node_host_ip $node)
|
||||
filter_node $node
|
||||
local TCP_NODE_TYPE=$(echo $(config_get $node type) | tr 'A-Z' 'a-z')
|
||||
if [ "$TCP_NODE_TYPE" == "brook" ]; then
|
||||
$ipt_m -A PSW_ACL -p tcp -m socket -j MARK --set-mark 1
|
||||
@ -356,11 +380,6 @@ add_firewall_rule() {
|
||||
|
||||
[ "$k" == 1 ] && {
|
||||
if [ "$TCP_NODE_TYPE" == "brook" ]; then
|
||||
[ -n "$TCP_NODE_IP" -a -n "$TCP_NODE_PORT" ] && {
|
||||
$ipt_m -A PSW -p tcp -d $TCP_NODE_IP --dport $TCP_NODE_PORT -j RETURN
|
||||
$ipt_m -A PSW_OUTPUT -p tcp -d $TCP_NODE_IP --dport $TCP_NODE_PORT -j RETURN
|
||||
}
|
||||
|
||||
[ "$use_tcp_node_resolve_dns" == 1 -a -n "$DNS_FORWARD" ] && {
|
||||
for dns in $DNS_FORWARD
|
||||
do
|
||||
@ -378,10 +397,6 @@ add_firewall_rule() {
|
||||
[ "$LOCALHOST_PROXY_MODE" == "gfwlist" ] && $ipt_m -A PSW_OUTPUT -p tcp $(dst $IPSET_GFW) $(factor $TCP_REDIR_PORTS "-m multiport --dport") -j MARK --set-mark 1
|
||||
[ "$LOCALHOST_PROXY_MODE" == "chnroute" ] && $ipt_m -A PSW_OUTPUT -p tcp -m set ! --match-set $IPSET_CHN dst $(factor $TCP_REDIR_PORTS "-m multiport --dport") -j MARK --set-mark 1
|
||||
else
|
||||
[ -n "$TCP_NODE_IP" -a -n "$TCP_NODE_PORT" ] && {
|
||||
$ipt_n -A PSW -p tcp -d $TCP_NODE_IP --dport $TCP_NODE_PORT -j RETURN
|
||||
$ipt_n -A PSW_OUTPUT -p tcp -d $TCP_NODE_IP --dport $TCP_NODE_PORT -j RETURN
|
||||
}
|
||||
PRE_INDEX=1
|
||||
KP_INDEX=$($ipt_n -L PREROUTING --line-numbers | grep "KOOLPROXY" | sed -n '$p' | awk '{print $1}')
|
||||
ADBYBY_INDEX=$($ipt_n -L PREROUTING --line-numbers | grep "ADBYBY" | sed -n '$p' | awk '{print $1}')
|
||||
@ -453,13 +468,8 @@ add_firewall_rule() {
|
||||
eval local_port=\$UDP_REDIR_PORT$k
|
||||
# 生成UDP转发规则
|
||||
if [ "$node" != "nil" ]; then
|
||||
local UDP_NODE_PORT=$(config_get $node port)
|
||||
local UDP_NODE_IP=$(get_node_host_ip $node)
|
||||
filter_node $node
|
||||
local UDP_NODE_TYPE=$(echo $(config_get $node type) | tr 'A-Z' 'a-z')
|
||||
[ -n "$UDP_NODE_IP" -a -n "$UDP_NODE_PORT" ] && {
|
||||
$ipt_m -A PSW -p udp -d $UDP_NODE_IP --dport $UDP_NODE_PORT -j RETURN
|
||||
$ipt_m -A PSW_OUTPUT -p udp -d $UDP_NODE_IP --dport $UDP_NODE_PORT -j RETURN
|
||||
}
|
||||
[ "$UDP_NODE_TYPE" == "brook" ] && $ipt_m -A PSW_ACL -p udp -m socket -j MARK --set-mark 1
|
||||
# 全局模式
|
||||
$ipt_m -A PSW_GLO$k -p udp -j TPROXY --tproxy-mark 0x1/0x1 --on-port $local_port
|
||||
@ -509,44 +519,31 @@ add_firewall_rule() {
|
||||
else
|
||||
echolog "UDP节点未选择,无法转发UDP!"
|
||||
fi
|
||||
|
||||
if [ -n "$balancing_node_address" ]; then
|
||||
balancing_node_address=$(echo -e $balancing_node_address)
|
||||
for balancing_node in $balancing_node_address
|
||||
do
|
||||
local ip=$(echo $balancing_node | awk -F ":" '{print $1}')
|
||||
local port=$(echo $balancing_node | awk -F ":" '{print $2}')
|
||||
$ipt_n -I PSW 2 -p tcp -d $ip --dport $port -j RETURN
|
||||
$ipt_n -I PSW_OUTPUT 2 -p tcp -d $ip --dport $port -j RETURN
|
||||
$ipt_m -I PSW 2 -p udp -d $ip --dport $port -j RETURN
|
||||
$ipt_m -I PSW_OUTPUT 2 -p udp -d $ip --dport $port -j RETURN
|
||||
done
|
||||
fi
|
||||
|
||||
# 加载ACLS
|
||||
config_foreach load_acl "acl_rule"
|
||||
|
||||
# 加载默认代理模式
|
||||
if [ "$PROXY_MODE" == "disable" ]; then
|
||||
[ "$TCP_NODE1" != "nil" ] && $ipt_n -A PSW_ACL -p tcp -m comment --comment "Default" -j $(get_action_chain $PROXY_MODE)
|
||||
[ "$UDP_NODE1" != "nil" ] && $ipt_m -A PSW_ACL -p udp -m comment --comment "Default" -j $(get_action_chain $PROXY_MODE)
|
||||
[ "$TCP_NODE1" != "nil" ] && $ipt_n -A PSW_ACL -p tcp $(comment "Default") -j $(get_action_chain $PROXY_MODE)
|
||||
[ "$UDP_NODE1" != "nil" ] && $ipt_m -A PSW_ACL -p udp $(comment "Default") -j $(get_action_chain $PROXY_MODE)
|
||||
else
|
||||
[ "$TCP_NODE1" != "nil" ] && {
|
||||
local TCP_NODE_TYPE1=$(echo $(config_get $TCP_NODE1 type) | tr 'A-Z' 'a-z')
|
||||
if [ "$TCP_NODE_TYPE1" == "brook" ]; then
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && $ipt_m -A PSW_ACL -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -m comment --comment "Default" -j RETURN
|
||||
$ipt_m -A PSW_ACL -p tcp $(dst $IPSET_BLACKLIST) $(factor $TCP_REDIR_PORTS "-m multiport --dport") -m comment --comment "Default" -j TPROXY --tproxy-mark 0x1/0x1 --on-port $TCP_REDIR_PORT1
|
||||
$ipt_m -A PSW_ACL -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") -m comment --comment "Default" -j $(get_action_chain $PROXY_MODE)1
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && $ipt_m -A PSW_ACL -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS $(comment "Default") -j RETURN
|
||||
$ipt_m -A PSW_ACL -p tcp $(dst $IPSET_BLACKLIST) $(factor $TCP_REDIR_PORTS "-m multiport --dport") $(comment "Default") -j TPROXY --tproxy-mark 0x1/0x1 --on-port $TCP_REDIR_PORT1
|
||||
$ipt_m -A PSW_ACL -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") $(comment "Default") -j $(get_action_chain $PROXY_MODE)1
|
||||
else
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && $ipt_n -A PSW_ACL -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -m comment --comment "Default" -j RETURN
|
||||
$ipt_n -A PSW_ACL -p tcp $(dst $IPSET_BLACKLIST) $(factor $TCP_REDIR_PORTS "-m multiport --dport") -m comment --comment "Default" -j REDIRECT --to-ports $TCP_REDIR_PORT1
|
||||
$ipt_n -A PSW_ACL -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") -m comment --comment "Default" -j $(get_action_chain $PROXY_MODE)1
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && $ipt_n -A PSW_ACL -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS $(comment "Default") -j RETURN
|
||||
$ipt_n -A PSW_ACL -p tcp $(dst $IPSET_BLACKLIST) $(factor $TCP_REDIR_PORTS "-m multiport --dport") $(comment "Default") -j REDIRECT --to-ports $TCP_REDIR_PORT1
|
||||
$ipt_n -A PSW_ACL -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") $(comment "Default") -j $(get_action_chain $PROXY_MODE)1
|
||||
fi
|
||||
}
|
||||
[ "$UDP_NODE1" != "nil" ] && {
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && $ipt_m -A PSW_ACL -p udp -m multiport --dport $UDP_NO_REDIR_PORTS -m comment --comment "Default" -j RETURN
|
||||
$ipt_m -A PSW_ACL -p udp $(dst $IPSET_BLACKLIST) $(factor $UDP_REDIR_PORTS "-m multiport --dport") -m comment --comment "Default" -j TPROXY --on-port $UDP_REDIR_PORT1 --tproxy-mark 0x1/0x1
|
||||
$ipt_m -A PSW_ACL -p udp $(factor $UDP_REDIR_PORTS "-m multiport --dport") -m comment --comment "Default" -j $(get_action_chain $PROXY_MODE)1
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && $ipt_m -A PSW_ACL -p udp -m multiport --dport $UDP_NO_REDIR_PORTS $(comment "Default") -j RETURN
|
||||
$ipt_m -A PSW_ACL -p udp $(dst $IPSET_BLACKLIST) $(factor $UDP_REDIR_PORTS "-m multiport --dport") $(comment "Default") -j TPROXY --on-port $UDP_REDIR_PORT1 --tproxy-mark 0x1/0x1
|
||||
$ipt_m -A PSW_ACL -p udp $(factor $UDP_REDIR_PORTS "-m multiport --dport") $(comment "Default") -j $(get_action_chain $PROXY_MODE)1
|
||||
}
|
||||
fi
|
||||
|
||||
|
||||
18
package/lienol/luci-app-pptp-vpnserver-manyusers/Makefile
Normal file
18
package/lienol/luci-app-pptp-vpnserver-manyusers/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2018-2020 Lienol <lawlienol@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the Apache License, Version 2.0 .
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for PPTP VPN Server
|
||||
LUCI_DEPENDS:=+pptpd +kmod-mppe +ppp
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=12-20190704
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
-- Copyright 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
module("luci.controller.pptpd", package.seeall)
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/pptpd") then return end
|
||||
|
||||
entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false
|
||||
entry({"admin", "vpn", "pptpd"}, alias("admin", "vpn", "pptpd", "settings"),
|
||||
_("PPTP VPN Server"), 48)
|
||||
entry({"admin", "vpn", "pptpd", "settings"}, cbi("pptpd/settings"),
|
||||
_("General Settings"), 10).leaf = true
|
||||
entry({"admin", "vpn", "pptpd", "users"}, cbi("pptpd/users"),
|
||||
_("Users Manager"), 20).leaf = true
|
||||
entry({"admin", "vpn", "pptpd", "online"}, cbi("pptpd/online"),
|
||||
_("Online Users"), 30).leaf = true
|
||||
entry({"admin", "vpn", "pptpd", "status"}, call("status")).leaf = true
|
||||
end
|
||||
|
||||
function status()
|
||||
local e = {}
|
||||
e.status = luci.sys.call("pidof %s >/dev/null" % "pptpd") == 0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
@ -0,0 +1,80 @@
|
||||
local e = {}
|
||||
local o = require "luci.dispatcher"
|
||||
local a = luci.util.execi("/bin/busybox top -bn1 | grep '/usr/sbin/pppd'")
|
||||
for t in a do
|
||||
local a, n, h, s, o, i = t:match(
|
||||
"^ *(%d+) +(%d+) +.+options%.pptpd +(%d+) +(%S.-%S)%:(%S.-%S) +.+ +(.+)")
|
||||
local t = tonumber(a)
|
||||
if t then
|
||||
e["%02i.%s" % {t, "online"}] = {
|
||||
['PID'] = a,
|
||||
['PPID'] = n,
|
||||
['SPEED'] = h,
|
||||
['GATEWAY'] = s,
|
||||
['VIP'] = o,
|
||||
['CIP'] = i,
|
||||
['BLACKLIST'] = 0
|
||||
}
|
||||
end
|
||||
end
|
||||
local a = luci.util.execi("sed = /etc/firewall.user | sed 'N;s/\\n/:/'")
|
||||
for t in a do
|
||||
local t, a = t:match("^ *(%d+)%:.+%#%# pptpd%-blacklist%-(.+)")
|
||||
local t = tonumber(t)
|
||||
if t then
|
||||
e["%02i.%s" % {t, "blacklist"}] =
|
||||
{
|
||||
['PID'] = "-1",
|
||||
['PPID'] = "-1",
|
||||
['SPEED'] = "-1",
|
||||
['GATEWAY'] = "-",
|
||||
['VIP'] = "-",
|
||||
['CIP'] = a,
|
||||
['BLACKLIST'] = 1
|
||||
}
|
||||
end
|
||||
end
|
||||
f = SimpleForm("processes", translate("PPTP VPN Server"))
|
||||
f.reset = false
|
||||
f.submit = false
|
||||
f.description = translate(
|
||||
"Simple, quick and convenient PPTP VPN, universal across the platform")
|
||||
t = f:section(Table, e, translate("Online Users"))
|
||||
t:option(DummyValue, "GATEWAY", translate("Server IP"))
|
||||
t:option(DummyValue, "VIP", translate("Client IP"))
|
||||
t:option(DummyValue, "CIP", translate("IP address"))
|
||||
blacklist = t:option(Button, "_blacklist", translate("Blacklist"))
|
||||
function blacklist.render(e, t, a)
|
||||
if e.map:get(t, "BLACKLIST") == 0 then
|
||||
e.title = translate("Add to Blacklist")
|
||||
e.inputstyle = "remove"
|
||||
else
|
||||
e.title = translate("Remove from Blacklist")
|
||||
e.inputstyle = "apply"
|
||||
end
|
||||
Button.render(e, t, a)
|
||||
end
|
||||
function blacklist.write(t, a)
|
||||
local e = t.map:get(a, "CIP")
|
||||
if t.map:get(a, "BLACKLIST") == 0 then
|
||||
luci.util.execi(
|
||||
"echo 'iptables -A input_rule -s %s -p tcp --dport 1723 -j DROP ## pptpd-blacklist-%s' >> /etc/firewall.user" %
|
||||
{e, e})
|
||||
luci.util.execi(
|
||||
"iptables -A input_rule -s %s -p tcp --dport 1723 -j DROP" % {e})
|
||||
null, t.tag_error[a] = luci.sys.process.signal(t.map:get(a, "PID"), 9)
|
||||
else
|
||||
luci.util.execi(
|
||||
"sed -i -e '/## pptpd-blacklist-%s/d' /etc/firewall.user" % {e})
|
||||
luci.util.execi(
|
||||
"iptables -D input_rule -s %s -p tcp --dport 1723 -j DROP" % {e})
|
||||
end
|
||||
luci.http.redirect(o.build_url("admin/vpn/pptpd/online"))
|
||||
end
|
||||
kill = t:option(Button, "_kill", translate("Forced offline"))
|
||||
kill.inputstyle = "reset"
|
||||
function kill.write(e, t)
|
||||
null, e.tag_error[t] = luci.sys.process.signal(e.map:get(t, "PID"), 9)
|
||||
luci.http.redirect(o.build_url("admin/vpn/pptpd/online"))
|
||||
end
|
||||
return f
|
||||
@ -0,0 +1,61 @@
|
||||
local s = require "luci.sys"
|
||||
local net = require"luci.model.network".init()
|
||||
local ifaces = s.net:devices()
|
||||
local m, s, o
|
||||
m = Map("pptpd", translate("PPTP VPN Server"))
|
||||
m.description = translate(
|
||||
"Simple, quick and convenient PPTP VPN, universal across the platform")
|
||||
m.template = "pptpd/index"
|
||||
|
||||
s = m:section(TypedSection, "service")
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(DummyValue, "pptpd_status", translate("Current Condition"))
|
||||
o.template = "pptpd/status"
|
||||
o.value = translate("Collecting data...")
|
||||
|
||||
o = s:option(Flag, "enabled", translate("Enable VPN Server"))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "localip", translate("Server IP"),
|
||||
translate("VPN Server IP address, it not required."))
|
||||
o.datatype = "ipaddr"
|
||||
o.placeholder = translate("192.168.1.2")
|
||||
o.rmempty = true
|
||||
o.default = "192.168.1.2"
|
||||
|
||||
o = s:option(Value, "remoteip", translate("Client IP"),
|
||||
translate("VPN Client IP address, it not required."))
|
||||
o.placeholder = translate("192.168.1.10-20")
|
||||
o.rmempty = true
|
||||
o.default = "192.168.1.10-20"
|
||||
|
||||
o = s:option(Value, "dns", translate("DNS IP address"),
|
||||
translate("This will be sent to the client, it not required."))
|
||||
o.placeholder = translate("192.168.1.1")
|
||||
o.datatype = "ipaddr"
|
||||
o.rmempty = true
|
||||
o.default = "192.168.1.1"
|
||||
|
||||
o = s:option(Flag, "mppe", translate("Enable MPPE Encryption"),
|
||||
translate("Allows 128-bit encrypted connection."))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Flag, "is_nat", translate("is_nat"))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "export_interface", translate("Interface"),
|
||||
translate("Specify interface forwarding traffic."))
|
||||
o:value("default", translate("default"))
|
||||
for _, iface in ipairs(ifaces) do
|
||||
if (iface:match("^br*") or iface:match("^eth*") or iface:match("^pppoe*") or
|
||||
iface:match("wlan*")) then
|
||||
local nets = net:get_interface(iface)
|
||||
nets = nets and nets:get_networks() or {}
|
||||
for k, v in pairs(nets) do nets[k] = nets[k].sid end
|
||||
nets = table.concat(nets, ",")
|
||||
o:value(iface, ((#nets > 0) and "%s (%s)" % {iface, nets} or iface))
|
||||
end
|
||||
end
|
||||
o:depends("is_nat", "1")
|
||||
return m
|
||||
@ -0,0 +1,24 @@
|
||||
m = Map("pptpd", translate("PPTP VPN Server"))
|
||||
m.description = translate(
|
||||
"Simple, quick and convenient PPTP VPN, universal across the platform")
|
||||
s = m:section(TypedSection, "users", translate("Users Manager"))
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
s.template = "cbi/tblsection"
|
||||
o = s:option(Flag, "enabled", translate("Enabled"))
|
||||
o.rmempty = false
|
||||
o = s:option(Value, "username", translate("User name"))
|
||||
o.placeholder = translate("User name")
|
||||
o.rmempty = true
|
||||
o = s:option(Value, "password", translate("Password"))
|
||||
o.rmempty = true
|
||||
o = s:option(Value, "ipaddress", translate("IP address"))
|
||||
o.placeholder = translate("Automatically")
|
||||
o.datatype = "ipaddr"
|
||||
o.rmempty = true
|
||||
function o.cfgvalue(e, t)
|
||||
value = e.map:get(t, "ipaddress")
|
||||
return value == "*" and "" or value
|
||||
end
|
||||
function o.remove(e, t) Value.write(e, t, "*") end
|
||||
return m
|
||||
@ -0,0 +1,13 @@
|
||||
<% include("cbi/map") %>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "vpn", "pptpd", "status")%>', null,
|
||||
function(x, result)
|
||||
{
|
||||
var status = document.getElementsByClassName('pptpd_status')[0];
|
||||
status.setAttribute("style","font-weight:bold;");
|
||||
status.setAttribute("color",result.status ? "green":"red");
|
||||
status.innerHTML = result.status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||
}
|
||||
)
|
||||
//]]>
|
||||
</script>
|
||||
@ -0,0 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<font class="pptpd_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,89 @@
|
||||
msgid "PPTP VPN Server"
|
||||
msgstr "PPTP VPN 服务器"
|
||||
|
||||
msgid "Simple, quick and convenient PPTP VPN, universal across the platform"
|
||||
msgstr "简单快捷方便的PPTP VPN,全平台通用。"
|
||||
|
||||
msgid "PPTP VPN Server status"
|
||||
msgstr "PPTP VPN 服务器运行状态"
|
||||
|
||||
msgid "Current Condition"
|
||||
msgstr "当前状态"
|
||||
|
||||
msgid "General settings"
|
||||
msgstr "基本设置"
|
||||
|
||||
msgid "Enable VPN Server"
|
||||
msgstr "启用 VPN 服务器"
|
||||
|
||||
msgid "Server IP"
|
||||
msgstr "服务器 IP 地址"
|
||||
|
||||
msgid "VPN Server IP address, it not required."
|
||||
msgstr "VPN 服务器远程地址,留空将自动设置。"
|
||||
|
||||
msgid "Client IP"
|
||||
msgstr "客户端 IP 地址"
|
||||
|
||||
msgid "VPN Client IP address, it not required."
|
||||
msgstr "分配给客户端的 IP 地址范围,留空将自动设置。"
|
||||
|
||||
msgid "DNS IP address"
|
||||
msgstr "DNS IP 地址"
|
||||
|
||||
msgid "This will be sent to the client, it not required."
|
||||
msgstr "设置 VPN 服务器默认 DNS 服务器,该设置非必须。"
|
||||
|
||||
msgid "Enable MPPE Encryption"
|
||||
msgstr "启用MPPE 加密"
|
||||
|
||||
msgid "Allows 128-bit encrypted connection."
|
||||
msgstr "允许使用 128 位加密连接。"
|
||||
|
||||
msgid "is_nat"
|
||||
msgstr "NAT转发"
|
||||
|
||||
msgid "Interface"
|
||||
msgstr "接口"
|
||||
|
||||
msgid "Specify interface forwarding traffic."
|
||||
msgstr "指定接口转发流量。"
|
||||
|
||||
msgid "Users Manager"
|
||||
msgstr "用户管理"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "User name"
|
||||
msgstr "用户名"
|
||||
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
msgid "IP address"
|
||||
msgstr "IP 地址"
|
||||
|
||||
msgid "Automatically"
|
||||
msgstr "自动分配"
|
||||
|
||||
msgid "Online Users""
|
||||
msgstr "在线用户"
|
||||
|
||||
msgid "Blacklist"
|
||||
msgstr "黑名单"
|
||||
|
||||
msgid "Add to Blacklist"
|
||||
msgstr "加入黑名单"
|
||||
|
||||
msgid "Remove from Blacklist"
|
||||
msgstr "移出黑名单"
|
||||
|
||||
msgid "Forced offline"
|
||||
msgstr "强制下线"
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
@ -0,0 +1,16 @@
|
||||
|
||||
config service 'pptpd'
|
||||
option mppe '1'
|
||||
option localip '192.168.2.1'
|
||||
option remoteip '192.168.2.10-20'
|
||||
option dns '192.168.0.2'
|
||||
option is_nat '1'
|
||||
option export_interface 'default'
|
||||
option enabled '0'
|
||||
|
||||
config users
|
||||
option enabled '1'
|
||||
option ipaddress '*'
|
||||
option username 'guest'
|
||||
option password '123456'
|
||||
|
||||
95
package/lienol/luci-app-pptp-vpnserver-manyusers/root/etc/init.d/pptpd
Executable file
95
package/lienol/luci-app-pptp-vpnserver-manyusers/root/etc/init.d/pptpd
Executable file
@ -0,0 +1,95 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
|
||||
START=99
|
||||
CONFIG=pptpd
|
||||
CONFIG_FILE=/var/etc/$CONFIG.conf
|
||||
BIN=/usr/sbin/$CONFIG
|
||||
DEFAULT=/etc/default/$BIN
|
||||
RUN_D=/var/run
|
||||
PID_F=$RUN_D/$BIN.pid
|
||||
CHAP_SECRETS=/var/etc/chap-secrets
|
||||
SERVER_NAME="pptp-server"
|
||||
TEMP=/tmp/pptpd.tmp
|
||||
|
||||
setup_dns() {
|
||||
[ -n "$1" ] || return 0
|
||||
echo ms-dns $1>>/etc/ppp/options.pptpd
|
||||
}
|
||||
setup_login() {
|
||||
config_get enabled $1 enabled
|
||||
[ "$enabled" -eq 0 ] && return 0
|
||||
config_get ipaddress $1 ipaddress
|
||||
[ -n "$ipaddress" ] || local ipaddress = "*"
|
||||
config_get username $1 username
|
||||
config_get password $1 password
|
||||
[ -n "$username" ] || return 0
|
||||
[ -n "$password" ] || return 0
|
||||
echo "$username $SERVER_NAME $password $ipaddress" >> $CHAP_SECRETS
|
||||
}
|
||||
|
||||
setup_config() {
|
||||
config_get enabled $1 enabled
|
||||
[ "$enabled" -eq 0 ] && return 1
|
||||
|
||||
mkdir -p /var/etc
|
||||
cp /etc/pptpd.conf $CONFIG_FILE
|
||||
|
||||
config_get localip $1 localip
|
||||
config_get remoteip $1 remoteip
|
||||
config_get is_nat $1 is_nat
|
||||
|
||||
[ -z "$localip" ] && localip=$(ifconfig br-lan 2>/dev/null | grep "inet addr:" | grep -E -o "[0-9]+\.[0-9]+\.[0-9]+\."|head -1)2
|
||||
[ -z "$localip" ] && remoteip=$(ifconfig br-lan 2>/dev/null | grep "inet addr:" | grep -E -o "[0-9]+\.[0-9]+\.[0-9]+\."|head -1)10-20
|
||||
options="cat /etc/pptpd.conf |grep options"
|
||||
[ -n "$localip" ] && echo "localip $localip" >> $CONFIG_FILE
|
||||
[ -n "$remoteip" ] && echo "remoteip $remoteip" >> $CONFIG_FILE
|
||||
[ -n "$options" ] && echo "option /etc/ppp/options.pptpd" >> $CONFIG_FILE
|
||||
|
||||
fw3 reload
|
||||
|
||||
config_get mppe $1 mppe
|
||||
[ -n "$(cat "/etc/ppp/options.pptpd" |grep mppe)" ] && sed -i '/mppe/'d /etc/ppp/options.pptpd
|
||||
[ -z "$(cat "/etc/ppp/options.pptpd" |grep mppe)" ] && echo "mppe required,no40,no56,stateless" >> /etc/ppp/options.pptpd
|
||||
if [ "$mppe" -eq 0 ]; then
|
||||
sed -i -e 's/mppe/#mppe/g' /etc/ppp/options.pptpd
|
||||
fi
|
||||
sed -i -e '/ms-dns/d' /etc/ppp/options.pptpd
|
||||
config_get dns $1 dns
|
||||
setup_dns $dns
|
||||
echo ms-dns 223.5.5.5 >>/etc/ppp/options.pptpd
|
||||
return 0
|
||||
}
|
||||
|
||||
start_pptpd() {
|
||||
[ -f $DEFAULT ] && . $DEFAULT
|
||||
mkdir -p $RUN_D
|
||||
for m in arc4 sha1_generic slhc crc-ccitt ppp_generic ppp_async ppp_mppe; do
|
||||
insmod $m >/dev/null 2>&1
|
||||
done
|
||||
ln -sfn $CHAP_SECRETS /etc/ppp/chap-secrets
|
||||
chmod 600 /etc/ppp/*-secrets
|
||||
service_start $BIN $OPTIONS -c $CONFIG_FILE
|
||||
}
|
||||
|
||||
del_user()
|
||||
{
|
||||
cat $CHAP_SECRETS | grep -v $SERVER_NAME > $TEMP
|
||||
cat $TEMP > $CHAP_SECRETS
|
||||
rm $TEMP
|
||||
}
|
||||
|
||||
start() {
|
||||
config_load $CONFIG
|
||||
setup_config $CONFIG || return
|
||||
del_user
|
||||
config_foreach setup_login users
|
||||
start_pptpd
|
||||
}
|
||||
|
||||
stop() {
|
||||
service_stop $BIN
|
||||
ps | grep "pppd local" | grep -v "grep" | awk '{print $1}' | xargs kill -9
|
||||
fw3 reload
|
||||
del_user
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete firewall.pptpd
|
||||
set firewall.pptpd=include
|
||||
set firewall.pptpd.type=script
|
||||
set firewall.pptpd.path=/usr/share/pptpd/firewall.include
|
||||
set firewall.pptpd.reload=1
|
||||
EOF
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@pptpd[-1]
|
||||
add ucitrack pptpd
|
||||
set ucitrack.@pptpd[-1].init=pptpd
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
chmod a+x /usr/share/pptpd/* >/dev/null 2>&1
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
iptables -D INPUT -p tcp --dport 1723 -m comment --comment "Rule For PPTP VPN Server" -j ACCEPT 2> /dev/null
|
||||
pptp_nums=`iptables -t nat -L POSTROUTING 2> /dev/null|grep -c "Rule For PPTP VPN Server"`
|
||||
if [ -n "$pptp_nums" ]; then
|
||||
until [ "$pptp_nums" = 0 ]
|
||||
do
|
||||
pptp_rules=`iptables -t nat -L POSTROUTING --line-num 2> /dev/null|grep "Rule For PPTP VPN Server" |awk '{print $1}'`
|
||||
for pptp_rule in $pptp_rules
|
||||
do
|
||||
iptables -t nat -D POSTROUTING $pptp_rule 2> /dev/null
|
||||
break
|
||||
done
|
||||
pptp_nums=`expr $pptp_nums - 1`
|
||||
done
|
||||
fi
|
||||
nums=`iptables -L forwarding_rule 2> /dev/null|grep -c "Rule For PPTP VPN Server"`
|
||||
if [ -n "$nums" ]; then
|
||||
until [ "$nums" = 0 ]
|
||||
do
|
||||
rules=`iptables -L forwarding_rule --line-num 2> /dev/null|grep "Rule For PPTP VPN Server" |awk '{print $1}'`
|
||||
for rule in $rules
|
||||
do
|
||||
iptables -D forwarding_rule $rule 2> /dev/null
|
||||
break
|
||||
done
|
||||
nums=`expr $nums - 1`
|
||||
done
|
||||
fi
|
||||
|
||||
enable=$(uci get pptpd.pptpd.enabled)
|
||||
if [ $enable -eq 1 ]; then
|
||||
is_nat=$(uci get pptpd.pptpd.is_nat)
|
||||
if [ "$is_nat" -eq 1 ];then
|
||||
localip=$(uci get pptpd.pptpd.localip)
|
||||
export_interface=$(uci get pptpd.pptpd.export_interface)
|
||||
if [ "$export_interface" != "default" ];then
|
||||
iptables -t nat -I POSTROUTING -s ${localip%.*}.0/24 -o ${export_interface} -m comment --comment "Rule For PPTP VPN Server" -j MASQUERADE
|
||||
else
|
||||
iptables -t nat -I POSTROUTING -s ${localip%.*}.0/24 -m comment --comment "Rule For PPTP VPN Server" -j MASQUERADE
|
||||
fi
|
||||
iptables -I forwarding_rule -s ${localip%.*}.0/24 -m comment --comment "Rule For PPTP VPN Server" -j ACCEPT
|
||||
fi
|
||||
iptables -I INPUT -p tcp --dport 1723 -m comment --comment "Rule For PPTP VPN Server" -j ACCEPT 2>/dev/null
|
||||
fi
|
||||
18
package/lienol/luci-app-ssr-python-pro-server/Makefile
Normal file
18
package/lienol/luci-app-ssr-python-pro-server/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
# Copyright (C) 2018-2020 Lienol <lawlienol@gmail.com>
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
#
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for SSR Python Pro Server
|
||||
LUCI_DEPENDS:=+libsodium +luci-lib-jsonc +python
|
||||
LUCI_PKGARCH:=all
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=6-20190704
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
-- Copyright 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
module("luci.controller.ssr_python_pro_server", package.seeall)
|
||||
local http = require "luci.http"
|
||||
|
||||
function index()
|
||||
if not nixio.fs.access("/etc/config/ssr_python_pro_server") then return end
|
||||
entry({"admin", "vpn"}, firstchild(), "VPN", 45).dependent = false
|
||||
if nixio.fs.access("/usr/share/ssr_python_pro_server") then
|
||||
entry({"admin", "vpn", "ssr_python_pro_server"},
|
||||
cbi("ssr_python_pro_server/index"), _("SSR Python Server"), 2).dependent =
|
||||
true
|
||||
end
|
||||
|
||||
entry({"admin", "vpn", "ssr_python_pro_server", "config"},
|
||||
cbi("ssr_python_pro_server/config")).leaf = true
|
||||
|
||||
entry({"admin", "vpn", "ssr_python_pro_server", "status"},
|
||||
call("act_ssr_python_status")).leaf = true
|
||||
entry({"admin", "vpn", "ssr_python_pro_server", "users_status"},
|
||||
call("act_ssr_python_users_status")).leaf = true
|
||||
entry({"admin", "vpn", "ssr_python_pro_server", "get_total_traffic"},
|
||||
call("act_ssr_python_get_total_traffic")).leaf = true
|
||||
entry({"admin", "vpn", "ssr_python_pro_server", "get_link"},
|
||||
call("act_ssr_python_get_link")).leaf = true
|
||||
entry({"admin", "vpn", "ssr_python_pro_server", "clear_traffic"},
|
||||
call("act_ssr_python_clear_traffic")).leaf = true
|
||||
entry({"admin", "vpn", "ssr_python_pro_server", "clear_traffic_all_users"},
|
||||
call("act_ssr_python_clear_traffic_all_users")).leaf = true
|
||||
end
|
||||
|
||||
local function http_write_json(content)
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(content or {code = 1})
|
||||
end
|
||||
|
||||
function act_ssr_python_status()
|
||||
local e = {}
|
||||
e.status = luci.sys.call(
|
||||
"ps -w | grep -v grep | grep '/usr/share/ssr_python_pro_server/server.py' >/dev/null") ==
|
||||
0
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
function act_ssr_python_users_status()
|
||||
local e = {}
|
||||
e.index = luci.http.formvalue("index")
|
||||
e.status = luci.sys.call("netstat -an | grep '" ..
|
||||
luci.http.formvalue("port") .. "' >/dev/null") ==
|
||||
0
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
function act_ssr_python_get_total_traffic()
|
||||
local e = {}
|
||||
local result = nil
|
||||
local total_traffic_str = luci.sys.exec(
|
||||
"cd /usr/share/ssr_python_pro_server && ./mujson_mgr.py -l -I " ..
|
||||
luci.http.formvalue("section") ..
|
||||
" | sed -n 19p"):gsub("^%s*(.-)%s*$", "%1")
|
||||
local total_traffic = luci.sys.exec("echo " .. total_traffic_str ..
|
||||
" | awk '{print $3}'"):gsub(
|
||||
"^%s*(.-)%s*$", "%1")
|
||||
if total_traffic == "" then total_traffic = 0 end
|
||||
local unit = luci.sys.exec("echo " .. total_traffic_str ..
|
||||
" | awk '{print $4}'"):gsub("^%s*(.-)%s*$",
|
||||
"%1")
|
||||
result = string.format("%0.2f", total_traffic) .. unit
|
||||
e.result = result
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
function act_ssr_python_get_link()
|
||||
local e = {}
|
||||
local link = luci.sys.exec(
|
||||
"cd /usr/share/ssr_python_pro_server && ./mujson_mgr.py -l -I " ..
|
||||
luci.http.formvalue("section") .. " | sed -n 21p"):gsub(
|
||||
"^%s*(.-)%s*$", "%1")
|
||||
if link ~= "" then e.link = link end
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
function act_ssr_python_clear_traffic()
|
||||
local e = {}
|
||||
e.status = luci.sys.call(
|
||||
"cd /usr/share/ssr_python_pro_server && ./mujson_mgr.py -c -I '" ..
|
||||
luci.http.formvalue("id") .. "' >/dev/null") == 0
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
function act_ssr_python_clear_traffic_all_users()
|
||||
local e = {}
|
||||
e.status = luci.sys.call(
|
||||
"/usr/share/ssr_python_pro_server/sh/clear_traffic_all_users.sh >/dev/null") ==
|
||||
0
|
||||
http_write_json(e)
|
||||
end
|
||||
@ -0,0 +1,79 @@
|
||||
local i = "ssr_python_pro_server"
|
||||
local d = require "luci.dispatcher"
|
||||
local a, t, e
|
||||
|
||||
local methods = {
|
||||
"none", "table", "rc4", "rc4-md5", "aes-128-cfb", "aes-192-cfb",
|
||||
"aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "bf-cfb",
|
||||
"cast5-cfb", "des-cfb", "rc2-cfb", "salsa20", "chacha20", "chacha20-ietf"
|
||||
}
|
||||
local protocols = {
|
||||
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
|
||||
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
|
||||
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
|
||||
"auth_chain_d"
|
||||
}
|
||||
local obfss = {
|
||||
"plain", "http_simple", "http_post", "random_head", "tls_simple",
|
||||
"tls1.0_session_auth", "tls1.2_ticket_auth"
|
||||
}
|
||||
|
||||
a = Map(i, "ShadowsocksR Python " .. translate("Server Config"))
|
||||
a.redirect = d.build_url("admin", "vpn", "ssr_python_pro_server")
|
||||
|
||||
t = a:section(NamedSection, arg[1], "user", "")
|
||||
t.addremove = false
|
||||
t.dynamic = false
|
||||
|
||||
e = t:option(Flag, "enable", translate("Enable"))
|
||||
e.default = "1"
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(Value, "remarks", translate("Remarks"))
|
||||
e.default = translate("Remarks")
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(Value, "port", translate("Port"))
|
||||
e.datatype = "port"
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(Value, "password", translate("Password"))
|
||||
e.password = true
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(ListValue, "method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(methods) do e:value(t) end
|
||||
|
||||
e = t:option(ListValue, "protocol", translate("Protocol"))
|
||||
for a, t in ipairs(protocols) do e:value(t) end
|
||||
|
||||
e = t:option(ListValue, "obfs", translate("Obfs"))
|
||||
for a, t in ipairs(obfss) do e:value(t) end
|
||||
|
||||
e = t:option(Value, "device_limit", translate("Device Limit"), translate(
|
||||
"Number of clients that can be linked at the same time (multi-port mode, each port is calculated independently), a minimum of 2 is recommended."))
|
||||
e.default = "2"
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(Value, "speed_limit_per_con", translate("Speed Limit Per Con"),
|
||||
translate(
|
||||
"Single thread speed limit upper limit, multithreading is invalid. Zero means no speed limit. (unit: KB/S)"))
|
||||
e.default = "0"
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(Value, "speed_limit_per_user", translate("Speed Limit Per User"),
|
||||
translate(
|
||||
"Total speed limit upper limit, single port overall speed limit. Zero means no speed limit. (unit: KB/S)"))
|
||||
e.default = "0"
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(Value, "forbidden_port", translate("Forbidden Port"), translate(
|
||||
"For example, if port 25 is not allowed, the user will not be able to access the mail port 25 through the SSR agent. If 80,443 is disabled, the user will not be able to access the HTTP/HTTPS website normally. <br>blocked single port format: 25<br>blocked multiple port format: 23,465<br>blocked port format: 233-266<br>blocked multiple port format: 25,465,233-666"))
|
||||
|
||||
e = t:option(Value, "transfer_enable", translate("Available Total Flow"),
|
||||
translate(
|
||||
"Maximum amount of total traffic available (GB, 1-838868), Zero means infinite."))
|
||||
e.default = "0"
|
||||
e.rmempty = false
|
||||
|
||||
return a
|
||||
@ -0,0 +1,71 @@
|
||||
local uci = require"luci.model.uci".cursor()
|
||||
local sys = require "luci.sys"
|
||||
local jsonc = require "luci.jsonc"
|
||||
|
||||
local json = {}
|
||||
|
||||
function genconfig(i, section, d, u)
|
||||
local server = uci:get_all("ssr_python_pro_server", section)
|
||||
local enable = server.enable
|
||||
local remarks = server.remarks
|
||||
local port = server.port
|
||||
local password = server.password
|
||||
local method = server.method
|
||||
local protocol = server.protocol
|
||||
local obfs = server.obfs
|
||||
local device_limit = server.device_limit
|
||||
local speed_limit_per_con = server.speed_limit_per_con
|
||||
local speed_limit_per_user = server.speed_limit_per_user
|
||||
local forbidden_port = server.forbidden_port
|
||||
local transfer_enable = server.transfer_enable
|
||||
|
||||
transfer_enable = transfer_enable and tonumber(transfer_enable) == 0 and
|
||||
838868 or transfer_enable
|
||||
|
||||
json[i] = {
|
||||
id = section,
|
||||
enable = tonumber(enable),
|
||||
user = remarks,
|
||||
port = tonumber(port),
|
||||
passwd = password,
|
||||
method = method,
|
||||
protocol = protocol,
|
||||
obfs = obfs,
|
||||
protocol_param = device_limit,
|
||||
speed_limit_per_con = tonumber(speed_limit_per_con),
|
||||
speed_limit_per_user = tonumber(speed_limit_per_user),
|
||||
forbidden_port = forbidden_port and forbidden_port or "",
|
||||
transfer_enable = transfer_enable and transfer_enable * 1024 * 1024 *
|
||||
1024 or 1073741824,
|
||||
d = d and tonumber(d) or 0,
|
||||
u = u and tonumber(u) or 0
|
||||
}
|
||||
end
|
||||
|
||||
local mudbjson = luci.sys.exec("cat /usr/share/ssr_python_pro_server/mudb.json")
|
||||
local mudbjson_object = jsonc.parse(mudbjson)
|
||||
|
||||
local i = 0
|
||||
uci:foreach("ssr_python_pro_server", "user", function(s)
|
||||
i = i + 1
|
||||
local section = s[".name"]
|
||||
if mudbjson_object then
|
||||
local flag = true
|
||||
for index = 1, table.maxn(mudbjson_object) do
|
||||
local object = mudbjson_object[index]
|
||||
if mudbjson_object[index] ~= nil then
|
||||
if object.id == section then
|
||||
flag = false
|
||||
genconfig(i, section, object.d, object.u)
|
||||
mudbjson_object[index] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if flag == true then genconfig(i, section, 0, 0) end
|
||||
else
|
||||
genconfig(i, section, 0, 0)
|
||||
end
|
||||
end)
|
||||
|
||||
print(jsonc.stringify(json, 1))
|
||||
@ -0,0 +1,186 @@
|
||||
local o = require "luci.dispatcher"
|
||||
local fs = require "nixio.fs"
|
||||
local sys = require "luci.sys"
|
||||
local cursor = luci.model.uci.cursor()
|
||||
local appname = "ssr_python_pro_server"
|
||||
local a, t, e
|
||||
|
||||
a = Map(appname, translate("ShadowsocksR Python Server"))
|
||||
|
||||
t = a:section(TypedSection, "global", translate("Global Settings"))
|
||||
t.anonymous = true
|
||||
t.addremove = false
|
||||
|
||||
e = t:option(DummyValue, "status", translate("Current Condition"))
|
||||
e.template = appname .. "/status"
|
||||
e.value = translate("Collecting data...")
|
||||
|
||||
e = t:option(Flag, "enable", translate("Enable"))
|
||||
e.rmempty = false
|
||||
|
||||
e =
|
||||
t:option(Flag, "auto_clear_transfer", translate("Enable Auto Clear Traffic"))
|
||||
e.default = 0
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(Value, "auto_clear_transfer_time",
|
||||
translate("Clear Traffic Time Interval"),
|
||||
translate("*,*,*,*,* is Min Hour Day Mon Week"))
|
||||
e.default = "0,2,1,*,*"
|
||||
e:depends("auto_clear_transfer", 1)
|
||||
|
||||
e = t:option(Button, "clear_transfer", translate("Clear All Users Traffic"))
|
||||
e.inputstyle = "remove"
|
||||
function e.write(t, section)
|
||||
local url = luci.dispatcher.build_url("admin", "vpn",
|
||||
"ssr_python_pro_server",
|
||||
"clear_traffic_all_users")
|
||||
e.description = "<script>if (confirm('确认吗?')==true){XHR.get('" ..
|
||||
url ..
|
||||
"',null,function(x,result){window.location.replace(window.location.href)})}</script>"
|
||||
end
|
||||
|
||||
t = a:section(TypedSection, "user", translate("Users Manager"))
|
||||
t.anonymous = true
|
||||
t.addremove = true
|
||||
t.template = "cbi/tblsection"
|
||||
t.extedit = o.build_url("admin", "vpn", appname, "config", "%s")
|
||||
function t.create(e, t)
|
||||
local e = TypedSection.create(e, t)
|
||||
luci.http.redirect(o.build_url("admin", "vpn", appname, "config", e))
|
||||
end
|
||||
|
||||
function t.remove(t, a)
|
||||
t.map.proceed = true
|
||||
t.map:del(a)
|
||||
luci.http.redirect(o.build_url("admin", "vpn", appname))
|
||||
end
|
||||
|
||||
e = t:option(Flag, "enable", translate("Enable"))
|
||||
e.width = "5%"
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(DummyValue, "status", translate("Status"))
|
||||
e.template = "ssr_python_pro_server/users_status"
|
||||
e.width = "5%"
|
||||
|
||||
e = t:option(DummyValue, "remarks", translate("Remarks"))
|
||||
e.width = "10%"
|
||||
|
||||
e = t:option(DummyValue, "port", translate("Port"))
|
||||
e.width = "10%"
|
||||
|
||||
e = t:option(DummyValue, "forbidden_port", translate("Forbidden Port"))
|
||||
e.width = "10%"
|
||||
e.cfgvalue = function(t, n)
|
||||
local str = translate("Null")
|
||||
local forbidden_port = a.uci:get(appname, n, "forbidden_port")
|
||||
if forbidden_port then str = forbidden_port end
|
||||
return str
|
||||
end
|
||||
|
||||
e = t:option(DummyValue, "device_limit", translate("Device Limit"))
|
||||
e.width = "10%"
|
||||
|
||||
e =
|
||||
t:option(DummyValue, "speed_limit_per_con", translate("Speed Limit Per Con"))
|
||||
e.width = "10%"
|
||||
e.cfgvalue = function(t, section)
|
||||
local str = translate("No Speed Limit")
|
||||
local speed_limit_per_con = a.uci:get(appname, section,
|
||||
"speed_limit_per_con")
|
||||
if speed_limit_per_con and tonumber(speed_limit_per_con) > 0 then
|
||||
speed_limit_per_con = tonumber(speed_limit_per_con)
|
||||
if speed_limit_per_con < 1024 then
|
||||
str = speed_limit_per_con .. "Kb/s"
|
||||
elseif speed_limit_per_con < 1024 * 1024 then
|
||||
str = string.format("%0.2f", speed_limit_per_con / 1024) .. "Mb/s"
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
e = t:option(DummyValue, "speed_limit_per_user",
|
||||
translate("Speed Limit Per User"))
|
||||
e.width = "10%"
|
||||
e.cfgvalue = function(t, section)
|
||||
local str = translate("No Speed Limit")
|
||||
local speed_limit_per_user = a.uci:get(appname, section,
|
||||
"speed_limit_per_user")
|
||||
if speed_limit_per_user and tonumber(speed_limit_per_user) > 0 then
|
||||
speed_limit_per_user = tonumber(speed_limit_per_user)
|
||||
if speed_limit_per_user < 1024 then
|
||||
str = speed_limit_per_user .. "Kb/s"
|
||||
elseif speed_limit_per_user < 1024 * 1024 then
|
||||
str = string.format("%0.2f", speed_limit_per_user / 1024) .. "Mb/s"
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
e = t:option(DummyValue, "transfer_enable", translate("Available Total Flow"))
|
||||
e.width = "10%"
|
||||
e.cfgvalue = function(t, section)
|
||||
local str = translate("Infinite")
|
||||
local transfer_enable = a.uci:get(appname, section, "transfer_enable")
|
||||
if transfer_enable and tonumber(transfer_enable) > 0 then
|
||||
str = transfer_enable .. "G"
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
--[[e=t:option(DummyValue,"u",translate("Used Upload Traffic"))
|
||||
e.width="10%"
|
||||
e.cfgvalue=function(t,section)
|
||||
local result = translate("Null")
|
||||
local u_str = luci.sys.exec("cd /usr/share/ssr_python_pro_server && ./mujson_mgr.py -l -I "..section.." | sed -n 10p"):gsub("^%s*(.-)%s*$", "%1")
|
||||
local u = luci.sys.exec("echo "..u_str.." | awk '{print $3}'"):gsub("^%s*(.-)%s*$", "%1")
|
||||
if u == "" then u = 0 end
|
||||
local unit = luci.sys.exec("echo "..u_str.." | awk '{print $4}'"):gsub("^%s*(.-)%s*$", "%1")
|
||||
result = string.format("%0.2f",u)..unit
|
||||
return result
|
||||
end
|
||||
|
||||
e=t:option(DummyValue,"d",translate("Used Download Traffic"))
|
||||
e.width="10%"
|
||||
e.cfgvalue=function(t,section)
|
||||
local result = translate("Null")
|
||||
local d_str = luci.sys.exec("cd /usr/share/ssr_python_pro_server && ./mujson_mgr.py -l -I "..section.." | sed -n 11p"):gsub("^%s*(.-)%s*$", "%1")
|
||||
local d = luci.sys.exec("echo "..d_str.." | awk '{print $3}'"):gsub("^%s*(.-)%s*$", "%1")
|
||||
if d == "" then d = 0 end
|
||||
local unit = luci.sys.exec("echo "..d_str.." | awk '{print $4}'"):gsub("^%s*(.-)%s*$", "%1")
|
||||
result = string.format("%0.2f",d)..unit
|
||||
return result
|
||||
end]] --
|
||||
|
||||
e = t:option(DummyValue, "used_total_traffic", translate("Used Total Traffic"))
|
||||
e.width = "10%"
|
||||
e.template = appname .. "/users_total_traffic"
|
||||
--[[e.cfgvalue=function(t,section)
|
||||
local result = translate("Null")
|
||||
local total_traffic_str = luci.sys.exec("cd /usr/share/ssr_python_pro_server && ./mujson_mgr.py -l -I "..section.." | sed -n 19p"):gsub("^%s*(.-)%s*$", "%1")
|
||||
local total_traffic = luci.sys.exec("echo "..total_traffic_str.." | awk '{print $3}'"):gsub("^%s*(.-)%s*$", "%1")
|
||||
if total_traffic == "" then total_traffic = 0 end
|
||||
local unit = luci.sys.exec("echo "..total_traffic_str.." | awk '{print $4}'"):gsub("^%s*(.-)%s*$", "%1")
|
||||
result = string.format("%0.2f",total_traffic)..unit
|
||||
return result
|
||||
end]] --
|
||||
|
||||
e = t:option(DummyValue, "ssr_link", translate("SSR Link"))
|
||||
e.width = "10%"
|
||||
e.template = appname .. "/users_link"
|
||||
|
||||
e = t:option(Button, "clear_transfer", translate("Clear Traffic"))
|
||||
e.inputstyle = "remove"
|
||||
function e.write(t, section)
|
||||
local url = luci.dispatcher.build_url("admin", "vpn",
|
||||
"ssr_python_pro_server",
|
||||
"clear_traffic")
|
||||
e.description = "<script>if (confirm('确认吗?')==true){XHR.get('" ..
|
||||
url .. "',{id:'" .. section ..
|
||||
"'},function(x,result){window.location.replace(window.location.href)})}</script>"
|
||||
end
|
||||
|
||||
a:append(Template(appname .. "/ssr_python"))
|
||||
|
||||
return a
|
||||
@ -0,0 +1,58 @@
|
||||
<%
|
||||
local dsp = require "luci.dispatcher"
|
||||
local ipkg = require "luci.model.ipkg"
|
||||
-%>
|
||||
|
||||
<% include("cbi/map") %>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
var ssr_python_status = document.getElementsByClassName('ssr_python_status')[0];
|
||||
ssr_python_status.setAttribute("style","font-weight:bold;");
|
||||
<% if ipkg.installed("python") then %>
|
||||
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "vpn", "ssr_python_pro_server", "status")%>', null,
|
||||
function(x, result)
|
||||
{
|
||||
ssr_python_status.setAttribute("color",result.status ? "green":"red");
|
||||
ssr_python_status.innerHTML = result.status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||
}
|
||||
)
|
||||
<% else %>
|
||||
ssr_python_status.setAttribute("color","red");
|
||||
ssr_python_status.innerHTML = '<%=translate("NOT INSTALLED")%><br><%=translate("please Install the python")%><br>opkg update && opkg install python</font></b>';
|
||||
<% end %>
|
||||
|
||||
function ssr_python_get_total_traffic(section,dom) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "vpn", "ssr_python_pro_server", "get_total_traffic")%>', { "section" : section },
|
||||
function(x, result)
|
||||
{
|
||||
if(x && x.status == 200)
|
||||
dom.outerHTML = result.result;
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function ssr_python_get_link(section) {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin", "vpn", "ssr_python_pro_server", "get_link")%>', { "section" : section },
|
||||
function(x, result)
|
||||
{
|
||||
if(x && x.status == 200)
|
||||
alert(result.link);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/* var ssr_python_users_status = document.getElementsByClassName('ssr_python_users_status');
|
||||
for(var i = 0; i < ssr_python_users_status.length; i++) {
|
||||
var id_div = ssr_python_users_status[i].parentElement.parentElement.parentElement.id;
|
||||
var port_div = document.getElementById(id_div+"-port");
|
||||
var port = port_div.outerText;
|
||||
XHR.poll(1,'<%=dsp.build_url("admin/vpn/ssr_python_pro_server/users_status")%>', {
|
||||
index: i,
|
||||
port: port
|
||||
},
|
||||
function(x, result) {
|
||||
ssr_python_users_status[result.index].innerHTML = (result.status ? '<b><font color="green">✓</font></b>' : '<b><font color="red">X</font></b>');
|
||||
}
|
||||
);
|
||||
} */
|
||||
//]]>
|
||||
</script>
|
||||
@ -0,0 +1,3 @@
|
||||
<%+cbi/valueheader%>
|
||||
<font class="ssr_python_status"><%=pcdata(self:cfgvalue(section) or self.default or "")%></font>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,5 @@
|
||||
<%+cbi/valueheader%>
|
||||
<span class="ssr_python_link" hint="<%=self:cfgvalue(section)%>">
|
||||
<a href="javascript:ssr_python_get_link('<%=section%>')"><%=translate("GET")%></a>
|
||||
</span>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,18 @@
|
||||
<%
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
function get_status(section)
|
||||
local port = uci:get("ssr_python_pro_server",section,"port")
|
||||
if port and port ~= "" then
|
||||
local status = luci.sys.call("netstat -an | grep '" .. port .. "' >/dev/null")==0
|
||||
if status then
|
||||
return '<font color="green"> ✓ </font>'
|
||||
else
|
||||
return '<font color="red"> X </font>'
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<%+cbi/valueheader%>
|
||||
<span class="ssr_python_users_status" hint="<%=self:cfgvalue(section)%>"><%=get_status(section)%></span>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,5 @@
|
||||
<%+cbi/valueheader%>
|
||||
<span class="ssr_python_total_traffic" hint="<%=self:cfgvalue(section)%>">
|
||||
<a href="javascript:void(0)" onclick="javascript:ssr_python_get_total_traffic('<%=section%>',this)"><%=translate("GET")%></a>
|
||||
</span>
|
||||
<%+cbi/valuefooter%>
|
||||
@ -0,0 +1,152 @@
|
||||
msgid "ShadowsocksR Python Server"
|
||||
msgstr "ShadowsocksR Python 服务器"
|
||||
|
||||
msgid "SSR Python Server"
|
||||
msgstr "SSR Python 服务器"
|
||||
|
||||
msgid "Global Settings"
|
||||
msgstr "全局设置"
|
||||
|
||||
msgid "Server Config"
|
||||
msgstr "服务器配置"
|
||||
|
||||
msgid "Users Manager"
|
||||
msgstr "用户管理"
|
||||
|
||||
msgid "Remarks"
|
||||
msgstr "备注"
|
||||
|
||||
msgid "Port"
|
||||
msgstr "端口"
|
||||
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
msgid "Encrypt Method"
|
||||
msgstr "加密"
|
||||
|
||||
msgid "Protocol"
|
||||
msgstr "协议"
|
||||
|
||||
msgid "Protocol Param"
|
||||
msgstr "协议参数"
|
||||
|
||||
msgid "Obfs"
|
||||
msgstr "混淆"
|
||||
|
||||
msgid "Obfs Param"
|
||||
msgstr "混淆参数"
|
||||
|
||||
msgid "Connection Timeout"
|
||||
msgstr "连接超时时间"
|
||||
|
||||
msgid "redirect"
|
||||
msgstr "重定向"
|
||||
|
||||
msgid "Fast Open"
|
||||
msgstr "快速打开"
|
||||
|
||||
msgid "UDP Forward"
|
||||
msgstr "UDP转发"
|
||||
|
||||
msgid "Null"
|
||||
msgstr "无"
|
||||
|
||||
msgid "No Speed Limit"
|
||||
msgstr "不限速"
|
||||
|
||||
msgid "Forbidden Port"
|
||||
msgstr "禁止的端口"
|
||||
|
||||
msgid "Device Limit"
|
||||
msgstr "设备数限制"
|
||||
|
||||
msgid "Speed Limit Per Con"
|
||||
msgstr "单线程限速"
|
||||
|
||||
msgid "Speed Limit Per User"
|
||||
msgstr "总限速"
|
||||
|
||||
msgid "Available Total Flow"
|
||||
msgstr "可用总流量"
|
||||
|
||||
msgid "Infinite"
|
||||
msgstr "无限"
|
||||
|
||||
msgid "Used Upload Traffic"
|
||||
msgstr "已用上传流量"
|
||||
|
||||
msgid "Used Download Traffic"
|
||||
msgstr "已用下载流量"
|
||||
|
||||
msgid "Used Total Traffic"
|
||||
msgstr "已用总流量"
|
||||
|
||||
msgid "SSR Link"
|
||||
msgstr "SSR链接"
|
||||
|
||||
msgid "GET"
|
||||
msgstr "获取"
|
||||
|
||||
msgid "Clear Traffic"
|
||||
msgstr "清空流量"
|
||||
|
||||
msgid "Clear All Users Traffic"
|
||||
msgstr "清空所有用户流量"
|
||||
|
||||
msgid "Enable Auto Clear Traffic"
|
||||
msgstr "启用自动清空流量"
|
||||
|
||||
msgid "Clear Traffic Time Interval"
|
||||
msgstr "流量清空时间间隔"
|
||||
|
||||
msgid "*,*,*,*,* is Min Hour Day Mon Week"
|
||||
msgstr "*,*,*,*,* 分别对应 分钟 小时 日份 月份 星期<br>0,2,1,*,* 代表 每月1日2点0分<br>0,2,15,*,* 代表 每月15日2点0分<br>0,2,*/7,*,* 代表 每7天2点0分<br>0,2,*,*,0 代表 每个星期日(7)<br>0,2,*,*,3 代表 每个星期三(3)"
|
||||
|
||||
msgid "Number of clients that can be linked at the same time (multi-port mode, each port is calculated independently), a minimum of 2 is recommended."
|
||||
msgstr "同一时间能链接的客户端数量(多端口模式,每个端口都是独立计算),建议最少 2个。"
|
||||
|
||||
msgid "Single thread speed limit upper limit, multithreading is invalid. Zero means no speed limit. (unit: KB/S)"
|
||||
msgstr "单线程的限速上限,多线程即无效。0代表不限速。(单位:KB/S)"
|
||||
|
||||
msgid "Total speed limit upper limit, single port overall speed limit. Zero means no speed limit. (unit: KB/S)"
|
||||
msgstr "总速度限速上限,单个端口整体限速。0代表不限速。(单位:KB/S)"
|
||||
|
||||
msgid "For example, if port 25 is not allowed, the user will not be able to access the mail port 25 through the SSR agent. If 80,443 is disabled, the user will not be able to access the HTTP/HTTPS website normally. <br>blocked single port format: 25<br>blocked multiple port format: 23,465<br>blocked port format: 233-266<br>blocked multiple port format: 25,465,233-666"
|
||||
msgstr "例如不允许访问 25端口,用户就无法通过SSR代理访问邮件端口25了,如果禁止了 80,443 那么用户将无法正常访问 http/https 网站。<br>封禁单个端口格式: 25<br>封禁多个端口格式: 23,465<br>封禁端口段格式: 233-266<br>封禁多种格式端口: 25,465,233-666"
|
||||
|
||||
msgid "Maximum amount of total traffic available (GB, 1-838868), Zero means infinite."
|
||||
msgstr "可使用的总流量上限(单位: GB, 1-838868),0代表无限"
|
||||
|
||||
msgid "Alter ID"
|
||||
msgstr "额外ID(AlterID)"
|
||||
|
||||
msgid "User Level"
|
||||
msgstr "用户等级(Level)"
|
||||
|
||||
msgid "Transport"
|
||||
msgstr "传输方式"
|
||||
|
||||
msgid "Camouflage Type"
|
||||
msgstr "伪装类型"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
msgid "Current Condition"
|
||||
msgstr "当前状态"
|
||||
|
||||
msgid "please Install the python"
|
||||
msgstr "请安装python环境"
|
||||
|
||||
msgid "NOT INSTALLED"
|
||||
msgstr "未安装"
|
||||
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
@ -0,0 +1,18 @@
|
||||
|
||||
config global
|
||||
option enable '0'
|
||||
option auto_clear_transfer '0'
|
||||
|
||||
config user
|
||||
option enable '1'
|
||||
option remarks 'test'
|
||||
option port '50005'
|
||||
option password '123456'
|
||||
option method 'none'
|
||||
option protocol 'auth_chain_a'
|
||||
option obfs 'plain'
|
||||
option device_limit '10'
|
||||
option speed_limit_per_con '0'
|
||||
option speed_limit_per_user '0'
|
||||
option transfer_enable '0'
|
||||
|
||||
@ -0,0 +1,100 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# Copyright (C) 2018-2019 Lienol <lawlienol@gmail.com>
|
||||
|
||||
START=99
|
||||
|
||||
CONFIG=ssr_python_pro_server
|
||||
ssr_python_path=/usr/share/$CONFIG
|
||||
|
||||
config_t_get() {
|
||||
local index=0
|
||||
[ -n "$4" ] && index=$4
|
||||
local ret=$(uci get $CONFIG.@$1[$index].$2 2>/dev/null)
|
||||
echo ${ret:=$3}
|
||||
}
|
||||
|
||||
gen_ssr_python_config_file() {
|
||||
cbi_ids="$cbi_ids $1"
|
||||
config_get enable $1 enable
|
||||
config_get remarks $1 remarks
|
||||
config_get port $1 port
|
||||
config_get password $1 password
|
||||
config_get method $1 method
|
||||
config_get protocol $1 protocol
|
||||
config_get obfs $1 obfs
|
||||
config_get device_limit $1 device_limit
|
||||
config_get speed_limit_per_con $1 speed_limit_per_con
|
||||
config_get speed_limit_per_user $1 speed_limit_per_user
|
||||
config_get forbidden_port $1 forbidden_port
|
||||
config_get transfer_enable $1 transfer_enable
|
||||
[ "$transfer_enable" = "0" ] && transfer_enable="838868"
|
||||
cd $ssr_python_path
|
||||
exist=$(echo `./mujson_mgr.py -l -I $1`)
|
||||
action="-e"
|
||||
[ -z "$exist" ] && action="-a"
|
||||
./mujson_mgr.py $action -I $1 -u "${remarks}" -p ${port} -k ${password} -m ${method} -O ${protocol} -G ${device_limit} -o ${obfs} -s ${speed_limit_per_con} -S ${speed_limit_per_user} -t ${transfer_enable} -f "${forbidden_port}"
|
||||
}
|
||||
|
||||
set_ssr_python_crontab() {
|
||||
if [ "$1" = "start" ];then
|
||||
auto_clear_transfer=$(config_t_get global auto_clear_transfer 0)
|
||||
if [ "$auto_clear_transfer" = "0" ];then
|
||||
sed -i '/clear_traffic_all_users.sh/d' /etc/crontabs/root >/dev/null 2>&1 &
|
||||
else
|
||||
auto_clear_transfer_time=$(config_t_get global auto_clear_transfer_time)
|
||||
[ -n "$auto_clear_transfer_time" ] && auto_clear_transfer_time=$(echo $auto_clear_transfer_time | tr ',' ' ')
|
||||
echo "$auto_clear_transfer_time $ssr_python_path/sh/clear_traffic_all_users.sh >/dev/null 2>&1" >> /etc/crontabs/root
|
||||
fi
|
||||
else
|
||||
sed -i '/clear_traffic_all_users.sh/d' /etc/crontabs/root >/dev/null 2>&1 &
|
||||
fi
|
||||
/etc/init.d/cron restart
|
||||
}
|
||||
|
||||
start_ssr_python_server() {
|
||||
python=/usr/bin/python
|
||||
lua /usr/lib/lua/luci/model/cbi/ssr_python_pro_server/genssrmudbconfig.lua > /tmp/mudb.json
|
||||
mv -f /tmp/mudb.json $ssr_python_path/mudb.json
|
||||
$python $ssr_python_path/server.py >> /var/log/$CONFIG.log 2>&1 &
|
||||
set_ssr_python_crontab "start"
|
||||
|
||||
fw3 reload
|
||||
|
||||
:<<EOF
|
||||
cbi_ids=
|
||||
config_foreach gen_ssr_python_config_file "ssr_python_users"
|
||||
if [ -z "$cbi_ids" ];then
|
||||
echo "[]" > $ssr_python_path/mudb.json
|
||||
else
|
||||
$python $ssr_python_path/server.py >> /var/log/$CONFIG.log 2>&1 &
|
||||
fi
|
||||
EOF
|
||||
}
|
||||
|
||||
stop_ssr_python_server() {
|
||||
fw3 reload
|
||||
ps -w | grep "/usr/bin/python $ssr_python_path/server.py" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
|
||||
rm -rf /var/log/$CONFIG.log
|
||||
set_ssr_python_crontab "stop"
|
||||
}
|
||||
|
||||
start() {
|
||||
config_load $CONFIG
|
||||
enable=$(config_t_get global enable 0)
|
||||
if [ "$enable" = "0" ];then
|
||||
stop_ssr_python_server
|
||||
else
|
||||
cp -rf $ssr_python_path/mudb.json $ssr_python_path/mudb_backup.json
|
||||
start_ssr_python_server
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
stop_ssr_python_server
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete firewall.ssr_python_pro_server
|
||||
set firewall.ssr_python_pro_server=include
|
||||
set firewall.ssr_python_pro_server.type=script
|
||||
set firewall.ssr_python_pro_server.path=/usr/share/ssr_python_pro_server/firewall.include
|
||||
set firewall.ssr_python_pro_server.reload=1
|
||||
EOF
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete ucitrack.@ssr_python_pro_server[-1]
|
||||
add ucitrack ssr_python_pro_server
|
||||
set ucitrack.@ssr_python_pro_server[-1].init=ssr_python_pro_server
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
chmod a+x /usr/share/ssr_python_pro_server/* >/dev/null 2>&1
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
@ -0,0 +1,21 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.3
|
||||
- 3.4
|
||||
cache:
|
||||
directories:
|
||||
- dante-1.4.0
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq build-essential dnsutils iproute nginx bc
|
||||
- sudo dd if=/dev/urandom of=/usr/share/nginx/www/file bs=1M count=10
|
||||
- sudo sh -c "echo '127.0.0.1 localhost' > /etc/hosts"
|
||||
- sudo service nginx restart
|
||||
- pip install pep8 pyflakes nose coverage PySocks cymysql
|
||||
- sudo tests/socksify/install.sh
|
||||
- sudo tests/libsodium/install.sh
|
||||
- sudo tests/setup_tc.sh
|
||||
script:
|
||||
- tests/jenkins.sh
|
||||
@ -0,0 +1,342 @@
|
||||
3.4.0 2017-07-27
|
||||
- add auth_chain_b
|
||||
- add initmudbjson.sh
|
||||
- allow set speed limit in runtime
|
||||
- fix bugs & mem leak
|
||||
|
||||
3.3.3 2017-06-03
|
||||
- add DNS cache
|
||||
- add tls1.2_ticket_fastauth
|
||||
- fix bugs
|
||||
|
||||
3.3.2 2017-05-20
|
||||
- revert http reply
|
||||
- refine tls1.2_ticket_auth error detector
|
||||
|
||||
3.3.1 2017-05-18
|
||||
- fix stop script
|
||||
- Async DNS query under UDP
|
||||
- fix old version of OpenSSL
|
||||
- http reply
|
||||
|
||||
3.3.0 2017-05-11
|
||||
- connect_log include local addr & port
|
||||
- fix auth_chain_a UDP bug
|
||||
- add "additional_ports_only"
|
||||
- add interface legendsockssr
|
||||
- run with newest python version
|
||||
- parse comment in hosts
|
||||
- update mujson_mgr
|
||||
- add cymysql setup script
|
||||
- new speed tester
|
||||
- fix leaks
|
||||
- bugs fixed
|
||||
|
||||
3.2.0 2017-04-27
|
||||
- add auth_chain_a
|
||||
- remove auth_aes128, auth_sha1, auth_sha1_v2, verify_simple, auth_simple, verify_sha1
|
||||
|
||||
3.1.2 2017-04-07
|
||||
- display UID
|
||||
- auto adjust TCP MSS
|
||||
|
||||
3.1.1 2017-03-25
|
||||
- add "New session ticket"
|
||||
- ignore bind 10.0.0.0/8 and 192.168.0.0/16 by default
|
||||
- improve rand size under auth_aes128_*
|
||||
- fix bugs
|
||||
|
||||
3.1.0 2017-03-16
|
||||
- add "glzjinmod" interface
|
||||
- rate limit
|
||||
- add additional_ports in config
|
||||
|
||||
3.0.4 2017-01-08
|
||||
- multi-user in single port
|
||||
|
||||
3.0.1 2017-01-03
|
||||
- remove auth_aes128_*_compatible
|
||||
|
||||
3.0.0 2016-12-23
|
||||
- http_simple fix bugs
|
||||
- tls1.2_ticket_auth fix bug & defaule time diff set to 86400s
|
||||
|
||||
2.9.7 2016-11-22
|
||||
- manage client with LRUCache
|
||||
- catch bind error
|
||||
- fix import error of resource on windows
|
||||
- print RLIMIT_NOFILE
|
||||
- always close cymysql objects
|
||||
- add init script
|
||||
|
||||
2.9.6 2016-10-17
|
||||
- tls1.2_ticket_auth random packet size
|
||||
|
||||
2.9.5.1 2016-10-16
|
||||
- UDP bind address
|
||||
|
||||
2.9.5 2016-10-13
|
||||
- add auth_aes128_md5 and auth_aes128_sha1
|
||||
|
||||
2.9.4 2016-10-11
|
||||
- sync client version
|
||||
|
||||
2.6.13 2015-11-02
|
||||
- add protocol setting
|
||||
|
||||
2.6.12 2015-10-27
|
||||
- IPv6 first
|
||||
- Fix mem leaks
|
||||
- auth_simple plugin
|
||||
- remove FORCE_NEW_PROTOCOL
|
||||
- optimize code
|
||||
|
||||
2.6.11 2015-10-20
|
||||
- Obfs plugin
|
||||
- Obfs parameters
|
||||
- UDP over TCP
|
||||
- TCP over UDP (experimental)
|
||||
- Fix socket leaks
|
||||
- Catch abnormal UDP package
|
||||
|
||||
2.6.10 2015-06-08
|
||||
- Optimize LRU cache
|
||||
- Refine logging
|
||||
|
||||
2.6.9 2015-05-19
|
||||
- Fix a stability issue on Windows
|
||||
|
||||
2.6.8 2015-02-10
|
||||
- Support multiple server ip on client side
|
||||
- Support --version
|
||||
- Minor fixes
|
||||
|
||||
2.6.7 2015-02-02
|
||||
- Support --user
|
||||
- Support CIDR format in --forbidden-ip
|
||||
- Minor fixes
|
||||
|
||||
2.6.6 2015-01-23
|
||||
- Fix a crash in forbidden list
|
||||
|
||||
2.6.5 2015-01-18
|
||||
- Try both 32 bit and 64 bit dll on Windows
|
||||
|
||||
2.6.4 2015-01-14
|
||||
- Also search lib* when searching libraries
|
||||
|
||||
2.6.3 2015-01-12
|
||||
- Support --forbidden-ip to ban some IP, i.e. localhost
|
||||
- Search OpenSSL and libsodium harder
|
||||
- Now works on OpenWRT
|
||||
|
||||
2.6.2 2015-01-03
|
||||
- Log client IP
|
||||
|
||||
2.6.1 2014-12-26
|
||||
- Fix a problem with TCP Fast Open on local side
|
||||
- Fix sometimes daemon_start returns wrong exit status
|
||||
|
||||
2.6 2014-12-21
|
||||
- Add daemon support
|
||||
|
||||
2.5 2014-12-11
|
||||
- Add salsa20 and chacha20
|
||||
|
||||
2.4.3 2014-11-10
|
||||
- Fix an issue on Python 3
|
||||
- Fix an issue with IPv6
|
||||
|
||||
2.4.2 2014-11-06
|
||||
- Fix command line arguments on Python 3
|
||||
- Support table on Python 3
|
||||
- Fix TCP Fast Open on Python 3
|
||||
|
||||
2.4.1 2014-11-01
|
||||
- Fix setup.py for non-utf8 locales on Python 3
|
||||
|
||||
2.4 2014-11-01
|
||||
- Python 3 support
|
||||
- Performance improvement
|
||||
- Fix LRU cache behavior
|
||||
|
||||
2.3.2 2014-10-11
|
||||
- Fix OpenSSL on Windows
|
||||
|
||||
2.3.1 2014-10-09
|
||||
- Does not require M2Crypto any more
|
||||
|
||||
2.3 2014-09-23
|
||||
- Support CFB1, CFB8 and CTR mode of AES
|
||||
- Do not require password config when using port_password
|
||||
- Use SIGTERM instead of SIGQUIT on Windows
|
||||
|
||||
2.2.2 2014-09-14
|
||||
- Fix when multiple DNS set, IPv6 only sites are broken
|
||||
|
||||
2.2.1 2014-09-10
|
||||
- Support graceful shutdown
|
||||
- Fix some bugs
|
||||
|
||||
2.2.0 2014-09-09
|
||||
- Add RC4-MD5 encryption
|
||||
|
||||
2.1.0 2014-08-10
|
||||
- Use only IPv4 DNS server
|
||||
- Does not ship config.json
|
||||
- Better error message
|
||||
|
||||
2.0.12 2014-07-26
|
||||
- Support -q quiet mode
|
||||
- Exit 0 when showing help with -h
|
||||
|
||||
2.0.11 2014-07-12
|
||||
- Prefers IP addresses over hostnames, more friendly with socksify and openvpn
|
||||
|
||||
2.0.10 2014-07-11
|
||||
- Fix UDP on local
|
||||
|
||||
2.0.9 2014-07-06
|
||||
- Fix EWOULDBLOCK on Windows
|
||||
- Fix Unicode config problem on some platforms
|
||||
|
||||
2.0.8 2014-06-23
|
||||
- Use multiple DNS to query hostnames
|
||||
|
||||
2.0.7 2014-06-21
|
||||
- Fix fastopen on local
|
||||
- Fallback when fastopen is not available
|
||||
- Add verbose logging mode -vv
|
||||
- Verify if hostname is valid
|
||||
|
||||
2.0.6 2014-06-19
|
||||
- Fix CPU 100% on POLL_HUP
|
||||
- More friendly logging
|
||||
|
||||
2.0.5 2014-06-18
|
||||
- Support a simple config format for multiple ports
|
||||
|
||||
2.0.4 2014-06-12
|
||||
- Fix worker master
|
||||
|
||||
2.0.3 2014-06-11
|
||||
- Fix table encryption with UDP
|
||||
|
||||
2.0.2 2014-06-11
|
||||
- Add asynchronous DNS in TCP relay
|
||||
|
||||
2.0.1 2014-06-05
|
||||
- Better logging
|
||||
- Maybe fix bad file descriptor
|
||||
|
||||
2.0 2014-06-05
|
||||
- Use a new event model
|
||||
- Remove gevent
|
||||
- Refuse to use default password
|
||||
- Fix a problem when using multiple passwords with table encryption
|
||||
|
||||
1.4.5 2014-05-24
|
||||
- Add timeout in TCP server
|
||||
- Close sockets in master process
|
||||
|
||||
1.4.4 2014-05-17
|
||||
- Support multiple workers
|
||||
|
||||
1.4.3 2014-05-13
|
||||
- Fix Windows
|
||||
|
||||
1.4.2 2014-05-10
|
||||
- Add salsa20-ctr cipher
|
||||
|
||||
1.4.1 2014-05-03
|
||||
- Fix error log
|
||||
- Fix EINPROGESS with some version of gevent
|
||||
|
||||
1.4.0 2014-05-02
|
||||
- Adds UDP relay
|
||||
- TCP fast open support on Linux 3.7+
|
||||
|
||||
1.3.7 2014-04-10
|
||||
- Fix a typo in help
|
||||
|
||||
1.3.6 2014-04-10
|
||||
- Fix a typo in help
|
||||
|
||||
1.3.5 2014-04-07
|
||||
- Add help
|
||||
- Change default local binding address into 127.0.0.1
|
||||
|
||||
1.3.4 2014-02-17
|
||||
- Fix a bug when no config file exists
|
||||
- Client now support multiple server ports and multiple server/port pairs
|
||||
- Better error message with bad config.json format and wrong password
|
||||
|
||||
1.3.3 2013-07-09
|
||||
- Fix default key length of rc2
|
||||
|
||||
1.3.2 2013-07-04
|
||||
- Server will listen at server IP specified in config
|
||||
- Check config file and show some warning messages
|
||||
|
||||
1.3.1 2013-06-29
|
||||
- Fix -c arg
|
||||
|
||||
1.3.0 2013-06-22
|
||||
- Move to pypi
|
||||
|
||||
1.2.3 2013-06-14
|
||||
- add bind address
|
||||
|
||||
1.2.2 2013-05-31
|
||||
- local can listen at ::0 with -6 arg; bump 1.2.2
|
||||
|
||||
1.2.1 2013-05-23
|
||||
- Fix an OpenSSL crash
|
||||
|
||||
1.2 2013-05-22
|
||||
- Use random iv, we finally have strong encryption
|
||||
|
||||
1.1.1 2013-05-21
|
||||
- Add encryption, AES, blowfish, etc.
|
||||
|
||||
1.1 2013-05-16
|
||||
- Support IPv6 addresses (type 4)
|
||||
- Drop Python 2.5 support
|
||||
|
||||
1.0 2013-04-03
|
||||
- Fix -6 IPv6
|
||||
|
||||
0.9.4 2013-03-04
|
||||
- Support Python 2.5
|
||||
|
||||
0.9.3 2013-01-14
|
||||
- Fix conn termination null data
|
||||
|
||||
0.9.2 2013-01-05
|
||||
- Change default timeout
|
||||
|
||||
0.9.1 2013-01-05
|
||||
- Add Travis-CI test
|
||||
|
||||
0.9 2012-12-30
|
||||
- Replace send with sendall, fix FreeBSD
|
||||
|
||||
0.6 2012-12-06
|
||||
- Support args
|
||||
|
||||
0.5 2012-11-08
|
||||
- Fix encryption with negative md5sum
|
||||
|
||||
0.4 2012-11-02
|
||||
- Move config into a JSON file
|
||||
- Auto-detect config path
|
||||
|
||||
0.3 2012-06-06
|
||||
- Move socks5 negotiation to local
|
||||
|
||||
0.2 2012-05-11
|
||||
- Add -6 arg for IPv6
|
||||
- Fix socket.error
|
||||
|
||||
0.1 2012-04-20
|
||||
- Initial version
|
||||
@ -0,0 +1,29 @@
|
||||
How to Contribute
|
||||
=================
|
||||
|
||||
Pull Requests
|
||||
-------------
|
||||
|
||||
1. Pull requests are welcome. If you would like to add a large feature
|
||||
or make a significant change, make sure to open an issue to discuss with
|
||||
people first.
|
||||
2. Follow PEP8.
|
||||
3. Make sure to pass the unit tests. Write unit tests for new modules if
|
||||
needed.
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
1. Only bugs and feature requests are accepted here.
|
||||
2. We'll only work on important features. If the feature you're asking only
|
||||
benefits a few people, you'd better implement the feature yourself and send us
|
||||
a pull request, or ask some of your friends to do so.
|
||||
3. We don't answer questions of any other types here. Since very few people
|
||||
are watching the issue tracker here, you'll probably get no help from here.
|
||||
Read [Troubleshooting] and get help from forums or [mailing lists].
|
||||
4. Issues in languages other than English will be Google translated into English
|
||||
later.
|
||||
|
||||
|
||||
[Troubleshooting]: https://github.com/clowwindy/shadowsocks/wiki/Troubleshooting
|
||||
[mailing lists]: https://groups.google.com/forum/#!forum/shadowsocks
|
||||
@ -0,0 +1,31 @@
|
||||
FROM alpine:3.6
|
||||
|
||||
ENV SERVER_ADDR 0.0.0.0
|
||||
ENV SERVER_PORT 51348
|
||||
ENV PASSWORD psw
|
||||
ENV METHOD aes-128-ctr
|
||||
ENV PROTOCOL auth_aes128_md5
|
||||
ENV PROTOCOLPARAM 32
|
||||
ENV OBFS tls1.2_ticket_auth_compatible
|
||||
ENV TIMEOUT 300
|
||||
ENV DNS_ADDR 8.8.8.8
|
||||
ENV DNS_ADDR_2 8.8.4.4
|
||||
|
||||
ARG BRANCH=manyuser
|
||||
ARG WORK=~
|
||||
|
||||
|
||||
RUN apk --no-cache add python \
|
||||
libsodium \
|
||||
wget
|
||||
|
||||
|
||||
RUN mkdir -p $WORK && \
|
||||
wget -qO- --no-check-certificate https://github.com/shadowsocksr/shadowsocksr/archive/$BRANCH.tar.gz | tar -xzf - -C $WORK
|
||||
|
||||
|
||||
WORKDIR $WORK/shadowsocksr-$BRANCH/shadowsocks
|
||||
|
||||
|
||||
EXPOSE $SERVER_PORT
|
||||
CMD python server.py -p $SERVER_PORT -k $PASSWORD -m $METHOD -O $PROTOCOL -o $OBFS -G $PROTOCOLPARAM
|
||||
@ -0,0 +1,3 @@
|
||||
recursive-include shadowsocks *.py
|
||||
include README.rst
|
||||
include LICENSE
|
||||
@ -0,0 +1,15 @@
|
||||
# Config
|
||||
API_INTERFACE = 'sspanelv2' #mudbjson, sspanelv2, sspanelv3, sspanelv3ssr, glzjinmod, legendsockssr, muapiv2(not support)
|
||||
UPDATE_TIME = 60
|
||||
SERVER_PUB_ADDR = '127.0.0.1' # mujson_mgr need this to generate ssr link
|
||||
|
||||
#mudb
|
||||
MUDB_FILE = 'mudb.json'
|
||||
|
||||
# Mysql
|
||||
MYSQL_CONFIG = 'usermysql.json'
|
||||
|
||||
# API
|
||||
MUAPI_CONFIG = 'usermuapi.json'
|
||||
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import time
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import re
|
||||
import logging
|
||||
from shadowsocks import common
|
||||
from shadowsocks import lru_cache
|
||||
from shadowsocks import eventloop
|
||||
import server_pool
|
||||
import Config
|
||||
|
||||
class ServerMgr(object):
|
||||
|
||||
def __init__(self):
|
||||
self._loop = None
|
||||
self._request_id = 1
|
||||
self._hosts = {}
|
||||
self._hostname_status = {}
|
||||
self._hostname_to_cb = {}
|
||||
self._cb_to_hostname = {}
|
||||
self._last_time = time.time()
|
||||
self._sock = None
|
||||
self._servers = None
|
||||
|
||||
def add_to_loop(self, loop):
|
||||
if self._loop:
|
||||
raise Exception('already add to loop')
|
||||
self._loop = loop
|
||||
# TODO when dns server is IPv6
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
||||
socket.SOL_UDP)
|
||||
self._sock.bind((Config.MANAGE_BIND_IP, Config.MANAGE_PORT))
|
||||
self._sock.setblocking(False)
|
||||
loop.add(self._sock, eventloop.POLL_IN, self)
|
||||
|
||||
def _handle_data(self, sock):
|
||||
data, addr = sock.recvfrom(128)
|
||||
#manage pwd:port:passwd:action
|
||||
args = data.split(':')
|
||||
if len(args) < 4:
|
||||
return
|
||||
if args[0] == Config.MANAGE_PASS:
|
||||
if args[3] == '0':
|
||||
server_pool.ServerPool.get_instance().cb_del_server(args[1])
|
||||
elif args[3] == '1':
|
||||
server_pool.ServerPool.get_instance().new_server(args[1], args[2])
|
||||
|
||||
def handle_event(self, sock, fd, event):
|
||||
if sock != self._sock:
|
||||
return
|
||||
if event & eventloop.POLL_ERR:
|
||||
logging.error('mgr socket err')
|
||||
self._loop.remove(self._sock)
|
||||
self._sock.close()
|
||||
# TODO when dns server is IPv6
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
||||
socket.SOL_UDP)
|
||||
self._sock.setblocking(False)
|
||||
self._loop.add(self._sock, eventloop.POLL_IN, self)
|
||||
else:
|
||||
self._handle_data(sock)
|
||||
|
||||
def close(self):
|
||||
if self._sock:
|
||||
if self._loop:
|
||||
self._loop.remove(self._sock)
|
||||
self._sock.close()
|
||||
self._sock = None
|
||||
|
||||
|
||||
def test():
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
@ -0,0 +1,25 @@
|
||||
{
|
||||
"server": "0.0.0.0",
|
||||
"server_ipv6": "::",
|
||||
"server_port": 8388,
|
||||
"local_address": "127.0.0.1",
|
||||
"local_port": 1080,
|
||||
|
||||
"password": "m",
|
||||
"method": "aes-128-ctr",
|
||||
"protocol": "auth_aes128_md5",
|
||||
"protocol_param": "",
|
||||
"obfs": "tls1.2_ticket_auth_compatible",
|
||||
"obfs_param": "",
|
||||
"speed_limit_per_con": 0,
|
||||
"speed_limit_per_user": 0,
|
||||
|
||||
"additional_ports" : {}, // only works under multi-user mode
|
||||
"additional_ports_only" : false, // only works under multi-user mode
|
||||
"timeout": 120,
|
||||
"udp_timeout": 60,
|
||||
"dns_ipv6": false,
|
||||
"connect_verbose_info": 0,
|
||||
"redirect": "",
|
||||
"fast_open": false
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
import importloader
|
||||
|
||||
g_config = None
|
||||
|
||||
def load_config():
|
||||
global g_config
|
||||
g_config = importloader.loads(['userapiconfig', 'apiconfig'])
|
||||
|
||||
def get_config():
|
||||
return g_config
|
||||
|
||||
load_config()
|
||||
|
||||
Binary file not shown.
@ -0,0 +1,631 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import logging
|
||||
import time
|
||||
import sys
|
||||
from server_pool import ServerPool
|
||||
import traceback
|
||||
from shadowsocks import common, shell, lru_cache, obfs
|
||||
from configloader import load_config, get_config
|
||||
import importloader
|
||||
|
||||
switchrule = None
|
||||
db_instance = None
|
||||
|
||||
class TransferBase(object):
|
||||
def __init__(self):
|
||||
import threading
|
||||
self.event = threading.Event()
|
||||
self.key_list = ['port', 'u', 'd', 'transfer_enable', 'passwd', 'enable']
|
||||
self.last_get_transfer = {} #上一次的实际流量
|
||||
self.last_update_transfer = {} #上一次更新到的流量(小于等于实际流量)
|
||||
self.force_update_transfer = set() #强制推入数据库的ID
|
||||
self.port_uid_table = {} #端口到uid的映射(仅v3以上有用)
|
||||
self.onlineuser_cache = lru_cache.LRUCache(timeout=60*30) #用户在线状态记录
|
||||
self.pull_ok = False #记录是否已经拉出过数据
|
||||
self.mu_ports = {}
|
||||
|
||||
def load_cfg(self):
|
||||
pass
|
||||
|
||||
def push_db_all_user(self):
|
||||
if self.pull_ok is False:
|
||||
return
|
||||
#更新用户流量到数据库
|
||||
last_transfer = self.last_update_transfer
|
||||
curr_transfer = ServerPool.get_instance().get_servers_transfer()
|
||||
#上次和本次的增量
|
||||
dt_transfer = {}
|
||||
for id in self.force_update_transfer: #此表中的用户统计上次未计入的流量
|
||||
if id in self.last_get_transfer and id in last_transfer:
|
||||
dt_transfer[id] = [self.last_get_transfer[id][0] - last_transfer[id][0], self.last_get_transfer[id][1] - last_transfer[id][1]]
|
||||
|
||||
for id in curr_transfer.keys():
|
||||
if id in self.force_update_transfer or id in self.mu_ports:
|
||||
continue
|
||||
#算出与上次记录的流量差值,保存于dt_transfer表
|
||||
if id in last_transfer:
|
||||
if curr_transfer[id][0] + curr_transfer[id][1] - last_transfer[id][0] - last_transfer[id][1] <= 0:
|
||||
continue
|
||||
dt_transfer[id] = [curr_transfer[id][0] - last_transfer[id][0],
|
||||
curr_transfer[id][1] - last_transfer[id][1]]
|
||||
else:
|
||||
if curr_transfer[id][0] + curr_transfer[id][1] <= 0:
|
||||
continue
|
||||
dt_transfer[id] = [curr_transfer[id][0], curr_transfer[id][1]]
|
||||
|
||||
#有流量的,先记录在线状态
|
||||
if id in self.last_get_transfer:
|
||||
if curr_transfer[id][0] + curr_transfer[id][1] > self.last_get_transfer[id][0] + self.last_get_transfer[id][1]:
|
||||
self.onlineuser_cache[id] = curr_transfer[id][0] + curr_transfer[id][1]
|
||||
else:
|
||||
self.onlineuser_cache[id] = curr_transfer[id][0] + curr_transfer[id][1]
|
||||
|
||||
self.onlineuser_cache.sweep()
|
||||
|
||||
update_transfer = self.update_all_user(dt_transfer) #返回有更新的表
|
||||
for id in update_transfer.keys(): #其增量加在此表
|
||||
if id not in self.force_update_transfer: #但排除在force_update_transfer内的
|
||||
last = self.last_update_transfer.get(id, [0,0])
|
||||
self.last_update_transfer[id] = [last[0] + update_transfer[id][0], last[1] + update_transfer[id][1]]
|
||||
self.last_get_transfer = curr_transfer
|
||||
for id in self.force_update_transfer:
|
||||
if id in self.last_update_transfer:
|
||||
del self.last_update_transfer[id]
|
||||
if id in self.last_get_transfer:
|
||||
del self.last_get_transfer[id]
|
||||
self.force_update_transfer = set()
|
||||
|
||||
def del_server_out_of_bound_safe(self, last_rows, rows):
|
||||
#停止超流量的服务
|
||||
#启动没超流量的服务
|
||||
try:
|
||||
switchrule = importloader.load('switchrule')
|
||||
except Exception as e:
|
||||
logging.error('load switchrule.py fail')
|
||||
cur_servers = {}
|
||||
new_servers = {}
|
||||
allow_users = {}
|
||||
mu_servers = {}
|
||||
config = shell.get_config(False)
|
||||
for row in rows:
|
||||
try:
|
||||
allow = switchrule.isTurnOn(row) and row['enable'] == 1 and row['u'] + row['d'] < row['transfer_enable']
|
||||
except Exception as e:
|
||||
allow = False
|
||||
|
||||
port = row['port']
|
||||
passwd = common.to_bytes(row['passwd'])
|
||||
if hasattr(passwd, 'encode'):
|
||||
passwd = passwd.encode('utf-8')
|
||||
cfg = {'password': passwd}
|
||||
if 'id' in row:
|
||||
self.port_uid_table[row['port']] = row['id']
|
||||
|
||||
read_config_keys = ['method', 'obfs', 'obfs_param', 'protocol', 'protocol_param', 'forbidden_ip', 'forbidden_port', 'speed_limit_per_con', 'speed_limit_per_user']
|
||||
for name in read_config_keys:
|
||||
if name in row and row[name]:
|
||||
cfg[name] = row[name]
|
||||
|
||||
merge_config_keys = ['password'] + read_config_keys
|
||||
for name in cfg.keys():
|
||||
if hasattr(cfg[name], 'encode'):
|
||||
try:
|
||||
cfg[name] = cfg[name].encode('utf-8')
|
||||
except Exception as e:
|
||||
logging.warning('encode cfg key "%s" fail, val "%s"' % (name, cfg[name]))
|
||||
|
||||
if port not in cur_servers:
|
||||
cur_servers[port] = passwd
|
||||
else:
|
||||
logging.error('more than one user use the same port [%s]' % (port,))
|
||||
continue
|
||||
|
||||
if 'protocol' in cfg and 'protocol_param' in cfg and common.to_str(cfg['protocol']) in obfs.mu_protocol():
|
||||
if '#' in common.to_str(cfg['protocol_param']):
|
||||
mu_servers[port] = passwd
|
||||
allow = True
|
||||
|
||||
if allow:
|
||||
if port not in mu_servers:
|
||||
allow_users[port] = cfg
|
||||
|
||||
cfgchange = False
|
||||
if port in ServerPool.get_instance().tcp_servers_pool:
|
||||
relay = ServerPool.get_instance().tcp_servers_pool[port]
|
||||
for name in merge_config_keys:
|
||||
if name in cfg and not self.cmp(cfg[name], relay._config[name]):
|
||||
cfgchange = True
|
||||
break
|
||||
if not cfgchange and port in ServerPool.get_instance().tcp_ipv6_servers_pool:
|
||||
relay = ServerPool.get_instance().tcp_ipv6_servers_pool[port]
|
||||
for name in merge_config_keys:
|
||||
if (name in cfg) and ((name not in relay._config) or not self.cmp(cfg[name], relay._config[name])):
|
||||
cfgchange = True
|
||||
break
|
||||
|
||||
if port in mu_servers:
|
||||
if ServerPool.get_instance().server_is_run(port) > 0:
|
||||
if cfgchange:
|
||||
logging.info('db stop server at port [%s] reason: config changed: %s' % (port, cfg))
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
self.force_update_transfer.add(port)
|
||||
new_servers[port] = (passwd, cfg)
|
||||
else:
|
||||
self.new_server(port, passwd, cfg)
|
||||
else:
|
||||
if ServerPool.get_instance().server_is_run(port) > 0:
|
||||
if config['additional_ports_only'] or not allow:
|
||||
logging.info('db stop server at port [%s]' % (port,))
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
self.force_update_transfer.add(port)
|
||||
else:
|
||||
if cfgchange:
|
||||
logging.info('db stop server at port [%s] reason: config changed: %s' % (port, cfg))
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
self.force_update_transfer.add(port)
|
||||
new_servers[port] = (passwd, cfg)
|
||||
|
||||
elif not config['additional_ports_only'] and allow and port > 0 and port < 65536 and ServerPool.get_instance().server_run_status(port) is False:
|
||||
self.new_server(port, passwd, cfg)
|
||||
|
||||
for row in last_rows:
|
||||
if row['port'] in cur_servers:
|
||||
pass
|
||||
else:
|
||||
logging.info('db stop server at port [%s] reason: port not exist' % (row['port']))
|
||||
ServerPool.get_instance().cb_del_server(row['port'])
|
||||
self.clear_cache(row['port'])
|
||||
if row['port'] in self.port_uid_table:
|
||||
del self.port_uid_table[row['port']]
|
||||
|
||||
if len(new_servers) > 0:
|
||||
from shadowsocks import eventloop
|
||||
self.event.wait(eventloop.TIMEOUT_PRECISION + eventloop.TIMEOUT_PRECISION / 2)
|
||||
for port in new_servers.keys():
|
||||
passwd, cfg = new_servers[port]
|
||||
self.new_server(port, passwd, cfg)
|
||||
|
||||
logging.debug('db allow users %s \nmu_servers %s' % (allow_users, mu_servers))
|
||||
for port in mu_servers:
|
||||
ServerPool.get_instance().update_mu_users(port, allow_users)
|
||||
|
||||
self.mu_ports = mu_servers
|
||||
|
||||
def clear_cache(self, port):
|
||||
if port in self.force_update_transfer: del self.force_update_transfer[port]
|
||||
if port in self.last_get_transfer: del self.last_get_transfer[port]
|
||||
if port in self.last_update_transfer: del self.last_update_transfer[port]
|
||||
|
||||
def new_server(self, port, passwd, cfg):
|
||||
protocol = cfg.get('protocol', ServerPool.get_instance().config.get('protocol', 'origin'))
|
||||
method = cfg.get('method', ServerPool.get_instance().config.get('method', 'None'))
|
||||
obfs = cfg.get('obfs', ServerPool.get_instance().config.get('obfs', 'plain'))
|
||||
logging.info('db start server at port [%s] pass [%s] protocol [%s] method [%s] obfs [%s]' % (port, passwd, protocol, method, obfs))
|
||||
ServerPool.get_instance().new_server(port, cfg)
|
||||
|
||||
def cmp(self, val1, val2):
|
||||
if type(val1) is bytes:
|
||||
val1 = common.to_str(val1)
|
||||
if type(val2) is bytes:
|
||||
val2 = common.to_str(val2)
|
||||
return val1 == val2
|
||||
|
||||
@staticmethod
|
||||
def del_servers():
|
||||
for port in [v for v in ServerPool.get_instance().tcp_servers_pool.keys()]:
|
||||
if ServerPool.get_instance().server_is_run(port) > 0:
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
for port in [v for v in ServerPool.get_instance().tcp_ipv6_servers_pool.keys()]:
|
||||
if ServerPool.get_instance().server_is_run(port) > 0:
|
||||
ServerPool.get_instance().cb_del_server(port)
|
||||
|
||||
@staticmethod
|
||||
def thread_db(obj):
|
||||
import socket
|
||||
import time
|
||||
global db_instance
|
||||
timeout = 60
|
||||
socket.setdefaulttimeout(timeout)
|
||||
last_rows = []
|
||||
db_instance = obj()
|
||||
ServerPool.get_instance()
|
||||
shell.log_shadowsocks_version()
|
||||
|
||||
try:
|
||||
import resource
|
||||
logging.info('current process RLIMIT_NOFILE resource: soft %d hard %d' % resource.getrlimit(resource.RLIMIT_NOFILE))
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
while True:
|
||||
load_config()
|
||||
db_instance.load_cfg()
|
||||
try:
|
||||
db_instance.push_db_all_user()
|
||||
rows = db_instance.pull_db_all_user()
|
||||
if rows:
|
||||
db_instance.pull_ok = True
|
||||
config = shell.get_config(False)
|
||||
for port in config['additional_ports']:
|
||||
val = config['additional_ports'][port]
|
||||
val['port'] = int(port)
|
||||
val['enable'] = 1
|
||||
val['transfer_enable'] = 1024 ** 7
|
||||
val['u'] = 0
|
||||
val['d'] = 0
|
||||
if "password" in val:
|
||||
val["passwd"] = val["password"]
|
||||
rows.append(val)
|
||||
db_instance.del_server_out_of_bound_safe(last_rows, rows)
|
||||
last_rows = rows
|
||||
except Exception as e:
|
||||
trace = traceback.format_exc()
|
||||
logging.error(trace)
|
||||
#logging.warn('db thread except:%s' % e)
|
||||
if db_instance.event.wait(get_config().UPDATE_TIME) or not ServerPool.get_instance().thread.is_alive():
|
||||
break
|
||||
except KeyboardInterrupt as e:
|
||||
pass
|
||||
db_instance.del_servers()
|
||||
ServerPool.get_instance().stop()
|
||||
db_instance = None
|
||||
|
||||
@staticmethod
|
||||
def thread_db_stop():
|
||||
global db_instance
|
||||
db_instance.event.set()
|
||||
|
||||
class DbTransfer(TransferBase):
|
||||
def __init__(self):
|
||||
super(DbTransfer, self).__init__()
|
||||
self.user_pass = {} #记录更新此用户流量时被跳过多少次
|
||||
self.cfg = {
|
||||
"host": "127.0.0.1",
|
||||
"port": 3306,
|
||||
"user": "ss",
|
||||
"password": "pass",
|
||||
"db": "shadowsocks",
|
||||
"node_id": 0,
|
||||
"transfer_mul": 1.0,
|
||||
"ssl_enable": 0,
|
||||
"ssl_ca": "",
|
||||
"ssl_cert": "",
|
||||
"ssl_key": ""}
|
||||
self.load_cfg()
|
||||
|
||||
def load_cfg(self):
|
||||
import json
|
||||
config_path = get_config().MYSQL_CONFIG
|
||||
cfg = None
|
||||
with open(config_path, 'rb+') as f:
|
||||
cfg = json.loads(f.read().decode('utf8'))
|
||||
|
||||
if cfg:
|
||||
self.cfg.update(cfg)
|
||||
|
||||
def update_all_user(self, dt_transfer):
|
||||
import cymysql
|
||||
update_transfer = {}
|
||||
|
||||
query_head = 'UPDATE user'
|
||||
query_sub_when = ''
|
||||
query_sub_when2 = ''
|
||||
query_sub_in = None
|
||||
last_time = time.time()
|
||||
|
||||
for id in dt_transfer.keys():
|
||||
transfer = dt_transfer[id]
|
||||
#小于最低更新流量的先不更新
|
||||
update_trs = 1024 * (2048 - self.user_pass.get(id, 0) * 64)
|
||||
if transfer[0] + transfer[1] < update_trs and id not in self.force_update_transfer:
|
||||
self.user_pass[id] = self.user_pass.get(id, 0) + 1
|
||||
continue
|
||||
if id in self.user_pass:
|
||||
del self.user_pass[id]
|
||||
|
||||
query_sub_when += ' WHEN %s THEN u+%s' % (id, int(transfer[0] * self.cfg["transfer_mul"]))
|
||||
query_sub_when2 += ' WHEN %s THEN d+%s' % (id, int(transfer[1] * self.cfg["transfer_mul"]))
|
||||
update_transfer[id] = transfer
|
||||
|
||||
if query_sub_in is not None:
|
||||
query_sub_in += ',%s' % id
|
||||
else:
|
||||
query_sub_in = '%s' % id
|
||||
|
||||
if query_sub_when == '':
|
||||
return update_transfer
|
||||
query_sql = query_head + ' SET u = CASE port' + query_sub_when + \
|
||||
' END, d = CASE port' + query_sub_when2 + \
|
||||
' END, t = ' + str(int(last_time)) + \
|
||||
' WHERE port IN (%s)' % query_sub_in
|
||||
if self.cfg["ssl_enable"] == 1:
|
||||
conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
|
||||
user=self.cfg["user"], passwd=self.cfg["password"],
|
||||
db=self.cfg["db"], charset='utf8',
|
||||
ssl={'ca':self.cfg["ssl_ca"],'cert':self.cfg["ssl_cert"],'key':self.cfg["ssl_key"]})
|
||||
else:
|
||||
conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
|
||||
user=self.cfg["user"], passwd=self.cfg["password"],
|
||||
db=self.cfg["db"], charset='utf8')
|
||||
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute(query_sql)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
update_transfer = {}
|
||||
|
||||
cur.close()
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
update_transfer = {}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return update_transfer
|
||||
|
||||
def pull_db_all_user(self):
|
||||
import cymysql
|
||||
#数据库所有用户信息
|
||||
if self.cfg["ssl_enable"] == 1:
|
||||
conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
|
||||
user=self.cfg["user"], passwd=self.cfg["password"],
|
||||
db=self.cfg["db"], charset='utf8',
|
||||
ssl={'ca':self.cfg["ssl_ca"],'cert':self.cfg["ssl_cert"],'key':self.cfg["ssl_key"]})
|
||||
else:
|
||||
conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
|
||||
user=self.cfg["user"], passwd=self.cfg["password"],
|
||||
db=self.cfg["db"], charset='utf8')
|
||||
|
||||
try:
|
||||
rows = self.pull_db_users(conn)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if not rows:
|
||||
logging.warn('no user in db')
|
||||
return rows
|
||||
|
||||
def pull_db_users(self, conn):
|
||||
try:
|
||||
switchrule = importloader.load('switchrule')
|
||||
keys = switchrule.getKeys(self.key_list)
|
||||
except Exception as e:
|
||||
keys = self.key_list
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT " + ','.join(keys) + " FROM user")
|
||||
rows = []
|
||||
for r in cur.fetchall():
|
||||
d = {}
|
||||
for column in range(len(keys)):
|
||||
d[keys[column]] = r[column]
|
||||
rows.append(d)
|
||||
cur.close()
|
||||
return rows
|
||||
|
||||
class Dbv3Transfer(DbTransfer):
|
||||
def __init__(self):
|
||||
super(Dbv3Transfer, self).__init__()
|
||||
self.update_node_state = True if get_config().API_INTERFACE != 'legendsockssr' else False
|
||||
if self.update_node_state:
|
||||
self.key_list += ['id']
|
||||
self.key_list += ['method']
|
||||
if self.update_node_state:
|
||||
self.ss_node_info_name = 'ss_node_info_log'
|
||||
if get_config().API_INTERFACE == 'sspanelv3ssr':
|
||||
self.key_list += ['obfs', 'protocol']
|
||||
if get_config().API_INTERFACE == 'glzjinmod':
|
||||
self.key_list += ['obfs', 'protocol']
|
||||
self.ss_node_info_name = 'ss_node_info'
|
||||
else:
|
||||
self.key_list += ['obfs', 'protocol']
|
||||
self.start_time = time.time()
|
||||
|
||||
def update_all_user(self, dt_transfer):
|
||||
import cymysql
|
||||
update_transfer = {}
|
||||
|
||||
query_head = 'UPDATE user'
|
||||
query_sub_when = ''
|
||||
query_sub_when2 = ''
|
||||
query_sub_in = None
|
||||
last_time = time.time()
|
||||
|
||||
alive_user_count = len(self.onlineuser_cache)
|
||||
bandwidth_thistime = 0
|
||||
|
||||
if self.cfg["ssl_enable"] == 1:
|
||||
conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
|
||||
user=self.cfg["user"], passwd=self.cfg["password"],
|
||||
db=self.cfg["db"], charset='utf8',
|
||||
ssl={'ca':self.cfg["ssl_ca"],'cert':self.cfg["ssl_cert"],'key':self.cfg["ssl_key"]})
|
||||
else:
|
||||
conn = cymysql.connect(host=self.cfg["host"], port=self.cfg["port"],
|
||||
user=self.cfg["user"], passwd=self.cfg["password"],
|
||||
db=self.cfg["db"], charset='utf8')
|
||||
conn.autocommit(True)
|
||||
|
||||
for id in dt_transfer.keys():
|
||||
transfer = dt_transfer[id]
|
||||
bandwidth_thistime = bandwidth_thistime + transfer[0] + transfer[1]
|
||||
|
||||
update_trs = 1024 * (2048 - self.user_pass.get(id, 0) * 64)
|
||||
if transfer[0] + transfer[1] < update_trs:
|
||||
self.user_pass[id] = self.user_pass.get(id, 0) + 1
|
||||
continue
|
||||
if id in self.user_pass:
|
||||
del self.user_pass[id]
|
||||
|
||||
query_sub_when += ' WHEN %s THEN u+%s' % (id, int(transfer[0] * self.cfg["transfer_mul"]))
|
||||
query_sub_when2 += ' WHEN %s THEN d+%s' % (id, int(transfer[1] * self.cfg["transfer_mul"]))
|
||||
update_transfer[id] = transfer
|
||||
|
||||
if self.update_node_state:
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
if id in self.port_uid_table:
|
||||
cur.execute("INSERT INTO `user_traffic_log` (`id`, `user_id`, `u`, `d`, `node_id`, `rate`, `traffic`, `log_time`) VALUES (NULL, '" + \
|
||||
str(self.port_uid_table[id]) + "', '" + str(transfer[0]) + "', '" + str(transfer[1]) + "', '" + \
|
||||
str(self.cfg["node_id"]) + "', '" + str(self.cfg["transfer_mul"]) + "', '" + \
|
||||
self.traffic_format((transfer[0] + transfer[1]) * self.cfg["transfer_mul"]) + "', unix_timestamp()); ")
|
||||
except:
|
||||
logging.warn('no `user_traffic_log` in db')
|
||||
cur.close()
|
||||
|
||||
if query_sub_in is not None:
|
||||
query_sub_in += ',%s' % id
|
||||
else:
|
||||
query_sub_in = '%s' % id
|
||||
|
||||
if query_sub_when != '':
|
||||
query_sql = query_head + ' SET u = CASE port' + query_sub_when + \
|
||||
' END, d = CASE port' + query_sub_when2 + \
|
||||
' END, t = ' + str(int(last_time)) + \
|
||||
' WHERE port IN (%s)' % query_sub_in
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute(query_sql)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
cur.close()
|
||||
|
||||
if self.update_node_state:
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("INSERT INTO `ss_node_online_log` (`id`, `node_id`, `online_user`, `log_time`) VALUES (NULL, '" + \
|
||||
str(self.cfg["node_id"]) + "', '" + str(alive_user_count) + "', unix_timestamp()); ")
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
cur.close()
|
||||
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("INSERT INTO `" + self.ss_node_info_name + "` (`id`, `node_id`, `uptime`, `load`, `log_time`) VALUES (NULL, '" + \
|
||||
str(self.cfg["node_id"]) + "', '" + str(self.uptime()) + "', '" + \
|
||||
str(self.load()) + "', unix_timestamp()); ")
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
cur.close()
|
||||
except:
|
||||
logging.warn('no `ss_node_online_log` or `" + self.ss_node_info_name + "` in db')
|
||||
|
||||
conn.close()
|
||||
return update_transfer
|
||||
|
||||
def pull_db_users(self, conn):
|
||||
try:
|
||||
switchrule = importloader.load('switchrule')
|
||||
keys = switchrule.getKeys(self.key_list)
|
||||
except Exception as e:
|
||||
keys = self.key_list
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
if self.update_node_state:
|
||||
node_info_keys = ['traffic_rate']
|
||||
try:
|
||||
cur.execute("SELECT " + ','.join(node_info_keys) +" FROM ss_node where `id`='" + str(self.cfg["node_id"]) + "'")
|
||||
nodeinfo = cur.fetchone()
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
nodeinfo = None
|
||||
|
||||
if nodeinfo == None:
|
||||
rows = []
|
||||
cur.close()
|
||||
conn.commit()
|
||||
logging.warn('None result when select node info from ss_node in db, maybe you set the incorrect node id')
|
||||
return rows
|
||||
cur.close()
|
||||
|
||||
node_info_dict = {}
|
||||
for column in range(len(nodeinfo)):
|
||||
node_info_dict[node_info_keys[column]] = nodeinfo[column]
|
||||
self.cfg['transfer_mul'] = float(node_info_dict['traffic_rate'])
|
||||
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
rows = []
|
||||
cur.execute("SELECT " + ','.join(keys) + " FROM user")
|
||||
for r in cur.fetchall():
|
||||
d = {}
|
||||
for column in range(len(keys)):
|
||||
d[keys[column]] = r[column]
|
||||
rows.append(d)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
cur.close()
|
||||
return rows
|
||||
|
||||
def load(self):
|
||||
import os
|
||||
return os.popen("cat /proc/loadavg | awk '{ print $1\" \"$2\" \"$3 }'").readlines()[0]
|
||||
|
||||
def uptime(self):
|
||||
return time.time() - self.start_time
|
||||
|
||||
def traffic_format(self, traffic):
|
||||
if traffic < 1024 * 8:
|
||||
return str(int(traffic)) + "B";
|
||||
|
||||
if traffic < 1024 * 1024 * 2:
|
||||
return str(round((traffic / 1024.0), 2)) + "KB";
|
||||
|
||||
return str(round((traffic / 1048576.0), 2)) + "MB";
|
||||
|
||||
class MuJsonTransfer(TransferBase):
|
||||
def __init__(self):
|
||||
super(MuJsonTransfer, self).__init__()
|
||||
|
||||
def update_all_user(self, dt_transfer):
|
||||
import json
|
||||
rows = None
|
||||
|
||||
config_path = get_config().MUDB_FILE
|
||||
with open(config_path, 'rb+') as f:
|
||||
rows = json.loads(f.read().decode('utf8'))
|
||||
for row in rows:
|
||||
if "port" in row:
|
||||
port = row["port"]
|
||||
if port in dt_transfer:
|
||||
row["u"] += dt_transfer[port][0]
|
||||
row["d"] += dt_transfer[port][1]
|
||||
|
||||
if rows:
|
||||
output = json.dumps(rows, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
with open(config_path, 'r+') as f:
|
||||
f.write(output)
|
||||
f.truncate()
|
||||
|
||||
return dt_transfer
|
||||
|
||||
def pull_db_all_user(self):
|
||||
import json
|
||||
rows = None
|
||||
|
||||
config_path = get_config().MUDB_FILE
|
||||
with open(config_path, 'rb+') as f:
|
||||
rows = json.loads(f.read().decode('utf8'))
|
||||
for row in rows:
|
||||
try:
|
||||
if 'forbidden_ip' in row:
|
||||
row['forbidden_ip'] = common.IPNetwork(row['forbidden_ip'])
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
try:
|
||||
if 'forbidden_port' in row:
|
||||
row['forbidden_port'] = common.PortRange(row['forbidden_port'])
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
if not rows:
|
||||
logging.warn('no user in json file')
|
||||
return rows
|
||||
|
||||
Binary file not shown.
@ -0,0 +1,5 @@
|
||||
shadowsocks (2.1.0-1) unstable; urgency=low
|
||||
|
||||
* Initial release (Closes: #758900)
|
||||
|
||||
-- Shell.Xu <shell909090@gmail.com> Sat, 23 Aug 2014 00:56:04 +0800
|
||||
@ -0,0 +1 @@
|
||||
8
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"server":"my_server_ip",
|
||||
"server_port":8388,
|
||||
"local_address": "127.0.0.1",
|
||||
"local_port":1080,
|
||||
"password":"mypassword",
|
||||
"timeout":300,
|
||||
"method":"aes-256-cfb",
|
||||
"fast_open": false,
|
||||
"workers": 1
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
Source: shadowsocks
|
||||
Section: python
|
||||
Priority: extra
|
||||
Maintainer: Shell.Xu <shell909090@gmail.com>
|
||||
Build-Depends: debhelper (>= 8), python-all (>= 2.6.6-3~), python-setuptools
|
||||
Standards-Version: 3.9.5
|
||||
Homepage: https://github.com/clowwindy/shadowsocks
|
||||
Vcs-Git: git://github.com/shell909090/shadowsocks.git
|
||||
Vcs-Browser: http://github.com/shell909090/shadowsocks
|
||||
|
||||
Package: shadowsocks
|
||||
Architecture: all
|
||||
Pre-Depends: dpkg (>= 1.15.6~)
|
||||
Depends: ${misc:Depends}, ${python:Depends}, python-pkg-resources, python-m2crypto
|
||||
Description: Fast tunnel proxy that helps you bypass firewalls
|
||||
A secure socks5 proxy, designed to protect your Internet traffic.
|
||||
.
|
||||
This package contain local and server part of shadowsocks, a fast,
|
||||
powerful tunnel proxy to bypass firewalls.
|
||||
@ -0,0 +1,30 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: shadowsocks
|
||||
Source: https://github.com/clowwindy/shadowsocks
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2014 Shell.Xu <shell909090@gmail.com>
|
||||
License: Expat
|
||||
|
||||
Files: *
|
||||
Copyright: 2014 clowwindy <clowwindy42@gmail.com>
|
||||
License: Expat
|
||||
|
||||
License: Expat
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -0,0 +1,2 @@
|
||||
README.md
|
||||
README.rst
|
||||
@ -0,0 +1,149 @@
|
||||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: shadowsocks
|
||||
# Required-Start: $network $local_fs $remote_fs
|
||||
# Required-Stop: $network $local_fs $remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Fast tunnel proxy that helps you bypass firewalls
|
||||
# Description: A secure socks5 proxy, designed to protect your Internet traffic.
|
||||
# This package contain local and server part of shadowsocks, a fast,
|
||||
# powerful tunnel proxy to bypass firewalls.
|
||||
### END INIT INFO
|
||||
|
||||
# Author: Shell.Xu <shell909090@gmail.com>
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC=shadowsocks # Introduce a short description here
|
||||
NAME=shadowsocks # Introduce the short server's name here
|
||||
DAEMON=/usr/bin/ssserver # Introduce the server's location here
|
||||
DAEMON_ARGS="" # Arguments to run the daemon with
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
LOGFILE=/var/log/$NAME.log
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x $DAEMON ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
|
||||
--background --make-pidfile --chdir / --chuid $USERID --no-close --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON \
|
||||
--background --make-pidfile --chdir / --chuid $USERID --no-close -- \
|
||||
$DAEMON_ARGS $DAEMON_OPTS >> $LOGFILE 2>&1 \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
#
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
#reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave 'force-reload' as an alias for 'restart'.
|
||||
#
|
||||
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
#do_reload
|
||||
#log_end_msg $?
|
||||
#;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:
|
||||
@ -0,0 +1 @@
|
||||
debian/config.json etc/shadowsocks/
|
||||
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
%:
|
||||
dh $@ --with python2 --buildsystem=python_distutils
|
||||
@ -0,0 +1,12 @@
|
||||
# Defaults for shadowsocks initscript
|
||||
# sourced by /etc/init.d/shadowsocks
|
||||
# installed at /etc/default/shadowsocks by the maintainer scripts
|
||||
|
||||
USERID="nobody"
|
||||
|
||||
#
|
||||
# This is a POSIX shell fragment
|
||||
#
|
||||
|
||||
# Additional options that are passed to the Daemon.
|
||||
DAEMON_OPTS="-q -c /etc/shadowsocks/config.json"
|
||||
@ -0,0 +1,2 @@
|
||||
debian/sslocal.1
|
||||
debian/ssserver.1
|
||||
@ -0,0 +1 @@
|
||||
3.0 (quilt)
|
||||
@ -0,0 +1,59 @@
|
||||
.\" Hey, EMACS: -*- nroff -*-
|
||||
.\" (C) Copyright 2014 Shell.Xu <shell909090@gmail.com>,
|
||||
.\"
|
||||
.TH SHADOWSOCKS 1 "August 23, 2014"
|
||||
.SH NAME
|
||||
shadowsocks \- Fast tunnel proxy that helps you bypass firewalls
|
||||
.SH SYNOPSIS
|
||||
.B ssserver
|
||||
.RI [ options ]
|
||||
.br
|
||||
.B sslocal
|
||||
.RI [ options ]
|
||||
.SH DESCRIPTION
|
||||
shadowsocks is a tunnel proxy helps you bypass firewall.
|
||||
.B ssserver
|
||||
is the server part, and
|
||||
.B sslocal
|
||||
is the local part.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Show this help message and exit.
|
||||
.TP
|
||||
.B \-s SERVER_ADDR
|
||||
Server address, default: 0.0.0.0.
|
||||
.TP
|
||||
.B \-p SERVER_PORT
|
||||
Server port, default: 8388.
|
||||
.TP
|
||||
.B \-k PASSWORD
|
||||
Password.
|
||||
.TP
|
||||
.B \-m METHOD
|
||||
Encryption method, default: aes-256-cfb.
|
||||
.TP
|
||||
.B \-t TIMEOUT
|
||||
Timeout in seconds, default: 300.
|
||||
.TP
|
||||
.B \-c CONFIG
|
||||
Path to config file.
|
||||
.TP
|
||||
.B \-\-fast-open
|
||||
Use TCP_FASTOPEN, requires Linux 3.7+.
|
||||
.TP
|
||||
.B \-\-workers WORKERS
|
||||
Number of workers, available on Unix/Linux.
|
||||
.TP
|
||||
.B \-v, \-vv
|
||||
Verbose mode.
|
||||
.TP
|
||||
.B \-q, \-qq
|
||||
Quiet mode, only show warnings/errors.
|
||||
.SH SEE ALSO
|
||||
.br
|
||||
The programs are documented fully by
|
||||
.IR "Shell Xu <shell909090@gmail.com>"
|
||||
and
|
||||
.IR "Clowwindy <clowwindy42@gmail.com>",
|
||||
available via the Info system.
|
||||
@ -0,0 +1,59 @@
|
||||
.\" Hey, EMACS: -*- nroff -*-
|
||||
.\" (C) Copyright 2014 Shell.Xu <shell909090@gmail.com>,
|
||||
.\"
|
||||
.TH SHADOWSOCKS 1 "August 23, 2014"
|
||||
.SH NAME
|
||||
shadowsocks \- Fast tunnel proxy that helps you bypass firewalls
|
||||
.SH SYNOPSIS
|
||||
.B ssserver
|
||||
.RI [ options ]
|
||||
.br
|
||||
.B sslocal
|
||||
.RI [ options ]
|
||||
.SH DESCRIPTION
|
||||
shadowsocks is a tunnel proxy helps you bypass firewall.
|
||||
.B ssserver
|
||||
is the server part, and
|
||||
.B sslocal
|
||||
is the local part.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \-h, \-\-help
|
||||
Show this help message and exit.
|
||||
.TP
|
||||
.B \-s SERVER_ADDR
|
||||
Server address, default: 0.0.0.0.
|
||||
.TP
|
||||
.B \-p SERVER_PORT
|
||||
Server port, default: 8388.
|
||||
.TP
|
||||
.B \-k PASSWORD
|
||||
Password.
|
||||
.TP
|
||||
.B \-m METHOD
|
||||
Encryption method, default: aes-256-cfb.
|
||||
.TP
|
||||
.B \-t TIMEOUT
|
||||
Timeout in seconds, default: 300.
|
||||
.TP
|
||||
.B \-c CONFIG
|
||||
Path to config file.
|
||||
.TP
|
||||
.B \-\-fast-open
|
||||
Use TCP_FASTOPEN, requires Linux 3.7+.
|
||||
.TP
|
||||
.B \-\-workers WORKERS
|
||||
Number of workers, available on Unix/Linux.
|
||||
.TP
|
||||
.B \-v, \-vv
|
||||
Verbose mode.
|
||||
.TP
|
||||
.B \-q, \-qq
|
||||
Quiet mode, only show warnings/errors.
|
||||
.SH SEE ALSO
|
||||
.br
|
||||
The programs are documented fully by
|
||||
.IR "Shell Xu <shell909090@gmail.com>"
|
||||
and
|
||||
.IR "Clowwindy <clowwindy42@gmail.com>",
|
||||
available via the Info system.
|
||||
@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
. $IPKG_INSTROOT/lib/functions.sh
|
||||
. $IPKG_INSTROOT/lib/functions/service.sh
|
||||
|
||||
gen_user_iptables() {
|
||||
config_get enable $1 enable
|
||||
[ "$enable" = "0" ] && return 0
|
||||
config_get remarks $1 remarks
|
||||
config_get port $1 port
|
||||
iptables -A SSR_PYTHON-SERVER -p tcp --dport $port -m comment --comment "$remarks" -j ACCEPT
|
||||
iptables -A SSR_PYTHON-SERVER -p udp --dport $port -m comment --comment "$remarks" -j ACCEPT
|
||||
}
|
||||
|
||||
iptables -F SSR_PYTHON-SERVER 2>/dev/null
|
||||
iptables -D INPUT -j SSR_PYTHON-SERVER 2>/dev/null
|
||||
iptables -X SSR_PYTHON-SERVER 2>/dev/null
|
||||
|
||||
enable=$(uci get ssr_python_pro_server.@global[0].enable)
|
||||
if [ $enable -eq 1 ]; then
|
||||
iptables -N SSR_PYTHON-SERVER
|
||||
iptables -I INPUT -j SSR_PYTHON-SERVER
|
||||
config_load ssr_python_pro_server
|
||||
config_foreach gen_user_iptables "user"
|
||||
fi
|
||||
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
def load(name):
|
||||
try:
|
||||
obj = __import__(name)
|
||||
reload(obj)
|
||||
return obj
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
import importlib
|
||||
obj = importlib.__import__(name)
|
||||
importlib.reload(obj)
|
||||
return obj
|
||||
except:
|
||||
pass
|
||||
|
||||
def loads(namelist):
|
||||
for name in namelist:
|
||||
obj = load(name)
|
||||
if obj is not None:
|
||||
return obj
|
||||
Binary file not shown.
@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
If Not Exist "userapiconfig.py" Copy "apiconfig.py" "userapiconfig.py"
|
||||
If Not Exist "user-config.json" Copy "config.json" "user-config.json"
|
||||
If Not Exist "usermysql.json" Copy "mysql.json" "usermysql.json"
|
||||
@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
chmod +x *.sh
|
||||
chmod +x shadowsocks/*.sh
|
||||
cp -n apiconfig.py userapiconfig.py
|
||||
cp -n config.json user-config.json
|
||||
cp -n mysql.json usermysql.json
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
bash initcfg.sh
|
||||
sed -i "s/API_INTERFACE = .\+\?\#/API_INTERFACE = \'mudbjson\' \#/g" userapiconfig.py
|
||||
ip_addr=`ifconfig -a|grep inet|grep -v inet6|grep -v "127.0.0."|grep -v -e "192\.168\..[0-9]\+\.[0-9]\+"|grep -v -e "10\.[0-9]\+\.[0-9]\+\.[0-9]\+"|awk '{print $2}'|tr -d "addr:"`
|
||||
ip_count=`echo $ip_addr|grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+$" -c`
|
||||
|
||||
if [[ $ip_count == 1 ]]; then
|
||||
ip_addr=`ip a|grep inet|grep -v inet6|grep -v "127.0.0."|grep -v -e "192\.168\..[0-9]\+\.[0-9]\+"|grep -v -e "10\.[0-9]\+\.[0-9]\+\.[0-9]\+"|awk '{print $2}'`
|
||||
ip_addr=${ip_addr%/*}
|
||||
ip_count=`echo $ip_addr|grep -e "^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+$" -c`
|
||||
fi
|
||||
if [[ $ip_count == 1 ]]; then
|
||||
echo "server IP is "${ip_addr}
|
||||
sed -i "s/SERVER_PUB_ADDR = .\+/SERVER_PUB_ADDR = \'"${ip_addr}"\'/g" userapiconfig.py
|
||||
user_count=`python mujson_mgr.py -l|grep -c -e "[0-9]"`
|
||||
if [[ $user_count == 0 ]]; then
|
||||
port=`python -c 'import random;print(random.randint(10000, 65536))'`
|
||||
python mujson_mgr.py -a -p ${port}
|
||||
fi
|
||||
else
|
||||
echo "unable to detect server IP"
|
||||
fi
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
#python_ver=$(ls /usr/bin|grep -e "^python[23]\.[1-9]\+$"|tail -1)
|
||||
eval $(ps -ef | grep "[0-9] python server\\.py m" | awk '{print "kill "$2}')
|
||||
ulimit -n 512000
|
||||
nohup python server.py m >> ssserver.log 2>&1 &
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
[
|
||||
{
|
||||
"d": 276776,
|
||||
"enable": 1,
|
||||
"forbidden_port": "",
|
||||
"id": "cfg05ae6f",
|
||||
"method": "none",
|
||||
"obfs": "tls1.2_ticket_auth",
|
||||
"passwd": "123456",
|
||||
"port": 11111,
|
||||
"protocol": "auth_chain_a",
|
||||
"protocol_param": "2",
|
||||
"speed_limit_per_con": 100,
|
||||
"speed_limit_per_user": 200,
|
||||
"transfer_enable": 10737418240,
|
||||
"u": 60612,
|
||||
"user": "\u563b\u563b"
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,372 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import traceback
|
||||
from shadowsocks import shell, common
|
||||
from configloader import load_config, get_config
|
||||
import random
|
||||
import getopt
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
|
||||
|
||||
class MuJsonLoader(object):
|
||||
def __init__(self):
|
||||
self.json = None
|
||||
|
||||
def load(self, path):
|
||||
l = "[]"
|
||||
try:
|
||||
with open(path, 'rb+') as f:
|
||||
l = f.read().decode('utf8')
|
||||
except:
|
||||
pass
|
||||
self.json = json.loads(l)
|
||||
|
||||
def save(self, path):
|
||||
if self.json is not None:
|
||||
output = json.dumps(self.json, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
with open(path, 'a'):
|
||||
pass
|
||||
with open(path, 'rb+') as f:
|
||||
f.write(output.encode('utf8'))
|
||||
f.truncate()
|
||||
|
||||
|
||||
class MuMgr(object):
|
||||
def __init__(self):
|
||||
self.config_path = get_config().MUDB_FILE
|
||||
try:
|
||||
self.server_addr = get_config().SERVER_PUB_ADDR
|
||||
except:
|
||||
self.server_addr = '127.0.0.1'
|
||||
self.data = MuJsonLoader()
|
||||
|
||||
if self.server_addr == '127.0.0.1':
|
||||
self.server_addr = self.getipaddr()
|
||||
|
||||
def getipaddr(self, ifname='eth0'):
|
||||
import socket
|
||||
import struct
|
||||
ret = '127.0.0.1'
|
||||
try:
|
||||
ret = socket.gethostbyname(socket.getfqdn(socket.gethostname()))
|
||||
except:
|
||||
pass
|
||||
if ret == '127.0.0.1':
|
||||
try:
|
||||
import fcntl
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
ret = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
|
||||
except:
|
||||
pass
|
||||
return ret
|
||||
|
||||
def ssrlink(self, user, encode, muid):
|
||||
protocol = user.get('protocol', '')
|
||||
obfs = user.get('obfs', '')
|
||||
protocol = protocol.replace("_compatible", "")
|
||||
obfs = obfs.replace("_compatible", "")
|
||||
protocol_param = ''
|
||||
if muid is not None:
|
||||
protocol_param_ = user.get('protocol_param', '')
|
||||
param = protocol_param_.split('#')
|
||||
if len(param) == 2:
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
param = str(muid) + ':' + row['passwd']
|
||||
protocol_param = '/?protoparam=' + common.to_str(base64.urlsafe_b64encode(common.to_bytes(param))).replace("=", "")
|
||||
break
|
||||
link = ("%s:%s:%s:%s:%s:%s" % (self.server_addr, user['port'], protocol, user['method'], obfs, common.to_str(base64.urlsafe_b64encode(common.to_bytes(user['passwd']))).replace("=", ""))) + protocol_param
|
||||
return "ssr://" + (encode and common.to_str(base64.urlsafe_b64encode(common.to_bytes(link))).replace("=", "") or link)
|
||||
|
||||
def userinfo(self, user, muid = None):
|
||||
ret = ""
|
||||
key_list = ['user', 'port', 'method', 'passwd', 'protocol', 'protocol_param', 'obfs', 'obfs_param', 'transfer_enable', 'u', 'd']
|
||||
for key in sorted(user):
|
||||
if key not in key_list:
|
||||
key_list.append(key)
|
||||
for key in key_list:
|
||||
if key in ['enable'] or key not in user:
|
||||
continue
|
||||
ret += '\n'
|
||||
if (muid is not None) and (key in ['protocol_param']):
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
ret += " %s : %s" % (key, str(muid) + ':' + row['passwd'])
|
||||
break
|
||||
elif key in ['transfer_enable', 'u', 'd']:
|
||||
if muid is not None:
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
val = row[key]
|
||||
break
|
||||
else:
|
||||
val = user[key]
|
||||
if val < 1024:
|
||||
ret += " %s : %s" % (key, val)
|
||||
elif val < 1024 ** 2:
|
||||
val /= float(1024)
|
||||
ret += " %s : %s K Bytes" % (key, val)
|
||||
elif val < 1024 ** 3:
|
||||
val /= float(1024 ** 2)
|
||||
ret += " %s : %s M Bytes" % (key, val)
|
||||
else:
|
||||
val /= float(1024 ** 3)
|
||||
ret += " %s : %s G Bytes" % (key, val)
|
||||
else:
|
||||
ret += " %s : %s" % (key, user[key])
|
||||
u_b = user['u']
|
||||
d_b = user['d']
|
||||
total_traffic_b = u_b + d_b
|
||||
total_traffic = total_traffic_b
|
||||
ret += "\n %s : %s" % ("u_b", u_b)
|
||||
ret += "\n %s : %s" % ("d_b", d_b)
|
||||
ret += "\n %s : %s" % ("total_traffic_b", total_traffic_b)
|
||||
ret += "\n"
|
||||
if total_traffic < 1024:
|
||||
ret += " %s : %s" % ("total_traffic", total_traffic)
|
||||
elif total_traffic < 1024 ** 2:
|
||||
total_traffic /= float(1024)
|
||||
ret += " %s : %s K Bytes" % ("total_traffic", total_traffic)
|
||||
elif total_traffic < 1024 ** 3:
|
||||
total_traffic /= float(1024 ** 2)
|
||||
ret += " %s : %s M Bytes" % ("total_traffic", total_traffic)
|
||||
else:
|
||||
total_traffic /= float(1024 ** 3)
|
||||
ret += " %s : %s G Bytes" % ("total_traffic", total_traffic)
|
||||
ret += "\n " + self.ssrlink(user, False, muid)
|
||||
ret += "\n " + self.ssrlink(user, True, muid)
|
||||
return ret
|
||||
|
||||
def rand_pass(self):
|
||||
return ''.join([random.choice('''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~-_=+(){}[]^&%$@''') for i in range(8)])
|
||||
|
||||
def add(self, user):
|
||||
up = {'enable': 1, 'u': 0, 'd': 0, 'method': "aes-128-ctr",
|
||||
'protocol': "auth_aes128_md5",
|
||||
'obfs': "tls1.2_ticket_auth_compatible",
|
||||
'transfer_enable': 9007199254740992}
|
||||
up['passwd'] = self.rand_pass()
|
||||
up.update(user)
|
||||
|
||||
self.data.load(self.config_path)
|
||||
for row in self.data.json:
|
||||
match = False
|
||||
if 'id' in user and row['id'] != user['id']:
|
||||
match = False
|
||||
if match:
|
||||
print("id %s user [%s] port [%s] already exist" % (row['id'], row['user'], row['port']))
|
||||
return
|
||||
self.data.json.append(up)
|
||||
print("### add user info %s" % self.userinfo(up))
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def edit(self, user):
|
||||
self.data.load(self.config_path)
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'id' in user and row['id'] != user['id']:
|
||||
match = False
|
||||
if match:
|
||||
print("edit user [%s]" % (row['user'],))
|
||||
row.update(user)
|
||||
print("### new id %s user info %s" % (row["id"], self.userinfo(row)))
|
||||
break
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def delete(self, user):
|
||||
self.data.load(self.config_path)
|
||||
index = 0
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'id' in user and row['id'] != user['id']:
|
||||
match = False
|
||||
if match:
|
||||
print("delete id %s user [%s]" % (row["id"], row['user']))
|
||||
del self.data.json[index]
|
||||
break
|
||||
index += 1
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def clear_ud(self, user):
|
||||
up = {'u': 0, 'd': 0}
|
||||
self.data.load(self.config_path)
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'id' in user and row['id'] != user['id']:
|
||||
match = False
|
||||
if match:
|
||||
row.update(up)
|
||||
print("clear id %s user [%s]" % (row["id"], row['user']))
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def list_user(self, user):
|
||||
self.data.load(self.config_path)
|
||||
if not user:
|
||||
for row in self.data.json:
|
||||
print("id %s user [%s] port %s" % (row["id"], row['user'], row['port']))
|
||||
return
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'id' in user and row['id'] != user['id']:
|
||||
match = False
|
||||
if match:
|
||||
muid = None
|
||||
if 'muid' in user:
|
||||
muid = user['muid']
|
||||
print("### id %s user [%s] info %s" % (row["id"], row['user'], self.userinfo(row, muid)))
|
||||
|
||||
|
||||
def print_server_help():
|
||||
print('''usage: python mujson_manage.py -a|-d|-e|-c|-l [OPTION]...
|
||||
|
||||
Actions:
|
||||
-a add/edit a user
|
||||
-d delete a user
|
||||
-e edit a user
|
||||
-c set u&d to zero
|
||||
-l display a user infomation or all users infomation
|
||||
|
||||
Options:
|
||||
-I ID the ID
|
||||
-u USER the user name
|
||||
-p PORT server port (only this option must be set if add a user)
|
||||
-k PASSWORD password
|
||||
-m METHOD encryption method, default: aes-128-ctr
|
||||
-O PROTOCOL protocol plugin, default: auth_aes128_md5
|
||||
-o OBFS obfs plugin, default: tls1.2_ticket_auth_compatible
|
||||
-G PROTOCOL_PARAM protocol plugin param
|
||||
-g OBFS_PARAM obfs plugin param
|
||||
-t TRANSFER max transfer for G bytes, default: 8388608 (8 PB or 8192 TB)
|
||||
-f FORBID set forbidden ports. Example (ban 1~79 and 81~100): -f "1-79,81-100"
|
||||
-i MUID set sub id to display (only work with -l)
|
||||
-s SPEED set speed_limit_per_con
|
||||
-S SPEED set speed_limit_per_user
|
||||
|
||||
General options:
|
||||
-h, --help show this help message and exit
|
||||
''')
|
||||
|
||||
|
||||
def main():
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf-8')
|
||||
shortopts = 'adeclI:u:i:p:k:O:o:G:g:m:t:f:hs:S:'
|
||||
longopts = ['help']
|
||||
action = None
|
||||
user = {}
|
||||
fast_set_obfs = {'0': 'plain',
|
||||
'+1': 'http_simple_compatible',
|
||||
'1': 'http_simple',
|
||||
'+2': 'tls1.2_ticket_auth_compatible',
|
||||
'2': 'tls1.2_ticket_auth'}
|
||||
fast_set_protocol = {'0': 'origin',
|
||||
's4': 'auth_sha1_v4',
|
||||
'+s4': 'auth_sha1_v4_compatible',
|
||||
'am': 'auth_aes128_md5',
|
||||
'as': 'auth_aes128_sha1',
|
||||
'ca': 'auth_chain_a',
|
||||
}
|
||||
fast_set_method = {'0': 'none',
|
||||
'a1c': 'aes-128-cfb',
|
||||
'a2c': 'aes-192-cfb',
|
||||
'a3c': 'aes-256-cfb',
|
||||
'r': 'rc4-md5',
|
||||
'r6': 'rc4-md5-6',
|
||||
'c': 'chacha20',
|
||||
'ci': 'chacha20-ietf',
|
||||
's': 'salsa20',
|
||||
'a1': 'aes-128-ctr',
|
||||
'a2': 'aes-192-ctr',
|
||||
'a3': 'aes-256-ctr'}
|
||||
try:
|
||||
optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
|
||||
for key, value in optlist:
|
||||
if key == '-a':
|
||||
action = 1
|
||||
elif key == '-d':
|
||||
action = 2
|
||||
elif key == '-e':
|
||||
action = 3
|
||||
elif key == '-l':
|
||||
action = 4
|
||||
elif key == '-c':
|
||||
action = 0
|
||||
elif key == '-I':
|
||||
user['id'] = value
|
||||
elif key == '-u':
|
||||
user['user'] = value
|
||||
elif key == '-i':
|
||||
user['muid'] = int(value)
|
||||
elif key == '-p':
|
||||
user['port'] = int(value)
|
||||
elif key == '-k':
|
||||
user['passwd'] = value
|
||||
elif key == '-o':
|
||||
if value in fast_set_obfs:
|
||||
user['obfs'] = fast_set_obfs[value]
|
||||
else:
|
||||
user['obfs'] = value
|
||||
elif key == '-O':
|
||||
if value in fast_set_protocol:
|
||||
user['protocol'] = fast_set_protocol[value]
|
||||
else:
|
||||
user['protocol'] = value
|
||||
elif key == '-g':
|
||||
user['obfs_param'] = value
|
||||
elif key == '-G':
|
||||
user['protocol_param'] = value
|
||||
elif key == '-s':
|
||||
user['speed_limit_per_con'] = int(value)
|
||||
elif key == '-S':
|
||||
user['speed_limit_per_user'] = int(value)
|
||||
elif key == '-m':
|
||||
if value in fast_set_method:
|
||||
user['method'] = fast_set_method[value]
|
||||
else:
|
||||
user['method'] = value
|
||||
elif key == '-f':
|
||||
user['forbidden_port'] = value
|
||||
elif key == '-t':
|
||||
val = float(value)
|
||||
try:
|
||||
val = int(value)
|
||||
except:
|
||||
pass
|
||||
user['transfer_enable'] = int(val * 1024) * (1024 ** 2)
|
||||
elif key in ('-h', '--help'):
|
||||
print_server_help()
|
||||
sys.exit(0)
|
||||
except getopt.GetoptError as e:
|
||||
print(e)
|
||||
sys.exit(2)
|
||||
|
||||
manage = MuMgr()
|
||||
if action == 0:
|
||||
manage.clear_ud(user)
|
||||
elif action == 1:
|
||||
if 'user' not in user and 'id' in user:
|
||||
user['user'] = str(user['id'])
|
||||
if 'user' in user and 'port' in user and 'id' in user:
|
||||
manage.add(user)
|
||||
else:
|
||||
print("You have to set the port with -p")
|
||||
elif action == 2:
|
||||
if 'user' in user or 'port' in user or 'id' in user:
|
||||
manage.delete(user)
|
||||
else:
|
||||
print("You have to set the user name or port with -u/-p")
|
||||
elif action == 3:
|
||||
if 'user' in user or 'port' in user or 'id' in user:
|
||||
manage.edit(user)
|
||||
else:
|
||||
print("You have to set the user name or port with -u/-p")
|
||||
elif action == 4:
|
||||
manage.list_user(user)
|
||||
elif action is None:
|
||||
print_server_help()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -0,0 +1,358 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import traceback
|
||||
from shadowsocks import shell, common
|
||||
from configloader import load_config, get_config
|
||||
import random
|
||||
import getopt
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
|
||||
|
||||
class MuJsonLoader(object):
|
||||
def __init__(self):
|
||||
self.json = None
|
||||
|
||||
def load(self, path):
|
||||
l = "[]"
|
||||
try:
|
||||
with open(path, 'rb+') as f:
|
||||
l = f.read().decode('utf8')
|
||||
except:
|
||||
pass
|
||||
self.json = json.loads(l)
|
||||
|
||||
def save(self, path):
|
||||
if self.json is not None:
|
||||
output = json.dumps(self.json, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
with open(path, 'a'):
|
||||
pass
|
||||
with open(path, 'rb+') as f:
|
||||
f.write(output.encode('utf8'))
|
||||
f.truncate()
|
||||
|
||||
|
||||
class MuMgr(object):
|
||||
def __init__(self):
|
||||
self.config_path = get_config().MUDB_FILE
|
||||
try:
|
||||
self.server_addr = get_config().SERVER_PUB_ADDR
|
||||
except:
|
||||
self.server_addr = '127.0.0.1'
|
||||
self.data = MuJsonLoader()
|
||||
|
||||
if self.server_addr == '127.0.0.1':
|
||||
self.server_addr = self.getipaddr()
|
||||
|
||||
def getipaddr(self, ifname='eth0'):
|
||||
import socket
|
||||
import struct
|
||||
ret = '127.0.0.1'
|
||||
try:
|
||||
ret = socket.gethostbyname(socket.getfqdn(socket.gethostname()))
|
||||
except:
|
||||
pass
|
||||
if ret == '127.0.0.1':
|
||||
try:
|
||||
import fcntl
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
ret = socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
|
||||
except:
|
||||
pass
|
||||
return ret
|
||||
|
||||
def ssrlink(self, user, encode, muid):
|
||||
protocol = user.get('protocol', '')
|
||||
obfs = user.get('obfs', '')
|
||||
protocol = protocol.replace("_compatible", "")
|
||||
obfs = obfs.replace("_compatible", "")
|
||||
protocol_param = ''
|
||||
if muid is not None:
|
||||
protocol_param_ = user.get('protocol_param', '')
|
||||
param = protocol_param_.split('#')
|
||||
if len(param) == 2:
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
param = str(muid) + ':' + row['passwd']
|
||||
protocol_param = '/?protoparam=' + common.to_str(base64.urlsafe_b64encode(common.to_bytes(param))).replace("=", "")
|
||||
break
|
||||
link = ("%s:%s:%s:%s:%s:%s" % (self.server_addr, user['port'], protocol, user['method'], obfs, common.to_str(base64.urlsafe_b64encode(common.to_bytes(user['passwd']))).replace("=", ""))) + protocol_param
|
||||
return "ssr://" + (encode and common.to_str(base64.urlsafe_b64encode(common.to_bytes(link))).replace("=", "") or link)
|
||||
|
||||
def userinfo(self, user, muid = None):
|
||||
ret = ""
|
||||
key_list = ['user', 'port', 'method', 'passwd', 'protocol', 'protocol_param', 'obfs', 'obfs_param', 'transfer_enable', 'u', 'd']
|
||||
for key in sorted(user):
|
||||
if key not in key_list:
|
||||
key_list.append(key)
|
||||
for key in key_list:
|
||||
if key in ['enable'] or key not in user:
|
||||
continue
|
||||
ret += '\n'
|
||||
if (muid is not None) and (key in ['protocol_param']):
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
ret += " %s : %s" % (key, str(muid) + ':' + row['passwd'])
|
||||
break
|
||||
elif key in ['transfer_enable', 'u', 'd']:
|
||||
if muid is not None:
|
||||
for row in self.data.json:
|
||||
if int(row['port']) == muid:
|
||||
val = row[key]
|
||||
break
|
||||
else:
|
||||
val = user[key]
|
||||
if val / 1024 < 4:
|
||||
ret += " %s : %s" % (key, val)
|
||||
elif val / 1024 ** 2 < 4:
|
||||
val /= float(1024)
|
||||
ret += " %s : %s K Bytes" % (key, val)
|
||||
elif val / 1024 ** 3 < 4:
|
||||
val /= float(1024 ** 2)
|
||||
ret += " %s : %s M Bytes" % (key, val)
|
||||
else:
|
||||
val /= float(1024 ** 3)
|
||||
ret += " %s : %s G Bytes" % (key, val)
|
||||
else:
|
||||
ret += " %s : %s" % (key, user[key])
|
||||
ret += "\n " + self.ssrlink(user, False, muid)
|
||||
ret += "\n " + self.ssrlink(user, True, muid)
|
||||
return ret
|
||||
|
||||
def rand_pass(self):
|
||||
return ''.join([random.choice('''ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~-_=+(){}[]^&%$@''') for i in range(8)])
|
||||
|
||||
def add(self, user):
|
||||
up = {'enable': 1, 'u': 0, 'd': 0, 'method': "aes-128-ctr",
|
||||
'protocol': "auth_aes128_md5",
|
||||
'obfs': "tls1.2_ticket_auth_compatible",
|
||||
'transfer_enable': 9007199254740992}
|
||||
up['passwd'] = self.rand_pass()
|
||||
up.update(user)
|
||||
|
||||
self.data.load(self.config_path)
|
||||
for row in self.data.json:
|
||||
match = False
|
||||
if 'user' in user and row['user'] == user['user']:
|
||||
match = True
|
||||
if 'port' in user and row['port'] == user['port']:
|
||||
match = True
|
||||
if match:
|
||||
print("user [%s] port [%s] already exist" % (row['user'], row['port']))
|
||||
return
|
||||
self.data.json.append(up)
|
||||
print("### add user info %s" % self.userinfo(up))
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def edit(self, user):
|
||||
self.data.load(self.config_path)
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'user' in user and row['user'] != user['user']:
|
||||
match = False
|
||||
if 'port' in user and row['port'] != user['port']:
|
||||
match = False
|
||||
if match:
|
||||
print("edit user [%s]" % (row['user'],))
|
||||
row.update(user)
|
||||
print("### new user info %s" % self.userinfo(row))
|
||||
break
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def delete(self, user):
|
||||
self.data.load(self.config_path)
|
||||
index = 0
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'user' in user and row['user'] != user['user']:
|
||||
match = False
|
||||
if 'port' in user and row['port'] != user['port']:
|
||||
match = False
|
||||
if match:
|
||||
print("delete user [%s]" % row['user'])
|
||||
del self.data.json[index]
|
||||
break
|
||||
index += 1
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def clear_ud(self, user):
|
||||
up = {'u': 0, 'd': 0}
|
||||
self.data.load(self.config_path)
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'user' in user and row['user'] != user['user']:
|
||||
match = False
|
||||
if 'port' in user and row['port'] != user['port']:
|
||||
match = False
|
||||
if match:
|
||||
row.update(up)
|
||||
print("clear user [%s]" % row['user'])
|
||||
self.data.save(self.config_path)
|
||||
|
||||
def list_user(self, user):
|
||||
self.data.load(self.config_path)
|
||||
if not user:
|
||||
for row in self.data.json:
|
||||
print("user [%s] port %s" % (row['user'], row['port']))
|
||||
return
|
||||
for row in self.data.json:
|
||||
match = True
|
||||
if 'user' in user and row['user'] != user['user']:
|
||||
match = False
|
||||
if 'port' in user and row['port'] != user['port']:
|
||||
match = False
|
||||
if match:
|
||||
muid = None
|
||||
if 'muid' in user:
|
||||
muid = user['muid']
|
||||
print("### user [%s] info %s" % (row['user'], self.userinfo(row, muid)))
|
||||
|
||||
|
||||
def print_server_help():
|
||||
print('''usage: python mujson_manage.py -a|-d|-e|-c|-l [OPTION]...
|
||||
|
||||
Actions:
|
||||
-a add/edit a user
|
||||
-d delete a user
|
||||
-e edit a user
|
||||
-c set u&d to zero
|
||||
-l display a user infomation or all users infomation
|
||||
|
||||
Options:
|
||||
-u USER the user name
|
||||
-p PORT server port (only this option must be set if add a user)
|
||||
-k PASSWORD password
|
||||
-m METHOD encryption method, default: aes-128-ctr
|
||||
-O PROTOCOL protocol plugin, default: auth_aes128_md5
|
||||
-o OBFS obfs plugin, default: tls1.2_ticket_auth_compatible
|
||||
-G PROTOCOL_PARAM protocol plugin param
|
||||
-g OBFS_PARAM obfs plugin param
|
||||
-t TRANSFER max transfer for G bytes, default: 8388608 (8 PB or 8192 TB)
|
||||
-f FORBID set forbidden ports. Example (ban 1~79 and 81~100): -f "1-79,81-100"
|
||||
-i MUID set sub id to display (only work with -l)
|
||||
-s SPEED set speed_limit_per_con
|
||||
-S SPEED set speed_limit_per_user
|
||||
|
||||
General options:
|
||||
-h, --help show this help message and exit
|
||||
''')
|
||||
|
||||
|
||||
def main():
|
||||
shortopts = 'adeclu:i:p:k:O:o:G:g:m:t:f:hs:S:'
|
||||
longopts = ['help']
|
||||
action = None
|
||||
user = {}
|
||||
fast_set_obfs = {'0': 'plain',
|
||||
'+1': 'http_simple_compatible',
|
||||
'1': 'http_simple',
|
||||
'+2': 'tls1.2_ticket_auth_compatible',
|
||||
'2': 'tls1.2_ticket_auth'}
|
||||
fast_set_protocol = {'0': 'origin',
|
||||
's4': 'auth_sha1_v4',
|
||||
'+s4': 'auth_sha1_v4_compatible',
|
||||
'am': 'auth_aes128_md5',
|
||||
'as': 'auth_aes128_sha1',
|
||||
'ca': 'auth_chain_a',
|
||||
}
|
||||
fast_set_method = {'0': 'none',
|
||||
'a1c': 'aes-128-cfb',
|
||||
'a2c': 'aes-192-cfb',
|
||||
'a3c': 'aes-256-cfb',
|
||||
'r': 'rc4-md5',
|
||||
'r6': 'rc4-md5-6',
|
||||
'c': 'chacha20',
|
||||
'ci': 'chacha20-ietf',
|
||||
's': 'salsa20',
|
||||
'a1': 'aes-128-ctr',
|
||||
'a2': 'aes-192-ctr',
|
||||
'a3': 'aes-256-ctr'}
|
||||
try:
|
||||
optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
|
||||
for key, value in optlist:
|
||||
if key == '-a':
|
||||
action = 1
|
||||
elif key == '-d':
|
||||
action = 2
|
||||
elif key == '-e':
|
||||
action = 3
|
||||
elif key == '-l':
|
||||
action = 4
|
||||
elif key == '-c':
|
||||
action = 0
|
||||
elif key == '-u':
|
||||
user['user'] = value
|
||||
elif key == '-i':
|
||||
user['muid'] = int(value)
|
||||
elif key == '-p':
|
||||
user['port'] = int(value)
|
||||
elif key == '-k':
|
||||
user['passwd'] = value
|
||||
elif key == '-o':
|
||||
if value in fast_set_obfs:
|
||||
user['obfs'] = fast_set_obfs[value]
|
||||
else:
|
||||
user['obfs'] = value
|
||||
elif key == '-O':
|
||||
if value in fast_set_protocol:
|
||||
user['protocol'] = fast_set_protocol[value]
|
||||
else:
|
||||
user['protocol'] = value
|
||||
elif key == '-g':
|
||||
user['obfs_param'] = value
|
||||
elif key == '-G':
|
||||
user['protocol_param'] = value
|
||||
elif key == '-s':
|
||||
user['speed_limit_per_con'] = int(value)
|
||||
elif key == '-S':
|
||||
user['speed_limit_per_user'] = int(value)
|
||||
elif key == '-m':
|
||||
if value in fast_set_method:
|
||||
user['method'] = fast_set_method[value]
|
||||
else:
|
||||
user['method'] = value
|
||||
elif key == '-f':
|
||||
user['forbidden_port'] = value
|
||||
elif key == '-t':
|
||||
val = float(value)
|
||||
try:
|
||||
val = int(value)
|
||||
except:
|
||||
pass
|
||||
user['transfer_enable'] = int(val * 1024) * (1024 ** 2)
|
||||
elif key in ('-h', '--help'):
|
||||
print_server_help()
|
||||
sys.exit(0)
|
||||
except getopt.GetoptError as e:
|
||||
print(e)
|
||||
sys.exit(2)
|
||||
|
||||
manage = MuMgr()
|
||||
if action == 0:
|
||||
manage.clear_ud(user)
|
||||
elif action == 1:
|
||||
if 'user' not in user and 'port' in user:
|
||||
user['user'] = str(user['port'])
|
||||
if 'user' in user and 'port' in user:
|
||||
manage.add(user)
|
||||
else:
|
||||
print("You have to set the port with -p")
|
||||
elif action == 2:
|
||||
if 'user' in user or 'port' in user:
|
||||
manage.delete(user)
|
||||
else:
|
||||
print("You have to set the user name or port with -u/-p")
|
||||
elif action == 3:
|
||||
if 'user' in user or 'port' in user:
|
||||
manage.edit(user)
|
||||
else:
|
||||
print("You have to set the user name or port with -u/-p")
|
||||
elif action == 4:
|
||||
manage.list_user(user)
|
||||
elif action is None:
|
||||
print_server_help()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -0,0 +1,13 @@
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"port": 3306,
|
||||
"user": "ss",
|
||||
"password": "pass",
|
||||
"db": "sspanel",
|
||||
"node_id": 0,
|
||||
"transfer_mul": 1.0,
|
||||
"ssl_enable": 0,
|
||||
"ssl_ca": "",
|
||||
"ssl_cert": "",
|
||||
"ssl_key": ""
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
cd `dirname $0`
|
||||
#python_ver=$(ls /usr/bin|grep -e "^python[23]\.[1-9]\+$"|tail -1)
|
||||
eval $(ps -ef | grep "[0-9] python server\\.py m" | awk '{print "kill "$2}')
|
||||
ulimit -n 512000
|
||||
nohup python server.py m>> /dev/null 2>&1 &
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2015 breakwall
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
import sys
|
||||
import threading
|
||||
import os
|
||||
|
||||
if __name__ == '__main__':
|
||||
import inspect
|
||||
os.chdir(os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe()))))
|
||||
|
||||
import server_pool
|
||||
import db_transfer
|
||||
from shadowsocks import shell
|
||||
from configloader import load_config, get_config
|
||||
|
||||
class MainThread(threading.Thread):
|
||||
def __init__(self, obj):
|
||||
super(MainThread, self).__init__()
|
||||
self.daemon = True
|
||||
self.obj = obj
|
||||
|
||||
def run(self):
|
||||
self.obj.thread_db(self.obj)
|
||||
|
||||
def stop(self):
|
||||
self.obj.thread_db_stop()
|
||||
|
||||
def main():
|
||||
shell.check_python()
|
||||
if False:
|
||||
db_transfer.DbTransfer.thread_db()
|
||||
else:
|
||||
if get_config().API_INTERFACE == 'mudbjson':
|
||||
thread = MainThread(db_transfer.MuJsonTransfer)
|
||||
elif get_config().API_INTERFACE == 'sspanelv2':
|
||||
thread = MainThread(db_transfer.DbTransfer)
|
||||
else:
|
||||
thread = MainThread(db_transfer.Dbv3Transfer)
|
||||
thread.start()
|
||||
try:
|
||||
while thread.is_alive():
|
||||
thread.join(10.0)
|
||||
except (KeyboardInterrupt, IOError, OSError) as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
thread.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import logging
|
||||
import struct
|
||||
import time
|
||||
from shadowsocks import shell, eventloop, tcprelay, udprelay, asyncdns, common
|
||||
import threading
|
||||
import sys
|
||||
import traceback
|
||||
from socket import *
|
||||
from configloader import load_config, get_config
|
||||
|
||||
class MainThread(threading.Thread):
|
||||
def __init__(self, params):
|
||||
super(MainThread, self).__init__()
|
||||
self.params = params
|
||||
|
||||
def run(self):
|
||||
ServerPool._loop(*self.params)
|
||||
|
||||
class ServerPool(object):
|
||||
|
||||
instance = None
|
||||
|
||||
def __init__(self):
|
||||
shell.check_python()
|
||||
self.config = shell.get_config(False)
|
||||
self.dns_resolver = asyncdns.DNSResolver()
|
||||
if not self.config.get('dns_ipv6', False):
|
||||
asyncdns.IPV6_CONNECTION_SUPPORT = False
|
||||
|
||||
self.mgr = None #asyncmgr.ServerMgr()
|
||||
|
||||
self.tcp_servers_pool = {}
|
||||
self.tcp_ipv6_servers_pool = {}
|
||||
self.udp_servers_pool = {}
|
||||
self.udp_ipv6_servers_pool = {}
|
||||
self.stat_counter = {}
|
||||
|
||||
self.loop = eventloop.EventLoop()
|
||||
self.thread = MainThread( (self.loop, self.dns_resolver, self.mgr) )
|
||||
self.thread.start()
|
||||
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
if ServerPool.instance is None:
|
||||
ServerPool.instance = ServerPool()
|
||||
return ServerPool.instance
|
||||
|
||||
def stop(self):
|
||||
self.loop.stop()
|
||||
|
||||
@staticmethod
|
||||
def _loop(loop, dns_resolver, mgr):
|
||||
try:
|
||||
if mgr is not None:
|
||||
mgr.add_to_loop(loop)
|
||||
dns_resolver.add_to_loop(loop)
|
||||
loop.run()
|
||||
except (KeyboardInterrupt, IOError, OSError) as e:
|
||||
logging.error(e)
|
||||
traceback.print_exc()
|
||||
os.exit(0)
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
traceback.print_exc()
|
||||
|
||||
def server_is_run(self, port):
|
||||
port = int(port)
|
||||
ret = 0
|
||||
if port in self.tcp_servers_pool:
|
||||
ret = 1
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
ret |= 2
|
||||
return ret
|
||||
|
||||
def server_run_status(self, port):
|
||||
if 'server' in self.config:
|
||||
if port not in self.tcp_servers_pool:
|
||||
return False
|
||||
if 'server_ipv6' in self.config:
|
||||
if port not in self.tcp_ipv6_servers_pool:
|
||||
return False
|
||||
return True
|
||||
|
||||
def new_server(self, port, user_config):
|
||||
ret = True
|
||||
port = int(port)
|
||||
ipv6_ok = False
|
||||
|
||||
if 'server_ipv6' in self.config:
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
logging.info("server already at %s:%d" % (self.config['server_ipv6'], port))
|
||||
return 'this port server is already running'
|
||||
else:
|
||||
a_config = self.config.copy()
|
||||
a_config.update(user_config)
|
||||
if len(a_config['server_ipv6']) > 2 and a_config['server_ipv6'][0] == "[" and a_config['server_ipv6'][-1] == "]":
|
||||
a_config['server_ipv6'] = a_config['server_ipv6'][1:-1]
|
||||
a_config['server'] = a_config['server_ipv6']
|
||||
a_config['server_port'] = port
|
||||
a_config['max_connect'] = 128
|
||||
a_config['method'] = common.to_str(a_config['method'])
|
||||
try:
|
||||
logging.info("starting server at [%s]:%d" % (common.to_str(a_config['server']), port))
|
||||
|
||||
tcp_server = tcprelay.TCPRelay(a_config, self.dns_resolver, False, stat_counter=self.stat_counter)
|
||||
tcp_server.add_to_loop(self.loop)
|
||||
self.tcp_ipv6_servers_pool.update({port: tcp_server})
|
||||
|
||||
udp_server = udprelay.UDPRelay(a_config, self.dns_resolver, False, stat_counter=self.stat_counter)
|
||||
udp_server.add_to_loop(self.loop)
|
||||
self.udp_ipv6_servers_pool.update({port: udp_server})
|
||||
|
||||
if common.to_str(a_config['server_ipv6']) == "::":
|
||||
ipv6_ok = True
|
||||
except Exception as e:
|
||||
logging.warn("IPV6 %s " % (e,))
|
||||
|
||||
if 'server' in self.config:
|
||||
if port in self.tcp_servers_pool:
|
||||
logging.info("server already at %s:%d" % (common.to_str(self.config['server']), port))
|
||||
return 'this port server is already running'
|
||||
else:
|
||||
a_config = self.config.copy()
|
||||
a_config.update(user_config)
|
||||
a_config['server_port'] = port
|
||||
a_config['max_connect'] = 128
|
||||
a_config['method'] = common.to_str(a_config['method'])
|
||||
try:
|
||||
logging.info("starting server at %s:%d" % (common.to_str(a_config['server']), port))
|
||||
|
||||
tcp_server = tcprelay.TCPRelay(a_config, self.dns_resolver, False)
|
||||
tcp_server.add_to_loop(self.loop)
|
||||
self.tcp_servers_pool.update({port: tcp_server})
|
||||
|
||||
udp_server = udprelay.UDPRelay(a_config, self.dns_resolver, False)
|
||||
udp_server.add_to_loop(self.loop)
|
||||
self.udp_servers_pool.update({port: udp_server})
|
||||
|
||||
except Exception as e:
|
||||
if not ipv6_ok:
|
||||
logging.warn("IPV4 %s " % (e,))
|
||||
|
||||
return True
|
||||
|
||||
def del_server(self, port):
|
||||
port = int(port)
|
||||
logging.info("del server at %d" % port)
|
||||
try:
|
||||
udpsock = socket(AF_INET, SOCK_DGRAM)
|
||||
udpsock.sendto('%s:%s:0:0' % (get_config().MANAGE_PASS, port), (get_config().MANAGE_BIND_IP, get_config().MANAGE_PORT))
|
||||
udpsock.close()
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
return True
|
||||
|
||||
def cb_del_server(self, port):
|
||||
port = int(port)
|
||||
|
||||
if port not in self.tcp_servers_pool:
|
||||
logging.info("stopped server at %s:%d already stop" % (self.config['server'], port))
|
||||
else:
|
||||
logging.info("stopped server at %s:%d" % (self.config['server'], port))
|
||||
try:
|
||||
self.tcp_servers_pool[port].close(True)
|
||||
del self.tcp_servers_pool[port]
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
try:
|
||||
self.udp_servers_pool[port].close(True)
|
||||
del self.udp_servers_pool[port]
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
|
||||
if 'server_ipv6' in self.config:
|
||||
if port not in self.tcp_ipv6_servers_pool:
|
||||
logging.info("stopped server at [%s]:%d already stop" % (self.config['server_ipv6'], port))
|
||||
else:
|
||||
logging.info("stopped server at [%s]:%d" % (self.config['server_ipv6'], port))
|
||||
try:
|
||||
self.tcp_ipv6_servers_pool[port].close(True)
|
||||
del self.tcp_ipv6_servers_pool[port]
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
try:
|
||||
self.udp_ipv6_servers_pool[port].close(True)
|
||||
del self.udp_ipv6_servers_pool[port]
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
|
||||
return True
|
||||
|
||||
def update_mu_users(self, port, users):
|
||||
port = int(port)
|
||||
if port in self.tcp_servers_pool:
|
||||
try:
|
||||
self.tcp_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
try:
|
||||
self.udp_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
try:
|
||||
self.tcp_ipv6_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
try:
|
||||
self.udp_ipv6_servers_pool[port].update_users(users)
|
||||
except Exception as e:
|
||||
logging.warn(e)
|
||||
|
||||
def get_server_transfer(self, port):
|
||||
port = int(port)
|
||||
uid = struct.pack('<I', port)
|
||||
ret = [0, 0]
|
||||
if port in self.tcp_servers_pool:
|
||||
ret[0], ret[1] = self.tcp_servers_pool[port].get_ud()
|
||||
if port in self.udp_servers_pool:
|
||||
u, d = self.udp_servers_pool[port].get_ud()
|
||||
ret[0] += u
|
||||
ret[1] += d
|
||||
if port in self.tcp_ipv6_servers_pool:
|
||||
u, d = self.tcp_ipv6_servers_pool[port].get_ud()
|
||||
ret[0] += u
|
||||
ret[1] += d
|
||||
if port in self.udp_ipv6_servers_pool:
|
||||
u, d = self.udp_ipv6_servers_pool[port].get_ud()
|
||||
ret[0] += u
|
||||
ret[1] += d
|
||||
return ret
|
||||
|
||||
def get_server_mu_transfer(self, server):
|
||||
return server.get_users_ud()
|
||||
|
||||
def update_mu_transfer(self, user_dict, u, d):
|
||||
for uid in u:
|
||||
port = struct.unpack('<I', uid)[0]
|
||||
if port not in user_dict:
|
||||
user_dict[port] = [0, 0]
|
||||
user_dict[port][0] += u[uid]
|
||||
for uid in d:
|
||||
port = struct.unpack('<I', uid)[0]
|
||||
if port not in user_dict:
|
||||
user_dict[port] = [0, 0]
|
||||
user_dict[port][1] += d[uid]
|
||||
|
||||
def get_servers_transfer(self):
|
||||
servers = self.tcp_servers_pool.copy()
|
||||
servers.update(self.tcp_ipv6_servers_pool)
|
||||
servers.update(self.udp_servers_pool)
|
||||
servers.update(self.udp_ipv6_servers_pool)
|
||||
ret = {}
|
||||
for port in servers.keys():
|
||||
ret[port] = self.get_server_transfer(port)
|
||||
for port in self.tcp_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.tcp_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
for port in self.tcp_ipv6_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.tcp_ipv6_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
for port in self.udp_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.udp_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
for port in self.udp_ipv6_servers_pool:
|
||||
u, d = self.get_server_mu_transfer(self.udp_ipv6_servers_pool[port])
|
||||
self.update_mu_transfer(ret, u, d)
|
||||
return ret
|
||||
|
||||
Binary file not shown.
@ -0,0 +1,39 @@
|
||||
import codecs
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
with codecs.open('README.rst', encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="shadowsocks",
|
||||
version="2.6.12",
|
||||
license='http://www.apache.org/licenses/LICENSE-2.0',
|
||||
description="A fast tunnel proxy that help you get through firewalls",
|
||||
author='clowwindy',
|
||||
author_email='clowwindy42@gmail.com',
|
||||
url='https://github.com/shadowsocks/shadowsocks',
|
||||
packages=['shadowsocks', 'shadowsocks.crypto', 'shadowsocks.obfsplugin'],
|
||||
package_data={
|
||||
'shadowsocks': ['README.rst', 'LICENSE']
|
||||
},
|
||||
install_requires=[],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
sslocal = shadowsocks.local:main
|
||||
ssserver = shadowsocks.server:main
|
||||
""",
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
'Topic :: Internet :: Proxy Servers',
|
||||
],
|
||||
long_description=long_description,
|
||||
)
|
||||
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
rm -rf CyMySQL
|
||||
rm -rf cymysql
|
||||
git clone https://github.com/nakagami/CyMySQL.git
|
||||
mv CyMySQL/cymysql ./
|
||||
rm -rf CyMySQL
|
||||
@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd /usr/share/ssr_python_pro_server
|
||||
user_total=$(./mujson_mgr.py -l | wc -l)
|
||||
[ $user_total -eq 0 ] && echo -e "没有发现用户,请检查 !" && exit 1
|
||||
for i in `seq 1 $user_total`
|
||||
do
|
||||
user_id=$(./mujson_mgr.py -l | sed -n ${i}p | awk '{print $2}')
|
||||
match_clear=$(./mujson_mgr.py -c -I "${user_id}" | grep 'clear')
|
||||
if [ -z "$match_clear" ]; then
|
||||
echo -e "$user_id已使用流量清零失败"
|
||||
else
|
||||
echo -e "$user_id已使用流量清零成功"
|
||||
fi
|
||||
done
|
||||
exit
|
||||
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2012-2015 clowwindy
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
Binary file not shown.
@ -0,0 +1,555 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014-2015 clowwindy
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import re
|
||||
import logging
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import inspect
|
||||
file_path = os.path.dirname(os.path.realpath(inspect.getfile(inspect.currentframe())))
|
||||
sys.path.insert(0, os.path.join(file_path, '../'))
|
||||
|
||||
from shadowsocks import common, lru_cache, eventloop, shell
|
||||
|
||||
|
||||
CACHE_SWEEP_INTERVAL = 30
|
||||
|
||||
VALID_HOSTNAME = re.compile(br"(?!-)[A-Z\d_-]{1,63}(?<!-)$", re.IGNORECASE)
|
||||
|
||||
common.patch_socket()
|
||||
|
||||
# rfc1035
|
||||
# format
|
||||
# +---------------------+
|
||||
# | Header |
|
||||
# +---------------------+
|
||||
# | Question | the question for the name server
|
||||
# +---------------------+
|
||||
# | Answer | RRs answering the question
|
||||
# +---------------------+
|
||||
# | Authority | RRs pointing toward an authority
|
||||
# +---------------------+
|
||||
# | Additional | RRs holding additional information
|
||||
# +---------------------+
|
||||
#
|
||||
# header
|
||||
# 1 1 1 1 1 1
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | ID |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# |QR| Opcode |AA|TC|RD|RA| Z | RCODE |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | QDCOUNT |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | ANCOUNT |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | NSCOUNT |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | ARCOUNT |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
|
||||
QTYPE_ANY = 255
|
||||
QTYPE_A = 1
|
||||
QTYPE_AAAA = 28
|
||||
QTYPE_CNAME = 5
|
||||
QTYPE_NS = 2
|
||||
QCLASS_IN = 1
|
||||
|
||||
def detect_ipv6_supprot():
|
||||
if 'has_ipv6' in dir(socket):
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
s.connect(('::1', 0))
|
||||
print('IPv6 support')
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
print('IPv6 not support')
|
||||
return False
|
||||
|
||||
IPV6_CONNECTION_SUPPORT = detect_ipv6_supprot()
|
||||
|
||||
def build_address(address):
|
||||
address = address.strip(b'.')
|
||||
labels = address.split(b'.')
|
||||
results = []
|
||||
for label in labels:
|
||||
l = len(label)
|
||||
if l > 63:
|
||||
return None
|
||||
results.append(common.chr(l))
|
||||
results.append(label)
|
||||
results.append(b'\0')
|
||||
return b''.join(results)
|
||||
|
||||
|
||||
def build_request(address, qtype):
|
||||
request_id = os.urandom(2)
|
||||
header = struct.pack('!BBHHHH', 1, 0, 1, 0, 0, 0)
|
||||
addr = build_address(address)
|
||||
qtype_qclass = struct.pack('!HH', qtype, QCLASS_IN)
|
||||
return request_id + header + addr + qtype_qclass
|
||||
|
||||
|
||||
def parse_ip(addrtype, data, length, offset):
|
||||
if addrtype == QTYPE_A:
|
||||
return socket.inet_ntop(socket.AF_INET, data[offset:offset + length])
|
||||
elif addrtype == QTYPE_AAAA:
|
||||
return socket.inet_ntop(socket.AF_INET6, data[offset:offset + length])
|
||||
elif addrtype in [QTYPE_CNAME, QTYPE_NS]:
|
||||
return parse_name(data, offset)[1]
|
||||
else:
|
||||
return data[offset:offset + length]
|
||||
|
||||
|
||||
def parse_name(data, offset):
|
||||
p = offset
|
||||
labels = []
|
||||
l = common.ord(data[p])
|
||||
while l > 0:
|
||||
if (l & (128 + 64)) == (128 + 64):
|
||||
# pointer
|
||||
pointer = struct.unpack('!H', data[p:p + 2])[0]
|
||||
pointer &= 0x3FFF
|
||||
r = parse_name(data, pointer)
|
||||
labels.append(r[1])
|
||||
p += 2
|
||||
# pointer is the end
|
||||
return p - offset, b'.'.join(labels)
|
||||
else:
|
||||
labels.append(data[p + 1:p + 1 + l])
|
||||
p += 1 + l
|
||||
l = common.ord(data[p])
|
||||
return p - offset + 1, b'.'.join(labels)
|
||||
|
||||
|
||||
# rfc1035
|
||||
# record
|
||||
# 1 1 1 1 1 1
|
||||
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | |
|
||||
# / /
|
||||
# / NAME /
|
||||
# | |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | TYPE |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | CLASS |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | TTL |
|
||||
# | |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
# | RDLENGTH |
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
|
||||
# / RDATA /
|
||||
# / /
|
||||
# +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|
||||
def parse_record(data, offset, question=False):
|
||||
nlen, name = parse_name(data, offset)
|
||||
if not question:
|
||||
record_type, record_class, record_ttl, record_rdlength = struct.unpack(
|
||||
'!HHiH', data[offset + nlen:offset + nlen + 10]
|
||||
)
|
||||
ip = parse_ip(record_type, data, record_rdlength, offset + nlen + 10)
|
||||
return nlen + 10 + record_rdlength, \
|
||||
(name, ip, record_type, record_class, record_ttl)
|
||||
else:
|
||||
record_type, record_class = struct.unpack(
|
||||
'!HH', data[offset + nlen:offset + nlen + 4]
|
||||
)
|
||||
return nlen + 4, (name, None, record_type, record_class, None, None)
|
||||
|
||||
|
||||
def parse_header(data):
|
||||
if len(data) >= 12:
|
||||
header = struct.unpack('!HBBHHHH', data[:12])
|
||||
res_id = header[0]
|
||||
res_qr = header[1] & 128
|
||||
res_tc = header[1] & 2
|
||||
res_ra = header[2] & 128
|
||||
res_rcode = header[2] & 15
|
||||
# assert res_tc == 0
|
||||
# assert res_rcode in [0, 3]
|
||||
res_qdcount = header[3]
|
||||
res_ancount = header[4]
|
||||
res_nscount = header[5]
|
||||
res_arcount = header[6]
|
||||
return (res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount,
|
||||
res_ancount, res_nscount, res_arcount)
|
||||
return None
|
||||
|
||||
|
||||
def parse_response(data):
|
||||
try:
|
||||
if len(data) >= 12:
|
||||
header = parse_header(data)
|
||||
if not header:
|
||||
return None
|
||||
res_id, res_qr, res_tc, res_ra, res_rcode, res_qdcount, \
|
||||
res_ancount, res_nscount, res_arcount = header
|
||||
|
||||
qds = []
|
||||
ans = []
|
||||
offset = 12
|
||||
for i in range(0, res_qdcount):
|
||||
l, r = parse_record(data, offset, True)
|
||||
offset += l
|
||||
if r:
|
||||
qds.append(r)
|
||||
for i in range(0, res_ancount):
|
||||
l, r = parse_record(data, offset)
|
||||
offset += l
|
||||
if r:
|
||||
ans.append(r)
|
||||
for i in range(0, res_nscount):
|
||||
l, r = parse_record(data, offset)
|
||||
offset += l
|
||||
for i in range(0, res_arcount):
|
||||
l, r = parse_record(data, offset)
|
||||
offset += l
|
||||
response = DNSResponse()
|
||||
if qds:
|
||||
response.hostname = qds[0][0]
|
||||
for an in qds:
|
||||
response.questions.append((an[1], an[2], an[3]))
|
||||
for an in ans:
|
||||
response.answers.append((an[1], an[2], an[3]))
|
||||
return response
|
||||
except Exception as e:
|
||||
shell.print_exception(e)
|
||||
return None
|
||||
|
||||
|
||||
def is_valid_hostname(hostname):
|
||||
if len(hostname) > 255:
|
||||
return False
|
||||
if hostname[-1] == b'.':
|
||||
hostname = hostname[:-1]
|
||||
return all(VALID_HOSTNAME.match(x) for x in hostname.split(b'.'))
|
||||
|
||||
|
||||
class DNSResponse(object):
|
||||
def __init__(self):
|
||||
self.hostname = None
|
||||
self.questions = [] # each: (addr, type, class)
|
||||
self.answers = [] # each: (addr, type, class)
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s' % (self.hostname, str(self.answers))
|
||||
|
||||
|
||||
STATUS_IPV4 = 0
|
||||
STATUS_IPV6 = 1
|
||||
|
||||
|
||||
class DNSResolver(object):
|
||||
|
||||
def __init__(self):
|
||||
self._loop = None
|
||||
self._hosts = {}
|
||||
self._hostname_status = {}
|
||||
self._hostname_to_cb = {}
|
||||
self._cb_to_hostname = {}
|
||||
self._cache = lru_cache.LRUCache(timeout=300)
|
||||
self._sock = None
|
||||
self._servers = None
|
||||
self._parse_resolv()
|
||||
self._parse_hosts()
|
||||
# TODO monitor hosts change and reload hosts
|
||||
# TODO parse /etc/gai.conf and follow its rules
|
||||
|
||||
def _parse_resolv(self):
|
||||
self._servers = []
|
||||
try:
|
||||
with open('dns.conf', 'rb') as f:
|
||||
content = f.readlines()
|
||||
for line in content:
|
||||
line = line.strip()
|
||||
if line:
|
||||
parts = line.split(b' ', 1)
|
||||
if len(parts) >= 2:
|
||||
server = parts[0]
|
||||
port = int(parts[1])
|
||||
else:
|
||||
server = parts[0]
|
||||
port = 53
|
||||
if common.is_ip(server) == socket.AF_INET:
|
||||
if type(server) != str:
|
||||
server = server.decode('utf8')
|
||||
self._servers.append((server, port))
|
||||
except IOError:
|
||||
pass
|
||||
if not self._servers:
|
||||
try:
|
||||
with open('/etc/resolv.conf', 'rb') as f:
|
||||
content = f.readlines()
|
||||
for line in content:
|
||||
line = line.strip()
|
||||
if line:
|
||||
if line.startswith(b'nameserver'):
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
server = parts[1]
|
||||
if common.is_ip(server) == socket.AF_INET:
|
||||
if type(server) != str:
|
||||
server = server.decode('utf8')
|
||||
self._servers.append((server, 53))
|
||||
except IOError:
|
||||
pass
|
||||
if not self._servers:
|
||||
self._servers = [('8.8.4.4', 53), ('8.8.8.8', 53)]
|
||||
logging.info('dns server: %s' % (self._servers,))
|
||||
|
||||
def _parse_hosts(self):
|
||||
etc_path = '/etc/hosts'
|
||||
if 'WINDIR' in os.environ:
|
||||
etc_path = os.environ['WINDIR'] + '/system32/drivers/etc/hosts'
|
||||
try:
|
||||
with open(etc_path, 'rb') as f:
|
||||
for line in f.readlines():
|
||||
line = line.strip()
|
||||
if b"#" in line:
|
||||
line = line[:line.find(b'#')]
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
ip = parts[0]
|
||||
if common.is_ip(ip):
|
||||
for i in range(1, len(parts)):
|
||||
hostname = parts[i]
|
||||
if hostname:
|
||||
self._hosts[hostname] = ip
|
||||
except IOError:
|
||||
self._hosts['localhost'] = '127.0.0.1'
|
||||
|
||||
def add_to_loop(self, loop):
|
||||
if self._loop:
|
||||
raise Exception('already add to loop')
|
||||
self._loop = loop
|
||||
# TODO when dns server is IPv6
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
||||
socket.SOL_UDP)
|
||||
self._sock.setblocking(False)
|
||||
loop.add(self._sock, eventloop.POLL_IN, self)
|
||||
loop.add_periodic(self.handle_periodic)
|
||||
|
||||
def _call_callback(self, hostname, ip, error=None):
|
||||
callbacks = self._hostname_to_cb.get(hostname, [])
|
||||
for callback in callbacks:
|
||||
if callback in self._cb_to_hostname:
|
||||
del self._cb_to_hostname[callback]
|
||||
if ip or error:
|
||||
callback((hostname, ip), error)
|
||||
else:
|
||||
callback((hostname, None),
|
||||
Exception('unable to parse hostname %s' % hostname))
|
||||
if hostname in self._hostname_to_cb:
|
||||
del self._hostname_to_cb[hostname]
|
||||
if hostname in self._hostname_status:
|
||||
del self._hostname_status[hostname]
|
||||
|
||||
def _handle_data(self, data):
|
||||
response = parse_response(data)
|
||||
if response and response.hostname:
|
||||
hostname = response.hostname
|
||||
ip = None
|
||||
for answer in response.answers:
|
||||
if answer[1] in (QTYPE_A, QTYPE_AAAA) and \
|
||||
answer[2] == QCLASS_IN:
|
||||
ip = answer[0]
|
||||
break
|
||||
if IPV6_CONNECTION_SUPPORT:
|
||||
if not ip and self._hostname_status.get(hostname, STATUS_IPV4) \
|
||||
== STATUS_IPV6:
|
||||
self._hostname_status[hostname] = STATUS_IPV4
|
||||
self._send_req(hostname, QTYPE_A)
|
||||
else:
|
||||
if ip:
|
||||
self._cache[hostname] = ip
|
||||
self._call_callback(hostname, ip)
|
||||
elif self._hostname_status.get(hostname, None) == STATUS_IPV4:
|
||||
for question in response.questions:
|
||||
if question[1] == QTYPE_A:
|
||||
self._call_callback(hostname, None)
|
||||
break
|
||||
else:
|
||||
if not ip and self._hostname_status.get(hostname, STATUS_IPV6) \
|
||||
== STATUS_IPV4:
|
||||
self._hostname_status[hostname] = STATUS_IPV6
|
||||
self._send_req(hostname, QTYPE_AAAA)
|
||||
else:
|
||||
if ip:
|
||||
self._cache[hostname] = ip
|
||||
self._call_callback(hostname, ip)
|
||||
elif self._hostname_status.get(hostname, None) == STATUS_IPV6:
|
||||
for question in response.questions:
|
||||
if question[1] == QTYPE_AAAA:
|
||||
self._call_callback(hostname, None)
|
||||
break
|
||||
|
||||
def handle_event(self, sock, fd, event):
|
||||
if sock != self._sock:
|
||||
return
|
||||
if event & eventloop.POLL_ERR:
|
||||
logging.error('dns socket err')
|
||||
self._loop.remove(self._sock)
|
||||
self._sock.close()
|
||||
# TODO when dns server is IPv6
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
|
||||
socket.SOL_UDP)
|
||||
self._sock.setblocking(False)
|
||||
self._loop.add(self._sock, eventloop.POLL_IN, self)
|
||||
else:
|
||||
data, addr = sock.recvfrom(1024)
|
||||
if addr not in self._servers:
|
||||
logging.warn('received a packet other than our dns')
|
||||
return
|
||||
self._handle_data(data)
|
||||
|
||||
def handle_periodic(self):
|
||||
self._cache.sweep()
|
||||
|
||||
def remove_callback(self, callback):
|
||||
hostname = self._cb_to_hostname.get(callback)
|
||||
if hostname:
|
||||
del self._cb_to_hostname[callback]
|
||||
arr = self._hostname_to_cb.get(hostname, None)
|
||||
if arr:
|
||||
arr.remove(callback)
|
||||
if not arr:
|
||||
del self._hostname_to_cb[hostname]
|
||||
if hostname in self._hostname_status:
|
||||
del self._hostname_status[hostname]
|
||||
|
||||
def _send_req(self, hostname, qtype):
|
||||
req = build_request(hostname, qtype)
|
||||
for server in self._servers:
|
||||
logging.debug('resolving %s with type %d using server %s',
|
||||
hostname, qtype, server)
|
||||
self._sock.sendto(req, server)
|
||||
|
||||
def resolve(self, hostname, callback):
|
||||
if type(hostname) != bytes:
|
||||
hostname = hostname.encode('utf8')
|
||||
if not hostname:
|
||||
callback(None, Exception('empty hostname'))
|
||||
elif common.is_ip(hostname):
|
||||
callback((hostname, hostname), None)
|
||||
elif hostname in self._hosts:
|
||||
logging.debug('hit hosts: %s', hostname)
|
||||
ip = self._hosts[hostname]
|
||||
callback((hostname, ip), None)
|
||||
elif hostname in self._cache:
|
||||
logging.debug('hit cache: %s', hostname)
|
||||
ip = self._cache[hostname]
|
||||
callback((hostname, ip), None)
|
||||
else:
|
||||
if not is_valid_hostname(hostname):
|
||||
callback(None, Exception('invalid hostname: %s' % hostname))
|
||||
return
|
||||
if False:
|
||||
addrs = socket.getaddrinfo(hostname, 0, 0,
|
||||
socket.SOCK_DGRAM, socket.SOL_UDP)
|
||||
if addrs:
|
||||
af, socktype, proto, canonname, sa = addrs[0]
|
||||
logging.debug('DNS resolve %s %s' % (hostname, sa[0]) )
|
||||
self._cache[hostname] = sa[0]
|
||||
callback((hostname, sa[0]), None)
|
||||
return
|
||||
arr = self._hostname_to_cb.get(hostname, None)
|
||||
if not arr:
|
||||
if IPV6_CONNECTION_SUPPORT:
|
||||
self._hostname_status[hostname] = STATUS_IPV6
|
||||
self._send_req(hostname, QTYPE_AAAA)
|
||||
else:
|
||||
self._hostname_status[hostname] = STATUS_IPV4
|
||||
self._send_req(hostname, QTYPE_A)
|
||||
self._hostname_to_cb[hostname] = [callback]
|
||||
self._cb_to_hostname[callback] = hostname
|
||||
else:
|
||||
arr.append(callback)
|
||||
# TODO send again only if waited too long
|
||||
if IPV6_CONNECTION_SUPPORT:
|
||||
self._send_req(hostname, QTYPE_AAAA)
|
||||
else:
|
||||
self._send_req(hostname, QTYPE_A)
|
||||
|
||||
def close(self):
|
||||
if self._sock:
|
||||
if self._loop:
|
||||
self._loop.remove_periodic(self.handle_periodic)
|
||||
self._loop.remove(self._sock)
|
||||
self._sock.close()
|
||||
self._sock = None
|
||||
|
||||
|
||||
def test():
|
||||
dns_resolver = DNSResolver()
|
||||
loop = eventloop.EventLoop()
|
||||
dns_resolver.add_to_loop(loop)
|
||||
|
||||
global counter
|
||||
counter = 0
|
||||
|
||||
def make_callback():
|
||||
global counter
|
||||
|
||||
def callback(result, error):
|
||||
global counter
|
||||
# TODO: what can we assert?
|
||||
print(result, error)
|
||||
counter += 1
|
||||
if counter == 9:
|
||||
dns_resolver.close()
|
||||
loop.stop()
|
||||
a_callback = callback
|
||||
return a_callback
|
||||
|
||||
assert(make_callback() != make_callback())
|
||||
|
||||
dns_resolver.resolve(b'google.com', make_callback())
|
||||
dns_resolver.resolve('google.com', make_callback())
|
||||
dns_resolver.resolve('example.com', make_callback())
|
||||
dns_resolver.resolve('ipv6.google.com', make_callback())
|
||||
dns_resolver.resolve('www.facebook.com', make_callback())
|
||||
dns_resolver.resolve('ns2.google.com', make_callback())
|
||||
dns_resolver.resolve('invalid.@!#$%^&$@.hostname', make_callback())
|
||||
dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'long.hostname', make_callback())
|
||||
dns_resolver.resolve('toooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'ooooooooooooooooooooooooooooooooooooooooooooooooooo'
|
||||
'long.hostname', make_callback())
|
||||
|
||||
loop.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
||||
Binary file not shown.
@ -0,0 +1,418 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2013-2015 clowwindy
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import socket
|
||||
import struct
|
||||
import logging
|
||||
import binascii
|
||||
import re
|
||||
|
||||
from shadowsocks import lru_cache
|
||||
|
||||
def compat_ord(s):
|
||||
if type(s) == int:
|
||||
return s
|
||||
return _ord(s)
|
||||
|
||||
|
||||
def compat_chr(d):
|
||||
if bytes == str:
|
||||
return _chr(d)
|
||||
return bytes([d])
|
||||
|
||||
|
||||
_ord = ord
|
||||
_chr = chr
|
||||
ord = compat_ord
|
||||
chr = compat_chr
|
||||
|
||||
connect_log = logging.debug
|
||||
|
||||
def to_bytes(s):
|
||||
if bytes != str:
|
||||
if type(s) == str:
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
|
||||
def to_str(s):
|
||||
if bytes != str:
|
||||
if type(s) == bytes:
|
||||
return s.decode('utf-8')
|
||||
return s
|
||||
|
||||
def int32(x):
|
||||
if x > 0xFFFFFFFF or x < 0:
|
||||
x &= 0xFFFFFFFF
|
||||
if x > 0x7FFFFFFF:
|
||||
x = int(0x100000000 - x)
|
||||
if x < 0x80000000:
|
||||
return -x
|
||||
else:
|
||||
return -2147483648
|
||||
return x
|
||||
|
||||
def inet_ntop(family, ipstr):
|
||||
if family == socket.AF_INET:
|
||||
return to_bytes(socket.inet_ntoa(ipstr))
|
||||
elif family == socket.AF_INET6:
|
||||
import re
|
||||
v6addr = ':'.join(('%02X%02X' % (ord(i), ord(j))).lstrip('0')
|
||||
for i, j in zip(ipstr[::2], ipstr[1::2]))
|
||||
v6addr = re.sub('::+', '::', v6addr, count=1)
|
||||
return to_bytes(v6addr)
|
||||
|
||||
|
||||
def inet_pton(family, addr):
|
||||
addr = to_str(addr)
|
||||
if family == socket.AF_INET:
|
||||
return socket.inet_aton(addr)
|
||||
elif family == socket.AF_INET6:
|
||||
if '.' in addr: # a v4 addr
|
||||
v4addr = addr[addr.rindex(':') + 1:]
|
||||
v4addr = socket.inet_aton(v4addr)
|
||||
v4addr = ['%02X' % ord(x) for x in v4addr]
|
||||
v4addr.insert(2, ':')
|
||||
newaddr = addr[:addr.rindex(':') + 1] + ''.join(v4addr)
|
||||
return inet_pton(family, newaddr)
|
||||
dbyts = [0] * 8 # 8 groups
|
||||
grps = addr.split(':')
|
||||
for i, v in enumerate(grps):
|
||||
if v:
|
||||
dbyts[i] = int(v, 16)
|
||||
else:
|
||||
for j, w in enumerate(grps[::-1]):
|
||||
if w:
|
||||
dbyts[7 - j] = int(w, 16)
|
||||
else:
|
||||
break
|
||||
break
|
||||
return b''.join((chr(i // 256) + chr(i % 256)) for i in dbyts)
|
||||
else:
|
||||
raise RuntimeError("What family?")
|
||||
|
||||
|
||||
def is_ip(address):
|
||||
for family in (socket.AF_INET, socket.AF_INET6):
|
||||
try:
|
||||
if type(address) != str:
|
||||
address = address.decode('utf8')
|
||||
inet_pton(family, address)
|
||||
return family
|
||||
except (TypeError, ValueError, OSError, IOError):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def match_regex(regex, text):
|
||||
regex = re.compile(regex)
|
||||
for item in regex.findall(text):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def patch_socket():
|
||||
if not hasattr(socket, 'inet_pton'):
|
||||
socket.inet_pton = inet_pton
|
||||
|
||||
if not hasattr(socket, 'inet_ntop'):
|
||||
socket.inet_ntop = inet_ntop
|
||||
|
||||
|
||||
patch_socket()
|
||||
|
||||
|
||||
ADDRTYPE_IPV4 = 1
|
||||
ADDRTYPE_IPV6 = 4
|
||||
ADDRTYPE_HOST = 3
|
||||
|
||||
|
||||
def pack_addr(address):
|
||||
address_str = to_str(address)
|
||||
for family in (socket.AF_INET, socket.AF_INET6):
|
||||
try:
|
||||
r = socket.inet_pton(family, address_str)
|
||||
if family == socket.AF_INET6:
|
||||
return b'\x04' + r
|
||||
else:
|
||||
return b'\x01' + r
|
||||
except (TypeError, ValueError, OSError, IOError):
|
||||
pass
|
||||
if len(address) > 255:
|
||||
address = address[:255] # TODO
|
||||
return b'\x03' + chr(len(address)) + address
|
||||
|
||||
def pre_parse_header(data):
|
||||
if not data:
|
||||
return None
|
||||
datatype = ord(data[0])
|
||||
if datatype == 0x80:
|
||||
if len(data) <= 2:
|
||||
return None
|
||||
rand_data_size = ord(data[1])
|
||||
if rand_data_size + 2 >= len(data):
|
||||
logging.warn('header too short, maybe wrong password or '
|
||||
'encryption method')
|
||||
return None
|
||||
data = data[rand_data_size + 2:]
|
||||
elif datatype == 0x81:
|
||||
data = data[1:]
|
||||
elif datatype == 0x82:
|
||||
if len(data) <= 3:
|
||||
return None
|
||||
rand_data_size = struct.unpack('>H', data[1:3])[0]
|
||||
if rand_data_size + 3 >= len(data):
|
||||
logging.warn('header too short, maybe wrong password or '
|
||||
'encryption method')
|
||||
return None
|
||||
data = data[rand_data_size + 3:]
|
||||
elif datatype == 0x88 or (~datatype & 0xff) == 0x88:
|
||||
if len(data) <= 7 + 7:
|
||||
return None
|
||||
data_size = struct.unpack('>H', data[1:3])[0]
|
||||
ogn_data = data
|
||||
data = data[:data_size]
|
||||
crc = binascii.crc32(data) & 0xffffffff
|
||||
if crc != 0xffffffff:
|
||||
logging.warn('uncorrect CRC32, maybe wrong password or '
|
||||
'encryption method')
|
||||
return None
|
||||
start_pos = 3 + ord(data[3])
|
||||
data = data[start_pos:-4]
|
||||
if data_size < len(ogn_data):
|
||||
data += ogn_data[data_size:]
|
||||
return data
|
||||
|
||||
def parse_header(data):
|
||||
addrtype = ord(data[0])
|
||||
dest_addr = None
|
||||
dest_port = None
|
||||
header_length = 0
|
||||
connecttype = (addrtype & 0x8) and 1 or 0
|
||||
addrtype &= ~0x8
|
||||
if addrtype == ADDRTYPE_IPV4:
|
||||
if len(data) >= 7:
|
||||
dest_addr = socket.inet_ntoa(data[1:5])
|
||||
dest_port = struct.unpack('>H', data[5:7])[0]
|
||||
header_length = 7
|
||||
else:
|
||||
logging.warn('header is too short')
|
||||
elif addrtype == ADDRTYPE_HOST:
|
||||
if len(data) > 2:
|
||||
addrlen = ord(data[1])
|
||||
if len(data) >= 4 + addrlen:
|
||||
dest_addr = data[2:2 + addrlen]
|
||||
dest_port = struct.unpack('>H', data[2 + addrlen:4 +
|
||||
addrlen])[0]
|
||||
header_length = 4 + addrlen
|
||||
else:
|
||||
logging.warn('header is too short')
|
||||
else:
|
||||
logging.warn('header is too short')
|
||||
elif addrtype == ADDRTYPE_IPV6:
|
||||
if len(data) >= 19:
|
||||
dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17])
|
||||
dest_port = struct.unpack('>H', data[17:19])[0]
|
||||
header_length = 19
|
||||
else:
|
||||
logging.warn('header is too short')
|
||||
else:
|
||||
logging.warn('unsupported addrtype %d, maybe wrong password or '
|
||||
'encryption method' % addrtype)
|
||||
if dest_addr is None:
|
||||
return None
|
||||
return connecttype, addrtype, to_bytes(dest_addr), dest_port, header_length
|
||||
|
||||
|
||||
class IPNetwork(object):
|
||||
ADDRLENGTH = {socket.AF_INET: 32, socket.AF_INET6: 128, False: 0}
|
||||
|
||||
def __init__(self, addrs):
|
||||
self.addrs_str = addrs
|
||||
self._network_list_v4 = []
|
||||
self._network_list_v6 = []
|
||||
if type(addrs) == str:
|
||||
addrs = addrs.split(',')
|
||||
list(map(self.add_network, addrs))
|
||||
|
||||
def add_network(self, addr):
|
||||
if addr is "":
|
||||
return
|
||||
block = addr.split('/')
|
||||
addr_family = is_ip(block[0])
|
||||
addr_len = IPNetwork.ADDRLENGTH[addr_family]
|
||||
if addr_family is socket.AF_INET:
|
||||
ip, = struct.unpack("!I", socket.inet_aton(block[0]))
|
||||
elif addr_family is socket.AF_INET6:
|
||||
hi, lo = struct.unpack("!QQ", inet_pton(addr_family, block[0]))
|
||||
ip = (hi << 64) | lo
|
||||
else:
|
||||
raise Exception("Not a valid CIDR notation: %s" % addr)
|
||||
if len(block) is 1:
|
||||
prefix_size = 0
|
||||
while (ip & 1) == 0 and ip is not 0:
|
||||
ip >>= 1
|
||||
prefix_size += 1
|
||||
logging.warn("You did't specify CIDR routing prefix size for %s, "
|
||||
"implicit treated as %s/%d" % (addr, addr, addr_len))
|
||||
elif block[1].isdigit() and int(block[1]) <= addr_len:
|
||||
prefix_size = addr_len - int(block[1])
|
||||
ip >>= prefix_size
|
||||
else:
|
||||
raise Exception("Not a valid CIDR notation: %s" % addr)
|
||||
if addr_family is socket.AF_INET:
|
||||
self._network_list_v4.append((ip, prefix_size))
|
||||
else:
|
||||
self._network_list_v6.append((ip, prefix_size))
|
||||
|
||||
def __contains__(self, addr):
|
||||
addr_family = is_ip(addr)
|
||||
if addr_family is socket.AF_INET:
|
||||
ip, = struct.unpack("!I", socket.inet_aton(addr))
|
||||
return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1],
|
||||
self._network_list_v4))
|
||||
elif addr_family is socket.AF_INET6:
|
||||
hi, lo = struct.unpack("!QQ", inet_pton(addr_family, addr))
|
||||
ip = (hi << 64) | lo
|
||||
return any(map(lambda n_ps: n_ps[0] == ip >> n_ps[1],
|
||||
self._network_list_v6))
|
||||
else:
|
||||
return False
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.addrs_str, other.addrs_str)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.addrs_str == other.addrs_str
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.addrs_str != other.addrs_str
|
||||
|
||||
class PortRange(object):
|
||||
def __init__(self, range_str):
|
||||
self.range_str = to_str(range_str)
|
||||
self.range = set()
|
||||
range_str = to_str(range_str).split(',')
|
||||
for item in range_str:
|
||||
try:
|
||||
int_range = item.split('-')
|
||||
if len(int_range) == 1:
|
||||
if item:
|
||||
self.range.add(int(item))
|
||||
elif len(int_range) == 2:
|
||||
int_range[0] = int(int_range[0])
|
||||
int_range[1] = int(int_range[1])
|
||||
if int_range[0] < 0:
|
||||
int_range[0] = 0
|
||||
if int_range[1] > 65535:
|
||||
int_range[1] = 65535
|
||||
i = int_range[0]
|
||||
while i <= int_range[1]:
|
||||
self.range.add(i)
|
||||
i += 1
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
def __contains__(self, val):
|
||||
return val in self.range
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.range_str, other.range_str)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.range_str == other.range_str
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.range_str != other.range_str
|
||||
|
||||
class UDPAsyncDNSHandler(object):
|
||||
dns_cache = lru_cache.LRUCache(timeout=1800)
|
||||
def __init__(self, params):
|
||||
self.params = params
|
||||
self.remote_addr = None
|
||||
self.call_back = None
|
||||
|
||||
def resolve(self, dns_resolver, remote_addr, call_back):
|
||||
if remote_addr in UDPAsyncDNSHandler.dns_cache:
|
||||
if call_back:
|
||||
call_back("", remote_addr, UDPAsyncDNSHandler.dns_cache[remote_addr], self.params)
|
||||
else:
|
||||
self.call_back = call_back
|
||||
self.remote_addr = remote_addr
|
||||
dns_resolver.resolve(remote_addr[0], self._handle_dns_resolved)
|
||||
UDPAsyncDNSHandler.dns_cache.sweep()
|
||||
|
||||
def _handle_dns_resolved(self, result, error):
|
||||
if error:
|
||||
logging.error("%s when resolve DNS" % (error,)) #drop
|
||||
return self.call_back(error, self.remote_addr, None, self.params)
|
||||
if result:
|
||||
ip = result[1]
|
||||
if ip:
|
||||
return self.call_back("", self.remote_addr, ip, self.params)
|
||||
logging.warning("can't resolve %s" % (self.remote_addr,))
|
||||
return self.call_back("fail to resolve", self.remote_addr, None, self.params)
|
||||
|
||||
def test_inet_conv():
|
||||
ipv4 = b'8.8.4.4'
|
||||
b = inet_pton(socket.AF_INET, ipv4)
|
||||
assert inet_ntop(socket.AF_INET, b) == ipv4
|
||||
ipv6 = b'2404:6800:4005:805::1011'
|
||||
b = inet_pton(socket.AF_INET6, ipv6)
|
||||
assert inet_ntop(socket.AF_INET6, b) == ipv6
|
||||
|
||||
|
||||
def test_parse_header():
|
||||
assert parse_header(b'\x03\x0ewww.google.com\x00\x50') == \
|
||||
(0, b'www.google.com', 80, 18)
|
||||
assert parse_header(b'\x01\x08\x08\x08\x08\x00\x35') == \
|
||||
(0, b'8.8.8.8', 53, 7)
|
||||
assert parse_header((b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00'
|
||||
b'\x00\x10\x11\x00\x50')) == \
|
||||
(0, b'2404:6800:4005:805::1011', 80, 19)
|
||||
|
||||
|
||||
def test_pack_header():
|
||||
assert pack_addr(b'8.8.8.8') == b'\x01\x08\x08\x08\x08'
|
||||
assert pack_addr(b'2404:6800:4005:805::1011') == \
|
||||
b'\x04$\x04h\x00@\x05\x08\x05\x00\x00\x00\x00\x00\x00\x10\x11'
|
||||
assert pack_addr(b'www.google.com') == b'\x03\x0ewww.google.com'
|
||||
|
||||
|
||||
def test_ip_network():
|
||||
ip_network = IPNetwork('127.0.0.0/24,::ff:1/112,::1,192.168.1.1,192.0.2.0')
|
||||
assert '127.0.0.1' in ip_network
|
||||
assert '127.0.1.1' not in ip_network
|
||||
assert ':ff:ffff' in ip_network
|
||||
assert '::ffff:1' not in ip_network
|
||||
assert '::1' in ip_network
|
||||
assert '::2' not in ip_network
|
||||
assert '192.168.1.1' in ip_network
|
||||
assert '192.168.1.2' not in ip_network
|
||||
assert '192.0.2.1' in ip_network
|
||||
assert '192.0.3.1' in ip_network # 192.0.2.0 is treated as 192.0.2.0/23
|
||||
assert 'www.google.com' not in ip_network
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_inet_conv()
|
||||
test_parse_header()
|
||||
test_pack_header()
|
||||
test_ip_network()
|
||||
Binary file not shown.
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2015 clowwindy
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
Binary file not shown.
@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import logging
|
||||
from ctypes import CDLL, c_char_p, c_int, c_ulonglong, byref, \
|
||||
create_string_buffer, c_void_p
|
||||
|
||||
__all__ = ['ciphers']
|
||||
|
||||
libsodium = None
|
||||
loaded = False
|
||||
|
||||
buf_size = 2048
|
||||
|
||||
# for salsa20 and chacha20
|
||||
BLOCK_SIZE = 64
|
||||
|
||||
|
||||
def load_libsodium():
|
||||
global loaded, libsodium, buf
|
||||
|
||||
from ctypes.util import find_library
|
||||
for p in ('sodium',):
|
||||
libsodium_path = find_library(p)
|
||||
if libsodium_path:
|
||||
break
|
||||
else:
|
||||
raise Exception('libsodium not found')
|
||||
logging.info('loading libsodium from %s', libsodium_path)
|
||||
libsodium = CDLL(libsodium_path)
|
||||
libsodium.sodium_init.restype = c_int
|
||||
libsodium.crypto_stream_salsa20_xor_ic.restype = c_int
|
||||
libsodium.crypto_stream_salsa20_xor_ic.argtypes = (c_void_p, c_char_p,
|
||||
c_ulonglong,
|
||||
c_char_p, c_ulonglong,
|
||||
c_char_p)
|
||||
libsodium.crypto_stream_chacha20_xor_ic.restype = c_int
|
||||
libsodium.crypto_stream_chacha20_xor_ic.argtypes = (c_void_p, c_char_p,
|
||||
c_ulonglong,
|
||||
c_char_p, c_ulonglong,
|
||||
c_char_p)
|
||||
|
||||
libsodium.sodium_init()
|
||||
|
||||
buf = create_string_buffer(buf_size)
|
||||
loaded = True
|
||||
|
||||
|
||||
class Salsa20Crypto(object):
|
||||
def __init__(self, cipher_name, key, iv, op):
|
||||
if not loaded:
|
||||
load_libsodium()
|
||||
self.key = key
|
||||
self.iv = iv
|
||||
self.key_ptr = c_char_p(key)
|
||||
self.iv_ptr = c_char_p(iv)
|
||||
if cipher_name == b'salsa20':
|
||||
self.cipher = libsodium.crypto_stream_salsa20_xor_ic
|
||||
elif cipher_name == b'chacha20':
|
||||
self.cipher = libsodium.crypto_stream_chacha20_xor_ic
|
||||
else:
|
||||
raise Exception('Unknown cipher')
|
||||
# byte counter, not block counter
|
||||
self.counter = 0
|
||||
|
||||
def update(self, data):
|
||||
global buf_size, buf
|
||||
l = len(data)
|
||||
|
||||
# we can only prepend some padding to make the encryption align to
|
||||
# blocks
|
||||
padding = self.counter % BLOCK_SIZE
|
||||
if buf_size < padding + l:
|
||||
buf_size = (padding + l) * 2
|
||||
buf = create_string_buffer(buf_size)
|
||||
|
||||
if padding:
|
||||
data = (b'\0' * padding) + data
|
||||
self.cipher(byref(buf), c_char_p(data), padding + l,
|
||||
self.iv_ptr, int(self.counter / BLOCK_SIZE), self.key_ptr)
|
||||
self.counter += l
|
||||
# buf is copied to a str object when we access buf.raw
|
||||
# strip off the padding
|
||||
return buf.raw[padding:padding + l]
|
||||
|
||||
|
||||
ciphers = {
|
||||
b'salsa20': (32, 8, Salsa20Crypto),
|
||||
b'chacha20': (32, 8, Salsa20Crypto),
|
||||
}
|
||||
|
||||
|
||||
def test_salsa20():
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
cipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 1)
|
||||
decipher = Salsa20Crypto(b'salsa20', b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
def test_chacha20():
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
cipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 1)
|
||||
decipher = Salsa20Crypto(b'chacha20', b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_chacha20()
|
||||
test_salsa20()
|
||||
@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2014 clowwindy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
import logging
|
||||
from ctypes import CDLL, c_char_p, c_int, c_long, byref,\
|
||||
create_string_buffer, c_void_p
|
||||
|
||||
__all__ = ['ciphers']
|
||||
|
||||
libcrypto = None
|
||||
loaded = False
|
||||
|
||||
buf_size = 2048
|
||||
|
||||
|
||||
def load_openssl():
|
||||
global loaded, libcrypto, buf
|
||||
|
||||
from ctypes.util import find_library
|
||||
for p in ('crypto', 'eay32', 'libeay32'):
|
||||
libcrypto_path = find_library(p)
|
||||
if libcrypto_path:
|
||||
break
|
||||
else:
|
||||
raise Exception('libcrypto(OpenSSL) not found')
|
||||
logging.info('loading libcrypto from %s', libcrypto_path)
|
||||
libcrypto = CDLL(libcrypto_path)
|
||||
libcrypto.EVP_get_cipherbyname.restype = c_void_p
|
||||
libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p
|
||||
|
||||
libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
|
||||
c_char_p, c_char_p, c_int)
|
||||
|
||||
libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p,
|
||||
c_char_p, c_int)
|
||||
|
||||
libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
|
||||
libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,)
|
||||
if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'):
|
||||
libcrypto.OpenSSL_add_all_ciphers()
|
||||
|
||||
buf = create_string_buffer(buf_size)
|
||||
loaded = True
|
||||
|
||||
|
||||
def load_cipher(cipher_name):
|
||||
func_name = b'EVP_' + cipher_name.replace(b'-', b'_')
|
||||
if bytes != str:
|
||||
func_name = str(func_name, 'utf-8')
|
||||
cipher = getattr(libcrypto, func_name, None)
|
||||
if cipher:
|
||||
cipher.restype = c_void_p
|
||||
return cipher()
|
||||
return None
|
||||
|
||||
|
||||
class CtypesCrypto(object):
|
||||
def __init__(self, cipher_name, key, iv, op):
|
||||
if not loaded:
|
||||
load_openssl()
|
||||
self._ctx = None
|
||||
cipher = libcrypto.EVP_get_cipherbyname(cipher_name)
|
||||
if not cipher:
|
||||
cipher = load_cipher(cipher_name)
|
||||
if not cipher:
|
||||
raise Exception('cipher %s not found in libcrypto' % cipher_name)
|
||||
key_ptr = c_char_p(key)
|
||||
iv_ptr = c_char_p(iv)
|
||||
self._ctx = libcrypto.EVP_CIPHER_CTX_new()
|
||||
if not self._ctx:
|
||||
raise Exception('can not create cipher context')
|
||||
r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
|
||||
key_ptr, iv_ptr, c_int(op))
|
||||
if not r:
|
||||
self.clean()
|
||||
raise Exception('can not initialize cipher context')
|
||||
|
||||
def update(self, data):
|
||||
global buf_size, buf
|
||||
cipher_out_len = c_long(0)
|
||||
l = len(data)
|
||||
if buf_size < l:
|
||||
buf_size = l * 2
|
||||
buf = create_string_buffer(buf_size)
|
||||
libcrypto.EVP_CipherUpdate(self._ctx, byref(buf),
|
||||
byref(cipher_out_len), c_char_p(data), l)
|
||||
# buf is copied to a str object when we access buf.raw
|
||||
return buf.raw[:cipher_out_len.value]
|
||||
|
||||
def __del__(self):
|
||||
self.clean()
|
||||
|
||||
def clean(self):
|
||||
if self._ctx:
|
||||
libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx)
|
||||
libcrypto.EVP_CIPHER_CTX_free(self._ctx)
|
||||
|
||||
|
||||
ciphers = {
|
||||
b'aes-128-cfb': (16, 16, CtypesCrypto),
|
||||
b'aes-192-cfb': (24, 16, CtypesCrypto),
|
||||
b'aes-256-cfb': (32, 16, CtypesCrypto),
|
||||
b'aes-128-ofb': (16, 16, CtypesCrypto),
|
||||
b'aes-192-ofb': (24, 16, CtypesCrypto),
|
||||
b'aes-256-ofb': (32, 16, CtypesCrypto),
|
||||
b'aes-128-ctr': (16, 16, CtypesCrypto),
|
||||
b'aes-192-ctr': (24, 16, CtypesCrypto),
|
||||
b'aes-256-ctr': (32, 16, CtypesCrypto),
|
||||
b'aes-128-cfb8': (16, 16, CtypesCrypto),
|
||||
b'aes-192-cfb8': (24, 16, CtypesCrypto),
|
||||
b'aes-256-cfb8': (32, 16, CtypesCrypto),
|
||||
b'aes-128-cfb1': (16, 16, CtypesCrypto),
|
||||
b'aes-192-cfb1': (24, 16, CtypesCrypto),
|
||||
b'aes-256-cfb1': (32, 16, CtypesCrypto),
|
||||
b'bf-cfb': (16, 8, CtypesCrypto),
|
||||
b'camellia-128-cfb': (16, 16, CtypesCrypto),
|
||||
b'camellia-192-cfb': (24, 16, CtypesCrypto),
|
||||
b'camellia-256-cfb': (32, 16, CtypesCrypto),
|
||||
b'cast5-cfb': (16, 8, CtypesCrypto),
|
||||
b'des-cfb': (8, 8, CtypesCrypto),
|
||||
b'idea-cfb': (16, 8, CtypesCrypto),
|
||||
b'rc2-cfb': (16, 8, CtypesCrypto),
|
||||
b'rc4': (16, 0, CtypesCrypto),
|
||||
b'seed-cfb': (16, 16, CtypesCrypto),
|
||||
}
|
||||
|
||||
|
||||
def run_method(method):
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
cipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 1)
|
||||
decipher = CtypesCrypto(method, b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
def test_aes_128_cfb():
|
||||
run_method(b'aes-128-cfb')
|
||||
|
||||
|
||||
def test_aes_256_cfb():
|
||||
run_method(b'aes-256-cfb')
|
||||
|
||||
|
||||
def test_aes_128_cfb8():
|
||||
run_method(b'aes-128-cfb8')
|
||||
|
||||
|
||||
def test_aes_256_ofb():
|
||||
run_method(b'aes-256-ofb')
|
||||
|
||||
|
||||
def test_aes_256_ctr():
|
||||
run_method(b'aes-256-ctr')
|
||||
|
||||
|
||||
def test_bf_cfb():
|
||||
run_method(b'bf-cfb')
|
||||
|
||||
|
||||
def test_rc4():
|
||||
run_method(b'rc4')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_aes_128_cfb()
|
||||
@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2015 clowwindy
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, \
|
||||
with_statement
|
||||
|
||||
from ctypes import c_char_p, c_int, c_long, byref,\
|
||||
create_string_buffer, c_void_p
|
||||
|
||||
from shadowsocks import common
|
||||
from shadowsocks.crypto import util
|
||||
|
||||
__all__ = ['ciphers']
|
||||
|
||||
libcrypto = None
|
||||
loaded = False
|
||||
|
||||
buf_size = 2048
|
||||
|
||||
|
||||
def load_openssl():
|
||||
global loaded, libcrypto, buf
|
||||
|
||||
libcrypto = util.find_library(('crypto', 'eay32'),
|
||||
'EVP_get_cipherbyname',
|
||||
'libcrypto')
|
||||
if libcrypto is None:
|
||||
raise Exception('libcrypto(OpenSSL) not found')
|
||||
|
||||
libcrypto.EVP_get_cipherbyname.restype = c_void_p
|
||||
libcrypto.EVP_CIPHER_CTX_new.restype = c_void_p
|
||||
|
||||
libcrypto.EVP_CipherInit_ex.argtypes = (c_void_p, c_void_p, c_char_p,
|
||||
c_char_p, c_char_p, c_int)
|
||||
|
||||
libcrypto.EVP_CipherUpdate.argtypes = (c_void_p, c_void_p, c_void_p,
|
||||
c_char_p, c_int)
|
||||
|
||||
if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"):
|
||||
libcrypto.EVP_CIPHER_CTX_cleanup.argtypes = (c_void_p,)
|
||||
else:
|
||||
libcrypto.EVP_CIPHER_CTX_reset.argtypes = (c_void_p,)
|
||||
libcrypto.EVP_CIPHER_CTX_free.argtypes = (c_void_p,)
|
||||
|
||||
libcrypto.RAND_bytes.restype = c_int
|
||||
libcrypto.RAND_bytes.argtypes = (c_void_p, c_int)
|
||||
|
||||
if hasattr(libcrypto, 'OpenSSL_add_all_ciphers'):
|
||||
libcrypto.OpenSSL_add_all_ciphers()
|
||||
|
||||
buf = create_string_buffer(buf_size)
|
||||
loaded = True
|
||||
|
||||
|
||||
def load_cipher(cipher_name):
|
||||
func_name = 'EVP_' + cipher_name.replace('-', '_')
|
||||
cipher = getattr(libcrypto, func_name, None)
|
||||
if cipher:
|
||||
cipher.restype = c_void_p
|
||||
return cipher()
|
||||
return None
|
||||
|
||||
def rand_bytes(length):
|
||||
if not loaded:
|
||||
load_openssl()
|
||||
buf = create_string_buffer(length)
|
||||
r = libcrypto.RAND_bytes(buf, length)
|
||||
if r <= 0:
|
||||
raise Exception('RAND_bytes return error')
|
||||
return buf.raw
|
||||
|
||||
class OpenSSLCrypto(object):
|
||||
def __init__(self, cipher_name, key, iv, op):
|
||||
self._ctx = None
|
||||
if not loaded:
|
||||
load_openssl()
|
||||
cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(cipher_name))
|
||||
if not cipher:
|
||||
cipher = load_cipher(cipher_name)
|
||||
if not cipher:
|
||||
raise Exception('cipher %s not found in libcrypto' % cipher_name)
|
||||
key_ptr = c_char_p(key)
|
||||
iv_ptr = c_char_p(iv)
|
||||
self._ctx = libcrypto.EVP_CIPHER_CTX_new()
|
||||
if not self._ctx:
|
||||
raise Exception('can not create cipher context')
|
||||
r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None,
|
||||
key_ptr, iv_ptr, c_int(op))
|
||||
if not r:
|
||||
self.clean()
|
||||
raise Exception('can not initialize cipher context')
|
||||
|
||||
def update(self, data):
|
||||
global buf_size, buf
|
||||
cipher_out_len = c_long(0)
|
||||
l = len(data)
|
||||
if buf_size < l:
|
||||
buf_size = l * 2
|
||||
buf = create_string_buffer(buf_size)
|
||||
libcrypto.EVP_CipherUpdate(self._ctx, byref(buf),
|
||||
byref(cipher_out_len), c_char_p(data), l)
|
||||
# buf is copied to a str object when we access buf.raw
|
||||
return buf.raw[:cipher_out_len.value]
|
||||
|
||||
def __del__(self):
|
||||
self.clean()
|
||||
|
||||
def clean(self):
|
||||
if self._ctx:
|
||||
if hasattr(libcrypto, "EVP_CIPHER_CTX_cleanup"):
|
||||
libcrypto.EVP_CIPHER_CTX_cleanup(self._ctx)
|
||||
else:
|
||||
libcrypto.EVP_CIPHER_CTX_reset(self._ctx)
|
||||
libcrypto.EVP_CIPHER_CTX_free(self._ctx)
|
||||
|
||||
|
||||
ciphers = {
|
||||
'aes-128-cbc': (16, 16, OpenSSLCrypto),
|
||||
'aes-192-cbc': (24, 16, OpenSSLCrypto),
|
||||
'aes-256-cbc': (32, 16, OpenSSLCrypto),
|
||||
'aes-128-cfb': (16, 16, OpenSSLCrypto),
|
||||
'aes-192-cfb': (24, 16, OpenSSLCrypto),
|
||||
'aes-256-cfb': (32, 16, OpenSSLCrypto),
|
||||
'aes-128-ofb': (16, 16, OpenSSLCrypto),
|
||||
'aes-192-ofb': (24, 16, OpenSSLCrypto),
|
||||
'aes-256-ofb': (32, 16, OpenSSLCrypto),
|
||||
'aes-128-ctr': (16, 16, OpenSSLCrypto),
|
||||
'aes-192-ctr': (24, 16, OpenSSLCrypto),
|
||||
'aes-256-ctr': (32, 16, OpenSSLCrypto),
|
||||
'aes-128-cfb8': (16, 16, OpenSSLCrypto),
|
||||
'aes-192-cfb8': (24, 16, OpenSSLCrypto),
|
||||
'aes-256-cfb8': (32, 16, OpenSSLCrypto),
|
||||
'aes-128-cfb1': (16, 16, OpenSSLCrypto),
|
||||
'aes-192-cfb1': (24, 16, OpenSSLCrypto),
|
||||
'aes-256-cfb1': (32, 16, OpenSSLCrypto),
|
||||
'bf-cfb': (16, 8, OpenSSLCrypto),
|
||||
'camellia-128-cfb': (16, 16, OpenSSLCrypto),
|
||||
'camellia-192-cfb': (24, 16, OpenSSLCrypto),
|
||||
'camellia-256-cfb': (32, 16, OpenSSLCrypto),
|
||||
'cast5-cfb': (16, 8, OpenSSLCrypto),
|
||||
'des-cfb': (8, 8, OpenSSLCrypto),
|
||||
'idea-cfb': (16, 8, OpenSSLCrypto),
|
||||
'rc2-cfb': (16, 8, OpenSSLCrypto),
|
||||
'rc4': (16, 0, OpenSSLCrypto),
|
||||
'seed-cfb': (16, 16, OpenSSLCrypto),
|
||||
}
|
||||
|
||||
|
||||
def run_method(method):
|
||||
|
||||
cipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 1)
|
||||
decipher = OpenSSLCrypto(method, b'k' * 32, b'i' * 16, 0)
|
||||
|
||||
util.run_cipher(cipher, decipher)
|
||||
|
||||
|
||||
def test_aes_128_cfb():
|
||||
run_method('aes-128-cfb')
|
||||
|
||||
|
||||
def test_aes_256_cfb():
|
||||
run_method('aes-256-cfb')
|
||||
|
||||
|
||||
def test_aes_128_cfb8():
|
||||
run_method('aes-128-cfb8')
|
||||
|
||||
|
||||
def test_aes_256_ofb():
|
||||
run_method('aes-256-ofb')
|
||||
|
||||
|
||||
def test_aes_256_ctr():
|
||||
run_method('aes-256-ctr')
|
||||
|
||||
|
||||
def test_bf_cfb():
|
||||
run_method('bf-cfb')
|
||||
|
||||
|
||||
def test_rc4():
|
||||
run_method('rc4')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_aes_128_cfb()
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user