From 199e4cf295906426870ef34b0c49be4e3db41908 Mon Sep 17 00:00:00 2001 From: wweir Date: Thu, 14 Mar 2019 08:08:53 +0800 Subject: [PATCH] For #11, expose suggest level option --- Makefile | 5 ++ conf/conf.go | 105 ++++++++++++++++++------------ conf/conf_other.go | 9 ++- conf/sower.toml | 7 +- dns/dns.go | 23 ++++--- dns/suggestlevel_string.go | 16 +++++ dns/util.go | 55 ++++++++++------ go.mod | 6 +- go.sum | 12 ++-- main.go | 28 +++++--- proxy/shadow/cipher.go | 9 +++ proxy/shadow/ciphertype_string.go | 4 +- 12 files changed, 183 insertions(+), 96 deletions(-) create mode 100644 dns/suggestlevel_string.go diff --git a/Makefile b/Makefile index c9e3375..7fb9497 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,12 @@ SERVER:=127.0.0.1:5533 default: test build generate: +ifeq ("", "$(shell which stringer)") + @echo installing generator + go get -v golang.org/x/tools/cmd/stringer +endif go generate ./... + test: go vet ./... go test ./... diff --git a/conf/conf.go b/conf/conf.go index dc42476..6c2168d 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -7,9 +7,10 @@ import ( "os/exec" "runtime" "strconv" - "sync" "time" + "github.com/pkg/errors" + "github.com/golang/glog" toml "github.com/pelletier/go-toml" "github.com/wweir/sower/util" @@ -28,6 +29,7 @@ var Conf = struct { DNSServer string `toml:"dns_server"` ClientIP string `toml:"client_ip"` + SuggestLevel string `toml:"suggest_level"` ClearDNSCache string `toml:"clear_dns_cache"` BlockList []string `toml:"blocklist"` @@ -36,8 +38,24 @@ var Conf = struct { Verbose int `toml:"verbose"` }{} -// OnRefreash will be executed while init and write new config -var OnRefreash = []func() (string, error){ +func init() { + initArgs() + + if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) { + glog.Warningln("no config file has been load:", Conf.ConfigFile) + return + } + for i := range refreshFns { + if action, err := refreshFns[i](); err != nil { + glog.Fatalln(action+":", err) + } + } + + go addSuggestions() +} + +// refreshFns will be executed while init and write new config +var refreshFns = []func() (string, error){ func() (string, error) { action := "load config" f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644) @@ -63,64 +81,65 @@ var OnRefreash = []func() (string, error){ switch runtime.GOOS { case "windows": - return action, exec.CommandContext(ctx, "cmd", "/c", Conf.ClearDNSCache).Run() + if out, err := exec.CommandContext(ctx, "cmd", "/c", Conf.ClearDNSCache).CombinedOutput(); err != nil { + return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out) + } default: - return action, exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).Run() + if out, err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).CombinedOutput(); err != nil { + return action, errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out) + } } } return action, nil }, } -func init() { - initArgs() - - if _, err := os.Stat(Conf.ConfigFile); os.IsNotExist(err) { - glog.Warningln("no config file has been load:", Conf.ConfigFile) - return - } - for i := range OnRefreash { - if action, err := OnRefreash[i](); err != nil { - glog.Fatalln(action+":", err) +// AddRefreshFn add refreshh function for reload config +func AddRefreshFn(init bool, fn func() (string, error)) error { + if init { + if _, err := fn(); err != nil { + return err } } -} -// mu keep synchronized add rule(write), do not care read while write -var mu = &sync.Mutex{} + refreshFns = append(refreshFns, fn) + return nil +} -// AddSuggestion add new domain into suggest rules -func AddSuggestion(domain string) { - mu.Lock() - defer mu.Unlock() +// SuggestCh add domain into suggestios +var SuggestCh = make(chan string) - Conf.Suggestions = append(Conf.Suggestions, domain) - Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq() +// addSuggestions add new domain into suggest rules +func addSuggestions() { + for domain := range SuggestCh { + Conf.Suggestions = append(Conf.Suggestions, domain) + Conf.Suggestions = util.NewReverseSecSlice(Conf.Suggestions).Sort().Uniq() - { // safe write - f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - glog.Errorln(err) - return - } + { // safe write + f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + glog.Errorln(err) + continue + } - if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil { - glog.Errorln(err) + if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil { + glog.Errorln(err) + f.Close() + continue + } f.Close() - return - } - f.Close() - if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil { - glog.Errorln(err) - return + if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil { + glog.Errorln(err) + continue + } } - } - // reload config - for i := range OnRefreash { - if action, err := OnRefreash[i](); err != nil { - glog.Errorln(action+":", err) + // reload config + for i := range refreshFns { + if action, err := refreshFns[i](); err != nil { + glog.Errorln(action+":", err) + } } } } diff --git a/conf/conf_other.go b/conf/conf_other.go index 1e15b41..7e210b0 100644 --- a/conf/conf_other.go +++ b/conf/conf_other.go @@ -8,19 +8,22 @@ import ( "path/filepath" "strings" + "github.com/wweir/sower/dns" + "github.com/wweir/sower/proxy/shadow" "github.com/wweir/sower/proxy/transport" ) func initArgs() { flag.StringVar(&Conf.ConfigFile, "f", filepath.Dir(os.Args[0])+"/sower.toml", "config file location") - flag.StringVar(&Conf.NetType, "n", "TCP", "proxy net type ("+strings.Join(transport.ListTransports(), "|")+")") - flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type (AES_128_GCM|AES_192_GCM|AES_256_GCM|CHACHA20_IETF_POLY1305|XCHACHA20_IETF_POLY1305)") + flag.StringVar(&Conf.NetType, "n", "TCP", "proxy net type: "+strings.Join(transport.ListTransports(), ",")) + flag.StringVar(&Conf.Cipher, "C", "AES_128_GCM", "cipher type: "+strings.Join(shadow.ListCiphers(), ",")) flag.StringVar(&Conf.Password, "p", "12345678", "password") flag.StringVar(&Conf.ServerPort, "P", "5533", "server mode listen port") - flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in client mode if set)") + flag.StringVar(&Conf.ServerAddr, "s", "", "server IP (run in CLIENT MODE if set)") flag.StringVar(&Conf.HTTPProxy, "H", "", "http proxy listen addr") flag.StringVar(&Conf.DNSServer, "d", "114.114.114.114", "client dns server") flag.StringVar(&Conf.ClientIP, "c", "127.0.0.1", "client dns service redirect IP") + flag.StringVar(&Conf.SuggestLevel, "S", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ",")) if !flag.Parsed() { flag.Set("logtostderr", "true") diff --git a/conf/sower.toml b/conf/sower.toml index 1646f0d..da464f6 100644 --- a/conf/sower.toml +++ b/conf/sower.toml @@ -1,12 +1,13 @@ -net_type="TCP" -cipher="AES_128_GCM" +net_type="TCP" # TCP, KCP, QUIC +cipher="AES_128_GCM" # AES_128_GCM, AES_192_GCM, AES_256_GCM, CHACHA20_IETF_POLY1305, XCHACHA20_IETF_POLY1305 password="12345678" server_port="5533" # server_addr="remote-server:5533" # replce it to remote server http_proxy=":8080" # eg: 192.168.0.2:8080 -dns_server="223.5.5.5" # Keep empty for dynamic setting from net env +dns_server="114.114.114.114" # Keep empty for dynamic setting from net env client_ip="127.0.0.1" # listen the IP, dns target is the IP # clear_dns_cache="pkill mDNSResponder" # Windows: "ipconfig /flushdnss" +suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP blocklist=[ "**.google.*", # google "**.goo.gl", diff --git a/dns/dns.go b/dns/dns.go index 572a727..7027937 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -9,15 +9,14 @@ import ( "github.com/golang/glog" "github.com/miekg/dns" mem "github.com/wweir/mem-go" - "github.com/wweir/sower/conf" ) const colon = byte(':') -func StartDNS(dnsServer, listenIP string) { +func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, suggestLevel string) { ip := net.ParseIP(listenIP) - suggest := &intelliSuggest{listenIP, time.Second} + suggest := &intelliSuggest{suggestCh, parseSuggestLevel(suggestLevel), listenIP, time.Second} mem.DefaultCache = mem.New(time.Hour) dhcpCh := make(chan struct{}) @@ -97,12 +96,17 @@ func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer } type intelliSuggest struct { - listenIP string - timeout time.Duration + suggestCh chan<- string + suggestLevel suggestLevel + listenIP string + timeout time.Duration } func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) { iface, e = struct{}{}, nil + if i.suggestLevel == DISABLE { + return + } // kill deadloop, for ugly wildcard setting dns setting addr := strings.TrimSuffix(domain.(string), ".") @@ -139,11 +143,14 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) } // remote ping faster - } else if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && pings[idx].viaAddr == i.listenIP { - atomic.AddInt32(score, 1) + } else if pings[idx].viaAddr == i.listenIP { + if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.suggestLevel == SPEEDUP { + atomic.AddInt32(score, 1) + } glog.V(1).Infof("remote ping %s faster", addr) } else { + atomic.CompareAndSwapInt32(protos[idx/2], 0, 2) return // score change trigger add suggestion } @@ -160,7 +167,7 @@ func (i *intelliSuggest) GetOne(domain interface{}) (iface interface{}, e error) // 2. all remote pings are faster if atomic.LoadInt32(score) >= int32(len(protos)) { old := atomic.SwapInt32(score, -1) // avoid readd the suggestion - conf.AddSuggestion(addr) + i.suggestCh <- addr glog.Infof("suggested domain: %s with score: %d", addr, old) } }(idx) diff --git a/dns/suggestlevel_string.go b/dns/suggestlevel_string.go new file mode 100644 index 0000000..3839562 --- /dev/null +++ b/dns/suggestlevel_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=suggestLevel util.go"; DO NOT EDIT. + +package dns + +import "strconv" + +const _suggestLevel_name = "DISABLEBLOCKSPEEDUPlevelEnd" + +var _suggestLevel_index = [...]uint8{0, 7, 12, 19, 27} + +func (i suggestLevel) String() string { + if i < 0 || i >= suggestLevel(len(_suggestLevel_index)-1) { + return "suggestLevel(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _suggestLevel_name[_suggestLevel_index[i]:_suggestLevel_index[i+1]] +} diff --git a/dns/util.go b/dns/util.go index dc6caa5..47df51b 100644 --- a/dns/util.go +++ b/dns/util.go @@ -5,34 +5,22 @@ import ( "github.com/golang/glog" "github.com/miekg/dns" - "github.com/wweir/sower/conf" "github.com/wweir/sower/util" ) var ( blockList *util.Node - whiteList *util.Node suggestList *util.Node + whiteList *util.Node ) -func init() { - host, _, _ := net.SplitHostPort(conf.Conf.ServerAddr) - - //first init - blockList = loadRules("block", conf.Conf.BlockList) - suggestList = loadRules("suggest", conf.Conf.Suggestions) - whiteList = loadRules("white", conf.Conf.WhiteList) +// LoadRules init rules from config +func LoadRules(blocklist, suggestions, whitelist []string, host string) { + blockList = loadRules("block", blocklist) + suggestList = loadRules("suggest", suggestions) + whiteList = loadRules("white", whitelist) whiteList.Add(host) - glog.V(1).Infoln("load config") - - conf.OnRefreash = append(conf.OnRefreash, func() (string, error) { - blockList = loadRules("block", conf.Conf.BlockList) - suggestList = loadRules("suggest", conf.Conf.Suggestions) - whiteList = loadRules("white", conf.Conf.WhiteList) - whiteList.Add(host) - glog.V(1).Infoln("reload config") - return "reload config", nil - }) + glog.V(1).Infoln("reloaded config") } func loadRules(name string, list []string) *util.Node { @@ -57,3 +45,32 @@ func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg { } return m } + +//go:generate stringer -type=suggestLevel $GOFILE +type suggestLevel int32 + +const ( + DISABLE suggestLevel = iota + BLOCK + SPEEDUP + levelEnd +) + +func ListSuggestLevels() []string { + list := make([]string, 0, int(levelEnd)) + for i := suggestLevel(0); i < levelEnd; i++ { + list = append(list, i.String()) + } + return list +} + +func parseSuggestLevel(level string) suggestLevel { + for i := suggestLevel(0); i < levelEnd; i++ { + if level == i.String() { + return i + } + } + + glog.Exitln("invalid suggest level: " + level) + return levelEnd +} diff --git a/go.mod b/go.mod index af95c05..4b5a608 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f // indirect github.com/lucas-clemente/quic-go v0.10.1 github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced // indirect - github.com/miekg/dns v1.1.5 + github.com/miekg/dns v1.1.6 github.com/pelletier/go-toml v1.2.0 github.com/pkg/errors v0.8.1 github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect @@ -23,7 +23,7 @@ require ( github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f // indirect github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296 github.com/xtaci/kcp-go v5.0.7+incompatible - golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 - golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f // indirect + golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a + golang.org/x/net v0.0.0-20190313220215-9f648a60d977 // indirect golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e ) diff --git a/go.sum b/go.sum index 0fb41a4..212b3a0 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/lucas-clemente/quic-go v0.10.1 h1:ipcMmYP9RT+b1YytOKGUY1qndxPGOczVEQk github.com/lucas-clemente/quic-go v0.10.1/go.mod h1:wuD+2XqEx8G9jtwx5ou2BEYBsE+whgQmlj0Vz/77PrY= github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced h1:zqEC1GJZFbGZA0tRyNZqRjep92K5fujFtFsu5ZW7Aug= github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= -github.com/miekg/dns v1.1.5 h1:7T0Xr4dLK+cpA3vIupAI3aDJCPRU4khl5O2J0LEAV+Y= -github.com/miekg/dns v1.1.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.6 h1:jVwb4GDwD65q/gtItR/lIZHjNH93QfeGxZUkzJcW9mc= +github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -42,10 +42,12 @@ github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296 h1:/HkUfg+ZMx/tNdnyJd github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296/go.mod h1:k7rjBGWoJ+JKwvfe8juAX0zgybjo/Yo3JGkca5f/06s= github.com/xtaci/kcp-go v5.0.7+incompatible h1:zs9tc8XRID0m+aetu3qPWZFyRt2UIMqbXIBgw+vcnlE= github.com/xtaci/kcp-go v5.0.7+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f h1:CxcNDReoTQBlkHuyVUepQrMJTSa7q1+j65kVRv6jK3c= -golang.org/x/net v0.0.0-20190310014029-b774fd8d5c0f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/main.go b/main.go index f6e54d6..d257552 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,8 @@ package main import ( + "net" + "github.com/golang/glog" "github.com/wweir/sower/conf" "github.com/wweir/sower/dns" @@ -11,24 +13,30 @@ import ( var version, date string func main() { - conf := conf.Conf - glog.Infof("Starting sower(%s %s): %v", version, date, conf) + cfg := &conf.Conf + glog.Infof("Starting sower(%s %s): %v", version, date, cfg) - tran, err := transport.GetTransport(conf.NetType) + tran, err := transport.GetTransport(cfg.NetType) if err != nil { glog.Exitln(err) } - if conf.ServerAddr == "" { - proxy.StartServer(tran, conf.ServerPort, conf.Cipher, conf.Password) + if cfg.ServerAddr == "" { + proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password) } else { - if conf.HTTPProxy != "" { - go proxy.StartHttpProxy(tran, conf.ServerAddr, - conf.Cipher, conf.Password, conf.HTTPProxy) + conf.AddRefreshFn(true, func() (string, error) { + host, _, _ := net.SplitHostPort(cfg.ServerAddr) + dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, host) + return "load rules", nil + }) + + if cfg.HTTPProxy != "" { + go proxy.StartHttpProxy(tran, cfg.ServerAddr, + cfg.Cipher, cfg.Password, cfg.HTTPProxy) } - go dns.StartDNS(conf.DNSServer, conf.ClientIP) - proxy.StartClient(tran, conf.ServerAddr, conf.Cipher, conf.Password, conf.ClientIP) + go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel) + proxy.StartClient(tran, cfg.ServerAddr, cfg.Cipher, cfg.Password, cfg.ClientIP) } } diff --git a/proxy/shadow/cipher.go b/proxy/shadow/cipher.go index 262a578..a553d3a 100644 --- a/proxy/shadow/cipher.go +++ b/proxy/shadow/cipher.go @@ -17,8 +17,17 @@ const ( AES_256_GCM CHACHA20_IETF_POLY1305 XCHACHA20_IETF_POLY1305 + cipherEnd ) +func ListCiphers() []string { + list := make([]string, 0, int(cipherEnd)) + for i := cipherType(0); i < cipherEnd; i++ { + list = append(list, i.String()) + } + return list +} + func pickCipher(cipherType, password string) (cipher.AEAD, error) { var blockSize int switch cipherType { diff --git a/proxy/shadow/ciphertype_string.go b/proxy/shadow/ciphertype_string.go index a240cb6..c5bf544 100644 --- a/proxy/shadow/ciphertype_string.go +++ b/proxy/shadow/ciphertype_string.go @@ -4,9 +4,9 @@ package shadow import "strconv" -const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305" +const _cipherType_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd" -var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78} +var _cipherType_index = [...]uint8{0, 11, 22, 33, 55, 78, 87} func (i cipherType) String() string { if i < 0 || i >= cipherType(len(_cipherType_index)-1) {