diff --git a/config/examples/9.transparent_proxy_without_dnsmasq/README.md b/config/examples/9.transparent_proxy_without_dnsmasq/README.md index 4971a38..e0cdd2b 100644 --- a/config/examples/9.transparent_proxy_without_dnsmasq/README.md +++ b/config/examples/9.transparent_proxy_without_dnsmasq/README.md @@ -88,10 +88,10 @@ Use the linux server's ip as your dns server. #### When client requesting to access http://example1.com (in office.rule), the whole process: DNS Resolving: -1. client sends a udp dns request to linux server, and glider will receive the request(as it listen on default dns port :53) +1. client sends a udp dns request to linux server, and glider will receive the request(as it listens on the default dns port :53) 2. upstream dns server choice: glider will lookup it's rule config and find out the dns server to use for this domain(matched "example1.com" in office.rule, so 208.67.222.222:53 will be chosen) -3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers. -4. glider updates it's office rule config, add the resolved ip address to it. +3. glider uses the forwarder in office.rule to ask 208.67.222.222:53 for the resolve answers(dns over proxy). +4. glider updates it's office rule config, adds the resolved ip address to it. 5. glider adds the resolved ip into ipset "glider", and return the dns answer to client. Destination Accessing: diff --git a/go.mod b/go.mod index 7347142..6a70e51 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,9 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/xtaci/kcp-go/v5 v5.5.15 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a - golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect - golang.org/x/sys v0.0.0-20200922070232-aee5d888a860 // indirect - golang.org/x/tools v0.0.0-20200923053713-ba800b16d873 // indirect + golang.org/x/net v0.0.0-20200923182212-328152dc79b1 // indirect + golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d // indirect + golang.org/x/tools v0.0.0-20200923182640-463111b69878 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 02d30b6..7fb260f 100644 --- a/go.sum +++ b/go.sum @@ -104,8 +104,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200923182212-328152dc79b1 h1:Iu68XRPd67wN4aRGGWwwq6bZo/25jR6uu52l/j2KkUE= +golang.org/x/net v0.0.0-20200923182212-328152dc79b1/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -120,15 +120,15 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9 h1:yi1hN8dcqI9l8klZfy4B8mJvFmmAxJEePIQQFNSd7Cs= golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860 h1:YEu4SMq7D0cmT7CBbXfcH0NZeuChAXwsHe/9XueUO6o= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d h1:L/IKR6COd7ubZrs2oTnTi73IhgqJ71c9s80WsQnh0Es= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c h1:iHhCR0b26amDCiiO+kBguKZom9aMF+NrFxh9zeKR/XU= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200923053713-ba800b16d873 h1:Q5Sq7Lt0bkn6Ax1NAraQhKRN7xxxy1LV4guxsyFHZx4= -golang.org/x/tools v0.0.0-20200923053713-ba800b16d873/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200923182640-463111b69878 h1:VUw1+Jf6KJPf82mbTQMia6HCnNMv2BbAipkEZ4KTcqQ= +golang.org/x/tools v0.0.0-20200923182640-463111b69878/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/main.go b/main.go index c4ff691..c3cca2c 100644 --- a/main.go +++ b/main.go @@ -50,14 +50,14 @@ func main() { } // global rule proxy - p := rule.NewProxy(conf.Forward, &conf.StrategyConfig, conf.rules) + pxy := rule.NewProxy(conf.Forward, &conf.StrategyConfig, conf.rules) // ipset manager ipsetM, _ := ipset.NewManager(conf.rules) // check and setup dns server if conf.DNS != "" { - d, err := dns.NewServer(conf.DNS, p, &conf.DNSConfig) + d, err := dns.NewServer(conf.DNS, pxy, &conf.DNSConfig) if err != nil { log.Fatal(err) } @@ -81,7 +81,7 @@ func main() { } // add a handler to update proxy rules when a domain resolved - d.AddHandler(p.AddDomainIP) + d.AddHandler(pxy.AddDomainIP) if ipsetM != nil { d.AddHandler(ipsetM.AddDomainIP) } @@ -90,11 +90,11 @@ func main() { } // enable checkers - p.Check() + pxy.Check() // Proxy Servers for _, listen := range conf.Listen { - local, err := proxy.ServerFromURL(listen, p) + local, err := proxy.ServerFromURL(listen, pxy) if err != nil { log.Fatal(err) } diff --git a/proxy/direct.go b/proxy/direct.go index 305c942..81b60e3 100644 --- a/proxy/direct.go +++ b/proxy/direct.go @@ -8,7 +8,7 @@ import ( "github.com/nadoo/glider/common/log" ) -// Direct proxy +// Direct proxy. type Direct struct { iface *net.Interface // interface specified by user ip net.IP @@ -16,7 +16,7 @@ type Direct struct { relayTimeout time.Duration } -// Default dialer +// Default dialer. var Default = &Direct{dialTimeout: time.Second * 3} // NewDirect returns a Direct dialer. diff --git a/proxy/proxy.go b/proxy/proxy.go index e643348..7b72f2f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -2,7 +2,7 @@ package proxy import "net" -// Proxy is a dialer manager +// Proxy is a dialer manager. type Proxy interface { // Dial connects to the given address via the proxy. Dial(network, addr string) (c net.Conn, dialer Dialer, err error) diff --git a/rule/config.go b/rule/config.go index 8820a18..57e2413 100644 --- a/rule/config.go +++ b/rule/config.go @@ -9,7 +9,7 @@ import ( "github.com/nadoo/conflag" ) -// Config of rule dialer. +// Config is config of rule. type Config struct { Name string @@ -24,6 +24,19 @@ type Config struct { CIDR []string } +// StrategyConfig is config of strategy. +type StrategyConfig struct { + Strategy string + CheckWebSite string + CheckInterval int + CheckTimeout int + CheckDisabledOnly bool + MaxFailures int + DialTimeout int + RelayTimeout int + IntFace string +} + // NewConfFromFile returns a new config from file. func NewConfFromFile(ruleFile string) (*Config, error) { p := &Config{Name: ruleFile} diff --git a/rule/forward.go b/rule/forward.go index d828718..32a6e02 100644 --- a/rule/forward.go +++ b/rule/forward.go @@ -12,10 +12,10 @@ import ( "github.com/nadoo/glider/proxy" ) -// StatusHandler function will be called when the forwarder's status changed +// StatusHandler function will be called when the forwarder's status changed. type StatusHandler func(*Forwarder) -// Forwarder is a forwarder +// Forwarder associates with a `-forward` command, usually a dialer or a chain of dialers. type Forwarder struct { proxy.Dialer addr string diff --git a/rule/strategy.go b/rule/group.go similarity index 72% rename from rule/strategy.go rename to rule/group.go index 1a8552a..ac17a66 100644 --- a/rule/strategy.go +++ b/rule/group.go @@ -15,20 +15,7 @@ import ( "github.com/nadoo/glider/proxy" ) -// StrategyConfig is strategy config struct. -type StrategyConfig struct { - Strategy string - CheckWebSite string - CheckInterval int - CheckTimeout int - CheckDisabledOnly bool - MaxFailures int - DialTimeout int - RelayTimeout int - IntFace string -} - -// forwarder slice orderd by priority +// forwarder slice orderd by priority. type priSlice []*Forwarder func (p priSlice) Len() int { return len(p) } @@ -69,7 +56,7 @@ func NewFwdrGroup(name string, s []string, c *StrategyConfig) *FwdrGroup { return newFwdrGroup(name, fwdrs, c) } -// newFwdrGroup returns a new Proxy. +// newFwdrGroup returns a new FwdrGroup. func newFwdrGroup(name string, fwdrs []*Forwarder, c *StrategyConfig) *FwdrGroup { p := &FwdrGroup{fwdrs: fwdrs, config: c} sort.Sort(p.fwdrs) @@ -80,22 +67,28 @@ func newFwdrGroup(name string, fwdrs []*Forwarder, c *StrategyConfig) *FwdrGroup p.config.CheckWebSite += ":80" } - switch c.Strategy { - case "rr": - p.next = p.scheduleRR - log.F("[strategy] %s: forward in round robin mode.", name) - case "ha": - p.next = p.scheduleHA - log.F("[strategy] %s: forward in high availability mode.", name) - case "lha": - p.next = p.scheduleLHA - log.F("[strategy] %s: forward in latency based high availability mode.", name) - case "dh": - p.next = p.scheduleDH - log.F("[strategy] %s: forward in destination hashing mode.", name) - default: - p.next = p.scheduleRR - log.F("[strategy] %s: not supported forward mode '%s', use round robin mode.", name, c.Strategy) + // default scheduler + p.next = p.scheduleRR + + // if there're more than 1 forwarders, we care about the strategy. + if count := len(fwdrs); count > 1 { + switch c.Strategy { + case "rr": + p.next = p.scheduleRR + log.F("[strategy] %s: %d forwarders forward in round robin mode.", name, count) + case "ha": + p.next = p.scheduleHA + log.F("[strategy] %s: %d forwarders forward in high availability mode.", name, count) + case "lha": + p.next = p.scheduleLHA + log.F("[strategy] %s: %d forwarders forward in latency based high availability mode.", name, count) + case "dh": + p.next = p.scheduleDH + log.F("[strategy] %s: %d forwarders forward in destination hashing mode.", name, count) + default: + p.next = p.scheduleRR + log.F("[strategy] %s: not supported forward mode '%s', use round robin mode for %d forwarders.", name, c.Strategy, count) + } } for _, f := range fwdrs { @@ -129,22 +122,6 @@ func (p *FwdrGroup) NextDialer(dstAddr string) proxy.Dialer { return p.next(dstAddr) } -// Record records result while using the dialer from proxy. -func (p *FwdrGroup) Record(dialer proxy.Dialer, success bool) { - OnRecord(dialer, success) -} - -// OnRecord records result while using the dialer from proxy. -func OnRecord(dialer proxy.Dialer, success bool) { - if fwdr, ok := dialer.(*Forwarder); ok { - if !success { - fwdr.IncFailures() - } else { - fwdr.Enable() - } - } -} - // Priority returns the active priority of dialer. func (p *FwdrGroup) Priority() uint32 { return atomic.LoadUint32(&p.priority) } @@ -170,7 +147,7 @@ func (p *FwdrGroup) init() { if len(p.avail) == 0 { // no available forwarders, set priority to 0 to check all forwarders in check func p.SetPriority(0) - // log.F("[strategy] no available forwarders, please check your config file or network settings") + // log.F("[group] no available forwarders, please check your config file or network settings") } } @@ -180,14 +157,14 @@ func (p *FwdrGroup) onStatusChanged(fwdr *Forwarder) { defer p.mu.Unlock() if fwdr.Enabled() { - log.F("[strategy] %s changed status from Disabled to Enabled ", fwdr.Addr()) + log.F("[group] %s(%d) changed status from DISABLED to ENABLED ", fwdr.Addr(), fwdr.Priority()) if fwdr.Priority() == p.Priority() { p.avail = append(p.avail, fwdr) } else if fwdr.Priority() > p.Priority() { p.init() } } else { - log.F("[strategy] %s changed status from Enabled to Disabled", fwdr.Addr()) + log.F("[group] %s(%d) changed status from ENABLED to DISABLED", fwdr.Addr(), fwdr.Priority()) for i, f := range p.avail { if f == fwdr { p.avail[i], p.avail = p.avail[len(p.avail)-1], p.avail[:len(p.avail)-1] @@ -250,7 +227,7 @@ func checkWebSite(fwdr *Forwarder, website string, timeout time.Duration, buf [] rc, err := fwdr.Dial("tcp", website) if err != nil { fwdr.Disable() - log.F("[check] %s(%d) -> %s, DISABLED. error in dial: %s", fwdr.Addr(), fwdr.Priority(), + log.F("[check] %s(%d) -> %s, FAILED. error in dial: %s", fwdr.Addr(), fwdr.Priority(), website, err) return false } @@ -263,7 +240,7 @@ func checkWebSite(fwdr *Forwarder, website string, timeout time.Duration, buf [] _, err = io.WriteString(rc, "GET / HTTP/1.0\r\n\r\n") if err != nil { fwdr.Disable() - log.F("[check] %s(%d) -> %s, DISABLED. error in write: %s", fwdr.Addr(), fwdr.Priority(), + log.F("[check] %s(%d) -> %s, FAILED. error in write: %s", fwdr.Addr(), fwdr.Priority(), website, err) return false } @@ -271,14 +248,14 @@ func checkWebSite(fwdr *Forwarder, website string, timeout time.Duration, buf [] _, err = io.ReadFull(rc, buf) if err != nil { fwdr.Disable() - log.F("[check] %s(%d) -> %s, DISABLED. error in read: %s", fwdr.Addr(), fwdr.Priority(), + log.F("[check] %s(%d) -> %s, FAILED. error in read: %s", fwdr.Addr(), fwdr.Priority(), website, err) return false } if !bytes.Equal([]byte("HTTP"), buf) { fwdr.Disable() - log.F("[check] %s(%d) -> %s, DISABLED. server response: %s", fwdr.Addr(), fwdr.Priority(), + log.F("[check] %s(%d) -> %s, FAILED. server response: %s", fwdr.Addr(), fwdr.Priority(), website, buf) return false } @@ -288,29 +265,29 @@ func checkWebSite(fwdr *Forwarder, website string, timeout time.Duration, buf [] if readTime > timeout { fwdr.Disable() - log.F("[check] %s(%d) -> %s, DISABLED. check timeout: %s", fwdr.Addr(), fwdr.Priority(), + log.F("[check] %s(%d) -> %s, FAILED. check timeout: %s", fwdr.Addr(), fwdr.Priority(), website, readTime) return false } fwdr.Enable() - log.F("[check] %s(%d) -> %s, ENABLED. connect time: %s", fwdr.Addr(), fwdr.Priority(), + log.F("[check] %s(%d) -> %s, SUCCEEDED. connect time: %s", fwdr.Addr(), fwdr.Priority(), website, readTime) return true } -// Round Robin +// Round Robin. func (p *FwdrGroup) scheduleRR(dstAddr string) *Forwarder { return p.avail[atomic.AddUint32(&p.index, 1)%uint32(len(p.avail))] } -// High Availability +// High Availability. func (p *FwdrGroup) scheduleHA(dstAddr string) *Forwarder { return p.avail[0] } -// Latency based High Availability +// Latency based High Availability. func (p *FwdrGroup) scheduleLHA(dstAddr string) *Forwarder { fwdr := p.avail[0] lowest := fwdr.Latency() @@ -323,7 +300,7 @@ func (p *FwdrGroup) scheduleLHA(dstAddr string) *Forwarder { return fwdr } -// Destination Hashing +// Destination Hashing. func (p *FwdrGroup) scheduleDH(dstAddr string) *Forwarder { fnv1a := fnv.New32a() fnv1a.Write([]byte(dstAddr)) diff --git a/rule/rule.go b/rule/proxy.go similarity index 75% rename from rule/rule.go rename to rule/proxy.go index 4d68188..47c3c4b 100644 --- a/rule/rule.go +++ b/rule/proxy.go @@ -9,7 +9,7 @@ import ( "github.com/nadoo/glider/proxy" ) -// Proxy struct. +// Proxy implements the proxy.Proxy interface with rule support. type Proxy struct { main *FwdrGroup all []*FwdrGroup @@ -41,11 +41,14 @@ func NewProxy(mainForwarders []string, mainStrategy *StrategyConfig, rules []*Co } } + // if there's any forwarder defined in main config, make sure they will be accessed directly. if len(mainForwarders) > 0 { - direct := NewFwdrGroup("backup", nil, mainStrategy) + direct := NewFwdrGroup("", nil, mainStrategy) for _, f := range rd.main.fwdrs { - // Note: the addr maybe ip address, but no matter here. - rd.domainMap.Store(strings.ToLower(strings.Split(f.addr, ":")[0]), direct) + host := strings.Split(f.addr, ":")[0] + if ip := net.ParseIP(host); ip == nil { + rd.domainMap.Store(strings.ToLower(host), direct) + } } } @@ -54,20 +57,18 @@ func NewProxy(mainForwarders []string, mainStrategy *StrategyConfig, rules []*Co // Dial dials to targer addr and return a conn. func (p *Proxy) Dial(network, addr string) (net.Conn, proxy.Dialer, error) { - return p.chooseProxy(addr).Dial(network, addr) + return p.findDialer(addr).Dial(network, addr) } // DialUDP connects to the given address via the proxy. func (p *Proxy) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) { - return p.chooseProxy(addr).DialUDP(network, addr) + return p.findDialer(addr).DialUDP(network, addr) } -// chooseProxy returns a proxy according to rule. -func (p *Proxy) chooseProxy(dstAddr string) *FwdrGroup { +// findDialer returns a dialer by dstAddr according to rule. +func (p *Proxy) findDialer(dstAddr string) *FwdrGroup { host, _, err := net.SplitHostPort(dstAddr) if err != nil { - // TODO: check here - // logf("[rule] SplitHostPort ERROR: %s", err) return p.main } @@ -86,7 +87,6 @@ func (p *Proxy) chooseProxy(dstAddr string) *FwdrGroup { ret = value.(*FwdrGroup) return false } - return true }) @@ -109,12 +109,18 @@ func (p *Proxy) chooseProxy(dstAddr string) *FwdrGroup { // NextDialer returns next dialer according to rule. func (p *Proxy) NextDialer(dstAddr string) proxy.Dialer { - return p.chooseProxy(dstAddr).NextDialer(dstAddr) + return p.findDialer(dstAddr).NextDialer(dstAddr) } // Record records result while using the dialer from proxy. func (p *Proxy) Record(dialer proxy.Dialer, success bool) { - OnRecord(dialer, success) + if fwdr, ok := dialer.(*Forwarder); ok { + if !success { + fwdr.IncFailures() + return + } + fwdr.Enable() + } } // AddDomainIP used to update ipMap rules according to domainMap rule. @@ -125,18 +131,19 @@ func (p *Proxy) AddDomainIP(domain, ip string) error { i = strings.LastIndexByte(domain[:i], '.') if dialer, ok := p.domainMap.Load(domain[i+1:]); ok { p.ipMap.Store(ip, dialer) - log.F("[rule] add ip=%s, based on rule: domain=%s & domain/ip: %s/%s\n", ip, domain[i+1:], domain, ip) + log.F("[rule] add ip=%s, based on rule: domain=%s & domain/ip: %s/%s\n", + ip, domain[i+1:], domain, ip) } } } return nil } -// Check . +// Check checks availability of forwarders inside proxy. func (p *Proxy) Check() { p.main.Check() - for _, fwdr := range p.all { - fwdr.Check() + for _, fwdrGroup := range p.all { + fwdrGroup.Check() } }