diff --git a/Makefile b/Makefile index 67aaffe..d738977 100644 --- a/Makefile +++ b/Makefile @@ -15,8 +15,8 @@ test: go test ./... build: go build -ldflags \ - "-X main.version=$(shell git describe --tags) \ - -X main.date=$(shell date +%Y-%m-%d)" + "-X conf.version=$(shell git describe --tags) \ + -X conf.date=$(shell date +%Y-%m-%d)" image: docker build -t sower -f .github/Dockerfile . diff --git a/conf/conf.go b/conf/conf.go index 97588ce..4442fa8 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -3,128 +3,153 @@ package conf import ( "flag" "os" - "strconv" + "sync" - "github.com/golang/glog" toml "github.com/pelletier/go-toml" "github.com/wweir/sower/util" + "github.com/wweir/utils/log" ) -// Conf define the config items -var Conf = struct { - ConfigFile string - NetType string `toml:"net_type"` - Cipher string `toml:"cipher"` - Password string `toml:"password"` +var ( + version, date string - ServerPort string `toml:"server_port"` - ServerAddr string `toml:"server_addr"` - HTTPProxy string `toml:"http_proxy"` + flushOnce = sync.Once{} + flushMu = sync.Mutex{} + flushCh = make(chan struct{}) - DNSServer string `toml:"dns_server"` - ClientIP string `toml:"client_ip"` - SuggestLevel string `toml:"suggest_level"` - ClearDNSCache string `toml:"clear_dns_cache"` + // Conf define the config items + Conf = struct { + ConfigFile string - BlockList []string `toml:"blocklist"` - WhiteList []string `toml:"whitelist"` - Suggestions []string `toml:"suggestions"` - Verbose int `toml:"verbose"` - VersionOnly bool `toml:"-"` -}{} + Upstream struct { + Socks5 string `toml:"socks5"` + DNS string `toml:"dns"` + } `toml:"upstream"` + + Downstream struct { + ServeIP string `toml:"serve_ip"` + HTTPProxy string `toml:"http_proxy"` + } `toml:"downstream"` + + Router struct { + FlushDNSCmd string `toml:"flush_dns_cmd"` + ProxyLevel int `toml:"proxy_level"` + + PortMapping map[string]string `toml:"port_mapping"` + ProxyList []string `toml:"proxy_list"` + DirectList []string `toml:"direct_list"` + DynamicList []string `toml:"dynamic_list"` + } `toml:"router"` + }{} +) func init() { - initArgs() - if Conf.VersionOnly { - return + flag.StringVar(&Conf.ConfigFile, "f", "", "config file, keep empty for dynamic detect proxy rule") + flag.StringVar(&Conf.Upstream.Socks5, "socks5", "127.0.0.1:1080", "upstream socks5 address") + flag.StringVar(&Conf.Upstream.DNS, "dns", "", "upstream dns ip, keep empty to dynamic detect") + flag.StringVar(&Conf.Downstream.ServeIP, "serve", "127.0.0.1", "serve on address") + flag.StringVar(&Conf.Downstream.HTTPProxy, "http_proxy", "", "serve http proxy, eg: 127.0.0.1:8080") + flag.IntVar(&Conf.Router.ProxyLevel, "level", 2, "dynamic proxy level: 0~4") + + Init() // execute platform init logic + if !flag.Parsed() { + flag.Parse() } - 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) + if _, err := os.Stat(Conf.ConfigFile); err == nil { + for i := range loadConfigFns { + if err := loadConfigFns[i].fn(); err != nil { + log.Fatalw("load config", "config", Conf.ConfigFile, "step", loadConfigFns[i].step, "err", err) + } } } - go addSuggestions() + log.Infow("start", "version", version, "date", date, "config", Conf) } // 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) - if err != nil { - return action, err - } - defer f.Close() - - //safe refresh config - file := Conf.ConfigFile - if err = toml.NewDecoder(f).Decode(&Conf); err != nil { - return action, err - } - Conf.ConfigFile = file - - return action, flag.Set("v", strconv.Itoa(Conf.Verbose)) - }, - func() (string, error) { - action := "clear dns cache" - if Conf.ClearDNSCache != "" { - return action, execute(Conf.ClearDNSCache) - } - return action, nil - }, -} - -// 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 - } +var loadConfigFns = []struct { + step string + fn func() error +}{{"load_config", func() error { + f, err := os.OpenFile(Conf.ConfigFile, os.O_RDONLY, 0644) + if err != nil { + return err } + defer f.Close() - refreshFns = append(refreshFns, fn) + //safe refresh config + file := Conf.ConfigFile + if err = toml.NewDecoder(f).Decode(&Conf); err != nil { + return err + } + Conf.ConfigFile = file return nil + +}}, {"flush_dns", func() error { + if Conf.Router.FlushDNSCmd != "" { + return execute(Conf.Router.FlushDNSCmd) + } + return nil +}}} + +// AddReloadConfigHook add hook function for reload config +func AddReloadConfigHook(step string, fn func() error) { + loadConfigFns = append(loadConfigFns, struct { + step string + fn func() error + }{step, fn}) } -// SuggestCh add domain into suggestios -var SuggestCh = make(chan string) +// AddDynamic add new domain into dynamic list +func AddDynamic(domain string) { + flushMu.Lock() + Conf.Router.DynamicList = append(Conf.Router.DynamicList, domain) + Conf.Router.DynamicList = util.NewReverseSecSlice(Conf.Router.DynamicList).Sort().Uniq() + flushMu.Unlock() -// 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() + flushOnce.Do(func() { + if Conf.ConfigFile != "" { + go flushConf() + } + }) - { // safe write + select { + case flushCh <- struct{}{}: + default: + } +} + +func flushConf() { + for range flushCh { + // safe write + if Conf.ConfigFile != "" { f, err := os.OpenFile(Conf.ConfigFile+"~", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { - glog.Errorln(err) + log.Errorw("flush config", "step", "flush", "err", err) continue } + flushMu.Lock() if err := toml.NewEncoder(f).ArraysWithOneElementPerLine(true).Encode(Conf); err != nil { - glog.Errorln(err) + log.Errorw("flush config", "step", "flush", "err", err) + flushMu.Unlock() f.Close() continue } + flushMu.Unlock() f.Close() if err = os.Rename(Conf.ConfigFile+"~", Conf.ConfigFile); err != nil { - glog.Errorln(err) + log.Errorw("flush config", "step", "flush", "err", err) continue } } // reload config - for i := range refreshFns { - if action, err := refreshFns[i](); err != nil { - glog.Errorln(action+":", err) + for i := range loadConfigFns { + if err := loadConfigFns[i].fn(); err != nil { + log.Errorw("flush config", "step", loadConfigFns[i].step, "err", err) } } } diff --git a/conf/conf_darwin.go b/conf/conf_darwin.go new file mode 100644 index 0000000..7ac14df --- /dev/null +++ b/conf/conf_darwin.go @@ -0,0 +1,26 @@ +// +build darwin + +package conf + +import ( + "context" + "flag" + "fmt" + "os/exec" + "time" +) + +func Init() { + flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "pkill mDNSResponder || true", "flush dns command") +} + +func execute(cmd string) error { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + out, err := exec.CommandContext(ctx, "sh", "-c", Conf.Router.FlushDNSCmd).CombinedOutput() + if err != nil { + return fmt.Errorf("cmd: %s, err: %s, output: %s", Conf.Router.FlushDNSCmd, err, out) + } + return nil +} diff --git a/conf/conf_linux.go b/conf/conf_linux.go new file mode 100644 index 0000000..c8e598d --- /dev/null +++ b/conf/conf_linux.go @@ -0,0 +1,25 @@ +// +build linux + +package conf + +import ( + "context" + "fmt" + "os/exec" + "time" +) + +func Init() { + flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "", "flush dns command") +} + +func execute(cmd string) error { + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + out, err := exec.CommandContext(ctx, "sh", "-c", Conf.Router.FlushDNSCmd).CombinedOutput() + if err != nil { + return fmt.Errorf("cmd: %s, err: %s, output: %s", Conf.Router.FlushDNSCmd, err, out) + } + return nil +} diff --git a/conf/conf_other.go b/conf/conf_other.go deleted file mode 100644 index 5131eea..0000000 --- a/conf/conf_other.go +++ /dev/null @@ -1,46 +0,0 @@ -// +build !windows - -package conf - -import ( - "context" - "flag" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/pkg/errors" - "github.com/wweir/sower/dns" - "github.com/wweir/sower/proxy/shadow" - "github.com/wweir/sower/proxy/transport" -) - -func initArgs() { - cfgFile, _ := filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "sower.toml")) - flag.StringVar(&Conf.ConfigFile, "f", cfgFile, "config file location") - flag.StringVar(&Conf.NetType, "n", "TCP", "net type (socks5 client only): "+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.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, "l", "SPEEDUP", "suggest level setting: "+strings.Join(dns.ListSuggestLevels(), ",")) - flag.BoolVar(&Conf.VersionOnly, "V", false, "print sower version") - - if !flag.Parsed() { - flag.Set("logtostderr", "true") - flag.Parse() - } -} - -func execute(cmd string) error { - ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) - defer cancel() - - out, err := exec.CommandContext(ctx, "sh", "-c", Conf.ClearDNSCache).CombinedOutput() - return errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out) -} diff --git a/conf/conf_windows.go b/conf/conf_windows.go index d301727..04f25ed 100644 --- a/conf/conf_windows.go +++ b/conf/conf_windows.go @@ -13,8 +13,7 @@ import ( "syscall" "time" - "github.com/golang/glog" - "github.com/pkg/errors" + "github.com/wweir/utils/log" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" @@ -24,19 +23,15 @@ import ( const name = "sower" const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue -func initArgs() { - cfgFile, _ := filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "sower.toml")) - flag.StringVar(&Conf.ConfigFile, "f", cfgFile, "config file location") - flag.BoolVar(&Conf.VersionOnly, "V", false, "print sower version") - install := flag.Bool("install", false, "install sower as a service") - uninstall := flag.Bool("uninstall", false, "uninstall sower from service list") - exePath,_:=filepath.Abs(os.Args[0]) +func Init() { + exePath, _ := filepath.Abs(os.Args[0]) + logFile := filepath.Join(filepath.Dir(exePath), name+".log") - if !flag.Parsed() { - os.Mkdir("log", 0755) - flag.Set("log_dir", filepath.Dir(os.Args[0])+"/log") - flag.Parse() - } + install := flag.Bool("i", false, "install sower as a service") + uninstall := flag.Bool("u", false, "uninstall sower from service list") + logFile := flag.String("log", logFile, name+" log file path") + flag.StringVar(&Conf.Router.FlushDNSCmd, "flush_dns", "ipconfig /flushdnss", "flush dns command") + flag.Parse() switch { case *install: @@ -46,7 +41,7 @@ func initArgs() { s.Close() return fmt.Errorf("service %s already exists", name) } - s, err = m.CreateService(name, exePath, mgr.Config{ + s, err = m.CreateService(name, exePath, mgr.Config{ DisplayName: "Sower Proxy", StartType: windows.SERVICE_AUTO_START, }) @@ -77,18 +72,18 @@ func initArgs() { default: os.Chdir(filepath.Dir(os.Args[0])) if active, err := svc.IsAnInteractiveSession(); err != nil { - glog.Exitf("failed to determine if we are running in an interactive session: %v", err) + log.Fatalf("failed to determine if we are running in an interactive session: %v", err) } else if !active { go func() { elog, err := eventlog.Open(name) if err != nil { - glog.Exitln(err) + log.Fatalw("install service", "err", err) } defer elog.Close() if err := svc.Run(name, &myservice{}); err != nil { elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err)) - glog.Exitln(err) + log.Fatalw("install service", "err", err) } elog.Info(1, fmt.Sprintf("winsvc.RunAsService: %s service stopped", name)) os.Exit(0) @@ -110,12 +105,12 @@ func serviceDo(fn func(*mgr.Service) error) { func mgrDo(fn func(m *mgr.Mgr) error) { m, err := mgr.Connect() if err != nil { - glog.Exitln(err) + log.Fatalw("install service", "err", err) } defer m.Disconnect() if err := fn(m); err != nil { - glog.Fatalln(err) + log.Fatalw("install service", "err", err) } } @@ -124,7 +119,7 @@ type myservice struct{} func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { elog, err := eventlog.Open(name) if err != nil { - glog.Errorln(err) + log.Errorw("install service", "err", err) return } defer elog.Close() @@ -174,6 +169,8 @@ func execute(cmd string) error { command := exec.CommandContext(ctx, cmds[0], cmds[1:]...) command.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - out, err := command.CombinedOutput() - return errors.Wrapf(err, "cmd: %s, output: %s, error", Conf.ClearDNSCache, out) + if out, err := command.CombinedOutput(); err != nil { + return fmt.Errorf("cmd: %s, output: %s, err: %w", Conf.ClearDNSCache, out, err) + } + return nil } diff --git a/conf/sower.toml b/conf/sower.toml index f5f2699..c07cd12 100644 --- a/conf/sower.toml +++ b/conf/sower.toml @@ -1,14 +1,18 @@ -net_type="TCP" # TCP, KCP, QUIC, SOCKS5(client only) -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" # replce it to remote server -http_proxy=":8080" # eg: 192.168.0.2:8080 -dns_server="" # eg: 223.5.5.5:53, 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 || true" # Windows: "ipconfig /flushdnss" -suggest_level="SPEEDUP" # DISABLE, BLOCK, SPEEDUP -blocklist=[ +[upstream] +socks5="127.0.0.1:1080" +dns="" # eg: 223.5.5.5, keep empty to get it from network environment + +[downstream] +serve_ip="127.0.0.1" +http_proxy="" # eg: :8080, 127.0.0.1:8080, keep empty to disable it + +[router.port_mapping] +# 2222="aaa.bbb.cc:22" + +[router] +flush_dns_cmd="" # Platform-related, keep empty to use default command +proxy_level=2 # dynamic detect level, (0~4), more bigger more harder to be proxy +proxy_list=[ "**.google.*", # google "**.goo.gl", "**.googleusercontent.com", @@ -31,7 +35,7 @@ blocklist=[ "*.githubusercontent.com", "*.github.*", ] -whitelist=[ +direct_list=[ "**.in-addr.arpa", "imap.*.*", "imap.*.*.*", @@ -48,4 +52,3 @@ whitelist=[ "**.163.com", "**.weiyun.com", ] -verbose=0 diff --git a/dns/dhcp_test.go b/dns/dhcp_test.go deleted file mode 100644 index 0b56b2b..0000000 --- a/dns/dhcp_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package dns - -import ( - "runtime" - "testing" -) - -func TestGetDefaultDNSServer(t *testing.T) { - switch runtime.GOOS { - case "windows": - case "darwin": - default: - t.Skip("skip for some enviroment not support dhcp and permission set") - return - } - - if got, err := GetDefaultDNSServer(); err != nil { - t.Errorf("GetDefaultDNSServer() return error: %s", err) - } else { - t.Logf("GetDefaultDNSServer() return IP: %v", got) - } -} diff --git a/dns/dns.go b/dns/dns.go index 7541715..59ea225 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -1,35 +1,37 @@ package dns +/* + * Deep integration with package conf: github.com/wweir/sower/conf + */ import ( "context" + "fmt" "net" "strings" + "sync" "sync/atomic" "time" - "github.com/golang/glog" "github.com/miekg/dns" - mem "github.com/wweir/mem-go" - "github.com/wweir/sower/util" + "github.com/wweir/sower/conf" + "github.com/wweir/sower/internal/http" + internal_net "github.com/wweir/sower/internal/net" + "github.com/wweir/sower/internal/socks5" + "github.com/wweir/utils/log" + "github.com/wweir/utils/mem" ) -const colon = byte(':') - -func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, level string) { - ip := net.ParseIP(listenIP) - - suggest := &intelliSuggest{suggestCh, parseSuggestLevel(level), listenIP, time.Second} - mem.DefaultCache = mem.New(time.Hour) - - dhcpCh := make(chan struct{}) - if dnsServer != "" { - if _, _, err := net.SplitHostPort(dnsServer); err != nil { - dnsServer = net.JoinHostPort(dnsServer, "53") - } - } else { - go dynamicSetUpstreamDNS(listenIP, &dnsServer, dhcpCh) - dhcpCh <- struct{}{} +func ServeDNS() { + serveIP := net.ParseIP(conf.Conf.Downstream.ServeIP) + if conf.Conf.Downstream.ServeIP == "" || serveIP.String() != conf.Conf.Downstream.ServeIP { + log.Fatalw("invalid listen ip", "ip", conf.Conf.Downstream.ServeIP) } + dnsServer, err := PickUpstreamDNS(serveIP.String(), conf.Conf.Upstream.DNS) + if err != nil { + log.Fatalw("") + } + + d := &detect{proxy: conf.Conf.Upstream.Socks5} dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) { // *Msg r has an TSIG record and it was validated @@ -44,162 +46,126 @@ func StartDNS(dnsServer, listenIP string, suggestCh chan<- string, level string) } domain := r.Question[0].Name - if idx := strings.IndexByte(domain, colon); idx > 0 { + if idx := strings.IndexByte(domain, ':'); idx > 0 { domain = domain[:idx] // trim port } - matchAndServe(w, r, domain, listenIP, dnsServer, dhcpCh, ip, suggest) + if err := matchAndServe(w, r, serveIP, d, domain, dnsServer); err != nil { + server, err := PickUpstreamDNS(serveIP.String(), dnsServer) + if err != nil { + log.Errorw("detect upstream dns fail", "err", err) + } else { + dnsServer = server + } + } }) - server := &dns.Server{Addr: net.JoinHostPort(listenIP, "53"), Net: "udp"} - glog.Fatalln(server.ListenAndServe()) + server := &dns.Server{Addr: ":53", Net: "udp"} + log.Fatalw("dns serve fail", "err", server.ListenAndServe()) } -func dynamicSetUpstreamDNS(listenIP string, dnsServer *string, dhcpCh <-chan struct{}) { - addr, _ := dns.ReverseAddr(listenIP) - msg := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Id: dns.Id(), - RecursionDesired: false, - }, - Question: []dns.Question{{ - Name: addr, - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }}, +func PickUpstreamDNS(listenIP string, dnsServer string) (string, error) { + if dnsServer == "" { + return internal_net.GetDefaultDNSServer() } - for { - <-dhcpCh - if _, err := dns.Exchange(msg, *dnsServer); err == nil { - continue - } - - host, err := GetDefaultDNSServer() - if err != nil { - glog.Errorln(err) - continue - } - - // atomic action - *dnsServer = net.JoinHostPort(host, "53") - glog.Infoln("set dns server to", host) + if _, port, err := net.SplitHostPort(dnsServer); err != nil { + return "", fmt.Errorf("parse upstream dns(%s) server fail: %w", dnsServer, err) + } else if port == "" { + return net.JoinHostPort(dnsServer, "53"), nil } + return dnsServer, nil } -func matchAndServe(w dns.ResponseWriter, r *dns.Msg, domain, listenIP, dnsServer string, - dhcpCh chan struct{}, ipNet net.IP, suggest *intelliSuggest) { - inWriteList := whiteList.Match(domain) - if !inWriteList && (blockList.Match(domain) || suggestList.Match(domain)) { - glog.V(2).Infof("match %s suss", domain) - w.WriteMsg(localA(r, domain, ipNet)) - return +const timeout = 200 * time.Millisecond + +func matchAndServe(w dns.ResponseWriter, r *dns.Msg, serveIP net.IP, d *detect, domain, dnsServer string) error { + if (!whiteList.Match(domain)) && + (blockList.Match(domain) || suggestList.Match(domain)) { + w.WriteMsg(localA(r, domain, serveIP)) + return nil } - go mem.Remember(suggest, domain) - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + if err := mem.Remember(d, domain); err != nil { + panic(err) + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() msg, err := dns.ExchangeContext(ctx, r, dnsServer) - if err != nil { - if dhcpCh != nil { - select { - case dhcpCh <- struct{}{}: - default: - } - } - glog.V(1).Infof("get dns of %s from %s fail: %s", domain, dnsServer, err) - return - } else if msg == nil { // expose any response except nil - glog.V(1).Infof("get dns of %s from %s return empty", domain, dnsServer) - return + if err != nil || msg == nil { + return err } w.WriteMsg(msg) + return nil } -type intelliSuggest struct { - suggestCh chan<- string - level level - listenIP string - timeout time.Duration +type detect struct { + proxy string + port http.Port } -func (i *intelliSuggest) GetOne(key interface{}) (iface interface{}, e error) { - iface, e = struct{}{}, nil - if i.level == DISABLE { - return - } - - // kill deadloop, for ugly wildcard setting dns setting +func (d *detect) Get(key interface{}) error { + // break deadloop, for ugly wildcard setting dns setting domain := strings.TrimSuffix(key.(string), ".") if strings.Count(domain, ".") > 10 { - return + return nil } - ip, err := net.LookupIP(domain) - if err != nil || len(ip) == 0 { - glog.V(1).Infoln(domain, ip, err) - return + wg := sync.WaitGroup{} + httpScore, httpsScore := new(int32), new(int32) + for _, ping := range [...]detect{{"", http.HTTP}, {"", http.HTTPS}} { + wg.Add(1) + go func(ping detect) { + defer wg.Done() + + if err := ping.port.Ping(domain, timeout); err != nil { + return + } + + switch ping.port { + case http.HTTP: + if !atomic.CompareAndSwapInt32(httpScore, 0, 2) { + atomic.AddInt32(httpScore, 1) + } + case http.HTTPS: + if !atomic.CompareAndSwapInt32(httpsScore, 0, 2) { + atomic.AddInt32(httpScore, 1) + } + } + }(ping) } + for _, ping := range [...]detect{{d.proxy, http.HTTP}, {d.proxy, http.HTTPS}} { + wg.Add(1) + go func(ping detect) { + defer wg.Done() - var ( - pings = [...]struct { - viaAddr string - port util.Port - }{ - {ip[0].String(), util.HTTP}, - {i.listenIP, util.HTTP}, - {ip[0].String(), util.HTTPS}, - {i.listenIP, util.HTTPS}, - } - protos = [...]*int32{ - new(int32), /*HTTP*/ - new(int32), /*HTTPS*/ - } - score = new(int32) - ) - for idx := range pings { - go func(idx int) { - if err := util.HTTPPing(pings[idx].viaAddr, domain, pings[idx].port, i.timeout); err != nil { - // local ping fail - if pings[idx].viaAddr == i.listenIP { - atomic.AddInt32(score, -1) - glog.V(1).Infof("remote ping %s fail", domain) - } else { - atomic.AddInt32(score, 1) - glog.V(1).Infof("local ping %s fail", domain) - } - - // remote ping faster - } else if pings[idx].viaAddr == i.listenIP { - if atomic.CompareAndSwapInt32(protos[idx/2], 0, 1) && i.level == SPEEDUP { - atomic.AddInt32(score, 1) - } - glog.V(1).Infof("remote ping %s faster", domain) - + if conn, err := net.Dial("tcp", ping.proxy); err != nil { + return } else { - atomic.CompareAndSwapInt32(protos[idx/2], 0, 2) - return // score change trigger add suggestion - } - - // check all remote pings are faster - if atomic.LoadInt32(score) == int32(len(protos)) { - for i := range protos { - if atomic.LoadInt32(protos[i]) != 1 { - return - } + conn = socks5.ToSocks5(conn, domain, ping.port.String()) + if err := ping.port.PingWithConn(domain, conn, timeout); err != nil { + return } } - // 1. local fail and remote success - // 2. all remote pings are faster - if atomic.LoadInt32(score) >= int32(len(protos)) { - old := atomic.SwapInt32(score, -1) // avoid readd the suggestion - i.suggestCh <- domain - glog.Infof("suggested domain: %s with score: %d", domain, old) + switch ping.port { + case http.HTTP: + if !atomic.CompareAndSwapInt32(httpScore, 0, -2) { + atomic.AddInt32(httpScore, -1) + } + case http.HTTPS: + if !atomic.CompareAndSwapInt32(httpsScore, 0, -2) { + atomic.AddInt32(httpScore, -1) + } } - }(idx) + }(ping) } - return + + wg.Wait() + if int(*httpScore+*httpsScore) >= conf.Conf.Router.ProxyLevel { + conf.AddDynamic(domain) + } + return nil } diff --git a/dns/level_string.go b/dns/level_string.go deleted file mode 100644 index ba6f934..0000000 --- a/dns/level_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// Code generated by "stringer -type=level util.go"; DO NOT EDIT. - -package dns - -import "strconv" - -const _level_name = "DISABLEBLOCKSPEEDUPlevelEnd" - -var _level_index = [...]uint8{0, 7, 12, 19, 27} - -func (i level) String() string { - if i < 0 || i >= level(len(_level_index)-1) { - return "level(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _level_name[_level_index[i]:_level_index[i+1]] -} diff --git a/dns/util.go b/dns/util.go index 0fa0ae1..42d50cd 100644 --- a/dns/util.go +++ b/dns/util.go @@ -3,30 +3,27 @@ package dns import ( "net" - "github.com/golang/glog" "github.com/miekg/dns" + "github.com/wweir/sower/conf" "github.com/wweir/sower/util" + "github.com/wweir/utils/log" ) -var ( - blockList *util.Node - suggestList *util.Node - whiteList *util.Node -) +var blockList *util.Node +var suggestList *util.Node +var whiteList *util.Node -// 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("reloaded config") -} +func init() { + reloadFn := func() error { + whiteList = util.NewNodeFromRules(".", conf.Conf.Router.DirectList...) + blockList = util.NewNodeFromRules(".", conf.Conf.Router.ProxyList...) + suggestList = util.NewNodeFromRules(".", conf.Conf.Router.DynamicList...) + log.Infow("reload config rules") + return nil + } -func loadRules(name string, list []string) *util.Node { - rule := util.NewNodeFromRules(".", list...) - glog.V(3).Infof("load %s rule:\n%s", name, rule) - return rule + reloadFn() + conf.AddReloadConfigHook("reload rules", reloadFn) } func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg { @@ -45,32 +42,3 @@ func localA(r *dns.Msg, domain string, localIP net.IP) *dns.Msg { } return m } - -//go:generate stringer -type=level $GOFILE -type level int32 - -const ( - DISABLE level = iota - BLOCK - SPEEDUP - levelEnd -) - -func ListSuggestLevels() []string { - list := make([]string, 0, int(levelEnd)) - for i := level(0); i < levelEnd; i++ { - list = append(list, i.String()) - } - return list -} - -func parseSuggestLevel(suggestLevel string) level { - for i := level(0); i < levelEnd; i++ { - if suggestLevel == i.String() { - return i - } - } - - glog.Exitln("invalid suggest level: " + suggestLevel) - return levelEnd -} diff --git a/go.mod b/go.mod index 2bc71ac..f0308dd 100644 --- a/go.mod +++ b/go.mod @@ -3,25 +3,11 @@ module github.com/wweir/sower go 1.13 require ( - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/guregu/null v3.4.0+incompatible // indirect - github.com/klauspost/cpuid v1.2.1 // indirect - github.com/klauspost/reedsolomon v1.9.2 // indirect github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 - github.com/lib/pq v1.2.0 // indirect github.com/libp2p/go-reuseport v0.0.1 - github.com/lucas-clemente/quic-go v0.12.0 - github.com/miekg/dns v1.1.18 - github.com/pelletier/go-toml v1.4.0 - github.com/pkg/errors v0.8.1 - github.com/satori/go.uuid v1.2.0 // indirect - github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect - github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b // indirect - github.com/tjfoc/gmsm v1.0.1 // indirect - 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.4.10+incompatible - github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect - golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 - golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe + github.com/miekg/dns v1.1.27 + github.com/pelletier/go-toml v1.6.0 + github.com/pkg/errors v0.9.1 + github.com/wweir/utils v0.0.0-20200201061343-d44781e298da + golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 ) diff --git a/go.sum b/go.sum index 6b99481..caf112c 100644 --- a/go.sum +++ b/go.sum @@ -1,110 +1,98 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= -github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM= github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/reedsolomon v1.9.2 h1:E9CMS2Pqbv+C7tsrYad4YC9MfhnMVWhMRsTi7U0UB18= -github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= +github.com/influxdata/influxdb v1.7.9/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 h1:t2c2B9g1ZVhMYduqmANSEGVD3/1WlsrEYNPtVoFlENk= github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FWpFLw= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= -github.com/lucas-clemente/quic-go v0.12.0 h1:TRbvZ6F++sofeGbh+Z2IIyIOhl8KyGnYuA06g2yrHdI= -github.com/lucas-clemente/quic-go v0.12.0/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s= -github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI= -github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI= -github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= -github.com/miekg/dns v1.1.18 h1:S82KA03bsvMvziY41d0WitiplMCt8QhawbSQLtoqsdI= -github.com/miekg/dns v1.1.18/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= +github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= -github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= -github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b h1:mnG1fcsIB1d/3vbkBak2MM0u+vhGhlQwpeimUi7QncM= -github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= -github.com/tjfoc/gmsm v1.0.1 h1:R11HlqhXkDospckjZEihx9SW/2VW0RgdwrykyWMFOQU= -github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f h1:QatZ4lsJBY3x1+Imst9g95+vUl7m52dqM9Pi4aSMW8w= github.com/ulule/deepcopier v0.0.0-20171107155558-ca99b135e50f/go.mod h1:BNLmYJ8oMJPIPpNx5968jCyUhwEU1XT3YsuOqtbo5qo= -github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296 h1:/HkUfg+ZMx/tNdnyJdVlhyv+xO3A7ZlpfL9nFLWLYcc= -github.com/wweir/mem-go v0.0.0-20190109100331-8673ab596296/go.mod h1:k7rjBGWoJ+JKwvfe8juAX0zgybjo/Yo3JGkca5f/06s= -github.com/xtaci/kcp-go v5.4.10+incompatible h1:FgH1ji3efEmRFaHEeyim1RYN4Q/c8BT8VqXMklBmh84= -github.com/xtaci/kcp-go v5.4.10+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= -github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= -github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= -golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +github.com/wweir/utils v0.0.0-20200201061343-d44781e298da h1:d4/Y71orUtfo1Z8AnKKCQWBqupjY9zZpCsoU3G5hssw= +github.com/wweir/utils v0.0.0-20200201061343-d44781e298da/go.mod h1:Nv4eBGkUJiHDPgVowJJlQNcJYgMfst6IkjaDThH2/yI= +go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/util/http_ping.go b/internal/http/http_ping.go similarity index 80% rename from util/http_ping.go rename to internal/http/http_ping.go index 5b8bf50..7184a21 100644 --- a/util/http_ping.go +++ b/internal/http/http_ping.go @@ -1,4 +1,4 @@ -package util +package http import ( "bytes" @@ -6,49 +6,46 @@ import ( "encoding/binary" "io" "net" + "strconv" "time" ) -// HTTPPing try connect to a http(s) server with domain though the http addr -func HTTPPing(viaHost, domain string, port Port, timeout time.Duration) (err error) { - conn, err := net.DialTimeout("tcp", port.JoinAddr(viaHost), timeout) +// Port ========================== +type Port uint16 + +const HTTP Port = 80 +const HTTPS Port = 443 + +// Ping try connect to a http(s) server with domain though the http addr +func (p Port) Ping(domain string, timeout time.Duration) error { + conn, err := net.DialTimeout("tcp", net.JoinHostPort(domain, p.String()), timeout) if err != nil { return err } defer conn.Close() + return p.PingWithConn(domain, conn, timeout) +} + +// PingWithConn try connect to a http(s) server with domain though the http addr +func (p Port) PingWithConn(domain string, conn net.Conn, timeout time.Duration) error { conn.SetDeadline(time.Now().Add(timeout)) - if _, err = conn.Write(port.PingMsg(domain)); err != nil { + if _, err := conn.Write(p.PingMsg(domain)); err != nil { return err } // err -> nil: read something succ // err -> io.EOF: no such domain or connection refused // err -> timeout: tcp package has been dropped - _, err = conn.Read(make([]byte, 1)) - if err == io.EOF && viaHost == domain { + _, err := conn.Read(make([]byte, 1)) + if err == nil || err == io.EOF { return nil } return err } -// Port ========================== -type Port uint16 - -const ( - HTTP Port = iota - HTTPS -) - -func (p Port) JoinAddr(addr string) string { - switch p { - case HTTP: - return addr + ":80" - case HTTPS: - return addr + ":443" - default: - panic("invalid port") - } +func (p Port) String() string { + return strconv.Itoa(int(p)) } func (p Port) PingMsg(domain string) []byte { diff --git a/dns/dhcp.go b/internal/net/dhcp.go similarity index 95% rename from dns/dhcp.go rename to internal/net/dhcp.go index bb80410..f2131aa 100644 --- a/dns/dhcp.go +++ b/internal/net/dhcp.go @@ -1,4 +1,4 @@ -package dns +package net import ( "math/rand" @@ -9,14 +9,13 @@ import ( "github.com/krolaw/dhcp4" "github.com/libp2p/go-reuseport" "github.com/pkg/errors" - "github.com/wweir/sower/util" ) var xid = make([]byte, 4) var broadcastAddr, _ = net.ResolveUDPAddr("udp", "255.255.255.255:67") func GetDefaultDNSServer() (string, error) { - iface, err := util.PickInterface() + iface, err := PickInternetInterface() if err != nil { return "", errors.Wrap(err, "pick interface") } diff --git a/util/pick_iface_other.go b/internal/net/pick_iface_other.go similarity index 78% rename from util/pick_iface_other.go rename to internal/net/pick_iface_other.go index a52d2a5..8b56e3d 100644 --- a/util/pick_iface_other.go +++ b/internal/net/pick_iface_other.go @@ -1,14 +1,14 @@ // +build !windows -package util +package net import ( "errors" "net" ) -// PickInterface pick the first active net interface -func PickInterface() (*Iface, error) { +// PickInternetInterface pick the first active net interface +func PickInternetInterface() (*Iface, error) { ifaces, err := net.Interfaces() if err != nil { return nil, err diff --git a/util/pick_iface_windows.go b/internal/net/pick_iface_windows.go similarity index 90% rename from util/pick_iface_windows.go rename to internal/net/pick_iface_windows.go index e38c5f8..2bcf8ea 100644 --- a/util/pick_iface_windows.go +++ b/internal/net/pick_iface_windows.go @@ -1,6 +1,6 @@ // +build windows -package util +package net import ( "bytes" @@ -11,8 +11,8 @@ import ( "unsafe" ) -// PickInterface pick the first active net interface -func PickInterface() (*Iface, error) { +// PickInternetInterface pick the first active net interface +func PickInternetInterface() (*Iface, error) { list, err := getAdapterList() if err != nil { return nil, err diff --git a/internal/net/util.go b/internal/net/util.go new file mode 100644 index 0000000..d2fdd8d --- /dev/null +++ b/internal/net/util.go @@ -0,0 +1,9 @@ +package net + +import "net" + +// Iface is net interface address info +type Iface struct { + net.HardwareAddr + net.IP +} diff --git a/proxy/socks5/socks5.go b/internal/socks5/socks5.go similarity index 96% rename from proxy/socks5/socks5.go rename to internal/socks5/socks5.go index ac0bc67..b346fc1 100644 --- a/proxy/socks5/socks5.go +++ b/internal/socks5/socks5.go @@ -2,11 +2,10 @@ package socks5 import ( "encoding/binary" + "fmt" "io" "net" "strconv" - - "github.com/pkg/errors" ) func ToSocks5(c net.Conn, domain, port string) net.Conn { @@ -75,7 +74,7 @@ func (c *conn) Write(b []byte) (n int, err error) { switch resp.REP { case 0x00: default: - return 0, errors.Errorf("socks5 handshake fail, return code: %d", resp.REP) + return 0, fmt.Errorf("socks5 handshake fail, return code: %d", resp.REP) } switch resp.ATYP { diff --git a/main.go b/main.go index 8525e6d..d4785a5 100644 --- a/main.go +++ b/main.go @@ -1,50 +1,89 @@ package main import ( - "encoding/json" - "fmt" + "io" "net" + "sync" + "sync/atomic" + "time" - "github.com/golang/glog" "github.com/wweir/sower/conf" "github.com/wweir/sower/dns" - "github.com/wweir/sower/proxy" - "github.com/wweir/sower/proxy/transport" + "github.com/wweir/sower/internal/socks5" + "github.com/wweir/sower/mux" + "github.com/wweir/utils/log" ) -var version, date string - func main() { - cfg := &conf.Conf - if cfg.VersionOnly { - config, _ := json.MarshalIndent(cfg, "", "\t") - fmt.Printf("Version:\n\t%s %s\nConfig:\n%s", version, date, config) - return - } - glog.Infof("Starting sower(%s %s): %v", version, date, cfg) + go dns.ServeDNS() - tran, err := transport.GetTransport(cfg.NetType) + go proxy(conf.Conf.Upstream.Socks5, conf.Conf.Downstream.ServeIP, "80", mux.ParseHTTP) + go proxy(conf.Conf.Upstream.Socks5, conf.Conf.Downstream.ServeIP, "443", mux.ParseHTTPS) + + for port, target := range conf.Conf.Router.PortMapping { + go proxy(conf.Conf.Upstream.Socks5, conf.Conf.Downstream.ServeIP, port, + func(conn net.Conn) (net.Conn, string, error) { + return conn, target, nil + }) + } + + select {} +} + +func proxy(socks5Addr, serveIP, port string, mux func(net.Conn) (net.Conn, string, error)) { + ln, err := net.Listen("tcp", net.JoinHostPort(serveIP, port)) if err != nil { - glog.Exitln(err) + log.Fatalw("listen", "ip", serveIP, "port", port, "err", err) } - if cfg.ServerAddr == "" { - proxy.StartServer(tran, cfg.ServerPort, cfg.Cipher, cfg.Password) - - } else { - conf.AddRefreshFn(true, func() (string, error) { - dns.LoadRules(cfg.BlockList, cfg.Suggestions, cfg.WhiteList, cfg.ServerAddr) - return "load rules", nil - }) - - isSocks5 := (cfg.NetType == "SOCKS5") - serverAddr := net.JoinHostPort(cfg.ServerAddr, cfg.ServerPort) - - if cfg.HTTPProxy != "" { - go proxy.StartHttpProxy(tran, isSocks5, serverAddr, cfg.Cipher, cfg.Password, cfg.HTTPProxy) + log.Infow("start proxy", "port", port) + for { + conn, err := ln.Accept() + if err != nil { + log.Fatalw("listen", "ip", serveIP, "port", port, "err", err) } - go dns.StartDNS(cfg.DNSServer, cfg.ClientIP, conf.SuggestCh, cfg.SuggestLevel) - proxy.StartClient(tran, isSocks5, serverAddr, cfg.Cipher, cfg.Password, cfg.ClientIP) + if conn, target, err := mux(conn); err != nil { + log.Errorw("mux parse", "target", target, "err", err) + continue + } else { + go relay(socks5Addr, target, conn) + } } } + +func relay(socks5Addr, target string, conn net.Conn) { + var socks5Conn net.Conn + if c, err := net.Dial("tcp", socks5Addr); err != nil { + log.Errorw("dial socks5", "addr", socks5Addr, "err", err) + return + } else if host, port, err := net.SplitHostPort(target); err != nil { + log.Errorw("dial socks5", "target", target, "err", err) + return + } else { + socks5Conn = socks5.ToSocks5(c, host, port) + } + + wg := &sync.WaitGroup{} + exitFlag := new(int32) + wg.Add(2) + go redirect(conn, socks5Conn, wg, exitFlag) + redirect(socks5Conn, conn, wg, exitFlag) + wg.Wait() +} + +func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) { + io.Copy(dst, src) + + if atomic.CompareAndSwapInt32(exitFlag, 0, 1) { + // wakeup blocked goroutine + now := time.Now() + src.SetDeadline(now) + dst.SetDeadline(now) + } else { + src.Close() + dst.Close() + } + + wg.Done() +} diff --git a/mux/parse.go b/mux/parse.go new file mode 100644 index 0000000..99dcc0f --- /dev/null +++ b/mux/parse.go @@ -0,0 +1,42 @@ +package mux + +import ( + "bufio" + "io" + "net" + "net/http" + "strings" + + "github.com/wweir/sower/util" +) + +func ParseHTTP(conn net.Conn) (net.Conn, string, error) { + teeConn := &util.TeeConn{Conn: conn} + teeConn.StartOrReset() + defer teeConn.Stop() + + b := bufio.NewReader(teeConn) + resp, err := http.ReadRequest(b) + if err != nil { + return teeConn, "", err + } + if strings.Contains(resp.Host, ":") { + return teeConn, resp.Host, nil + } + return teeConn, resp.Host + ":80", nil +} + +func ParseHTTPS(conn net.Conn) (net.Conn, string, error) { + teeConn := &util.TeeConn{Conn: conn} + teeConn.StartOrReset() + defer teeConn.Stop() + + host, _, err := extractSNI(io.Reader(teeConn)) + if err != nil { + return teeConn, "", err + } + if strings.Contains(host, ":") { + return teeConn, host, nil + } + return teeConn, host + ":443", nil +} diff --git a/proxy/parser/sni.go b/mux/sni.go similarity index 99% rename from proxy/parser/sni.go rename to mux/sni.go index aa665d1..687da76 100644 --- a/proxy/parser/sni.go +++ b/mux/sni.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package parser +package mux import ( "encoding/binary" diff --git a/proxy/client.go b/proxy/client.go deleted file mode 100644 index 6f814cc..0000000 --- a/proxy/client.go +++ /dev/null @@ -1,98 +0,0 @@ -package proxy - -import ( - "net" - - "github.com/golang/glog" - "github.com/wweir/sower/proxy/parser" - "github.com/wweir/sower/proxy/shadow" - "github.com/wweir/sower/proxy/socks5" - "github.com/wweir/sower/proxy/transport" -) - -func StartClient(tran transport.Transport, isSocks5 bool, server, cipher, password, listenIP string) { - conn80 := listenLocal(listenIP, "80") - conn443 := listenLocal(listenIP, "443") - var isHttp bool - var conn net.Conn - - glog.Infoln("Client started.") - for { - select { - case conn = <-conn80: - isHttp = true - case conn = <-conn443: - isHttp = false - } - - resolveAddr(&server) - glog.V(1).Infof("new conn from (%s) to (%s)", conn.RemoteAddr(), server) - - rc, err := tran.Dial(server) - if err != nil { - conn.Close() - glog.Errorln(err) - continue - } - - switch { - case isSocks5 && isHttp: - c, host, port, err := parser.ParseHttpAddr(conn) - if err != nil { - c.Close() - rc.Close() - glog.Errorln(err) - continue - } - - conn = c - rc = socks5.ToSocks5(rc, host, port) - - case isSocks5 && !isHttp: - c, host, err := parser.ParseHttpsHost(conn) - if err != nil { - c.Close() - rc.Close() - glog.Errorln(err) - continue - } - - conn = c - rc = socks5.ToSocks5(rc, host, "443") - - case !isSocks5 && isHttp: - rc = shadow.Shadow(rc, cipher, password) - rc = parser.NewHttpConn(rc) - - case !isSocks5 && !isHttp: - rc = shadow.Shadow(rc, cipher, password) - rc = parser.NewHttpsConn(rc, "443") - } - - go relay(conn, rc) - } -} - -func listenLocal(listenIP string, port string) <-chan net.Conn { - connCh := make(chan net.Conn, 10) - go func() { - ln, err := net.Listen("tcp", net.JoinHostPort(listenIP, port)) - if err != nil { - glog.Fatalln(err) - } - - for { - conn, err := ln.Accept() - if err != nil { - glog.Errorln("accept", listenIP+port, "fail:", err) - continue - } - - conn.(*net.TCPConn).SetKeepAlive(true) - connCh <- conn - } - }() - - glog.Infoln("listening port:", port) - return connCh -} diff --git a/proxy/http_proxy.go b/proxy/http_proxy.go deleted file mode 100644 index 627e88d..0000000 --- a/proxy/http_proxy.go +++ /dev/null @@ -1,127 +0,0 @@ -package proxy - -import ( - "context" - "crypto/tls" - "io" - "net" - "net/http" - "net/url" - "time" - - "github.com/golang/glog" - "github.com/wweir/sower/proxy/parser" - "github.com/wweir/sower/proxy/shadow" - "github.com/wweir/sower/proxy/socks5" - "github.com/wweir/sower/proxy/transport" -) - -func StartHttpProxy(tran transport.Transport, isSocks5 bool, server, cipher, password, addr string) { - srv := &http.Server{ - Addr: addr, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resolveAddr(&server) - - if r.Method == http.MethodConnect { - httpsProxy(w, r, tran, isSocks5, server, cipher, password) - } else { - httpProxy(w, r, tran, isSocks5, server, cipher, password) - } - }), - // Disable HTTP/2. - TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, - IdleTimeout: 90 * time.Second, - } - - glog.Fatalln(srv.ListenAndServe()) -} - -func httpProxy(w http.ResponseWriter, r *http.Request, - tran transport.Transport, isSocks5 bool, server, cipher, password string) { - - roundTripper := &http.Transport{ - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - - if isSocks5 { - roundTripper.Proxy = func(*http.Request) (*url.URL, error) { - return url.Parse("socks5://" + server) - } - - } else { - roundTripper.DialContext = func(context.Context, string, string) (net.Conn, error) { - conn, err := tran.Dial(server) - if err != nil { - return nil, err - } - - conn = shadow.Shadow(conn, cipher, password) - return parser.NewHttpConn(conn), nil - } - } - - resp, err := roundTripper.RoundTrip(r) - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - glog.Errorln("serve https proxy, get remote data:", err) - return - } - defer resp.Body.Close() - - for k, vs := range resp.Header { - for _, v := range vs { - w.Header().Add(k, v) - } - } - w.WriteHeader(resp.StatusCode) - io.Copy(w, resp.Body) -} - -func httpsProxy(w http.ResponseWriter, r *http.Request, - tran transport.Transport, isSocks5 bool, server, cipher, password string) { - - // local conn - conn, _, err := w.(http.Hijacker).Hijack() - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - return - } - conn.(*net.TCPConn).SetKeepAlive(true) - - if _, err := conn.Write([]byte(r.Proto + " 200 Connection established\r\n\r\n")); err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - conn.Close() - glog.Errorln("serve https proxy, write data fail:", err) - return - } - - // remote conn - rc, err := tran.Dial(server) - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - conn.Close() - glog.Errorln("serve https proxy, dial remote fail:", err) - return - } - - host, port, err := net.SplitHostPort(r.Host) - if err != nil { - http.Error(w, err.Error(), http.StatusServiceUnavailable) - conn.Close() - glog.Errorln("serve https proxy, dial remote fail:", err) - return - } - - if isSocks5 { - rc = socks5.ToSocks5(rc, host, port) - - } else { - rc = shadow.Shadow(rc, cipher, password) - rc = parser.NewHttpsConn(rc, port) - } - - relay(rc, conn) -} diff --git a/proxy/parser/doc.go b/proxy/parser/doc.go deleted file mode 100644 index bf127df..0000000 --- a/proxy/parser/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Package parser transter conn to be a parser conn -// -// init request payload: -// (1) + (2))(+Overhead) + (size+Overhead) -// data definition: -// 0x00(any): [size](1) + [addr:port] + content -// 0x01(http): content -// 0x02(https): [port](2) + content -// -// init response payload: -// ([status code](2) + (2))(+Overhead) + (size+Overhead) -package parser diff --git a/proxy/parser/parse.go b/proxy/parser/parse.go deleted file mode 100644 index c8e8d52..0000000 --- a/proxy/parser/parse.go +++ /dev/null @@ -1,163 +0,0 @@ -package parser - -import ( - "bufio" - "io" - "net" - "net/http" - "strconv" - "strings" - - "github.com/pkg/errors" - "github.com/wweir/sower/util" -) - -const ( - OTHER byte = iota - HTTP - HTTPS -) - -// Write Addr -type conn struct { - typ byte - domain string - port string - init bool - net.Conn -} - -func NewOtherConn(c net.Conn, domain, port string) net.Conn { - return &conn{ - typ: OTHER, - domain: domain, - port: port, - init: true, - Conn: c, - } -} -func NewHttpConn(c net.Conn) net.Conn { - return &conn{ - typ: HTTP, - init: true, - Conn: c, - } -} -func NewHttpsConn(c net.Conn, port string) net.Conn { - return &conn{ - typ: HTTPS, - port: port, - init: true, - Conn: c, - } -} - -func (c *conn) Write(b []byte) (n int, err error) { - if c.init { - var pkg []byte - var prefixLen int - switch c.typ { - case OTHER: - // type + domain + ':' + port + data - prefixLen = 1 + len(c.domain) + 1 + len(c.port) - pkg = make([]byte, 0, prefixLen+len(b)) - pkg = append(pkg, OTHER) - pkg = append(pkg, byte(len(c.domain)+1+len(c.port))) - pkg = append(pkg, []byte(c.domain+":"+c.port)...) - - case HTTP: - // type + data - prefixLen = 1 - pkg = make([]byte, 0, prefixLen+len(b)) - pkg = append(pkg, HTTP) - - case HTTPS: - // type + port + data - prefixLen = 1 + 2 - pkg = make([]byte, 0, prefixLen+len(b)) - pkg = append(pkg, HTTPS) - port, _ := strconv.Atoi(c.port) - pkg = append(pkg, byte(port>>8), byte(port)) - } - - c.init = false - n, err := c.Conn.Write(append(pkg, b...)) - // n should larger than prefix length, if not, err is not nil - return n - prefixLen, err - } - - return c.Conn.Write(b) -} - -// Read Addr -func ParseAddr(conn net.Conn) (net.Conn, string, string, error) { - buf := make([]byte, 1) - if _, err := io.ReadFull(conn, buf); err != nil { - return conn, "", "", err - } - - switch buf[0] { - case OTHER: - if _, err := io.ReadFull(conn, buf); err != nil { - return conn, "", "", err - } - buf = make([]byte, int(buf[0])) - if _, err := io.ReadFull(conn, buf); err != nil { - return conn, "", "", err - } - - addr := string(buf) - if idx := strings.LastIndex(addr, ":"); idx != -1 { - return conn, addr[:idx], addr[idx+1:], nil - } - return conn, "", "", errors.New("invalid payload") - - case HTTP: - return ParseHttpAddr(conn) - - case HTTPS: - buf = make([]byte, 2) - if _, err := io.ReadFull(conn, buf); err != nil { - return conn, "", "", err - } - port := strconv.Itoa(int(buf[0])<<8 + int(buf[1])) - - conn, domain, err := ParseHttpsHost(conn) - return conn, domain, port, err - - default: - return conn, "", "", errors.Errorf("not supported type (%v)", buf[0]) - } -} - -func ParseHttpAddr(conn net.Conn) (net.Conn, string, string, error) { - teeConn := &util.TeeConn{Conn: conn} - teeConn.StartOrReset() - defer teeConn.Stop() - - b := bufio.NewReader(teeConn) - resp, err := http.ReadRequest(b) - if err != nil { - return teeConn, "", "", err - } - - if idx := strings.LastIndex(resp.Host, ":"); idx != -1 { - return teeConn, resp.Host[:idx], resp.Host[idx+1:], nil - } - return teeConn, resp.Host, "80", nil -} - -func ParseHttpsHost(conn net.Conn) (net.Conn, string, error) { - teeConn := &util.TeeConn{Conn: conn} - teeConn.StartOrReset() - defer teeConn.Stop() - - domain, _, err := extractSNI(teeConn) - if err != nil { - return teeConn, "", err - } else if domain == "" { - return teeConn, "", errors.New("ClientHello did not present an SNI extension") - } - - return teeConn, domain, nil -} diff --git a/proxy/parser/parse_test.go b/proxy/parser/parse_test.go deleted file mode 100644 index add722a..0000000 --- a/proxy/parser/parse_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package parser - -import ( - "bufio" - "bytes" - "io/ioutil" - "net" - "net/http" - "testing" - - "github.com/wweir/sower/proxy/shadow" - "github.com/wweir/sower/util" -) - -func TestParseAddr1(t *testing.T) { - c1, c2 := net.Pipe() - - go func() { - c1 = NewHttpConn(c1) - req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3})) - req.Write(c1) - }() - - c2, host, port, err := ParseAddr(c2) - - if err != nil || host != "wweir.cc" || port != "80" { - t.Error(err, host, port) - } - - req, err := http.ReadRequest(bufio.NewReader(c2)) - if err != nil { - t.Error(err) - } - - data, err := ioutil.ReadAll(req.Body) - if err != nil || len(data) != 3 || data[0] != 1 { - t.Error(err, data) - } -} - -func TestParseAddr2(t *testing.T) { - c1, c2 := net.Pipe() - - go func() { - c1 = NewHttpsConn(c1, "443") - c1.Write(util.HTTPS.PingMsg("wweir.cc")) - }() - - _, host, port, err := ParseAddr(c2) - - if err != nil || host != "wweir.cc" || port != "443" { - t.Error(err, host, port) - } -} - -func TestParseAddr3(t *testing.T) { - c1, c2 := net.Pipe() - - go func() { - c1 = NewOtherConn(c1, "wweir.cc", "1080") - c1.Write(util.HTTPS.PingMsg("wweir.cc")) - }() - - _, host, port, err := ParseAddr(c2) - - if err != nil || host != "wweir.cc" || port != "1080" { - t.Error(err, host, port) - } -} - -func TestParseAddr4(t *testing.T) { - c1, c2 := net.Pipe() - - go func() { - c1 = shadow.Shadow(c1, "AES_128_GCM", "12345678") - c1 = NewHttpConn(c1) - req, _ := http.NewRequest("GET", "http://wweir.cc", bytes.NewReader([]byte{1, 2, 3})) - req.Write(c1) - }() - - c2 = shadow.Shadow(c2, "AES_128_GCM", "12345678") - c2, host, port, err := ParseAddr(c2) - - if err != nil || host != "wweir.cc" || port != "80" { - t.Error(err, host, port) - } - - req, err := http.ReadRequest(bufio.NewReader(c2)) - if err != nil { - t.Error(err) - } - - data, err := ioutil.ReadAll(req.Body) - if err != nil || len(data) != 3 || data[0] != 1 { - t.Error(err, data) - } -} diff --git a/proxy/server.go b/proxy/server.go deleted file mode 100644 index 99da7a6..0000000 --- a/proxy/server.go +++ /dev/null @@ -1,51 +0,0 @@ -package proxy - -import ( - "net" - "strings" - - "github.com/golang/glog" - "github.com/wweir/sower/proxy/parser" - "github.com/wweir/sower/proxy/shadow" - "github.com/wweir/sower/proxy/transport" -) - -func StartServer(tran transport.Transport, port, cipher, password string) { - if port == "" { - glog.Fatalln("port must set") - } - if !strings.HasPrefix(port, ":") { - port = ":" + port - } - - connCh, err := tran.Listen(port) - if err != nil { - glog.Fatalf("listen %v fail: %s", port, err) - } - - glog.Infoln("Server started.") - for { - go handle(<-connCh, cipher, password) - } -} - -func handle(conn net.Conn, cipher, password string) { - conn = shadow.Shadow(conn, cipher, password) - conn, host, port, err := parser.ParseAddr(conn) - if err != nil { - conn.Close() - glog.Warningln(err) - return - } - glog.V(1).Infof("new conn from %s to %s:%s", conn.RemoteAddr(), host, port) - - rc, err := net.Dial("tcp", net.JoinHostPort(host, port)) - if err != nil { - conn.Close() - glog.Warningln(err) - return - } - rc.(*net.TCPConn).SetKeepAlive(true) - - relay(rc, conn) -} diff --git a/proxy/shadow/cipher.go b/proxy/shadow/cipher.go deleted file mode 100644 index c6af813..0000000 --- a/proxy/shadow/cipher.go +++ /dev/null @@ -1,76 +0,0 @@ -package shadow - -import ( - "crypto/aes" - "crypto/cipher" - - "github.com/pkg/errors" - "golang.org/x/crypto/chacha20poly1305" -) - -//go:generate stringer -type=typ $GOFILE -type typ int - -const ( - AES_128_GCM typ = iota - AES_192_GCM - AES_256_GCM - CHACHA20_IETF_POLY1305 - XCHACHA20_IETF_POLY1305 - cipherEnd -) - -func ListCiphers() []string { - list := make([]string, 0, int(cipherEnd)) - for i := typ(0); i < cipherEnd; i++ { - list = append(list, i.String()) - } - return list -} - -func pickCipher(typ, password string) (cipher.AEAD, error) { - var blockSize int - switch typ { - case AES_128_GCM.String(): - blockSize = 16 - case AES_192_GCM.String(): - blockSize = 24 - case AES_256_GCM.String(): - blockSize = 32 - - case CHACHA20_IETF_POLY1305.String(): - return chacha20poly1305.New(genKey(password, 256)) - case XCHACHA20_IETF_POLY1305.String(): - return chacha20poly1305.NewX(genKey(password, 256)) - - default: - return nil, errors.New("do not support cipher type: " + typ) - } - - // aes gcm - block, err := aes.NewCipher(genKey(password, blockSize)) - if err != nil { - return nil, errors.Wrap(err, "password") - } - - aead, err := cipher.NewGCM(block) - if err != nil { - return nil, errors.Wrap(err, AES_128_GCM.String()) - } - return aead, nil -} - -func genKey(filler string, size int) []byte { - res := make([]byte, size) - if filler == "" { - panic("password should not be empty") - } - - fillerByte := []byte(filler) - length := len(fillerByte) - for i := 0; ; i++ { - if copy(res[i*length:], fillerByte) != length { - return res - } - } -} diff --git a/proxy/shadow/doc.go b/proxy/shadow/doc.go deleted file mode 100644 index 9bc17fe..0000000 --- a/proxy/shadow/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Package shadow transter conn to be a crypto conn -// support aead mode only -// data payload: -// (2+Overhead) + (size+Overhead) -package shadow diff --git a/proxy/shadow/shadow.go b/proxy/shadow/shadow.go deleted file mode 100644 index 510da42..0000000 --- a/proxy/shadow/shadow.go +++ /dev/null @@ -1,111 +0,0 @@ -package shadow - -import ( - "crypto/cipher" - "encoding/binary" - "io" - "math/rand" - "net" -) - -const MAX_SIZE = 0xFFFF - -type conn struct { - maxSize int - aead cipher.AEAD - encryptNonce func() []byte - decryptNonce func() []byte - writeBuf []byte - readBuf []byte - readOffset int - net.Conn -} - -func (c *conn) Read(b []byte) (n int, err error) { - // read from buffer - if c.readOffset != 0 { - dataSize := len(c.readBuf) - c.aead.Overhead() - n = copy(b, c.readBuf[c.readOffset:dataSize]) - c.readOffset += n - - if c.readOffset == dataSize { - c.readOffset = 0 - } - return - } - - // read from conn - dataSize := 0 - { //read data size - c.readBuf = make([]byte, 2+c.aead.Overhead()) - if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil { - return - } - if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil { - return - } - dataSize = int(c.readBuf[0])<<8 + int(c.readBuf[1]) - } - { // read data - c.readBuf = make([]byte, dataSize+c.aead.Overhead()) - if _, err = io.ReadFull(c.Conn, c.readBuf); err != nil { - return - } - if _, err = c.aead.Open(c.readBuf[:0], c.decryptNonce(), c.readBuf, nil); err != nil { - return - } - } - - // buffer extra data - if n = copy(b, c.readBuf[:dataSize]); n < dataSize { - c.readOffset = n - } - return -} - -func (c *conn) Write(b []byte) (n int, err error) { - bLen := len(b) - dataSize := MAX_SIZE - (2 + c.aead.Overhead()) - c.aead.Overhead() - if bLen < c.maxSize { - dataSize = bLen - } - - // BigEndian - c.writeBuf[0], c.writeBuf[1] = byte(dataSize>>8), byte(dataSize) - - c.aead.Seal(c.writeBuf[:0], c.encryptNonce(), c.writeBuf[:2], nil) - c.aead.Seal(c.writeBuf[:2+c.aead.Overhead()], c.encryptNonce(), b[:dataSize], nil) - - _, err = c.Conn.Write(c.writeBuf[:dataSize+(2+c.aead.Overhead())+c.aead.Overhead()]) - if err != nil { - return 0, err - } - return dataSize, err -} - -func Shadow(c net.Conn, cipher, password string) net.Conn { - aead, err := pickCipher(cipher, password) - if err != nil { - panic(err) - } - - return &conn{ - maxSize: MAX_SIZE - (2 - aead.Overhead()) - aead.Overhead(), - aead: aead, - encryptNonce: newNonce(password, aead.NonceSize()), - decryptNonce: newNonce(password, aead.NonceSize()), - writeBuf: make([]byte, 0xFFFF), - Conn: c, - } -} - -func newNonce(password string, size int) func() []byte { - num, _ := binary.Varint([]byte(password)) - rnd := rand.New(rand.NewSource(num)) - - buf := make([]byte, size) - return func() []byte { - rnd.Read(buf) - return buf - } -} diff --git a/proxy/shadow/shadow_test.go b/proxy/shadow/shadow_test.go deleted file mode 100644 index 4ddb953..0000000 --- a/proxy/shadow/shadow_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package shadow - -import ( - "net" - "testing" -) - -func TestShadow(t *testing.T) { - c1, c2 := net.Pipe() - - go func() { - conn := Shadow(c1, "AES_128_GCM", "12345678") - conn.Write([]byte{1, 2}) - }() - - conn := Shadow(c2, "AES_128_GCM", "12345678") - buf := make([]byte, 3) - n, _ := conn.Read(buf) - if n!=2|| buf[0] != 1 || buf[1] != 2 { - t.Error(buf) - } -} diff --git a/proxy/shadow/typ_string.go b/proxy/shadow/typ_string.go deleted file mode 100644 index d6e344a..0000000 --- a/proxy/shadow/typ_string.go +++ /dev/null @@ -1,16 +0,0 @@ -// Code generated by "stringer -type=typ cipher.go"; DO NOT EDIT. - -package shadow - -import "strconv" - -const _typ_name = "AES_128_GCMAES_192_GCMAES_256_GCMCHACHA20_IETF_POLY1305XCHACHA20_IETF_POLY1305cipherEnd" - -var _typ_index = [...]uint8{0, 11, 22, 33, 55, 78, 87} - -func (i typ) String() string { - if i < 0 || i >= typ(len(_typ_index)-1) { - return "typ(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _typ_name[_typ_index[i]:_typ_index[i+1]] -} diff --git a/proxy/transport/kcp.go b/proxy/transport/kcp.go deleted file mode 100644 index 301bcd5..0000000 --- a/proxy/transport/kcp.go +++ /dev/null @@ -1,115 +0,0 @@ -package transport - -import ( - "net" - - "github.com/golang/glog" - "github.com/pkg/errors" - kcp "github.com/xtaci/kcp-go" -) - -type kcpTran struct { - client - server -} -type client struct { - DataShard int - ParityShard int - DSCP int - SockBuf int - AckNodelay bool - NoDelay int - Interval int - Resend int - NoCongestion int - SndWnd int - RcvWnd int - MTU int -} -type server struct { - DataShard int - ParityShard int - DSCP int - SockBuf int -} - -func init() { - transports["KCP"] = &kcpTran{ - client: client{ - DataShard: 10, - ParityShard: 3, - DSCP: 0, - SockBuf: 4194304, - NoDelay: 0, - Interval: 50, - Resend: 0, - NoCongestion: 0, - SndWnd: 0, - RcvWnd: 0, - MTU: 1350, - }, - server: server{ - DataShard: 10, - ParityShard: 3, - DSCP: 0, - SockBuf: 4194304, - }, - } -} - -func (c *client) Dial(server string) (net.Conn, error) { - conn, err := kcp.DialWithOptions(server, nil, c.DataShard, c.ParityShard) - if err != nil { - return nil, errors.Wrap(err, "dial") - } - - conn.SetStreamMode(true) - conn.SetWriteDelay(false) - conn.SetNoDelay(c.NoDelay, c.Interval, c.Resend, c.NoCongestion) - conn.SetWindowSize(c.SndWnd, c.RcvWnd) - conn.SetMtu(c.MTU) - conn.SetACKNoDelay(c.AckNodelay) - - if err := conn.SetDSCP(c.DSCP); err != nil { - return nil, errors.Wrap(err, "SetDSCP") - } - if err := conn.SetReadBuffer(c.SockBuf); err != nil { - return nil, errors.Wrap(err, "SetReadBuffer") - } - if err := conn.SetWriteBuffer(c.SockBuf); err != nil { - return nil, errors.Wrap(err, "SetWriteBuffer") - } - - return conn, nil -} - -func (s *server) Listen(port string) (<-chan net.Conn, error) { - ln, err := kcp.ListenWithOptions(port, nil, s.DataShard, s.ParityShard) - if err != nil { - return nil, err - } - - if err := ln.SetDSCP(s.DSCP); err != nil { - return nil, errors.Wrap(err, "SetDSCP") - } - if err := ln.SetReadBuffer(s.SockBuf); err != nil { - return nil, errors.Wrap(err, "SetReadBuffer") - } - if err := ln.SetWriteBuffer(s.SockBuf); err != nil { - return nil, errors.Wrap(err, "SetWriteBuffer") - } - - connCh := make(chan net.Conn) - go func() { - for { - conn, err := ln.AcceptKCP() - if err != nil { - glog.Fatalln("KCP listen:", err) - } - - connCh <- conn - } - }() - - return connCh, nil -} diff --git a/proxy/transport/quic.go b/proxy/transport/quic.go deleted file mode 100644 index 36114fa..0000000 --- a/proxy/transport/quic.go +++ /dev/null @@ -1,137 +0,0 @@ -package transport - -import ( - "context" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "math/big" - "net" - "time" - - "github.com/golang/glog" - quic "github.com/lucas-clemente/quic-go" - "github.com/pkg/errors" - "github.com/wweir/sower/util" -) - -type quicTran struct { - clientConf *quic.Config - sess quic.Session - - serverConf *quic.Config -} - -func init() { - transports["QUIC"] = &quicTran{ - - clientConf: &quic.Config{ - HandshakeTimeout: time.Second, - KeepAlive: true, - IdleTimeout: time.Minute, - }, - serverConf: &quic.Config{ - MaxIncomingStreams: 1024, - }, - } -} - -func (c *quicTran) Dial(server string) (net.Conn, error) { - if c.sess == nil { - if sess, err := quic.DialAddr(server, &tls.Config{InsecureSkipVerify: true}, c.clientConf); err != nil { - return nil, errors.Wrap(err, "session") - } else { - go func() { - <-sess.Context().Done() - sess.Close() - c.sess = nil - }() - c.sess = sess - } - } - - var stream quic.Stream - if err := util.WithTimeout(func() (err error) { - if stream, err = c.sess.OpenStream(); err != nil { - c.sess = nil - } - return - }, time.Second); err != nil { - return nil, errors.Wrap(err, "stream") - } - - return &streamConn{ - Stream: stream, - sess: c.sess, - }, nil -} - -type streamConn struct { - quic.Stream - sess quic.Session -} - -func (s *streamConn) LocalAddr() net.Addr { - return s.sess.LocalAddr() -} - -func (s *streamConn) RemoteAddr() net.Addr { - return s.sess.RemoteAddr() -} - -func mockTlsPem() *tls.Config { - key, err := rsa.GenerateKey(rand.Reader, 1024) - if err != nil { - glog.Fatalln(err) - } - template := x509.Certificate{SerialNumber: big.NewInt(1)} - certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key) - if err != nil { - glog.Fatalln(err) - } - - certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) - keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) - - tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) - if err != nil { - glog.Fatalln(err) - } - return &tls.Config{Certificates: []tls.Certificate{tlsCert}} -} - -func (s *quicTran) Listen(port string) (<-chan net.Conn, error) { - ln, err := quic.ListenAddr(port, mockTlsPem(), s.serverConf) - if err != nil { - return nil, errors.WithStack(err) - } - - connCh := make(chan net.Conn) - go func() { - for { - sess, err := ln.Accept(context.Background()) - if err != nil { - glog.Fatalln(err) - } - go accept(sess, connCh) - } - }() - return connCh, nil -} - -func accept(sess quic.Session, connCh chan<- net.Conn) { - glog.V(1).Infoln("new session from ", sess.RemoteAddr()) - defer sess.Close() - - for { - stream, err := sess.AcceptStream(context.Background()) - if err != nil { - glog.Errorln(err) - return - } - - connCh <- &streamConn{stream, sess} - } -} diff --git a/proxy/transport/tcp.go b/proxy/transport/tcp.go deleted file mode 100644 index b3b876a..0000000 --- a/proxy/transport/tcp.go +++ /dev/null @@ -1,58 +0,0 @@ -package transport - -import ( - "net" - "time" - - "github.com/golang/glog" -) - -type tcp struct { - DialTimeout time.Duration - isSocks5 bool -} - -func init() { - transports["TCP"] = &tcp{ - DialTimeout: 5 * time.Second, - } - transports["SOCKS5"] = &tcp{ - DialTimeout: 5 * time.Second, - isSocks5: true, - } -} - -func (t *tcp) Dial(server string) (net.Conn, error) { - conn, err := net.DialTimeout("tcp", server, t.DialTimeout) - if err != nil { - return nil, err - } - - conn.(*net.TCPConn).SetKeepAlive(true) - return conn, nil -} - -func (t *tcp) Listen(port string) (<-chan net.Conn, error) { - if t.isSocks5 { - panic("not support run as socks5 server") - } - - ln, err := net.Listen("tcp", port) - if err != nil { - return nil, err - } - - connCh := make(chan net.Conn) - go func() { - for { - conn, err := ln.Accept() - if err != nil { - glog.Fatalln("TCP listen:", err) - } - - conn.(*net.TCPConn).SetKeepAlive(true) - connCh <- conn - } - }() - return connCh, nil -} diff --git a/proxy/transport/util.go b/proxy/transport/util.go deleted file mode 100644 index 15e1229..0000000 --- a/proxy/transport/util.go +++ /dev/null @@ -1,30 +0,0 @@ -package transport - -import ( - "net" - - "github.com/pkg/errors" -) - -type Transport interface { - Dial(server string) (net.Conn, error) - Listen(port string) (<-chan net.Conn, error) -} - -var transports = map[string]Transport{} - -func ListTransports() []string { - list := make([]string, 0, len(transports)) - for key := range transports { - list = append(list, key) - } - return list -} - -func GetTransport(netType string) (Transport, error) { - tran, ok := transports[netType] - if !ok { - return nil, errors.New("invalid net type: " + netType) - } - return tran, nil -} diff --git a/proxy/util.go b/proxy/util.go deleted file mode 100644 index 2210bb2..0000000 --- a/proxy/util.go +++ /dev/null @@ -1,53 +0,0 @@ -package proxy - -import ( - "io" - "net" - "sync" - "sync/atomic" - "time" - - "github.com/golang/glog" -) - -// race safe -var resolved = false - -func resolveAddr(server *string) { - if !resolved { - if addr, err := net.ResolveTCPAddr("tcp", *server); err != nil { - glog.Errorln(err) - } else { - glog.Infof("remote server (%s)=>(%s)", *server, addr) - *server = addr.String() - resolved = true - } - } -} - -func relay(conn1, conn2 net.Conn) { - wg := &sync.WaitGroup{} - exitFlag := new(int32) - wg.Add(2) - go redirect(conn2, conn1, wg, exitFlag) - redirect(conn1, conn2, wg, exitFlag) - wg.Wait() -} - -func redirect(dst, src net.Conn, wg *sync.WaitGroup, exitFlag *int32) { - if _, err := io.Copy(dst, src); err != nil { - glog.V(1).Infof("%s<>%s -> %s<>%s: %s", src.RemoteAddr(), src.LocalAddr(), dst.LocalAddr(), dst.RemoteAddr(), err) - } - - if atomic.CompareAndSwapInt32(exitFlag, 0, 1) { - // wakeup blocked goroutine - now := time.Now() - src.SetDeadline(now) - dst.SetDeadline(now) - } else { - src.Close() - dst.Close() - } - - wg.Done() -} diff --git a/util/pick_iface_test.go b/util/pick_iface_test.go deleted file mode 100644 index b81b6f2..0000000 --- a/util/pick_iface_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package util - -import ( - "runtime" - "testing" -) - -func TestPickInterface(t *testing.T) { - switch runtime.GOOS { - case "windows": - case "darwin": - default: - t.Skip("skip for some enviroment not have net interface") - return - } - - got, err := PickInterface() - if err != nil { - t.Errorf("PickInterface() error = %v", err) - } else { - t.Logf("PickInterface() got: MAC: %s IP: %s", got.HardwareAddr, got.IP) - } -} diff --git a/util/util.go b/util/util.go deleted file mode 100644 index 69cea98..0000000 --- a/util/util.go +++ /dev/null @@ -1,30 +0,0 @@ -package util - -import ( - "errors" - "net" - "time" -) - -// Iface is net interface address info -type Iface struct { - net.HardwareAddr - net.IP -} - -func WithTimeout(fn func() error, timeout time.Duration) error { - var okCh = make(chan struct{}) - var err error - - go func() { - err = fn() - close(okCh) - }() - - select { - case <-okCh: - return err - case <-time.After(timeout): - return errors.New("timeout: " + timeout.String()) - } -}