From 652d49182a838a73f3422259a2baf167cb6c6769 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Wed, 23 Aug 2017 16:35:39 +0800 Subject: [PATCH] 1. general: restructured codes; 2. rule: add dnsserver to ip rule map; 3. dns: add DNSAnswerHandler so we can define a custom handler when a domain resolved --- README.md | 3 +- conf.go | 197 +++++++++++++++++++++++++++++++++++++++++++++++++ dialer.go | 49 ++++++++++++ direct.go | 9 +-- dns.go | 34 +++++++-- dnstun.go | 18 +++-- forwarder.go | 24 ++++++ http.go | 30 ++++---- main.go | 183 ++++++--------------------------------------- mixed.go | 108 ++++++++++++++------------- proxy.go | 159 --------------------------------------- redir_linux.go | 10 ++- redir_other.go | 11 ++- rule.go | 150 ++++++++++++++++++++++++------------- rules.go | 102 ------------------------- server.go | 63 ++++++++++++++++ socks5.go | 35 +++++---- ss.go | 27 ++++--- strategy.go | 159 ++++++++++++++++++++++++++------------- tcptun.go | 13 ++-- 20 files changed, 725 insertions(+), 659 deletions(-) create mode 100644 conf.go create mode 100644 dialer.go create mode 100644 forwarder.go delete mode 100644 proxy.go delete mode 100644 rules.go create mode 100644 server.go diff --git a/README.md b/README.md index f19303c..26efab6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ General: - Rule proxy based on destionation: [Config Examples](examples) TODO: -- Specify different remote dns server in rule file +- Specify different remote dns server in rule file (DONE) +- Improve DNS forwarder to resolve domain name and add ip to proxy rules (WIP) - IPSet management - Improve DNS forwarder to resolve domain name and add ip to ipset - TUN/TAP device support diff --git a/conf.go b/conf.go new file mode 100644 index 0000000..89bab55 --- /dev/null +++ b/conf.go @@ -0,0 +1,197 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/nadoo/conflag" +) + +var flag = conflag.New() + +var conf struct { + Verbose bool + Strategy string + CheckWebSite string + CheckDuration int + Listen []string + Forward []string + RuleFile []string + RulesDir string + + DNS string + DNSServer []string + + IPSet string + + rules []*RuleConf +} + +func confInit() { + flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode") + flag.StringVar(&conf.Strategy, "strategy", "rr", "forward strategy, default: rr") + flag.StringVar(&conf.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80") + flag.IntVar(&conf.CheckDuration, "checkduration", 30, "proxy check duration(seconds)") + flag.StringSliceUniqVar(&conf.Listen, "listen", nil, "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT") + flag.StringSliceUniqVar(&conf.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]") + flag.StringSliceUniqVar(&conf.RuleFile, "rulefile", nil, "rule file path") + flag.StringVar(&conf.RulesDir, "rules-dir", "rules.d", "rule file folder") + + flag.StringVar(&conf.DNS, "dns", "", "dns listen address") + flag.StringSliceUniqVar(&conf.DNSServer, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server") + + flag.StringVar(&conf.IPSet, "ipset", "glider", "ipset name") + + flag.Usage = usage + err := flag.Parse() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) + os.Exit(-1) + } + + if len(conf.Listen) == 0 && conf.DNS == "" { + flag.Usage() + fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n") + os.Exit(-1) + } + + // rulefiles + for _, ruleFile := range conf.RuleFile { + rule, err := NewRuleConfFromFile(ruleFile) + if err != nil { + log.Fatal(err) + } + + conf.rules = append(conf.rules, rule) + } + + ruleFolderFiles, _ := listDir(conf.RulesDir, ".rule") + for _, ruleFile := range ruleFolderFiles { + rule, err := NewRuleConfFromFile(ruleFile) + if err != nil { + log.Fatal(err) + } + + conf.rules = append(conf.rules, rule) + } +} + +// RuleConf , every ruleForwarder points to a rule file +type RuleConf struct { + Forward []string + Strategy string + CheckWebSite string + CheckDuration int + + DNSServer []string + IPSet string + + Domain []string + IP []string + CIDR []string + + name string +} + +// NewRuleConfFromFile . +func NewRuleConfFromFile(ruleFile string) (*RuleConf, error) { + p := &RuleConf{name: ruleFile} + + f := conflag.NewFromFile("rule", ruleFile) + f.StringSliceUniqVar(&p.Forward, "forward", nil, "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", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80") + f.IntVar(&p.CheckDuration, "checkduration", 30, "proxy check duration(seconds)") + + f.StringSliceUniqVar(&p.DNSServer, "dnsserver", nil, "remote dns server") + f.StringVar(&p.IPSet, "ipset", "", "ipset name") + + f.StringSliceUniqVar(&p.Domain, "domain", nil, "domain") + f.StringSliceUniqVar(&p.IP, "ip", nil, "ip") + f.StringSliceUniqVar(&p.CIDR, "cidr", nil, "cidr") + + err := f.Parse() + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) + return nil, err + } + + return p, err +} + +func usage() { + app := os.Args[0] + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, "%s v%s usage:\n", app, VERSION) + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available Schemas:\n") + fmt.Fprintf(os.Stderr, " mixed: serve as a http/socks5 proxy on the same port. (default)\n") + fmt.Fprintf(os.Stderr, " ss: ss proxy\n") + fmt.Fprintf(os.Stderr, " socks5: socks5 proxy\n") + fmt.Fprintf(os.Stderr, " http: http proxy\n") + fmt.Fprintf(os.Stderr, " redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)\n") + fmt.Fprintf(os.Stderr, " tcptun: a simple tcp tunnel\n") + fmt.Fprintf(os.Stderr, " dnstun: listen on udp port and forward all dns requests to remote dns server via forwarders(tcp)\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available schemas for different modes:\n") + fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir tcptun dnstun\n") + fmt.Fprintf(os.Stderr, " forward: ss socks5 http\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available methods for ss:\n") + // fmt.Fprintf(os.Stderr, " "+ListCipher()) + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " NOTE: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Available forward strategies:\n") + fmt.Fprintf(os.Stderr, " rr: Round Robin mode\n") + fmt.Fprintf(os.Stderr, " ha: High Availability mode\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Config file format(see `"+app+".conf.example` as an example):\n") + fmt.Fprintf(os.Stderr, " # COMMENT LINE\n") + fmt.Fprintf(os.Stderr, " KEY=VALUE\n") + fmt.Fprintf(os.Stderr, " KEY=VALUE\n") + fmt.Fprintf(os.Stderr, " # KEY equals to command line flag name: listen forward strategy...\n") + fmt.Fprintf(os.Stderr, "\n") + + fmt.Fprintf(os.Stderr, "Examples:\n") + fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n") + fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n") + fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\n") + fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n") + fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\n") + fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, in verbose mode.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n") + fmt.Fprintf(os.Stderr, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443\n") + fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\n") + fmt.Fprintf(os.Stderr, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n") + fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -listen dnstun://:53=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2\n") + fmt.Fprintf(os.Stderr, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\n") + fmt.Fprintf(os.Stderr, "\n") + fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n") + fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in roundrbin mode.\n") + fmt.Fprintf(os.Stderr, "\n") +} diff --git a/dialer.go b/dialer.go new file mode 100644 index 0000000..71c3ff9 --- /dev/null +++ b/dialer.go @@ -0,0 +1,49 @@ +package main + +import ( + "errors" + "net" + "net/url" +) + +// A Dialer means to establish a connection and relay it. +type Dialer interface { + // Addr() + Addr() string + + // Dial connects to the given address via the proxy. + Dial(network, addr string) (c net.Conn, err error) +} + +// DialerFromURL parses url and get a Proxy +// TODO: table +func DialerFromURL(s string, cDialer Dialer) (Dialer, error) { + u, err := url.Parse(s) + if err != nil { + logf("parse err: %s", err) + return nil, err + } + + addr := u.Host + var user, pass string + if u.User != nil { + user = u.User.Username() + pass, _ = u.User.Password() + } + + if cDialer == nil { + cDialer = Direct + } + + switch u.Scheme { + case "http": + return NewHTTP(addr, cDialer, nil) + case "socks5": + return NewSOCKS5("tcp", addr, user, pass, cDialer, nil) + case "ss": + p, err := NewSS(addr, user, pass, cDialer, nil) + return p, err + } + + return nil, errors.New("unknown schema '" + u.Scheme + "'") +} diff --git a/direct.go b/direct.go index 84da69a..59b03b2 100644 --- a/direct.go +++ b/direct.go @@ -9,14 +9,7 @@ type direct struct { // Direct proxy var Direct = &direct{} -func (d *direct) Addr() string { return "127.0.0.1" } -func (d *direct) ListenAndServe() { logf("direct proxy ListenAndServe") } -func (d *direct) Serve(c net.Conn) { logf("direct 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) Addr() string { return "DIRECT" } func (d *direct) Dial(network, addr string) (net.Conn, error) { c, err := net.Dial(network, addr) diff --git a/dns.go b/dns.go index b47c861..66cecf1 100644 --- a/dns.go +++ b/dns.go @@ -38,7 +38,7 @@ type dnsQuery struct { } type dnsAnswer struct { - DomainName string + // DomainName string QueryType uint16 QueryClass uint16 TTL uint32 @@ -48,18 +48,26 @@ type dnsAnswer struct { IP string } +// DNSAnswerHandler . +type DNSAnswerHandler func(domain, ip string) error + // DNS . type DNS struct { - *proxy + *Forwarder // as proxy client + sDialer Dialer // dialer for server + dnsServer string - dnsServerMap map[string]string + dnsServerMap map[string]string + answerHandlers []DNSAnswerHandler } // NewDNS returns a dns forwarder. client -> dns.udp -> glider -> forwarder -> remote dns addr -func NewDNS(addr, raddr string, upProxy Proxy) (*DNS, error) { +func NewDNS(addr, raddr string, sDialer Dialer) (*DNS, error) { s := &DNS{ - proxy: NewProxy(addr, upProxy), + Forwarder: NewForwarder(addr, nil), + sDialer: sDialer, + dnsServer: raddr, dnsServerMap: make(map[string]string), } @@ -95,8 +103,9 @@ func (s *DNS) ListenAndServe() { domain := query.DomainName dnsServer := s.GetServer(domain) - // TODO: check here - rc, err := s.GetProxy(domain+":53").Dial("tcp", dnsServer) + + // TODO: check here; ADD dnsServer to rule ip lists + rc, err := s.sDialer.Dial("tcp", dnsServer) if err != nil { logf("failed to connect to server %v: %v", dnsServer, err) return @@ -129,6 +138,10 @@ func (s *DNS) ListenAndServe() { ip += answer.IP + "," } + for _, h := range s.answerHandlers { + h(query.DomainName, answer.IP) + } + } } @@ -139,7 +152,7 @@ func (s *DNS) ListenAndServe() { } } - logf("proxy-dns %s, %s <-> %s, ip: %s", domain, clientAddr.String(), dnsServer, ip) + logf("proxy-dns %s <-> %s, %s: %s", clientAddr.String(), dnsServer, domain, ip) }() } @@ -166,6 +179,11 @@ func (s *DNS) GetServer(domain string) string { return s.dnsServer } +// AddAnswerHandler . +func (s *DNS) AddAnswerHandler(h DNSAnswerHandler) { + s.answerHandlers = append(s.answerHandlers, h) +} + func parseQuery(p []byte) *dnsQuery { q := &dnsQuery{} diff --git a/dnstun.go b/dnstun.go index 0055204..df4b53f 100644 --- a/dnstun.go +++ b/dnstun.go @@ -4,22 +4,26 @@ package main // DNSTun . type DNSTun struct { - *proxy + *Forwarder // as client + sDialer Dialer // dialer for server + raddr string - udp Proxy - tcp Proxy + udp *DNS + tcp *TCPTun } // NewDNSTun returns a dns forwarder. -func NewDNSTun(addr, raddr string, upProxy Proxy) (*DNSTun, error) { +func NewDNSTun(addr, raddr string, sDialer Dialer) (*DNSTun, error) { s := &DNSTun{ - proxy: NewProxy(addr, upProxy), + Forwarder: NewForwarder(addr, nil), + sDialer: sDialer, + raddr: raddr, } - s.udp, _ = NewDNS(addr, raddr, upProxy) - s.tcp, _ = NewTCPTun(addr, raddr, upProxy) + s.udp, _ = NewDNS(addr, raddr, sDialer) + s.tcp, _ = NewTCPTun(addr, raddr, sDialer) return s, nil } diff --git a/forwarder.go b/forwarder.go new file mode 100644 index 0000000..ffb1a80 --- /dev/null +++ b/forwarder.go @@ -0,0 +1,24 @@ +package main + +import "net" + +// Forwarder . +type Forwarder struct { + addr string + cDialer Dialer +} + +// NewForwarder . +func NewForwarder(addr string, cDialer Dialer) *Forwarder { + if cDialer == nil { + cDialer = Direct + } + + return &Forwarder{addr: addr, cDialer: cDialer} +} + +func (p *Forwarder) Addr() string { return p.addr } + +func (p *Forwarder) Dial(network, addr string) (net.Conn, error) { + return p.cDialer.Dial(network, addr) +} diff --git a/http.go b/http.go index 811c14c..e4d9ec4 100644 --- a/http.go +++ b/http.go @@ -16,22 +16,24 @@ import ( "time" ) -// HTTPProxy . -type HTTPProxy struct { - *proxy +// HTTP . +type HTTP struct { + *Forwarder // as client + sDialer Dialer // dialer for server } -// NewHTTPProxy returns a http proxy. -func NewHTTPProxy(addr string, upProxy Proxy) (*HTTPProxy, error) { - s := &HTTPProxy{ - proxy: NewProxy(addr, upProxy), +// NewHTTP returns a http proxy. +func NewHTTP(addr string, cDialer Dialer, sDialer Dialer) (*HTTP, error) { + s := &HTTP{ + Forwarder: NewForwarder(addr, cDialer), + sDialer: sDialer, } return s, nil } // ListenAndServe . -func (s *HTTPProxy) ListenAndServe() { +func (s *HTTP) ListenAndServe() { l, err := net.Listen("tcp", s.addr) if err != nil { logf("failed to listen on %s: %v", s.addr, err) @@ -53,7 +55,7 @@ func (s *HTTPProxy) ListenAndServe() { } // Serve . -func (s *HTTPProxy) Serve(c net.Conn) { +func (s *HTTP) Serve(c net.Conn) { defer c.Close() if c, ok := c.(*net.TCPConn); ok { @@ -92,7 +94,7 @@ func (s *HTTPProxy) Serve(c net.Conn) { tgt += ":80" } - rc, err := s.GetProxy(tgt).Dial("tcp", tgt) + rc, err := s.sDialer.Dial("tcp", tgt) if err != nil { fmt.Fprintf(c, "%s 502 ERROR\r\n\r\n", proto) logf("failed to dial: %v", err) @@ -147,8 +149,8 @@ func (s *HTTPProxy) Serve(c net.Conn) { } -func (s *HTTPProxy) servHTTPS(method, requestURI, proto string, c net.Conn) { - rc, err := s.GetProxy(requestURI).Dial("tcp", requestURI) +func (s *HTTP) servHTTPS(method, requestURI, proto string, c net.Conn) { + rc, err := s.sDialer.Dial("tcp", requestURI) if err != nil { c.Write([]byte(proto)) c.Write([]byte(" 502 ERROR\r\n\r\n")) @@ -170,8 +172,8 @@ func (s *HTTPProxy) servHTTPS(method, requestURI, proto string, c net.Conn) { } // Dial connects to the address addr on the network net via the proxy. -func (s *HTTPProxy) Dial(network, addr string) (net.Conn, error) { - rc, err := s.GetProxy(s.addr).Dial("tcp", s.addr) +func (s *HTTP) Dial(network, addr string) (net.Conn, error) { + rc, err := s.cDialer.Dial("tcp", s.addr) if err != nil { logf("dial to %s error: %s", s.addr, err) return nil, err diff --git a/main.go b/main.go index ae9f47b..7850625 100644 --- a/main.go +++ b/main.go @@ -1,155 +1,30 @@ package main import ( - "fmt" "log" "os" "os/signal" "strings" "syscall" - - "github.com/nadoo/conflag" ) // VERSION . const VERSION = "0.4.0" -var conf struct { - Verbose bool - Strategy string - CheckWebSite string - CheckDuration int - Listen []string - Forward []string - RuleFile []string - RulesDir string - - DNS string - DNSServer []string - - IPSet string -} - -var flag = conflag.New() - func logf(f string, v ...interface{}) { if conf.Verbose { log.Printf(f, v...) } } -func usage() { - app := os.Args[0] - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, "%s v%s usage:\n", app, VERSION) - flag.PrintDefaults() - fmt.Fprintf(os.Stderr, "\n") - - fmt.Fprintf(os.Stderr, "Available Schemas:\n") - fmt.Fprintf(os.Stderr, " mixed: serve as a http/socks5 proxy on the same port. (default)\n") - fmt.Fprintf(os.Stderr, " ss: ss proxy\n") - fmt.Fprintf(os.Stderr, " socks5: socks5 proxy\n") - fmt.Fprintf(os.Stderr, " http: http proxy\n") - fmt.Fprintf(os.Stderr, " redir: redirect proxy. (used on linux as a transparent proxy with iptables redirect rules)\n") - fmt.Fprintf(os.Stderr, " tcptun: a simple tcp tunnel\n") - fmt.Fprintf(os.Stderr, " dnstun: listen on udp port and forward all dns requests to remote dns server via forwarders(tcp)\n") - fmt.Fprintf(os.Stderr, "\n") - - fmt.Fprintf(os.Stderr, "Available schemas for different modes:\n") - fmt.Fprintf(os.Stderr, " listen: mixed ss socks5 http redir tcptun dnstun\n") - fmt.Fprintf(os.Stderr, " forward: ss socks5 http\n") - fmt.Fprintf(os.Stderr, "\n") - - fmt.Fprintf(os.Stderr, "Available methods for ss:\n") - fmt.Fprintf(os.Stderr, " "+ListCipher()) - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " NOTE: chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305\n") - fmt.Fprintf(os.Stderr, "\n") - - fmt.Fprintf(os.Stderr, "Available forward strategies:\n") - fmt.Fprintf(os.Stderr, " rr: Round Robin mode\n") - fmt.Fprintf(os.Stderr, " ha: High Availability mode\n") - fmt.Fprintf(os.Stderr, "\n") - - fmt.Fprintf(os.Stderr, "Config file format(see `"+app+".conf.example` as an example):\n") - fmt.Fprintf(os.Stderr, " # COMMENT LINE\n") - fmt.Fprintf(os.Stderr, " KEY=VALUE\n") - fmt.Fprintf(os.Stderr, " KEY=VALUE\n") - fmt.Fprintf(os.Stderr, " # KEY equals to command line flag name: listen forward strategy...\n") - fmt.Fprintf(os.Stderr, "\n") - - fmt.Fprintf(os.Stderr, "Examples:\n") - fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf\n") - fmt.Fprintf(os.Stderr, " -run glider with specified config file.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -config glider.conf -rulefile office.rule -rulefile home.rule\n") - fmt.Fprintf(os.Stderr, " -run glider with specified global config file and rule config files.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen :8443\n") - fmt.Fprintf(os.Stderr, " -listen on :8443, serve as http/socks5 proxy on the same port.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen ss://AEAD_CHACHA20_POLY1305:pass@:8443\n") - fmt.Fprintf(os.Stderr, " -listen on 0.0.0.0:8443 as a ss server.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -verbose\n") - fmt.Fprintf(os.Stderr, " -listen on :1080 as a socks5 proxy server, in verbose mode.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen http://:8080 -forward socks5://127.0.0.1:1080\n") - fmt.Fprintf(os.Stderr, " -listen on :8080 as a http proxy server, forward all requests via socks5 server.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -forward ss://method:pass@1.1.1.1:8443\n") - fmt.Fprintf(os.Stderr, " -listen on :1081 as a transparent redirect server, forward all requests via remote ss server.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen tcptun://:80=2.2.2.2:80 -forward ss://method:pass@1.1.1.1:8443\n") - fmt.Fprintf(os.Stderr, " -listen on :80 and forward all requests to 2.2.2.2:80 via remote ss server.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -listen http://:8080 -forward ss://method:pass@1.1.1.1:8443\n") - fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, :8080 as http proxy server, forward all requests via remote ss server.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen redir://:1081 -listen dnstun://:53=8.8.8.8:53 -forward ss://method:pass@server1:port1,ss://method:pass@server2:port2\n") - fmt.Fprintf(os.Stderr, " -listen on :1081 as transparent redirect server, :53 as dns server, use forward chain: server1 -> server2.\n") - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, " "+app+" -listen socks5://:1080 -forward ss://method:pass@server1:port1 -forward ss://method:pass@server2:port2 -strategy rr\n") - fmt.Fprintf(os.Stderr, " -listen on :1080 as socks5 server, forward requests via server1 and server2 in roundrbin mode.\n") - fmt.Fprintf(os.Stderr, "\n") -} - -func main() { - - flag.BoolVar(&conf.Verbose, "verbose", false, "verbose mode") - flag.StringVar(&conf.Strategy, "strategy", "rr", "forward strategy, default: rr") - flag.StringVar(&conf.CheckWebSite, "checkwebsite", "www.apple.com", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80") - flag.IntVar(&conf.CheckDuration, "checkduration", 30, "proxy check duration(seconds)") - flag.StringSliceUniqVar(&conf.Listen, "listen", nil, "listen url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT") - flag.StringSliceUniqVar(&conf.Forward, "forward", nil, "forward url, format: SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT[,SCHEMA://[USER|METHOD:PASSWORD@][HOST]:PORT]") - flag.StringSliceUniqVar(&conf.RuleFile, "rulefile", nil, "rule file path") - flag.StringVar(&conf.RulesDir, "rules-dir", "rules.d", "rule file folder") - - flag.StringVar(&conf.DNS, "dns", "", "dns listen address") - flag.StringSliceUniqVar(&conf.DNSServer, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server") - - flag.StringVar(&conf.IPSet, "ipset", "glider", "ipset name") - - flag.Usage = usage - err := flag.Parse() - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) - return - } - - if len(conf.Listen) == 0 && conf.DNS == "" { - flag.Usage() - fmt.Fprintf(os.Stderr, "ERROR: listen url must be specified.\n") - return - } - +func dialerFromConf() Dialer { // global forwarders in xx.conf - var forwarders []Proxy + var forwarders []Dialer for _, chain := range conf.Forward { - var forward Proxy + var forward Dialer var err error for _, url := range strings.Split(chain, ",") { - forward, err = ProxyFromURL(url, forward) + forward, err = DialerFromURL(url, forward) if err != nil { log.Fatal(err) } @@ -157,36 +32,18 @@ func main() { forwarders = append(forwarders, forward) } - // combine forwarders to a single strategy forwarder - forwarder := NewStrategyForwarder(conf.Strategy, forwarders) + forwarder := NewStrategyDialer(conf.Strategy, forwarders, conf.CheckWebSite, conf.CheckDuration) - // rule forwarders - var ruleForwarders []*RuleForwarder - for _, ruleFile := range conf.RuleFile { - ruleForwarder, err := NewRuleForwarderFromFile(ruleFile) - if err != nil { - log.Fatal(err) - } + return NewRuleDialer(conf.rules, forwarder) +} - ruleForwarders = append(ruleForwarders, ruleForwarder) - } +func main() { - // rules folder - ruleFolderFiles, _ := listDir(conf.RulesDir, ".rule") - for _, ruleFile := range ruleFolderFiles { - ruleForwarder, err := NewRuleForwarderFromFile(ruleFile) - if err != nil { - log.Fatal(err) - } - - ruleForwarders = append(ruleForwarders, ruleForwarder) - } - - // combine ruleforwarders and global strategy forwarder - forwarder = NewRulesForwarder(ruleForwarders, forwarder) + confInit() + sDialer := dialerFromConf() for _, listen := range conf.Listen { - local, err := ProxyFromURL(listen, forwarder) + local, err := ServerFromURL(listen, sDialer) if err != nil { log.Fatal(err) } @@ -194,20 +51,14 @@ func main() { go local.ListenAndServe() } - if len(forwarders) > 1 { - for _, forward := range forwarders { - go check(forward, conf.CheckWebSite, conf.CheckDuration) - } - } - if conf.DNS != "" { - dns, err := NewDNS(conf.DNS, conf.DNSServer[0], forwarder) + dns, err := NewDNS(conf.DNS, conf.DNSServer[0], sDialer) if err != nil { log.Fatal(err) } // rule - for _, frwder := range ruleForwarders { + for _, frwder := range conf.rules { for _, domain := range frwder.Domain { if len(frwder.DNSServer) > 0 { dns.SetServer(domain, frwder.DNSServer[0]) @@ -215,6 +66,14 @@ func main() { } } + // test here + dns.AddAnswerHandler(func(domain, ip string) error { + if ip != "" { + logf("domain: %s, ip: %s\n", domain, ip) + } + return nil + }) + go dns.ListenAndServe() } diff --git a/mixed.go b/mixed.go index 5845aac..95f625d 100644 --- a/mixed.go +++ b/mixed.go @@ -19,23 +19,26 @@ var httpMethods = [...][]byte{ // MixedProxy . type MixedProxy struct { - *proxy - http Proxy - socks5 Proxy - ss Proxy + sDialer Dialer + + addr string + http *HTTP + socks5 *SOCKS5 + ss *SS } // NewMixedProxy returns a mixed proxy. -func NewMixedProxy(network, addr, user, pass string, upProxy Proxy) (*MixedProxy, error) { +func NewMixedProxy(network, addr, user, pass string, sDialer Dialer) (*MixedProxy, error) { p := &MixedProxy{ - proxy: NewProxy(addr, upProxy), + sDialer: sDialer, + addr: addr, } - p.http, _ = NewHTTPProxy(addr, upProxy) - p.socks5, _ = NewSOCKS5Proxy(network, addr, user, pass, upProxy) + p.http, _ = NewHTTP(addr, nil, sDialer) + p.socks5, _ = NewSOCKS5(network, addr, user, pass, nil, sDialer) if user != "" && pass != "" { - p.ss, _ = NewSSProxy(addr, user, pass, upProxy) + p.ss, _ = NewSS(addr, user, pass, nil, sDialer) } return p, nil @@ -58,48 +61,49 @@ func (p *MixedProxy) ListenAndServe() { continue } - go func() { - defer c.Close() - - if c, ok := c.(*net.TCPConn); ok { - c.SetKeepAlive(true) - } - - c := newConn(c) - - if p.socks5 != nil { - head, err := c.Peek(1) - if err != nil { - logf("peek error: %s", err) - return - } - - // check socks5, client send socksversion: 5 as the first byte - if head[0] == socks5Version { - p.socks5.Serve(c) - return - } - } - - if p.http != nil { - head, err := c.Peek(8) - if err != nil { - logf("peek error: %s", err) - return - } - - for _, method := range httpMethods { - if bytes.HasPrefix(head, method) { - p.http.Serve(c) - return - } - } - } - - if p.ss != nil { - p.ss.Serve(c) - } - - }() + go p.Serve(c) + } +} + +func (p *MixedProxy) Serve(conn net.Conn) { + defer conn.Close() + + if c, ok := conn.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } + + c := newConn(conn) + + if p.socks5 != nil { + head, err := c.Peek(1) + if err != nil { + logf("peek error: %s", err) + return + } + + // check socks5, client send socksversion: 5 as the first byte + if head[0] == socks5Version { + p.socks5.Serve(c) + return + } + } + + if p.http != nil { + head, err := c.Peek(8) + if err != nil { + logf("peek error: %s", err) + return + } + + for _, method := range httpMethods { + if bytes.HasPrefix(head, method) { + p.http.Serve(c) + return + } + } + } + + if p.ss != nil { + p.ss.Serve(c) } } diff --git a/proxy.go b/proxy.go deleted file mode 100644 index eb741dc..0000000 --- a/proxy.go +++ /dev/null @@ -1,159 +0,0 @@ -package main - -import ( - "bytes" - "errors" - "io" - "net" - "net/url" - "strings" - "time" -) - -// 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 current proxy - CurrentProxy() Proxy - - // Get a proxy based on the destAddr and strategy - GetProxy(dstAddr string) Proxy - - // Switch to the next proxy - NextProxy() Proxy - - // Get the status of proxy - Enabled() bool - - // Set the proxy status - SetEnable(enable bool) - - // Dial connects to the given address via the proxy. - Dial(network, addr string) (c net.Conn, err error) -} - -// proxy -type proxy struct { - addr string - forward Proxy - enabled bool -} - -// NewProxy . -func NewProxy(addr string, forward Proxy) *proxy { - if forward == nil { - forward = Direct - } - - 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 } -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) Dial(network, addr string) (net.Conn, error) { - return p.forward.Dial(network, addr) -} - -// ProxyFromURL parses url and get a Proxy -// TODO: table -func ProxyFromURL(s string, forwarder Proxy) (Proxy, error) { - if !strings.Contains(s, "://") { - s = "mixed://" + s - } - - u, err := url.Parse(s) - if err != nil { - logf("parse err: %s", err) - return nil, err - } - - addr := u.Host - var user, pass string - if u.User != nil { - user = u.User.Username() - pass, _ = u.User.Password() - } - - switch u.Scheme { - case "mixed": - return NewMixedProxy("tcp", addr, user, pass, forwarder) - case "http": - return NewHTTPProxy(addr, forwarder) - case "socks5": - return NewSOCKS5Proxy("tcp", addr, user, pass, forwarder) - case "ss": - p, err := NewSSProxy(addr, user, pass, forwarder) - return p, err - case "redir": - return NewRedirProxy(addr, forwarder) - case "tcptun": - d := strings.Split(addr, "=") - return NewTCPTun(d[0], d[1], forwarder) - case "dnstun": - d := strings.Split(addr, "=") - return NewDNSTun(d[0], d[1], forwarder) - } - - return nil, errors.New("unknown schema '" + u.Scheme + "'") -} - -// Check proxy -func check(p Proxy, webhost string, duration int) { - retry := 1 - buf := make([]byte, 4) - - if strings.IndexByte(webhost, ':') == -1 { - webhost = webhost + ":80" - } - - for { - time.Sleep(time.Duration(duration) * time.Second * time.Duration(retry>>1)) - retry <<= 1 - - if retry > 16 { - retry = 16 - } - - startTime := time.Now() - c, err := p.Dial("tcp", webhost) - if err != nil { - p.SetEnable(false) - logf("proxy-check %s -> %s, set to DISABLED. error in dial: %s", p.Addr(), webhost, err) - continue - } - - c.Write([]byte("GET / HTTP/1.0")) - c.Write([]byte("\r\n\r\n")) - - _, err = io.ReadFull(c, buf) - if err != nil { - p.SetEnable(false) - logf("proxy-check %s -> %s, set to DISABLED. error in read: %s", p.Addr(), webhost, err) - } else if bytes.Equal([]byte("HTTP"), buf) { - p.SetEnable(true) - retry = 2 - dialTime := time.Since(startTime) - logf("proxy-check %s -> %s, set to ENABLED. connect time: %s", p.Addr(), webhost, dialTime.String()) - } else { - p.SetEnable(false) - logf("proxy-check %s -> %s, set to DISABLED. server response: %s", p.Addr(), webhost, buf) - } - - c.Close() - } -} diff --git a/redir_linux.go b/redir_linux.go index fbb6e6f..167e2ca 100644 --- a/redir_linux.go +++ b/redir_linux.go @@ -18,13 +18,15 @@ const ( ) type RedirProxy struct { - *proxy + *Forwarder // as client + sDialer Dialer // dialer for server } // NewRedirProxy returns a redirect proxy. -func NewRedirProxy(addr string, upProxy Proxy) (*RedirProxy, error) { +func NewRedirProxy(addr string, sDialer Dialer) (*RedirProxy, error) { s := &RedirProxy{ - proxy: NewProxy(addr, upProxy), + Forwarder: NewForwarder(addr, nil), + sDialer: sDialer, } return s, nil @@ -60,7 +62,7 @@ func (s *RedirProxy) ListenAndServe() { return } - rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String()) + rc, err := s.sDialer.Dial("tcp", tgt.String()) if err != nil { logf("failed to connect to target: %v", err) return diff --git a/redir_other.go b/redir_other.go index 44e72fc..9487e7a 100644 --- a/redir_other.go +++ b/redir_other.go @@ -2,14 +2,17 @@ package main -import "log" +import ( + "errors" + "log" +) // RedirProxy . -type RedirProxy struct{ *proxy } +type RedirProxy struct{} // NewRedirProxy returns a redirect proxy. -func NewRedirProxy(addr string, upProxy Proxy) (Proxy, error) { - return &RedirProxy{proxy: NewProxy(addr, upProxy)}, nil +func NewRedirProxy(addr string, sDialer Dialer) (*RedirProxy, error) { + return nil, errors.New("redir not supported on this os") } // ListenAndServe redirected requests as a server. diff --git a/rule.go b/rule.go index 9946ad9..55f76d9 100644 --- a/rule.go +++ b/rule.go @@ -1,75 +1,119 @@ package main import ( - "fmt" "log" - "os" + "net" "strings" - - "github.com/nadoo/conflag" ) -// RuleForwarder , every ruleForwarder points to a rule file -type RuleForwarder struct { - Forward []string - Strategy string - CheckWebSite string - CheckDuration int +// RuleDialer . +type RuleDialer struct { + gDialer Dialer - DNSServer []string - IPSet string - - Domain []string - IP []string - CIDR []string - - name string - Proxy + domainMap map[string]Dialer + ipMap map[string]Dialer + cidrMap map[string]Dialer } -// NewRuleForwarderFromFile . -func NewRuleForwarderFromFile(ruleFile string) (*RuleForwarder, error) { - p := &RuleForwarder{name: ruleFile} +// NewRuleDialer . +func NewRuleDialer(rules []*RuleConf, gDialer Dialer) Dialer { - f := conflag.NewFromFile("rule", ruleFile) - f.StringSliceUniqVar(&p.Forward, "forward", nil, "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", "proxy check HTTP(NOT HTTPS) website address, format: HOST[:PORT], default port: 80") - f.IntVar(&p.CheckDuration, "checkduration", 30, "proxy check duration(seconds)") - - f.StringSliceUniqVar(&p.DNSServer, "dnsserver", nil, "remote dns server") - f.StringVar(&p.IPSet, "ipset", "", "ipset name") - - f.StringSliceUniqVar(&p.Domain, "domain", nil, "domain") - f.StringSliceUniqVar(&p.IP, "ip", nil, "ip") - f.StringSliceUniqVar(&p.CIDR, "cidr", nil, "cidr") - - err := f.Parse() - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: %s\n", err) - return nil, err + if len(rules) == 0 { + return gDialer } - 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) + rd := &RuleDialer{gDialer: gDialer} + + for _, r := range rules { + var forwarders []Dialer + for _, chain := range r.Forward { + var forward Dialer + var err error + for _, url := range strings.Split(chain, ",") { + forward, err = DialerFromURL(url, forward) + if err != nil { + log.Fatal(err) + } + } + forwarders = append(forwarders, forward) + } + + sd := NewStrategyDialer(r.Strategy, forwarders, r.CheckWebSite, r.CheckDuration) + + rd.domainMap = make(map[string]Dialer) + for _, domain := range r.Domain { + rd.domainMap[domain] = sd + } + + rd.ipMap = make(map[string]Dialer) + for _, ip := range r.IP { + rd.ipMap[ip] = sd + } + + // dnsserver should use rule forwarder too + for _, dnss := range r.DNSServer { + ip, _, err := net.SplitHostPort(dnss) if err != nil { - log.Fatal(err) + logf("SplitHostPort ERROR: %s", err) + continue + } + rd.ipMap[ip] = sd + } + + rd.cidrMap = make(map[string]Dialer) + for _, cidr := range r.CIDR { + rd.cidrMap[cidr] = sd + } + } + + return rd +} + +func (rd *RuleDialer) Addr() string { return "RULES" } + +func (p *RuleDialer) NextDialer(dstAddr string) Dialer { + + // TODO: change to index finders + host, _, err := net.SplitHostPort(dstAddr) + if err != nil { + // TODO: check here + logf("SplitHostPort ERROR: %s", err) + return p.gDialer + } + + // find ip + if ip := net.ParseIP(host); ip != nil { + // check ip + if d, ok := p.ipMap[ip.String()]; ok { + return d + } + + // check cidr + // TODO: do not parse cidr every time + for cidrStr, d := range p.cidrMap { + if _, net, err := net.ParseCIDR(cidrStr); err == nil { + if net.Contains(ip) { + return d + } } } - forwarders = append(forwarders, forward) } - forwarder := NewStrategyForwarder(p.Strategy, forwarders) + domainParts := strings.Split(host, ".") + length := len(domainParts) + for i := length - 2; i >= 0; i-- { + domain := strings.Join(domainParts[i:length], ".") - for _, forward := range forwarders { - go check(forward, p.CheckWebSite, p.CheckDuration) + // find in domainMap + if d, ok := p.domainMap[domain]; ok { + return d + } } - p.Proxy = forwarder - - return p, err + return p.gDialer +} + +func (rd *RuleDialer) Dial(network, addr string) (net.Conn, error) { + d := rd.NextDialer(addr) + return d.Dial(network, addr) } diff --git a/rules.go b/rules.go deleted file mode 100644 index a5b751c..0000000 --- a/rules.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - "net" - "strings" -) - -// RulesForwarder . -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 { - - if len(ruleForwarders) == 0 { - return globalForwarder - } - - p := &RulesForwarder{globalForwarder: globalForwarder} - - for _, f := range ruleForwarders { - p.domainMap = make(map[string]Proxy) - for _, domain := range f.Domain { - p.domainMap[domain] = f.Proxy - } - - p.ipMap = make(map[string]Proxy) - for _, ip := range f.IP { - p.ipMap[ip] = f.Proxy - } - - p.cidrMap = make(map[string]Proxy) - for _, cidr := range f.CIDR { - p.cidrMap[cidr] = f.Proxy - } - } - - return p -} - -func (p *RulesForwarder) Addr() string { return "rules 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 { - - // TODO: change to index finders - host, _, err := net.SplitHostPort(dstAddr) - if err != nil { - // TODO: check here - logf("SplitHostPort ERROR: %s", err) - return p.globalForwarder.GetProxy(dstAddr) - } - - // find ip - if ip := net.ParseIP(host); ip != nil { - // check ip - if proxy, ok := p.ipMap[ip.String()]; ok { - return proxy - } - - // check cidr - // TODO: do not parse cidr every time - for cidrStr, proxy := range p.cidrMap { - if _, net, err := net.ParseCIDR(cidrStr); err == nil { - if net.Contains(ip) { - return proxy - } - } - } - } - - domainParts := strings.Split(host, ".") - length := len(domainParts) - for i := length - 2; i >= 0; i-- { - domain := strings.Join(domainParts[i:length], ".") - - // find in domainMap - if proxy, ok := p.domainMap[domain]; ok { - return proxy - } - } - - 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/server.go b/server.go new file mode 100644 index 0000000..bbc5682 --- /dev/null +++ b/server.go @@ -0,0 +1,63 @@ +package main + +import ( + "errors" + "net/url" + "strings" +) + +// Server . +type Server interface { + // ListenAndServe as proxy server, use only in server mode. + ListenAndServe() + + // Serve + // Serve(c net.Conn) +} + +// ServerFromURL parses url and get a Proxy +// TODO: table +func ServerFromURL(s string, sDialer Dialer) (Server, error) { + if !strings.Contains(s, "://") { + s = "mixed://" + s + } + + u, err := url.Parse(s) + if err != nil { + logf("parse err: %s", err) + return nil, err + } + + addr := u.Host + var user, pass string + if u.User != nil { + user = u.User.Username() + pass, _ = u.User.Password() + } + + if sDialer == nil { + sDialer = Direct + } + + switch u.Scheme { + case "mixed": + return NewMixedProxy("tcp", addr, user, pass, sDialer) + case "http": + return NewHTTP(addr, nil, sDialer) + case "socks5": + return NewSOCKS5("tcp", addr, user, pass, nil, sDialer) + case "ss": + p, err := NewSS(addr, user, pass, nil, sDialer) + return p, err + case "redir": + return NewRedirProxy(addr, sDialer) + case "tcptun": + d := strings.Split(addr, "=") + return NewTCPTun(d[0], d[1], sDialer) + case "dnstun": + d := strings.Split(addr, "=") + return NewDNSTun(d[0], d[1], sDialer) + } + + return nil, errors.New("unknown schema '" + u.Scheme + "'") +} diff --git a/socks5.go b/socks5.go index e266563..1922f5e 100644 --- a/socks5.go +++ b/socks5.go @@ -55,28 +55,31 @@ var socks5Errors = []string{ "address type not supported", } -// SOCKS5Proxy . -type SOCKS5Proxy struct { - *proxy +// SOCKS5 . +type SOCKS5 struct { + *Forwarder + sDialer Dialer + network string user string password string } -// NewSOCKS5Proxy returns a Proxy that makes SOCKSv5 connections to the given address +// NewSOCKS5 returns a Proxy that makes SOCKSv5 connections to the given address // with an optional username and password. See RFC 1928. -func NewSOCKS5Proxy(network, addr, user, pass string, upProxy Proxy) (*SOCKS5Proxy, error) { - s := &SOCKS5Proxy{ - proxy: NewProxy(addr, upProxy), - user: user, - password: pass, +func NewSOCKS5(network, addr, user, pass string, cDialer Dialer, sDialer Dialer) (*SOCKS5, error) { + s := &SOCKS5{ + Forwarder: NewForwarder(addr, cDialer), + sDialer: sDialer, + user: user, + password: pass, } return s, nil } // ListenAndServe connects to the address addr on the network net via the SOCKS5 proxy. -func (s *SOCKS5Proxy) ListenAndServe() { +func (s *SOCKS5) ListenAndServe() { l, err := net.Listen("tcp", s.addr) if err != nil { logf("failed to listen on %s: %v", s.addr, err) @@ -97,7 +100,7 @@ func (s *SOCKS5Proxy) ListenAndServe() { } // Serve . -func (s *SOCKS5Proxy) Serve(c net.Conn) { +func (s *SOCKS5) Serve(c net.Conn) { defer c.Close() if c, ok := c.(*net.TCPConn); ok { @@ -110,7 +113,7 @@ func (s *SOCKS5Proxy) Serve(c net.Conn) { return } - rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String()) + rc, err := s.sDialer.Dial("tcp", tgt.String()) if err != nil { logf("failed to connect to target: %v", err) return @@ -129,14 +132,14 @@ func (s *SOCKS5Proxy) Serve(c net.Conn) { } // Dial connects to the address addr on the network net via the SOCKS5 proxy. -func (s *SOCKS5Proxy) Dial(network, addr string) (net.Conn, error) { +func (s *SOCKS5) Dial(network, addr string) (net.Conn, error) { switch network { case "tcp", "tcp6", "tcp4": default: return nil, errors.New("proxy: no support for SOCKS5 proxy connections of type " + network) } - c, err := s.GetProxy(s.addr).Dial(s.network, s.addr) + c, err := s.cDialer.Dial(s.network, s.addr) if err != nil { logf("dial to %s error: %s", s.addr, err) return nil, err @@ -157,7 +160,7 @@ func (s *SOCKS5Proxy) Dial(network, addr string) (net.Conn, error) { // connect takes an existing connection to a socks5 proxy server, // and commands the server to extend that connection to target, // which must be a canonical address with a host and port. -func (s *SOCKS5Proxy) connect(conn net.Conn, target string) error { +func (s *SOCKS5) connect(conn net.Conn, target string) error { host, portStr, err := net.SplitHostPort(target) if err != nil { return err @@ -288,7 +291,7 @@ func (s *SOCKS5Proxy) connect(conn net.Conn, target string) error { } // Handshake fast-tracks SOCKS initialization to get target address to connect. -func (s *SOCKS5Proxy) handshake(rw io.ReadWriter) (Addr, error) { +func (s *SOCKS5) handshake(rw io.ReadWriter) (Addr, error) { // Read RFC 1928 for request and reply structure and sizes. buf := make([]byte, MaxAddrLen) // read VER, NMETHODS, METHODS diff --git a/ss.go b/ss.go index 39c8347..923f220 100644 --- a/ss.go +++ b/ss.go @@ -9,21 +9,24 @@ import ( "github.com/shadowsocks/go-shadowsocks2/core" ) -// SSProxy . -type SSProxy struct { - *proxy +// SS . +type SS struct { + *Forwarder + sDialer Dialer + core.StreamConnCipher } -// NewSSProxy returns a shadowsocks proxy. -func NewSSProxy(addr, method, pass string, upProxy Proxy) (*SSProxy, error) { +// NewSS returns a shadowsocks proxy. +func NewSS(addr, method, pass string, cDialer Dialer, sDialer Dialer) (*SS, error) { ciph, err := core.PickCipher(method, nil, pass) if err != nil { log.Fatalf("PickCipher for '%s', error: %s", method, err) } - s := &SSProxy{ - proxy: NewProxy(addr, upProxy), + s := &SS{ + Forwarder: NewForwarder(addr, cDialer), + sDialer: sDialer, StreamConnCipher: ciph, } @@ -31,7 +34,7 @@ func NewSSProxy(addr, method, pass string, upProxy Proxy) (*SSProxy, error) { } // ListenAndServe shadowsocks requests as a server. -func (s *SSProxy) ListenAndServe() { +func (s *SS) ListenAndServe() { l, err := net.Listen("tcp", s.addr) if err != nil { logf("failed to listen on %s: %v", s.addr, err) @@ -51,7 +54,7 @@ func (s *SSProxy) ListenAndServe() { } // Serve . -func (s *SSProxy) Serve(c net.Conn) { +func (s *SS) Serve(c net.Conn) { defer c.Close() if c, ok := c.(*net.TCPConn); ok { @@ -66,7 +69,7 @@ func (s *SSProxy) Serve(c net.Conn) { return } - rc, err := s.GetProxy(tgt.String()).Dial("tcp", tgt.String()) + rc, err := s.sDialer.Dial("tcp", tgt.String()) if err != nil { logf("failed to connect to target: %v", err) return @@ -86,14 +89,14 @@ func (s *SSProxy) Serve(c net.Conn) { } // Dial connects to the address addr on the network net via the proxy. -func (s *SSProxy) Dial(network, addr string) (net.Conn, error) { +func (s *SS) Dial(network, addr string) (net.Conn, error) { target := ParseAddr(addr) if target == nil { return nil, errors.New("Unable to parse address: " + addr) } - c, err := s.GetProxy(s.addr).Dial("tcp", s.addr) + c, err := s.cDialer.Dial("tcp", s.addr) if err != nil { logf("dial to %s error: %s", s.addr, err) return nil, err diff --git a/strategy.go b/strategy.go index 08036bf..02371ed 100644 --- a/strategy.go +++ b/strategy.go @@ -1,64 +1,80 @@ package main -import "net" +import ( + "bytes" + "io" + "net" + "strings" + "time" +) -// NewStrategyForwarder . -func NewStrategyForwarder(strategy string, forwarders []Proxy) Proxy { - var proxy Proxy - if len(forwarders) == 0 { - proxy = Direct - } else if len(forwarders) == 1 { - proxy = forwarders[0] - } else if len(forwarders) > 1 { +// NewStrategyDialer . +func NewStrategyDialer(strategy string, dialers []Dialer, website string, duration int) Dialer { + var dialer Dialer + if len(dialers) == 0 { + dialer = Direct + } else if len(dialers) == 1 { + dialer = dialers[0] + } else if len(dialers) > 1 { switch strategy { case "rr": - proxy = newRRProxy("", forwarders) + dialer = newRRDialer(dialers, website, duration) logf("forward to remote servers in round robin mode.") case "ha": - proxy = newHAProxy("", forwarders) + dialer = newHADialer(dialers, website, duration) logf("forward to remote servers in high availability mode.") default: logf("not supported forward mode '%s', just use the first forward server.", conf.Strategy) - proxy = forwarders[0] + dialer = dialers[0] } } - return proxy + return dialer } -// rrProxy -type rrProxy struct { - forwarders []Proxy - idx int +// rrDialer +type rrDialer struct { + dialers []Dialer + idx int + + status map[int]bool + + // for checking + website string + duration int } -// newRRProxy . -func newRRProxy(addr string, forwarders []Proxy) Proxy { - if len(forwarders) == 0 { - return Direct - } else if len(forwarders) == 1 { - return NewProxy(addr, forwarders[0]) +// newRRDialer . +func newRRDialer(dialers []Dialer, website string, duration int) *rrDialer { + rr := &rrDialer{dialers: dialers} + + rr.status = make(map[int]bool) + rr.website = website + rr.duration = duration + + for k := range dialers { + rr.status[k] = true + go rr.checkDialer(k) } - return &rrProxy{forwarders: forwarders} + return rr } -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] } -func (p *rrProxy) GetProxy(dstAddr string) Proxy { return p.NextProxy() } +func (rr *rrDialer) Addr() string { return "STRATEGY" } +func (rr *rrDialer) Dial(network, addr string) (net.Conn, error) { + return rr.NextDialer().Dial(network, addr) +} -func (p *rrProxy) NextProxy() Proxy { - n := len(p.forwarders) +func (rr *rrDialer) NextDialer() Dialer { + n := len(rr.dialers) if n == 1 { - return p.forwarders[0] + rr.idx = 0 } found := false for i := 0; i < n; i++ { - p.idx = (p.idx + 1) % n - if p.forwarders[p.idx].Enabled() { + rr.idx = (rr.idx + 1) % n + if rr.status[rr.idx] { found = true break } @@ -68,34 +84,73 @@ func (p *rrProxy) NextProxy() Proxy { logf("NO AVAILABLE PROXY FOUND! please check your network or proxy server settings.") } - return p.forwarders[p.idx] + return rr.dialers[rr.idx] } -func (p *rrProxy) Enabled() bool { return true } -func (p *rrProxy) SetEnable(enable bool) {} +// Check dialer +func (rr *rrDialer) checkDialer(idx int) { + retry := 1 + buf := make([]byte, 4) -func (p *rrProxy) Dial(network, addr string) (net.Conn, error) { - return p.GetProxy(addr).Dial(network, addr) + if strings.IndexByte(rr.website, ':') == -1 { + rr.website = rr.website + ":80" + } + + d := rr.dialers[idx] + + for { + time.Sleep(time.Duration(rr.duration) * time.Second * time.Duration(retry>>1)) + retry <<= 1 + + if retry > 16 { + retry = 16 + } + + startTime := time.Now() + c, err := d.Dial("tcp", rr.website) + if err != nil { + rr.status[idx] = false + logf("proxy-check %s -> %s, set to DISABLED. error in dial: %s", d.Addr(), rr.website, err) + continue + } + + c.Write([]byte("GET / HTTP/1.0")) + c.Write([]byte("\r\n\r\n")) + + _, err = io.ReadFull(c, buf) + if err != nil { + rr.status[idx] = false + logf("proxy-check %s -> %s, set to DISABLED. error in read: %s", d.Addr(), rr.website, err) + } else if bytes.Equal([]byte("HTTP"), buf) { + rr.status[idx] = true + retry = 2 + dialTime := time.Since(startTime) + logf("proxy-check %s -> %s, set to ENABLED. connect time: %s", d.Addr(), rr.website, dialTime.String()) + } else { + rr.status[idx] = false + logf("proxy-check %s -> %s, set to DISABLED. server response: %s", d.Addr(), rr.website, buf) + } + + c.Close() + } } // high availability proxy -type haProxy struct { - Proxy +type haDialer struct { + *rrDialer } -// newHAProxy . -func newHAProxy(addr string, forwarders []Proxy) Proxy { - return &haProxy{Proxy: newRRProxy(addr, forwarders)} +// newHADialer . +func newHADialer(dialers []Dialer, webhost string, duration int) Dialer { + return &haDialer{rrDialer: newRRDialer(dialers, webhost, duration)} } -func (p *haProxy) GetProxy(dstAddr string) Proxy { - proxy := p.CurrentProxy() - if proxy.Enabled() == false { - return p.NextProxy() +func (ha *haDialer) Dial(network, addr string) (net.Conn, error) { + d := ha.dialers[ha.idx] + + if !ha.status[ha.idx] { + d = ha.NextDialer() } - return proxy -} -func (p *haProxy) Dial(network, addr string) (net.Conn, error) { - return p.GetProxy(addr).Dial(network, addr) + return d.Dial(network, addr) } diff --git a/tcptun.go b/tcptun.go index 49d9a92..c9fe687 100644 --- a/tcptun.go +++ b/tcptun.go @@ -4,15 +4,18 @@ import "net" // TCPTun . type TCPTun struct { - *proxy + *Forwarder + sDialer Dialer + raddr string } // NewTCPTun returns a redirect proxy. -func NewTCPTun(addr, raddr string, upProxy Proxy) (*TCPTun, error) { +func NewTCPTun(addr, raddr string, sDialer Dialer) (*TCPTun, error) { s := &TCPTun{ - proxy: NewProxy(addr, upProxy), - raddr: raddr, + Forwarder: NewForwarder(addr, nil), + sDialer: sDialer, + raddr: raddr, } return s, nil @@ -42,7 +45,7 @@ func (s *TCPTun) ListenAndServe() { c.SetKeepAlive(true) } - rc, err := s.GetProxy(s.raddr).Dial("tcp", s.raddr) + rc, err := s.sDialer.Dial("tcp", s.raddr) if err != nil { logf("failed to connect to target: %v", err)