From 4dd429b7545f12c6ffa317879ded29876af68d90 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Sun, 30 Jul 2017 01:54:19 +0800 Subject: [PATCH] add proxy rule file support. --- .gitignore | 1 + direct.go | 15 +++++--- glider.conf.example | 7 ++++ main.go | 19 ++++++++++ office.rule.example | 29 +++++++++++++++ proxy.go | 8 ++-- rule.go | 91 +++++++++++++++++++++++++++++++++++++++++++++ rules.go | 89 ++++++++++++++++++++++++++++++++++++++++++++ strategy.go | 13 +++---- 9 files changed, 254 insertions(+), 18 deletions(-) create mode 100644 office.rule.example create mode 100644 rule.go create mode 100644 rules.go diff --git a/.gitignore b/.gitignore index 4e64827..7310a88 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,6 @@ # custom *.zip *.conf +*.rule glider doc/ diff --git a/direct.go b/direct.go index ceda39e..a2deb56 100644 --- a/direct.go +++ b/direct.go @@ -4,16 +4,19 @@ import "net" // direct proxy type direct struct { - Proxy } // Direct proxy -var Direct = &direct{Proxy: &proxy{addr: "127.0.0.1"}} +var Direct = &direct{} -// Direct proxy always enabled -func (d *direct) Enabled() bool { - return true -} +func (d *direct) Addr() string { return "127.0.0.1" } +func (d *direct) ListenAndServe() { logf("base proxy ListenAndServe") } +func (d *direct) Serve(c net.Conn) { logf("base proxy Serve") } +func (d *direct) CurrentProxy() Proxy { return d } +func (d *direct) GetProxy(dstAddr string) Proxy { return d } +func (d *direct) NextProxy() Proxy { return d } +func (d *direct) Enabled() bool { return true } +func (d *direct) SetEnable(enable bool) {} func (d *direct) Dial(network, addr string) (net.Conn, error) { c, err := net.Dial(network, addr) diff --git a/glider.conf.example b/glider.conf.example index 3d38c53..8656872 100644 --- a/glider.conf.example +++ b/glider.conf.example @@ -94,3 +94,10 @@ checkwebsite=www.apple.com:443 # check duration(seconds) checkduration=30 + + +# RULE FILES +# ---------- +# Specify additional forward rules +#rulefile=office.rule +#rulefile=home.rule diff --git a/main.go b/main.go index eb2fb7b..60cf25b 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ var conf struct { CheckDuration int Listen arrFlags Forward arrFlags + RuleFile arrFlags } var flag = conflag.New() @@ -125,6 +126,7 @@ func main() { flag.IntVar(&conf.CheckDuration, "checkduration", 30, "proxy check duration(seconds)") flag.Var(&conf.Listen, "listen", "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT") flag.Var(&conf.Forward, "forward", "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]") + flag.Var(&conf.RuleFile, "rulefile", "rule file path") flag.Usage = usage err := flag.Parse() @@ -139,6 +141,7 @@ func main() { return } + // global forwarders in xx.conf var forwarders []Proxy for _, chain := range conf.Forward { var forward Proxy @@ -152,7 +155,23 @@ func main() { forwarders = append(forwarders, forward) } + // combine forwarders to a singer strategy forwarder forwarder := newStrategyForwarder(conf.Strategy, forwarders) + + // rule forwarders + var ruleForwarders []*ruleForwarder + for _, ruleFile := range conf.RuleFile { + ruleForwarder, err := newRuleProxyFromFile(ruleFile) + if err != nil { + log.Fatal(err) + } + + ruleForwarders = append(ruleForwarders, ruleForwarder) + } + + // combine ruleforwarders and global strategy forwarder + forwarder = newRulesForwarder(ruleForwarders, forwarder) + for _, listen := range conf.Listen { local, err := ProxyFromURL(listen, forwarder) if err != nil { diff --git a/office.rule.example b/office.rule.example new file mode 100644 index 0000000..362c741 --- /dev/null +++ b/office.rule.example @@ -0,0 +1,29 @@ +# Glider rule configuration file. +# +# Format is the same as glider main config file. +# EXCEPTION: Listeners are NOT allowed to setup here. + +# FORWARDERS +# ---------- +# Forwarders, we can setup multiple forwarders. +forward=socks5://192.168.1.10:1080 +forward=ss://method:pass@1.1.1.1:443 +forward=http://192.168.2.1:8080,socks5://192.168.2.2:1080 + +strategy=rr + +checkwebsite=www.apple.com:443 +checkduration=30 + +# DESTINATIONS +# ------------ +# ALL destinations matches the following rules will be forward using forwarders specified above + +# matches abc.com and *.abc.com +domain=abc.com + +# matches 1.1.1.1 +ip=1.1.1.1 + +# matches 192.168.100.0/24 +cidr=192.168.100.0/24 diff --git a/proxy.go b/proxy.go index a088ad3..17952f7 100644 --- a/proxy.go +++ b/proxy.go @@ -11,15 +11,15 @@ import ( // A Proxy means to establish a connection and relay it. type Proxy interface { + // Get address + Addr() string + // ListenAndServe as proxy server, use only in server mode. ListenAndServe() // Serve as proxy server, use only in server mode. Serve(c net.Conn) - // Get address - Addr() string - // Get current proxy CurrentProxy() Proxy @@ -55,6 +55,7 @@ func newProxy(addr string, forward Proxy) *proxy { return &proxy{addr: addr, forward: forward, enabled: true} } +func (p *proxy) Addr() string { return p.addr } func (p *proxy) ListenAndServe() { logf("base proxy ListenAndServe") } func (p *proxy) Serve(c net.Conn) { logf("base proxy Serve") } func (p *proxy) CurrentProxy() Proxy { return p.forward } @@ -62,7 +63,6 @@ func (p *proxy) GetProxy(dstAddr string) Proxy { return p.forward } func (p *proxy) NextProxy() Proxy { return p.forward } func (p *proxy) Enabled() bool { return p.enabled } func (p *proxy) SetEnable(enable bool) { p.enabled = enable } -func (p *proxy) Addr() string { return p.addr } func (p *proxy) Dial(network, addr string) (net.Conn, error) { return p.forward.Dial(network, addr) diff --git a/rule.go b/rule.go new file mode 100644 index 0000000..c67a6a8 --- /dev/null +++ b/rule.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + "log" + "net" + "os" + "strings" + + "github.com/nadoo/conflag" +) + +// ruleForwarder, every ruleForwarder points to a rule file +type ruleForwarder struct { + Forward arrFlags + Strategy string + CheckWebSite string + CheckDuration int + + Domain arrFlags + IP arrFlags + CIDR arrFlags + + name string + sForwarder Proxy +} + +// newRuleProxyFromFile . +func newRuleProxyFromFile(ruleFile string) (*ruleForwarder, error) { + p := &ruleForwarder{name: ruleFile} + + f := conflag.NewFromFile("rule", ruleFile) + f.Var(&p.Forward, "forward", "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]") + f.StringVar(&p.Strategy, "strategy", "rr", "forward strategy, default: rr") + f.StringVar(&p.CheckWebSite, "checkwebsite", "www.apple.com:443", "proxy check website address") + f.IntVar(&p.CheckDuration, "checkduration", 30, "proxy check duration(seconds)") + + f.Var(&p.Domain, "domain", "domain") + f.Var(&p.IP, "ip", "ip") + f.Var(&p.CIDR, "cidr", "cidr") + + err := f.Parse() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) + return nil, err + } + + var forwarders []Proxy + for _, chain := range p.Forward { + var forward Proxy + var err error + for _, url := range strings.Split(chain, ",") { + forward, err = ProxyFromURL(url, forward) + if err != nil { + log.Fatal(err) + } + } + forwarders = append(forwarders, forward) + } + + forwarder := newStrategyForwarder(p.Strategy, forwarders) + + for _, forward := range forwarders { + go check(forward, p.CheckWebSite, p.CheckDuration) + } + + p.sForwarder = forwarder + + return p, err +} + +func (p *ruleForwarder) Addr() string { return "rule forwarder" } +func (p *ruleForwarder) ListenAndServe() {} +func (p *ruleForwarder) Serve(c net.Conn) {} +func (p *ruleForwarder) CurrentProxy() Proxy { return p.sForwarder.CurrentProxy() } + +func (p *ruleForwarder) GetProxy(dstAddr string) Proxy { + + return p.sForwarder.NextProxy() +} + +func (p *ruleForwarder) NextProxy() Proxy { + return p.sForwarder.NextProxy() +} + +func (p *ruleForwarder) Enabled() bool { return true } +func (p *ruleForwarder) SetEnable(enable bool) {} + +func (p *ruleForwarder) Dial(network, addr string) (net.Conn, error) { + return p.NextProxy().Dial(network, addr) +} diff --git a/rules.go b/rules.go new file mode 100644 index 0000000..a860bf3 --- /dev/null +++ b/rules.go @@ -0,0 +1,89 @@ +package main + +import ( + "net" + "strings" +) + +type rulesForwarder struct { + globalForwarder Proxy + + domainMap map[string]Proxy + ipMap map[string]Proxy + cidrMap map[string]Proxy +} + +// newRulesForwarder . +func newRulesForwarder(ruleForwarders []*ruleForwarder, globalForwarder Proxy) Proxy { + p := &rulesForwarder{globalForwarder: globalForwarder} + + for _, f := range ruleForwarders { + p.domainMap = make(map[string]Proxy) + for _, domain := range f.Domain { + p.domainMap[domain] = f.sForwarder + } + + p.ipMap = make(map[string]Proxy) + for _, ip := range f.IP { + p.ipMap[ip] = f.sForwarder + } + + p.cidrMap = make(map[string]Proxy) + for _, cidr := range f.CIDR { + p.cidrMap[cidr] = f.sForwarder + } + } + + return p +} + +func (p *rulesForwarder) Addr() string { return "rule forwarder" } +func (p *rulesForwarder) ListenAndServe() {} +func (p *rulesForwarder) Serve(c net.Conn) {} +func (p *rulesForwarder) CurrentProxy() Proxy { return p.globalForwarder.CurrentProxy() } + +func (p *rulesForwarder) GetProxy(dstAddr string) Proxy { + + logf("dstAddr: %s", dstAddr) + + host, _, err := net.SplitHostPort(dstAddr) + if err != nil { + // TODO: check here + logf("%s", err) + return p.globalForwarder.GetProxy(dstAddr) + } + + // find ip + if ip := net.ParseIP(host); ip != nil { + // check cidr + + // check ip + if p, ok := p.ipMap[ip.String()]; ok { + return p + } + } + + domainParts := strings.Split(host, ".") + length := len(domainParts) + for i := length - 2; i >= 0; i-- { + domain := strings.Join(domainParts[i:length], ".") + + // find in domainMap + if p, ok := p.domainMap[domain]; ok { + return p + } + } + + return p.globalForwarder.GetProxy(dstAddr) +} + +func (p *rulesForwarder) NextProxy() Proxy { + return p.globalForwarder.NextProxy() +} + +func (p *rulesForwarder) Enabled() bool { return true } +func (p *rulesForwarder) SetEnable(enable bool) {} + +func (p *rulesForwarder) Dial(network, addr string) (net.Conn, error) { + return p.GetProxy(addr).Dial(network, addr) +} diff --git a/strategy.go b/strategy.go index 14dd344..b1967f7 100644 --- a/strategy.go +++ b/strategy.go @@ -1,9 +1,6 @@ package main -import ( - "net" - "time" -) +import "net" // newStrategyForwarder . func newStrategyForwarder(strategy string, forwarders []Proxy) Proxy { @@ -46,6 +43,7 @@ func newRRProxy(addr string, forwarders []Proxy) Proxy { return &rrProxy{forwarders: forwarders} } +func (p *rrProxy) Addr() string { return "strategy forwarder" } func (p *rrProxy) ListenAndServe() {} func (p *rrProxy) Serve(c net.Conn) {} func (p *rrProxy) CurrentProxy() Proxy { return p.forwarders[p.idx] } @@ -73,10 +71,9 @@ func (p *rrProxy) NextProxy() Proxy { return p.forwarders[p.idx] } -func (p *rrProxy) Enabled() bool { return true } -func (p *rrProxy) SetEnable(enable bool) {} -func (p *rrProxy) Check(proxy Proxy, target string, duration time.Duration) {} -func (p *rrProxy) Addr() string { return "" } +func (p *rrProxy) Enabled() bool { return true } +func (p *rrProxy) SetEnable(enable bool) {} + func (p *rrProxy) Dial(network, addr string) (net.Conn, error) { return p.NextProxy().Dial(network, addr) }