mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 17:35:40 +08:00
check: support full url check and expect resp setting(#195)
This commit is contained in:
parent
38f84a625d
commit
65d606d29c
23
config.go
23
config.go
@ -21,7 +21,7 @@ type Config struct {
|
|||||||
Listens []string
|
Listens []string
|
||||||
|
|
||||||
Forwards []string
|
Forwards []string
|
||||||
StrategyConfig rule.StrategyConfig
|
Strategy rule.Strategy
|
||||||
|
|
||||||
RuleFiles []string
|
RuleFiles []string
|
||||||
RulesDir string
|
RulesDir string
|
||||||
@ -43,17 +43,16 @@ func parseConfig() *Config {
|
|||||||
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
|
flag.StringSliceUniqVar(&conf.Listens, "listen", nil, "listen url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS")
|
||||||
|
|
||||||
flag.StringSliceUniqVar(&conf.Forwards, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
flag.StringSliceUniqVar(&conf.Forwards, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||||
flag.StringVar(&conf.StrategyConfig.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
flag.StringVar(&conf.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||||
flag.StringVar(&conf.StrategyConfig.CheckType, "checktype", "http", "fowarder check type, http/tcp")
|
flag.StringVar(&conf.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=disable: disable health check\ncheck=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]")
|
||||||
flag.StringVar(&conf.StrategyConfig.CheckAddr, "checkaddr", "www.apple.com:80", "fowarder check addr, format: HOST[:PORT], default port: 80,")
|
flag.IntVar(&conf.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
||||||
flag.IntVar(&conf.StrategyConfig.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
flag.IntVar(&conf.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
||||||
flag.IntVar(&conf.StrategyConfig.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
flag.IntVar(&conf.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
||||||
flag.IntVar(&conf.StrategyConfig.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
flag.BoolVar(&conf.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
||||||
flag.BoolVar(&conf.StrategyConfig.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
flag.IntVar(&conf.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
||||||
flag.IntVar(&conf.StrategyConfig.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
flag.IntVar(&conf.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
||||||
flag.IntVar(&conf.StrategyConfig.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
flag.IntVar(&conf.Strategy.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)")
|
||||||
flag.IntVar(&conf.StrategyConfig.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)")
|
flag.StringVar(&conf.Strategy.IntFace, "interface", "", "source ip or source interface")
|
||||||
flag.StringVar(&conf.StrategyConfig.IntFace, "interface", "", "source ip or source interface")
|
|
||||||
|
|
||||||
flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path")
|
flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path")
|
||||||
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
||||||
|
4
go.mod
4
go.mod
@ -15,8 +15,8 @@ require (
|
|||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1
|
github.com/xtaci/kcp-go/v5 v5.6.1
|
||||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||||
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 // indirect
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||||
golang.org/x/tools v0.0.0-20201117152513-9036a0f9af11 // indirect
|
golang.org/x/tools v0.0.0-20201120032337-6d151481565c // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
8
go.sum
8
go.sum
@ -128,8 +128,8 @@ golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 h1:AYCWBZhgIw6XobZ5CibNJr0Rc4ZofGGKvWa1vcx2IGk=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||||
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -141,8 +141,8 @@ golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roY
|
|||||||
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c=
|
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c=
|
||||||
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.0.0-20201117152513-9036a0f9af11 h1:gqcmLJzeDSNhSzkyhJ4kxP6CtTimi/5hWFDGp0lFd1w=
|
golang.org/x/tools v0.0.0-20201120032337-6d151481565c h1:IXtuZap6vTKIQ3jemmcwf2gY4BT+lwfZHBYwxMGe5/k=
|
||||||
golang.org/x/tools v0.0.0-20201117152513-9036a0f9af11/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201120032337-6d151481565c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
2
main.go
2
main.go
@ -24,7 +24,7 @@ var (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// global rule proxy
|
// global rule proxy
|
||||||
pxy := rule.NewProxy(config.Forwards, &config.StrategyConfig, config.rules)
|
pxy := rule.NewProxy(config.Forwards, &config.Strategy, config.rules)
|
||||||
|
|
||||||
// ipset manager
|
// ipset manager
|
||||||
ipsetM, _ := ipset.NewManager(config.rules)
|
ipsetM, _ := ipset.NewManager(config.rules)
|
||||||
|
117
rule/check.go
Normal file
117
rule/check.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nadoo/glider/log"
|
||||||
|
"github.com/nadoo/glider/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Checker is a forwarder health checker.
|
||||||
|
type Checker interface {
|
||||||
|
Check(fwdr *Forwarder) (healthy bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tcpChecker struct {
|
||||||
|
addr string
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTcpChecker(addr string, timeout time.Duration) *tcpChecker {
|
||||||
|
return &tcpChecker{addr, timeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tcpChecker) Check(fwdr *Forwarder) bool {
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
rc, err := fwdr.Dial("tcp", c.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[check] tcp:%s(%d), FAILED. error in dial: %s", fwdr.Addr(), fwdr.Priority(), err)
|
||||||
|
fwdr.Disable()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
if c.timeout > 0 {
|
||||||
|
rc.SetDeadline(time.Now().Add(c.timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
fwdr.SetLatency(int64(elapsed))
|
||||||
|
|
||||||
|
if elapsed > c.timeout {
|
||||||
|
log.F("[check] tcp:%s(%d), FAILED. check timeout: %s", fwdr.Addr(), fwdr.Priority(), elapsed)
|
||||||
|
fwdr.Disable()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("[check] tcp:%s(%d), SUCCESS. elapsed: %s", fwdr.Addr(), fwdr.Priority(), elapsed)
|
||||||
|
fwdr.Enable()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpChecker struct {
|
||||||
|
addr string
|
||||||
|
uri string
|
||||||
|
expect string
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHttpChecker(addr, uri, expect string, timeout time.Duration) *httpChecker {
|
||||||
|
return &httpChecker{addr, uri, expect, timeout}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpChecker) Check(fwdr *Forwarder) bool {
|
||||||
|
startTime := time.Now()
|
||||||
|
rc, err := fwdr.Dial("tcp", c.addr)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[check] %s(%d) -> http://%s, FAILED. error in dial: %s", fwdr.Addr(), fwdr.Priority(), c.addr, err)
|
||||||
|
fwdr.Disable()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
if c.timeout > 0 {
|
||||||
|
rc.SetDeadline(time.Now().Add(c.timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.WriteString(rc, "GET "+c.uri+" HTTP/1.1\r\nHost:"+c.addr+"\r\nConnection: close"+"\r\n\r\n")
|
||||||
|
if err != nil {
|
||||||
|
log.F("[check] %s(%d) -> http://%s, FAILED. error in write: %s", fwdr.Addr(), fwdr.Priority(), c.addr, err)
|
||||||
|
fwdr.Disable()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
r := pool.GetBufReader(rc)
|
||||||
|
defer pool.PutBufReader(r)
|
||||||
|
|
||||||
|
line, _, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
log.F("[check] %s(%d) -> http://%s, FAILED. error in read: %s", fwdr.Addr(), fwdr.Priority(), c.addr, err)
|
||||||
|
fwdr.Disable()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Contains(line, []byte(c.expect)) {
|
||||||
|
log.F("[check] %s(%d) -> http://%s, FAILED. expect: %s, server response: %s", fwdr.Addr(), fwdr.Priority(), c.addr, c.expect, line)
|
||||||
|
fwdr.Disable()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
fwdr.SetLatency(int64(elapsed))
|
||||||
|
|
||||||
|
if elapsed > c.timeout {
|
||||||
|
log.F("[check] %s(%d) -> http://%s, FAILED. check timeout: %s", fwdr.Addr(), fwdr.Priority(), c.addr, elapsed)
|
||||||
|
fwdr.Disable()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
log.F("[check] %s(%d) -> http://%s, SUCCESS. elapsed: %s", fwdr.Addr(), fwdr.Priority(), c.addr, elapsed)
|
||||||
|
fwdr.Enable()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
@ -13,7 +13,7 @@ type Config struct {
|
|||||||
Name string
|
Name string
|
||||||
|
|
||||||
Forward []string
|
Forward []string
|
||||||
StrategyConfig StrategyConfig
|
Strategy Strategy
|
||||||
|
|
||||||
DNSServers []string
|
DNSServers []string
|
||||||
IPSet string
|
IPSet string
|
||||||
@ -23,11 +23,10 @@ type Config struct {
|
|||||||
CIDR []string
|
CIDR []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrategyConfig is config of strategy.
|
// Strategy configurations.
|
||||||
type StrategyConfig struct {
|
type Strategy struct {
|
||||||
Strategy string
|
Strategy string
|
||||||
CheckType string
|
Check string
|
||||||
CheckAddr string
|
|
||||||
CheckInterval int
|
CheckInterval int
|
||||||
CheckTimeout int
|
CheckTimeout int
|
||||||
CheckTolerance int
|
CheckTolerance int
|
||||||
@ -44,17 +43,16 @@ func NewConfFromFile(ruleFile string) (*Config, error) {
|
|||||||
|
|
||||||
f := conflag.NewFromFile("rule", ruleFile)
|
f := conflag.NewFromFile("rule", ruleFile)
|
||||||
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||||
f.StringVar(&p.StrategyConfig.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
f.StringVar(&p.Strategy.Strategy, "strategy", "rr", "forward strategy, default: rr")
|
||||||
f.StringVar(&p.StrategyConfig.CheckType, "checktype", "http", "fowarder check type, http/tcp")
|
f.StringVar(&p.Strategy.Check, "check", "http://www.msftconnecttest.com/connecttest.txt#expect=200", "check=disable: disable health check\ncheck=tcp[://HOST:PORT]: tcp port connect check\ncheck=http://HOST[:PORT][/URI][#expect=STRING_IN_RESP_LINE]")
|
||||||
f.StringVar(&p.StrategyConfig.CheckAddr, "checkaddr", "www.apple.com:80", "fowarder check addr, format: HOST[:PORT], default port: 80,")
|
f.IntVar(&p.Strategy.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
||||||
f.IntVar(&p.StrategyConfig.CheckInterval, "checkinterval", 30, "fowarder check interval(seconds)")
|
f.IntVar(&p.Strategy.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
||||||
f.IntVar(&p.StrategyConfig.CheckTimeout, "checktimeout", 10, "fowarder check timeout(seconds)")
|
f.IntVar(&p.Strategy.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
||||||
f.IntVar(&p.StrategyConfig.CheckTolerance, "checktolerance", 0, "fowarder check tolerance(ms), switch only when new_latency < old_latency - tolerance, only used in lha mode")
|
f.BoolVar(&p.Strategy.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
||||||
f.BoolVar(&p.StrategyConfig.CheckDisabledOnly, "checkdisabledonly", false, "check disabled fowarders only")
|
f.IntVar(&p.Strategy.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
||||||
f.IntVar(&p.StrategyConfig.MaxFailures, "maxfailures", 3, "max failures to change forwarder status to disabled")
|
f.IntVar(&p.Strategy.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
||||||
f.IntVar(&p.StrategyConfig.DialTimeout, "dialtimeout", 3, "dial timeout(seconds)")
|
f.IntVar(&p.Strategy.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)")
|
||||||
f.IntVar(&p.StrategyConfig.RelayTimeout, "relaytimeout", 0, "relay timeout(seconds)")
|
f.StringVar(&p.Strategy.IntFace, "interface", "", "source ip or source interface")
|
||||||
f.StringVar(&p.StrategyConfig.IntFace, "interface", "", "source ip or source interface")
|
|
||||||
|
|
||||||
f.StringSliceUniqVar(&p.DNSServers, "dnsserver", nil, "remote dns server")
|
f.StringSliceUniqVar(&p.DNSServers, "dnsserver", nil, "remote dns server")
|
||||||
f.StringVar(&p.IPSet, "ipset", "", "ipset name")
|
f.StringVar(&p.IPSet, "ipset", "", "ipset name")
|
||||||
|
149
rule/group.go
149
rule/group.go
@ -1,10 +1,9 @@
|
|||||||
package rule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -24,7 +23,7 @@ func (p priSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|||||||
|
|
||||||
// FwdrGroup is a forwarder group.
|
// FwdrGroup is a forwarder group.
|
||||||
type FwdrGroup struct {
|
type FwdrGroup struct {
|
||||||
config *StrategyConfig
|
config *Strategy
|
||||||
fwdrs priSlice
|
fwdrs priSlice
|
||||||
avail []*Forwarder // available forwarders
|
avail []*Forwarder // available forwarders
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@ -34,7 +33,7 @@ type FwdrGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFwdrGroup returns a new forward group.
|
// NewFwdrGroup returns a new forward group.
|
||||||
func NewFwdrGroup(name string, s []string, c *StrategyConfig) *FwdrGroup {
|
func NewFwdrGroup(name string, s []string, c *Strategy) *FwdrGroup {
|
||||||
var fwdrs []*Forwarder
|
var fwdrs []*Forwarder
|
||||||
for _, chain := range s {
|
for _, chain := range s {
|
||||||
fwdr, err := ForwarderFromURL(chain, c.IntFace,
|
fwdr, err := ForwarderFromURL(chain, c.IntFace,
|
||||||
@ -57,16 +56,12 @@ func NewFwdrGroup(name string, s []string, c *StrategyConfig) *FwdrGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newFwdrGroup returns a new FwdrGroup.
|
// newFwdrGroup returns a new FwdrGroup.
|
||||||
func newFwdrGroup(name string, fwdrs []*Forwarder, c *StrategyConfig) *FwdrGroup {
|
func newFwdrGroup(name string, fwdrs []*Forwarder, c *Strategy) *FwdrGroup {
|
||||||
p := &FwdrGroup{fwdrs: fwdrs, config: c}
|
p := &FwdrGroup{fwdrs: fwdrs, config: c}
|
||||||
sort.Sort(p.fwdrs)
|
sort.Sort(p.fwdrs)
|
||||||
|
|
||||||
p.init()
|
p.init()
|
||||||
|
|
||||||
if strings.IndexByte(p.config.CheckAddr, ':') == -1 {
|
|
||||||
p.config.CheckAddr += ":80"
|
|
||||||
}
|
|
||||||
|
|
||||||
// default scheduler
|
// default scheduler
|
||||||
p.next = p.scheduleRR
|
p.next = p.scheduleRR
|
||||||
|
|
||||||
@ -178,24 +173,55 @@ func (p *FwdrGroup) onStatusChanged(fwdr *Forwarder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check implements the Checker interface.
|
// Check runs the forwarder checks.
|
||||||
func (p *FwdrGroup) Check() {
|
func (p *FwdrGroup) Check() {
|
||||||
if p.config.CheckType != "http" && p.config.CheckType != "tcp" {
|
if len(p.fwdrs) == 1 {
|
||||||
p.config.MaxFailures = 0
|
p.config.MaxFailures = 0
|
||||||
|
log.F("[group] only 1 forwarder found, disable health checking")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// no need to check when there's only 1 forwarder
|
if !strings.Contains(p.config.Check, "://") {
|
||||||
if len(p.fwdrs) > 1 {
|
p.config.Check += "://"
|
||||||
for i := 0; i < len(p.fwdrs); i++ {
|
|
||||||
go p.check(p.fwdrs[i], p.config.CheckType == "http")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(p.config.Check)
|
||||||
|
if err != nil {
|
||||||
|
log.F("[group] parse check config error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := u.Host
|
||||||
|
if strings.IndexByte(addr, ':') == -1 {
|
||||||
|
addr += ":80"
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.Duration(p.config.CheckTimeout) * time.Second
|
||||||
|
|
||||||
|
var checker Checker
|
||||||
|
switch u.Scheme {
|
||||||
|
case "tcp":
|
||||||
|
checker = newTcpChecker(addr, timeout)
|
||||||
|
case "http":
|
||||||
|
expect := "HTTP" // default: check the first 4 chars in response
|
||||||
|
params, _ := url.ParseQuery(u.Fragment)
|
||||||
|
if ex := params.Get("expect"); ex != "" {
|
||||||
|
expect = ex
|
||||||
|
}
|
||||||
|
checker = newHttpChecker(addr, u.RequestURI(), expect, timeout)
|
||||||
|
default:
|
||||||
|
p.config.MaxFailures = 0
|
||||||
|
log.F("[group] invalid check config `%s`, disable health checking", p.config.Check)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(p.fwdrs); i++ {
|
||||||
|
go p.check(p.fwdrs[i], checker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FwdrGroup) check(f *Forwarder, http bool) {
|
func (p *FwdrGroup) check(f *Forwarder, checker Checker) {
|
||||||
wait := uint8(0)
|
wait := uint8(0)
|
||||||
buf := make([]byte, 4)
|
|
||||||
intval := time.Duration(p.config.CheckInterval) * time.Second
|
intval := time.Duration(p.config.CheckInterval) * time.Second
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -210,17 +236,10 @@ func (p *FwdrGroup) check(f *Forwarder, http bool) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if http {
|
if checker.Check(f) {
|
||||||
if checkHttp(f, p.config.CheckAddr, time.Duration(p.config.CheckTimeout)*time.Second, buf) {
|
|
||||||
wait = 1
|
wait = 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if checkTcp(f, p.config.CheckAddr, time.Duration(p.config.CheckTimeout)*time.Second) {
|
|
||||||
wait = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if wait == 0 {
|
if wait == 0 {
|
||||||
wait = 1
|
wait = 1
|
||||||
@ -233,86 +252,6 @@ func (p *FwdrGroup) check(f *Forwarder, http bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTcp(fwdr *Forwarder, addr string, timeout time.Duration) bool {
|
|
||||||
startTime := time.Now()
|
|
||||||
|
|
||||||
rc, err := fwdr.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[check] tcp://%s(%d), FAILED. error in dial: %s", fwdr.Addr(), fwdr.Priority(), err)
|
|
||||||
fwdr.Disable()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
if timeout > 0 {
|
|
||||||
rc.SetDeadline(time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed := time.Since(startTime)
|
|
||||||
fwdr.SetLatency(int64(elapsed))
|
|
||||||
|
|
||||||
if elapsed > timeout {
|
|
||||||
log.F("[check] tcp://%s(%d), FAILED. check timeout: %s", fwdr.Addr(), fwdr.Priority(), elapsed)
|
|
||||||
fwdr.Disable()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
log.F("[check] tcp://%s(%d), SUCCESS. elapsed: %s", fwdr.Addr(), fwdr.Priority(), elapsed)
|
|
||||||
fwdr.Enable()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkHttp(fwdr *Forwarder, addr string, timeout time.Duration, buf []byte) bool {
|
|
||||||
startTime := time.Now()
|
|
||||||
|
|
||||||
rc, err := fwdr.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[check] %s(%d) -> http://%s, FAILED. error in dial: %s", fwdr.Addr(), fwdr.Priority(), addr, err)
|
|
||||||
fwdr.Disable()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
if timeout > 0 {
|
|
||||||
rc.SetDeadline(time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.WriteString(rc, "GET / HTTP/1.1\r\nHost:"+addr+"\r\nConnection: close"+"\r\n\r\n")
|
|
||||||
if err != nil {
|
|
||||||
log.F("[check] %s(%d) -> http://%s, FAILED. error in write: %s", fwdr.Addr(), fwdr.Priority(), addr, err)
|
|
||||||
fwdr.Disable()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.ReadFull(rc, buf)
|
|
||||||
if err != nil {
|
|
||||||
log.F("[check] %s(%d) -> http://%s, FAILED. error in read: %s", fwdr.Addr(), fwdr.Priority(), addr, err)
|
|
||||||
fwdr.Disable()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal([]byte("HTTP"), buf) {
|
|
||||||
log.F("[check] %s(%d) -> http://%s, FAILED. server response: %s", fwdr.Addr(), fwdr.Priority(), addr, buf)
|
|
||||||
fwdr.Disable()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed := time.Since(startTime)
|
|
||||||
fwdr.SetLatency(int64(elapsed))
|
|
||||||
|
|
||||||
if elapsed > timeout {
|
|
||||||
log.F("[check] %s(%d) -> http://%s, FAILED. check timeout: %s", fwdr.Addr(), fwdr.Priority(), addr, elapsed)
|
|
||||||
fwdr.Disable()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
log.F("[check] %s(%d) -> http://%s, SUCCESS. elapsed: %s", fwdr.Addr(), fwdr.Priority(), addr, elapsed)
|
|
||||||
fwdr.Enable()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Round Robin.
|
// Round Robin.
|
||||||
func (p *FwdrGroup) scheduleRR(dstAddr string) *Forwarder {
|
func (p *FwdrGroup) scheduleRR(dstAddr string) *Forwarder {
|
||||||
return p.avail[atomic.AddUint32(&p.index, 1)%uint32(len(p.avail))]
|
return p.avail[atomic.AddUint32(&p.index, 1)%uint32(len(p.avail))]
|
||||||
|
@ -19,11 +19,11 @@ type Proxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewProxy returns a new rule proxy.
|
// NewProxy returns a new rule proxy.
|
||||||
func NewProxy(mainForwarders []string, mainStrategy *StrategyConfig, rules []*Config) *Proxy {
|
func NewProxy(mainForwarders []string, mainStrategy *Strategy, rules []*Config) *Proxy {
|
||||||
rd := &Proxy{main: NewFwdrGroup("main", mainForwarders, mainStrategy)}
|
rd := &Proxy{main: NewFwdrGroup("main", mainForwarders, mainStrategy)}
|
||||||
|
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
group := NewFwdrGroup(r.Name, r.Forward, &r.StrategyConfig)
|
group := NewFwdrGroup(r.Name, r.Forward, &r.Strategy)
|
||||||
rd.all = append(rd.all, group)
|
rd.all = append(rd.all, group)
|
||||||
|
|
||||||
for _, domain := range r.Domain {
|
for _, domain := range r.Domain {
|
||||||
|
Loading…
Reference in New Issue
Block a user