From 39b45513afa941372ace4f2c09801f6d6a573990 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Thu, 12 Jul 2018 18:03:02 +0800 Subject: [PATCH 001/535] Improve: delete useless code and code coverage is now 100% --- observable/iterable.go | 15 -------------- observable/observable.go | 1 - observable/observable_test.go | 37 +++++++++++++++++++++-------------- observable/util.go | 15 -------------- 4 files changed, 22 insertions(+), 46 deletions(-) delete mode 100644 observable/util.go diff --git a/observable/iterable.go b/observable/iterable.go index 7892994557..ad0943b5e3 100644 --- a/observable/iterable.go +++ b/observable/iterable.go @@ -1,18 +1,3 @@ package observable -import ( - "errors" -) - type Iterable <-chan interface{} - -func NewIterable(any interface{}) (Iterable, error) { - switch any := any.(type) { - case chan interface{}: - return Iterable(any), nil - case <-chan interface{}: - return Iterable(any), nil - default: - return nil, errors.New("type error") - } -} diff --git a/observable/observable.go b/observable/observable.go index 5ba292ec7d..4b216d4f81 100644 --- a/observable/observable.go +++ b/observable/observable.go @@ -50,7 +50,6 @@ func (o *Observable) Subscribe() (Subscription, error) { func (o *Observable) UnSubscribe(sub Subscription) { elm, exist := o.listener.Load(sub) if !exist { - println("not exist") return } subscriber := elm.(*Subscriber) diff --git a/observable/observable_test.go b/observable/observable_test.go index 10bef10d3c..c3d178dbcb 100644 --- a/observable/observable_test.go +++ b/observable/observable_test.go @@ -27,11 +27,7 @@ func TestObservable(t *testing.T) { t.Error(err) } count := 0 - for { - _, open := <-data - if !open { - break - } + for range data { count = count + 1 } if count != 5 { @@ -49,11 +45,7 @@ func TestObservable_MutilSubscribe(t *testing.T) { var wg sync.WaitGroup wg.Add(2) waitCh := func(ch <-chan interface{}) { - for { - _, open := <-ch - if !open { - break - } + for range ch { count = count + 1 } wg.Done() @@ -80,6 +72,25 @@ func TestObservable_UnSubscribe(t *testing.T) { } } +func TestObservable_SubscribeClosedSource(t *testing.T) { + iter := iterator([]interface{}{1}) + src := NewObservable(iter) + data, _ := src.Subscribe() + <-data + + _, closed := src.Subscribe() + if closed == nil { + t.Error("Observable should be closed") + } +} + +func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { + sub := Subscription(make(chan interface{})) + iter := iterator([]interface{}{1}) + src := NewObservable(iter) + src.UnSubscribe(sub) +} + func TestObservable_SubscribeGoroutineLeak(t *testing.T) { // waiting for other goroutine recycle time.Sleep(120 * time.Millisecond) @@ -97,11 +108,7 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) { var wg sync.WaitGroup wg.Add(max) waitCh := func(ch <-chan interface{}) { - for { - _, open := <-ch - if !open { - break - } + for range ch { } wg.Done() } diff --git a/observable/util.go b/observable/util.go deleted file mode 100644 index d7d02b0b1f..0000000000 --- a/observable/util.go +++ /dev/null @@ -1,15 +0,0 @@ -package observable - -func mergeWithBytes(ch <-chan interface{}, buf []byte) chan interface{} { - out := make(chan interface{}) - go func() { - defer close(out) - if len(buf) != 0 { - out <- buf - } - for elm := range ch { - out <- elm - } - }() - return out -} From 0eef9bbf5d86bbbd2626dfcef7830d4268e8271c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 12 Jul 2018 23:28:38 +0800 Subject: [PATCH 002/535] Add: selector and proxys & rules router --- adapters/direct.go | 4 ++ adapters/reject.go | 4 ++ adapters/selector.go | 65 ++++++++++++++++++++ adapters/shadowsocks.go | 4 ++ adapters/urltest.go | 8 +++ constant/adapters.go | 30 ++++++++++ hub/configs.go | 64 +++++++------------- hub/proxys.go | 129 ++++++++++++++++++++++++++++++++++++++++ hub/rules.go | 53 +++++++++++++++++ hub/server.go | 2 + tunnel/mode.go | 22 +++++++ tunnel/tunnel.go | 52 ++++++++++++++-- 12 files changed, 390 insertions(+), 47 deletions(-) create mode 100644 adapters/selector.go create mode 100644 hub/proxys.go create mode 100644 hub/rules.go create mode 100644 tunnel/mode.go diff --git a/adapters/direct.go b/adapters/direct.go index f561ee9c60..3df410da6d 100644 --- a/adapters/direct.go +++ b/adapters/direct.go @@ -35,6 +35,10 @@ func (d *Direct) Name() string { return "Direct" } +func (d *Direct) Type() C.AdapterType { + return C.Direct +} + func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { c, err := net.Dial("tcp", net.JoinHostPort(addr.String(), addr.Port)) if err != nil { diff --git a/adapters/reject.go b/adapters/reject.go index 897e1c600d..91bdfd1ef4 100644 --- a/adapters/reject.go +++ b/adapters/reject.go @@ -31,6 +31,10 @@ func (r *Reject) Name() string { return "Reject" } +func (r *Reject) Type() C.AdapterType { + return C.Reject +} + func (r *Reject) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { return &RejectAdapter{}, nil } diff --git a/adapters/selector.go b/adapters/selector.go new file mode 100644 index 0000000000..62651530ec --- /dev/null +++ b/adapters/selector.go @@ -0,0 +1,65 @@ +package adapters + +import ( + "errors" + + C "github.com/Dreamacro/clash/constant" +) + +type Selector struct { + name string + selected C.Proxy + proxys map[string]C.Proxy +} + +func (s *Selector) Name() string { + return s.name +} + +func (s *Selector) Type() C.AdapterType { + return C.Selector +} + +func (s *Selector) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { + return s.selected.Generator(addr) +} + +func (s *Selector) Now() string { + return s.selected.Name() +} + +func (s *Selector) All() []string { + var all []string + for k, _ := range s.proxys { + all = append(all, k) + } + return all +} + +func (s *Selector) Set(name string) error { + proxy, exist := s.proxys[name] + if !exist { + return errors.New("Proxy does not exist") + } + s.selected = proxy + return nil +} + +func NewSelector(name string, proxys map[string]C.Proxy) (*Selector, error) { + if len(proxys) == 0 { + return nil, errors.New("Provide at least one proxy") + } + + mapping := make(map[string]C.Proxy) + var init string + for k, v := range proxys { + mapping[k] = v + init = k + } + s := &Selector{ + name: name, + proxys: mapping, + selected: proxys[init], + } + return s, nil +} diff --git a/adapters/shadowsocks.go b/adapters/shadowsocks.go index f4950c59da..6884aacc56 100644 --- a/adapters/shadowsocks.go +++ b/adapters/shadowsocks.go @@ -44,6 +44,10 @@ func (ss *ShadowSocks) Name() string { return ss.name } +func (ss *ShadowSocks) Type() C.AdapterType { + return C.Shadowsocks +} + func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { c, err := net.Dial("tcp", ss.server) if err != nil { diff --git a/adapters/urltest.go b/adapters/urltest.go index 7608024fbb..a68b2b90ef 100644 --- a/adapters/urltest.go +++ b/adapters/urltest.go @@ -26,6 +26,14 @@ func (u *URLTest) Name() string { return u.name } +func (u *URLTest) Type() C.AdapterType { + return C.URLTest +} + +func (u *URLTest) Now() string { + return u.fast.Name() +} + func (u *URLTest) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { return u.fast.Generator(addr) } diff --git a/constant/adapters.go b/constant/adapters.go index 101115c557..df265bdc53 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -5,6 +5,15 @@ import ( "net" ) +// Adapter Type +const ( + Direct AdapterType = iota + Reject + Selector + Shadowsocks + URLTest +) + type ProxyAdapter interface { ReadWriter() io.ReadWriter Conn() net.Conn @@ -19,5 +28,26 @@ type ServerAdapter interface { type Proxy interface { Name() string + Type() AdapterType Generator(addr *Addr) (ProxyAdapter, error) } + +// AdapterType is enum of adapter type +type AdapterType int + +func (at AdapterType) String() string { + switch at { + case Direct: + return "Direct" + case Reject: + return "Reject" + case Selector: + return "Selector" + case Shadowsocks: + return "Shadowsocks" + case URLTest: + return "URLTest" + default: + return "Unknow" + } +} diff --git a/hub/configs.go b/hub/configs.go index 3cc46a3207..63179e8a7c 100644 --- a/hub/configs.go +++ b/hub/configs.go @@ -3,65 +3,47 @@ package hub import ( "net/http" + "github.com/Dreamacro/clash/tunnel" + "github.com/go-chi/chi" "github.com/go-chi/render" ) -type Configs struct { - Proxys []Proxy `json:"proxys"` - Rules []Rule `json:"rules"` -} - -type Proxy struct { - Name string `json:"name"` -} - -type Rule struct { - Name string `json:"name"` - Payload string `json:"type"` -} - func configRouter() http.Handler { r := chi.NewRouter() - r.Get("/", getConfig) r.Put("/", updateConfig) return r } -func getConfig(w http.ResponseWriter, r *http.Request) { - rulesCfg, proxysCfg := tun.Config() - - var ( - rules []Rule - proxys []Proxy - ) - - for _, rule := range rulesCfg { - rules = append(rules, Rule{ - Name: rule.RuleType().String(), - Payload: rule.Payload(), - }) - } - - for _, proxy := range proxysCfg { - proxys = append(proxys, Proxy{Name: proxy.Name()}) - } +type General struct { + Mode string `json:mode` +} - w.WriteHeader(http.StatusOK) - render.JSON(w, r, Configs{ - Rules: rules, - Proxys: proxys, - }) +var modeMapping = map[string]tunnel.Mode{ + "global": tunnel.Global, + "rule": tunnel.Rule, + "direct": tunnel.Direct, } func updateConfig(w http.ResponseWriter, r *http.Request) { - err := tun.UpdateConfig() + general := &General{} + err := render.DecodeJSON(r.Body, general) if err != nil { - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusBadRequest) + render.JSON(w, r, Error{ + Error: "Format error", + }) + return + } + + mode, ok := modeMapping[general.Mode] + if !ok { + w.WriteHeader(http.StatusBadRequest) render.JSON(w, r, Error{ - Error: err.Error(), + Error: "Mode error", }) return } + tun.SetMode(mode) w.WriteHeader(http.StatusNoContent) } diff --git a/hub/proxys.go b/hub/proxys.go new file mode 100644 index 0000000000..04ff643174 --- /dev/null +++ b/hub/proxys.go @@ -0,0 +1,129 @@ +package hub + +import ( + "fmt" + "net/http" + + A "github.com/Dreamacro/clash/adapters" + C "github.com/Dreamacro/clash/constant" + + "github.com/go-chi/chi" + "github.com/go-chi/render" +) + +func proxyRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getProxys) + r.Get("/{name}", getProxy) + r.Put("/{name}", updateProxy) + return r +} + +type SampleProxy struct { + Type string `json:"type"` +} + +type Selector struct { + Type string `json:"type"` + Now string `json:"now"` + All []string `json:"all"` +} + +type URLTest struct { + Type string `json:"type"` + Now string `json:"now"` +} + +func transformProxy(proxy C.Proxy) interface{} { + t := proxy.Type() + switch t { + case C.Selector: + selector := proxy.(*A.Selector) + return Selector{ + Type: t.String(), + Now: selector.Now(), + All: selector.All(), + } + case C.URLTest: + return URLTest{ + Type: t.String(), + Now: proxy.(*A.URLTest).Now(), + } + default: + return SampleProxy{ + Type: proxy.Type().String(), + } + } +} + +type GetProxysResponse struct { + Proxys map[string]interface{} `json:"proxys"` +} + +func getProxys(w http.ResponseWriter, r *http.Request) { + _, rawProxys := tun.Config() + proxys := make(map[string]interface{}) + for name, proxy := range rawProxys { + proxys[name] = transformProxy(proxy) + } + render.JSON(w, r, GetProxysResponse{Proxys: proxys}) +} + +func getProxy(w http.ResponseWriter, r *http.Request) { + name := chi.URLParam(r, "name") + _, proxys := tun.Config() + proxy, exist := proxys[name] + if !exist { + w.WriteHeader(http.StatusNotFound) + render.JSON(w, r, Error{ + Error: "Proxy not found", + }) + return + } + render.JSON(w, r, transformProxy(proxy)) +} + +type UpdateProxyRequest struct { + Name string `json:"name"` +} + +func updateProxy(w http.ResponseWriter, r *http.Request) { + req := UpdateProxyRequest{} + if err := render.DecodeJSON(r.Body, &req); err != nil { + w.WriteHeader(http.StatusBadRequest) + render.JSON(w, r, Error{ + Error: "Format error", + }) + return + } + + name := chi.URLParam(r, "name") + _, proxys := tun.Config() + proxy, exist := proxys[name] + if !exist { + w.WriteHeader(http.StatusNotFound) + render.JSON(w, r, Error{ + Error: "Proxy not found", + }) + return + } + + selector, ok := proxy.(*A.Selector) + if !ok { + w.WriteHeader(http.StatusBadRequest) + render.JSON(w, r, Error{ + Error: "Proxy can't update", + }) + return + } + + if err := selector.Set(req.Name); err != nil { + w.WriteHeader(http.StatusBadRequest) + render.JSON(w, r, Error{ + Error: fmt.Sprintf("Selector update error: %s", err.Error()), + }) + return + } + + w.WriteHeader(http.StatusNoContent) +} diff --git a/hub/rules.go b/hub/rules.go new file mode 100644 index 0000000000..6524134c02 --- /dev/null +++ b/hub/rules.go @@ -0,0 +1,53 @@ +package hub + +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/go-chi/render" +) + +func ruleRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getRules) + r.Put("/", updateRules) + return r +} + +type Rule struct { + Name string `json:"name"` + Payload string `json:"type"` +} + +type GetRulesResponse struct { + Rules []Rule `json:"rules"` +} + +func getRules(w http.ResponseWriter, r *http.Request) { + rulesCfg, _ := tun.Config() + + var rules []Rule + for _, rule := range rulesCfg { + rules = append(rules, Rule{ + Name: rule.RuleType().String(), + Payload: rule.Payload(), + }) + } + + w.WriteHeader(http.StatusOK) + render.JSON(w, r, GetRulesResponse{ + Rules: rules, + }) +} + +func updateRules(w http.ResponseWriter, r *http.Request) { + err := tun.UpdateConfig() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + render.JSON(w, r, Error{ + Error: err.Error(), + }) + return + } + w.WriteHeader(http.StatusNoContent) +} diff --git a/hub/server.go b/hub/server.go index 5c9f40384e..1689d0e858 100644 --- a/hub/server.go +++ b/hub/server.go @@ -31,6 +31,8 @@ func NewHub(addr string) { r.Get("/traffic", traffic) r.Get("/logs", getLogs) r.Mount("/configs", configRouter()) + r.Mount("/proxys", proxyRouter()) + r.Mount("/rules", ruleRouter()) err := http.ListenAndServe(addr, r) if err != nil { diff --git a/tunnel/mode.go b/tunnel/mode.go new file mode 100644 index 0000000000..d82a0aee28 --- /dev/null +++ b/tunnel/mode.go @@ -0,0 +1,22 @@ +package tunnel + +type Mode int + +const ( + Global Mode = iota + Rule + Direct +) + +func (m Mode) String() string { + switch m { + case Global: + return "Global" + case Rule: + return "Rule" + case Direct: + return "Direct" + default: + return "Unknow" + } +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index d1d581ab2f..5208ec924c 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -28,6 +28,8 @@ type Tunnel struct { logCh chan interface{} configLock *sync.RWMutex traffic *C.Traffic + mode Mode + selector *adapters.Selector } func (t *Tunnel) Add(req C.ServerAdapter) { @@ -46,6 +48,10 @@ func (t *Tunnel) Log() *observable.Observable { return t.observable } +func (t *Tunnel) SetMode(mode Mode) { + t.mode = mode +} + func (t *Tunnel) UpdateConfig() (err error) { cfg, err := C.GetConfig() if err != nil { @@ -62,11 +68,10 @@ func (t *Tunnel) UpdateConfig() (err error) { // parse proxy for _, key := range proxysConfig.Keys() { - proxy := strings.Split(key.Value(), ",") + proxy := key.Strings(",") if len(proxy) == 0 { continue } - proxy = trimArr(proxy) switch proxy[0] { // ss, server, port, cipter, password case "ss": @@ -106,12 +111,12 @@ func (t *Tunnel) UpdateConfig() (err error) { // parse proxy groups for _, key := range groupsConfig.Keys() { rule := strings.Split(key.Value(), ",") - if len(rule) < 4 { - continue - } rule = trimArr(rule) switch rule[0] { case "url-test": + if len(rule) < 4 { + return fmt.Errorf("URLTest need more than 4 param") + } proxyNames := rule[1 : len(rule)-2] delay, _ := strconv.Atoi(rule[len(rule)-1]) url := rule[len(rule)-2] @@ -127,6 +132,24 @@ func (t *Tunnel) UpdateConfig() (err error) { return fmt.Errorf("Config error: %s", err.Error()) } proxys[key.Name()] = adapter + case "select": + if len(rule) < 3 { + return fmt.Errorf("Selector need more than 3 param") + } + proxyNames := rule[1:] + selectProxy := make(map[string]C.Proxy) + for _, name := range proxyNames { + proxy, exist := proxys[name] + if !exist { + return fmt.Errorf("Proxy %s not exist", name) + } + selectProxy[name] = proxy + } + selector, err := adapters.NewSelector(key.Name(), selectProxy) + if err != nil { + return fmt.Errorf("Selector create error: %s", err.Error()) + } + proxys[key.Name()] = selector } } @@ -145,8 +168,14 @@ func (t *Tunnel) UpdateConfig() (err error) { } } + s, err := adapters.NewSelector("Proxy", proxys) + if err != nil { + return err + } + t.proxys = proxys t.rules = rules + t.selector = s return nil } @@ -163,7 +192,17 @@ func (t *Tunnel) process() { func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer localConn.Close() addr := localConn.Addr() - proxy := t.match(addr) + + var proxy C.Proxy + switch t.mode { + case Direct: + proxy = t.proxys["DIRECT"] + case Global: + proxy = t.selector + // Rule + default: + proxy = t.match(addr) + } remoConn, err := proxy.Generator(addr) if err != nil { t.logCh <- newLog(WARNING, "Proxy connect error: %s", err.Error()) @@ -201,6 +240,7 @@ func newTunnel() *Tunnel { logCh: logCh, configLock: &sync.RWMutex{}, traffic: C.NewTraffic(time.Second), + mode: Rule, } go tunnel.process() go tunnel.subscribeLogs() From 3cacfb8a7ffba7eab237450fd5b4550a113c2cde Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 15 Jul 2018 22:23:20 +0800 Subject: [PATCH 003/535] Update: add config route --- constant/config.go | 11 +++- constant/proxy.go | 7 +++ hub/common.go | 31 ++++++++++ hub/configs.go | 57 ++++++++++++----- hub/proxys.go | 6 +- hub/rules.go | 4 +- hub/server.go | 26 +++----- main.go | 28 +++------ proxy/http/server.go | 33 ++++++++-- proxy/listener.go | 143 +++++++++++++++++++++++++++++++++++++++++++ proxy/socks/tcp.go | 44 +++++++++---- tunnel/tunnel.go | 4 ++ 12 files changed, 318 insertions(+), 76 deletions(-) create mode 100644 constant/proxy.go create mode 100644 hub/common.go create mode 100644 proxy/listener.go diff --git a/constant/config.go b/constant/config.go index 63c8785991..c915ae6c73 100644 --- a/constant/config.go +++ b/constant/config.go @@ -16,8 +16,8 @@ import ( const ( Name = "clash" - DefalutHTTPPort = "7890" - DefalutSOCKSPort = "7891" + DefalutHTTPPort = 7890 + DefalutSOCKSPort = 7891 ) var ( @@ -26,6 +26,13 @@ var ( MMDBPath string ) +type General struct { + Mode *string `json:"mode,omitempty"` + AllowLan *bool `json:"allow-lan,omitempty"` + Port *int `json:"port,omitempty"` + SocksPort *int `json:"socks-port,omitempty"` +} + func init() { currentUser, err := user.Current() if err != nil { diff --git a/constant/proxy.go b/constant/proxy.go new file mode 100644 index 0000000000..302e92c328 --- /dev/null +++ b/constant/proxy.go @@ -0,0 +1,7 @@ +package constant + +// ProxySignal is used to handle graceful shutdown of proxy +type ProxySignal struct { + Done chan<- struct{} + Closed <-chan struct{} +} diff --git a/hub/common.go b/hub/common.go new file mode 100644 index 0000000000..bd900c2800 --- /dev/null +++ b/hub/common.go @@ -0,0 +1,31 @@ +package hub + +import ( + "github.com/Dreamacro/clash/proxy" + T "github.com/Dreamacro/clash/tunnel" +) + +var ( + tunnel = T.GetInstance() + listener = proxy.Instance() +) + +type Error struct { + Error string `json:"error"` +} + +type Errors struct { + Errors map[string]string `json:"errors"` +} + +func formatErrors(errorsMap map[string]error) (bool, Errors) { + errors := make(map[string]string) + hasError := false + for key, err := range errorsMap { + if err != nil { + errors[key] = err.Error() + hasError = true + } + } + return hasError, Errors{Errors: errors} +} diff --git a/hub/configs.go b/hub/configs.go index 63179e8a7c..63d7fff4db 100644 --- a/hub/configs.go +++ b/hub/configs.go @@ -1,9 +1,12 @@ package hub import ( + "fmt" "net/http" - "github.com/Dreamacro/clash/tunnel" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/proxy" + T "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -11,22 +14,26 @@ import ( func configRouter() http.Handler { r := chi.NewRouter() - r.Put("/", updateConfig) + r.Get("/", getConfigs) + r.Put("/", updateConfigs) return r } -type General struct { - Mode string `json:mode` +var modeMapping = map[string]T.Mode{ + "Global": T.Global, + "Rule": T.Rule, + "Direct": T.Direct, } -var modeMapping = map[string]tunnel.Mode{ - "global": tunnel.Global, - "rule": tunnel.Rule, - "direct": tunnel.Direct, +func getConfigs(w http.ResponseWriter, r *http.Request) { + info := listener.Info() + mode := tunnel.GetMode().String() + info.Mode = &mode + render.JSON(w, r, info) } -func updateConfig(w http.ResponseWriter, r *http.Request) { - general := &General{} +func updateConfigs(w http.ResponseWriter, r *http.Request) { + general := &C.General{} err := render.DecodeJSON(r.Body, general) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -36,14 +43,32 @@ func updateConfig(w http.ResponseWriter, r *http.Request) { return } - mode, ok := modeMapping[general.Mode] - if !ok { + // update errors + var proxyErr, modeErr error + + // update proxy + listener := proxy.Instance() + proxyErr = listener.Update(general.AllowLan, general.Port, general.SocksPort) + + // update mode + if general.Mode != nil { + mode, ok := modeMapping[*general.Mode] + if !ok { + modeErr = fmt.Errorf("Mode error") + } else { + tunnel.SetMode(mode) + } + } + + hasError, errors := formatErrors(map[string]error{ + "proxy": proxyErr, + "mode": modeErr, + }) + + if hasError { w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, Error{ - Error: "Mode error", - }) + render.JSON(w, r, errors) return } - tun.SetMode(mode) w.WriteHeader(http.StatusNoContent) } diff --git a/hub/proxys.go b/hub/proxys.go index 04ff643174..52d6297898 100644 --- a/hub/proxys.go +++ b/hub/proxys.go @@ -61,7 +61,7 @@ type GetProxysResponse struct { } func getProxys(w http.ResponseWriter, r *http.Request) { - _, rawProxys := tun.Config() + _, rawProxys := tunnel.Config() proxys := make(map[string]interface{}) for name, proxy := range rawProxys { proxys[name] = transformProxy(proxy) @@ -71,7 +71,7 @@ func getProxys(w http.ResponseWriter, r *http.Request) { func getProxy(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") - _, proxys := tun.Config() + _, proxys := tunnel.Config() proxy, exist := proxys[name] if !exist { w.WriteHeader(http.StatusNotFound) @@ -98,7 +98,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { } name := chi.URLParam(r, "name") - _, proxys := tun.Config() + _, proxys := tunnel.Config() proxy, exist := proxys[name] if !exist { w.WriteHeader(http.StatusNotFound) diff --git a/hub/rules.go b/hub/rules.go index 6524134c02..3f6dbe143c 100644 --- a/hub/rules.go +++ b/hub/rules.go @@ -24,7 +24,7 @@ type GetRulesResponse struct { } func getRules(w http.ResponseWriter, r *http.Request) { - rulesCfg, _ := tun.Config() + rulesCfg, _ := tunnel.Config() var rules []Rule for _, rule := range rulesCfg { @@ -41,7 +41,7 @@ func getRules(w http.ResponseWriter, r *http.Request) { } func updateRules(w http.ResponseWriter, r *http.Request) { - err := tun.UpdateConfig() + err := tunnel.UpdateConfig() if err != nil { w.WriteHeader(http.StatusInternalServerError) render.JSON(w, r, Error{ diff --git a/hub/server.go b/hub/server.go index 1689d0e858..5efec54c82 100644 --- a/hub/server.go +++ b/hub/server.go @@ -5,26 +5,18 @@ import ( "net/http" "time" - "github.com/Dreamacro/clash/tunnel" + T "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" log "github.com/sirupsen/logrus" ) -var ( - tun = tunnel.GetInstance() -) - type Traffic struct { Up int64 `json:"up"` Down int64 `json:"down"` } -type Error struct { - Error string `json:"error"` -} - func NewHub(addr string) { r := chi.NewRouter() @@ -44,7 +36,7 @@ func traffic(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) tick := time.NewTicker(time.Second) - t := tun.Traffic() + t := tunnel.Traffic() for range tick.C { up, down := t.Now() if err := json.NewEncoder(w).Encode(Traffic{ @@ -73,11 +65,11 @@ func getLogs(w http.ResponseWriter, r *http.Request) { req.Level = "info" } - mapping := map[string]tunnel.LogLevel{ - "info": tunnel.INFO, - "debug": tunnel.DEBUG, - "error": tunnel.ERROR, - "warning": tunnel.WARNING, + mapping := map[string]T.LogLevel{ + "info": T.INFO, + "debug": T.DEBUG, + "error": T.ERROR, + "warning": T.WARNING, } level, ok := mapping[req.Level] @@ -89,7 +81,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) { return } - src := tun.Log() + src := tunnel.Log() sub, err := src.Subscribe() defer src.UnSubscribe(sub) if err != nil { @@ -101,7 +93,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) { } render.Status(r, http.StatusOK) for elm := range sub { - log := elm.(tunnel.Log) + log := elm.(T.Log) if log.LogLevel > level { continue } diff --git a/main.go b/main.go index 8f409cba64..309f1281e8 100644 --- a/main.go +++ b/main.go @@ -7,38 +7,28 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub" - "github.com/Dreamacro/clash/proxy/http" - "github.com/Dreamacro/clash/proxy/socks" + "github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/tunnel" log "github.com/sirupsen/logrus" ) func main() { - cfg, err := C.GetConfig() - if err != nil { - log.Fatalf("Read config error: %s", err.Error()) - } - - port, socksPort := C.DefalutHTTPPort, C.DefalutSOCKSPort - section := cfg.Section("General") - if key, err := section.GetKey("port"); err == nil { - port = key.Value() + if err := tunnel.GetInstance().UpdateConfig(); err != nil { + log.Fatalf("Parse config error: %s", err.Error()) } - if key, err := section.GetKey("socks-port"); err == nil { - socksPort = key.Value() + if err := proxy.Instance().Run(); err != nil { + log.Fatalf("Proxy listen error: %s", err.Error()) } - err = tunnel.GetInstance().UpdateConfig() + // Hub + cfg, err := C.GetConfig() if err != nil { - log.Fatalf("Parse config error: %s", err.Error()) + log.Fatalf("Read config error: %s", err.Error()) } - go http.NewHttpProxy(port) - go socks.NewSocksProxy(socksPort) - - // Hub + section := cfg.Section("General") if key, err := section.GetKey("external-controller"); err == nil { go hub.NewHub(key.Value()) } diff --git a/proxy/http/server.go b/proxy/http/server.go index 9dcefd0673..47dbc5bc81 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -1,7 +1,7 @@ package http import ( - "fmt" + "context" "net" "net/http" "strings" @@ -17,9 +17,20 @@ var ( tun = tunnel.GetInstance() ) -func NewHttpProxy(port string) { +func NewHttpProxy(addr string) (*C.ProxySignal, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + done := make(chan struct{}) + closed := make(chan struct{}) + signal := &C.ProxySignal{ + Done: done, + Closed: closed, + } + server := &http.Server{ - Addr: fmt.Sprintf(":%s", port), Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodConnect { handleTunneling(w, r) @@ -28,8 +39,20 @@ func NewHttpProxy(port string) { } }), } - log.Infof("HTTP proxy :%s", port) - server.ListenAndServe() + + go func() { + log.Infof("HTTP proxy listening at: %s", addr) + server.Serve(l) + }() + + go func() { + <-done + server.Shutdown(context.Background()) + l.Close() + closed <- struct{}{} + }() + + return signal, nil } func handleHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/proxy/listener.go b/proxy/listener.go new file mode 100644 index 0000000000..3e03e36ba8 --- /dev/null +++ b/proxy/listener.go @@ -0,0 +1,143 @@ +package proxy + +import ( + "fmt" + "sync" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/socks" + + log "github.com/sirupsen/logrus" +) + +var ( + listener *Listener + once sync.Once +) + +type Listener struct { + httpPort int + socksPort int + allowLan bool + + // signal for update + httpSignal *C.ProxySignal + socksSignal *C.ProxySignal +} + +// Info returns the proxys's current configuration +func (l *Listener) Info() (info C.General) { + return C.General{ + Port: &l.httpPort, + SocksPort: &l.socksPort, + AllowLan: &l.allowLan, + } +} + +func (l *Listener) Update(allowLan *bool, httpPort *int, socksPort *int) error { + if allowLan != nil { + l.allowLan = *allowLan + } + + var socksErr, httpErr error + if allowLan != nil || httpPort != nil { + newHTTPPort := l.httpPort + if httpPort != nil { + newHTTPPort = *httpPort + } + httpErr = l.updateHTTP(newHTTPPort) + } + + if allowLan != nil || socksPort != nil { + newSocksPort := l.socksPort + if socksPort != nil { + newSocksPort = *socksPort + } + socksErr = l.updateSocks(newSocksPort) + } + + if socksErr != nil && httpErr != nil { + return fmt.Errorf("%s\n%s", socksErr.Error(), httpErr.Error()) + } else if socksErr != nil { + return socksErr + } else if httpErr != nil { + return httpErr + } else { + return nil + } +} + +func (l *Listener) updateHTTP(port int) error { + if l.httpSignal != nil { + signal := l.httpSignal + signal.Done <- struct{}{} + <-signal.Closed + l.httpSignal = nil + } + + signal, err := http.NewHttpProxy(l.genAddr(port)) + if err != nil { + return err + } + + l.httpSignal = signal + l.httpPort = port + return nil +} + +func (l *Listener) updateSocks(port int) error { + if l.socksSignal != nil { + signal := l.socksSignal + signal.Done <- struct{}{} + <-signal.Closed + l.socksSignal = nil + } + + signal, err := socks.NewSocksProxy(l.genAddr(port)) + if err != nil { + return err + } + + l.socksSignal = signal + l.socksPort = port + return nil +} + +func (l *Listener) genAddr(port int) string { + host := "127.0.0.1" + if l.allowLan { + host = "" + } + return fmt.Sprintf("%s:%d", host, port) +} + +func (l *Listener) Run() error { + return l.Update(&l.allowLan, &l.httpPort, &l.socksPort) +} + +func newListener() *Listener { + cfg, err := C.GetConfig() + if err != nil { + log.Fatalf("Read config error: %s", err.Error()) + } + + general := cfg.Section("General") + + port := general.Key("port").RangeInt(C.DefalutHTTPPort, 1, 65535) + socksPort := general.Key("socks-port").RangeInt(C.DefalutSOCKSPort, 1, 65535) + allowLan := general.Key("allow-lan").MustBool() + + return &Listener{ + httpPort: port, + socksPort: socksPort, + allowLan: allowLan, + } +} + +func Instance() *Listener { + once.Do(func() { + listener = newListener() + }) + return listener +} diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 163fdc5573..c6e863564b 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -1,7 +1,6 @@ package socks import ( - "fmt" "io" "net" "strconv" @@ -17,20 +16,41 @@ var ( tun = tunnel.GetInstance() ) -func NewSocksProxy(port string) { - l, err := net.Listen("tcp", fmt.Sprintf(":%s", port)) - defer l.Close() +func NewSocksProxy(addr string) (*C.ProxySignal, error) { + l, err := net.Listen("tcp", addr) if err != nil { - return + return nil, err } - log.Infof("SOCKS proxy :%s", port) - for { - c, err := l.Accept() - if err != nil { - continue - } - go handleSocks(c) + + done := make(chan struct{}) + closed := make(chan struct{}) + signal := &C.ProxySignal{ + Done: done, + Closed: closed, } + + go func() { + log.Infof("SOCKS proxy listening at: %s", addr) + for { + c, err := l.Accept() + if err != nil { + if _, open := <-done; !open { + break + } + continue + } + go handleSocks(c) + } + }() + + go func() { + <-done + close(done) + l.Close() + closed <- struct{}{} + }() + + return signal, nil } func handleSocks(conn net.Conn) { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 5208ec924c..805a7d6752 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -52,6 +52,10 @@ func (t *Tunnel) SetMode(mode Mode) { t.mode = mode } +func (t *Tunnel) GetMode() Mode { + return t.mode +} + func (t *Tunnel) UpdateConfig() (err error) { cfg, err := C.GetConfig() if err != nil { From 88c7ccf74919311131d63a19746a68e85a0cf871 Mon Sep 17 00:00:00 2001 From: fossabot Date: Sun, 15 Jul 2018 07:32:20 -0700 Subject: [PATCH 004/535] Add: license scan report and status * Add license scan report and status Signed-off-by: fossabot * Improve: adjust indentation and position --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1b494b98c2..8431595bcd 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ + @@ -88,6 +89,9 @@ GEOIP,CN,DIRECT FINAL,,Proxy # note: there is two "," ``` +## License +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) + ## TODO - [ ] Complementing the necessary rule operators From d55e1b664bd73dd684b0bc7c02c5fdf20339126f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 16 Jul 2018 00:47:03 +0800 Subject: [PATCH 005/535] Lint: simplify code --- adapters/selector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/selector.go b/adapters/selector.go index 62651530ec..7305665a49 100644 --- a/adapters/selector.go +++ b/adapters/selector.go @@ -30,7 +30,7 @@ func (s *Selector) Now() string { func (s *Selector) All() []string { var all []string - for k, _ := range s.proxys { + for k := range s.proxys { all = append(all, k) } return all From d540a0d29f869e6c6a8c6c83c29f7736ef8352d3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 18 Jul 2018 21:50:16 +0800 Subject: [PATCH 006/535] Fix: typo --- README.md | 2 +- adapters/selector.go | 16 +++++++------- adapters/urltest.go | 38 ++++++++++++++++----------------- hub/{proxys.go => proxies.go} | 26 +++++++++++------------ hub/server.go | 2 +- proxy/listener.go | 2 +- tunnel/tunnel.go | 40 +++++++++++++++++------------------ 7 files changed, 63 insertions(+), 63 deletions(-) rename hub/{proxys.go => proxies.go} (80%) diff --git a/README.md b/README.md index 8431595bcd..d4be64ab4e 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Proxy2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password [Proxy Group] # url-test select which proxy will be used by benchmarking speed to a URL. -# name = url-test, [proxys], url, interval(second) +# name = url-test, [proxies], url, interval(second) Proxy = url-test, Proxy1, Proxy2, http://www.google.com/generate_204, 300 [Rule] diff --git a/adapters/selector.go b/adapters/selector.go index 7305665a49..42214e0eac 100644 --- a/adapters/selector.go +++ b/adapters/selector.go @@ -9,7 +9,7 @@ import ( type Selector struct { name string selected C.Proxy - proxys map[string]C.Proxy + proxies map[string]C.Proxy } func (s *Selector) Name() string { @@ -30,14 +30,14 @@ func (s *Selector) Now() string { func (s *Selector) All() []string { var all []string - for k := range s.proxys { + for k := range s.proxies { all = append(all, k) } return all } func (s *Selector) Set(name string) error { - proxy, exist := s.proxys[name] + proxy, exist := s.proxies[name] if !exist { return errors.New("Proxy does not exist") } @@ -45,21 +45,21 @@ func (s *Selector) Set(name string) error { return nil } -func NewSelector(name string, proxys map[string]C.Proxy) (*Selector, error) { - if len(proxys) == 0 { +func NewSelector(name string, proxies map[string]C.Proxy) (*Selector, error) { + if len(proxies) == 0 { return nil, errors.New("Provide at least one proxy") } mapping := make(map[string]C.Proxy) var init string - for k, v := range proxys { + for k, v := range proxies { mapping[k] = v init = k } s := &Selector{ name: name, - proxys: mapping, - selected: proxys[init], + proxies: mapping, + selected: proxies[init], } return s, nil } diff --git a/adapters/urltest.go b/adapters/urltest.go index a68b2b90ef..00e877876d 100644 --- a/adapters/urltest.go +++ b/adapters/urltest.go @@ -12,14 +12,14 @@ import ( ) type URLTest struct { - name string - proxys []C.Proxy - url *url.URL - rawURL string - addr *C.Addr - fast C.Proxy - delay time.Duration - done chan struct{} + name string + proxies []C.Proxy + url *url.URL + rawURL string + addr *C.Addr + fast C.Proxy + delay time.Duration + done chan struct{} } func (u *URLTest) Name() string { @@ -58,12 +58,12 @@ Loop: func (u *URLTest) speedTest() { wg := sync.WaitGroup{} - wg.Add(len(u.proxys)) + wg.Add(len(u.proxies)) c := make(chan interface{}) fast := selectFast(c) timer := time.NewTimer(u.delay) - for _, p := range u.proxys { + for _, p := range u.proxies { go func(p C.Proxy) { err := getUrl(p, u.addr, u.rawURL) if err == nil { @@ -129,7 +129,7 @@ func selectFast(in chan interface{}) chan interface{} { return out } -func NewURLTest(name string, proxys []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) { +func NewURLTest(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) { u, err := url.Parse(rawURL) if err != nil { return nil, err @@ -154,14 +154,14 @@ func NewURLTest(name string, proxys []C.Proxy, rawURL string, delay time.Duratio } urlTest := &URLTest{ - name: name, - proxys: proxys[:], - rawURL: rawURL, - url: u, - addr: addr, - fast: proxys[0], - delay: delay, - done: make(chan struct{}), + name: name, + proxies: proxies[:], + rawURL: rawURL, + url: u, + addr: addr, + fast: proxies[0], + delay: delay, + done: make(chan struct{}), } go urlTest.loop() return urlTest, nil diff --git a/hub/proxys.go b/hub/proxies.go similarity index 80% rename from hub/proxys.go rename to hub/proxies.go index 52d6297898..b8d90e2ff9 100644 --- a/hub/proxys.go +++ b/hub/proxies.go @@ -13,7 +13,7 @@ import ( func proxyRouter() http.Handler { r := chi.NewRouter() - r.Get("/", getProxys) + r.Get("/", getProxies) r.Get("/{name}", getProxy) r.Put("/{name}", updateProxy) return r @@ -56,23 +56,23 @@ func transformProxy(proxy C.Proxy) interface{} { } } -type GetProxysResponse struct { - Proxys map[string]interface{} `json:"proxys"` +type GetProxiesResponse struct { + Proxies map[string]interface{} `json:"proxies"` } -func getProxys(w http.ResponseWriter, r *http.Request) { - _, rawProxys := tunnel.Config() - proxys := make(map[string]interface{}) - for name, proxy := range rawProxys { - proxys[name] = transformProxy(proxy) +func getProxies(w http.ResponseWriter, r *http.Request) { + _, rawProxies := tunnel.Config() + proxies := make(map[string]interface{}) + for name, proxy := range rawProxies { + proxies[name] = transformProxy(proxy) } - render.JSON(w, r, GetProxysResponse{Proxys: proxys}) + render.JSON(w, r, GetProxiesResponse{Proxies: proxies}) } func getProxy(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") - _, proxys := tunnel.Config() - proxy, exist := proxys[name] + _, proxies := tunnel.Config() + proxy, exist := proxies[name] if !exist { w.WriteHeader(http.StatusNotFound) render.JSON(w, r, Error{ @@ -98,8 +98,8 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { } name := chi.URLParam(r, "name") - _, proxys := tunnel.Config() - proxy, exist := proxys[name] + _, proxies := tunnel.Config() + proxy, exist := proxies[name] if !exist { w.WriteHeader(http.StatusNotFound) render.JSON(w, r, Error{ diff --git a/hub/server.go b/hub/server.go index 5efec54c82..328e5108cb 100644 --- a/hub/server.go +++ b/hub/server.go @@ -23,7 +23,7 @@ func NewHub(addr string) { r.Get("/traffic", traffic) r.Get("/logs", getLogs) r.Mount("/configs", configRouter()) - r.Mount("/proxys", proxyRouter()) + r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) err := http.ListenAndServe(addr, r) diff --git a/proxy/listener.go b/proxy/listener.go index 3e03e36ba8..853508cf41 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -26,7 +26,7 @@ type Listener struct { socksSignal *C.ProxySignal } -// Info returns the proxys's current configuration +// Info returns the proxies's current configuration func (l *Listener) Info() (info C.General) { return C.General{ Port: &l.httpPort, diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 805a7d6752..16e436450a 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -23,7 +23,7 @@ var ( type Tunnel struct { queue *channels.InfiniteChannel rules []C.Rule - proxys map[string]C.Proxy + proxies map[string]C.Proxy observable *observable.Observable logCh chan interface{} configLock *sync.RWMutex @@ -41,7 +41,7 @@ func (t *Tunnel) Traffic() *C.Traffic { } func (t *Tunnel) Config() ([]C.Rule, map[string]C.Proxy) { - return t.rules, t.proxys + return t.rules, t.proxies } func (t *Tunnel) Log() *observable.Observable { @@ -62,16 +62,16 @@ func (t *Tunnel) UpdateConfig() (err error) { return } - // empty proxys and rules - proxys := make(map[string]C.Proxy) + // empty proxies and rules + proxies := make(map[string]C.Proxy) rules := []C.Rule{} - proxysConfig := cfg.Section("Proxy") + proxiesConfig := cfg.Section("Proxy") rulesConfig := cfg.Section("Rule") groupsConfig := cfg.Section("Proxy Group") // parse proxy - for _, key := range proxysConfig.Keys() { + for _, key := range proxiesConfig.Keys() { proxy := key.Strings(",") if len(proxy) == 0 { continue @@ -87,7 +87,7 @@ func (t *Tunnel) UpdateConfig() (err error) { if err != nil { return err } - proxys[key.Name()] = ss + proxies[key.Name()] = ss } } @@ -126,7 +126,7 @@ func (t *Tunnel) UpdateConfig() (err error) { url := rule[len(rule)-2] var ps []C.Proxy for _, name := range proxyNames { - if p, ok := proxys[name]; ok { + if p, ok := proxies[name]; ok { ps = append(ps, p) } } @@ -135,7 +135,7 @@ func (t *Tunnel) UpdateConfig() (err error) { if err != nil { return fmt.Errorf("Config error: %s", err.Error()) } - proxys[key.Name()] = adapter + proxies[key.Name()] = adapter case "select": if len(rule) < 3 { return fmt.Errorf("Selector need more than 3 param") @@ -143,7 +143,7 @@ func (t *Tunnel) UpdateConfig() (err error) { proxyNames := rule[1:] selectProxy := make(map[string]C.Proxy) for _, name := range proxyNames { - proxy, exist := proxys[name] + proxy, exist := proxies[name] if !exist { return fmt.Errorf("Proxy %s not exist", name) } @@ -153,31 +153,31 @@ func (t *Tunnel) UpdateConfig() (err error) { if err != nil { return fmt.Errorf("Selector create error: %s", err.Error()) } - proxys[key.Name()] = selector + proxies[key.Name()] = selector } } // init proxy - proxys["DIRECT"] = adapters.NewDirect(t.traffic) - proxys["REJECT"] = adapters.NewReject() + proxies["DIRECT"] = adapters.NewDirect(t.traffic) + proxies["REJECT"] = adapters.NewReject() t.configLock.Lock() defer t.configLock.Unlock() // stop url-test - for _, elm := range t.proxys { + for _, elm := range t.proxies { urlTest, ok := elm.(*adapters.URLTest) if ok { urlTest.Close() } } - s, err := adapters.NewSelector("Proxy", proxys) + s, err := adapters.NewSelector("Proxy", proxies) if err != nil { return err } - t.proxys = proxys + t.proxies = proxies t.rules = rules t.selector = s @@ -200,7 +200,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { var proxy C.Proxy switch t.mode { case Direct: - proxy = t.proxys["DIRECT"] + proxy = t.proxies["DIRECT"] case Global: proxy = t.selector // Rule @@ -223,7 +223,7 @@ func (t *Tunnel) match(addr *C.Addr) C.Proxy { for _, rule := range t.rules { if rule.IsMatch(addr) { - a, ok := t.proxys[rule.Adapter()] + a, ok := t.proxies[rule.Adapter()] if !ok { continue } @@ -232,14 +232,14 @@ func (t *Tunnel) match(addr *C.Addr) C.Proxy { } } t.logCh <- newLog(INFO, "%v doesn't match any rule using DIRECT", addr.String()) - return t.proxys["DIRECT"] + return t.proxies["DIRECT"] } func newTunnel() *Tunnel { logCh := make(chan interface{}) tunnel := &Tunnel{ queue: channels.NewInfiniteChannel(), - proxys: make(map[string]C.Proxy), + proxies: make(map[string]C.Proxy), observable: observable.NewObservable(logCh), logCh: logCh, configLock: &sync.RWMutex{}, From 7357d2d0c22374ad1a2e6bc9da09460c98df5352 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 19 Jul 2018 09:31:53 +0800 Subject: [PATCH 007/535] Add: cors for external controller --- Gopkg.lock | 16 +++++++++++----- Gopkg.toml | 6 +++++- hub/server.go | 10 ++++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index a879f70821..4dab52be38 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -19,6 +19,12 @@ revision = "e83ac2304db3c50cf03d96a2fcd39009d458bc35" version = "v3.3.2" +[[projects]] + name = "github.com/go-chi/cors" + packages = ["."] + revision = "dba6525398619dead495962a916728e7ee2ca322" + version = "v1.0.0" + [[projects]] name = "github.com/go-chi/render" packages = ["."] @@ -65,7 +71,7 @@ "poly1305", "ssh/terminal" ] - revision = "027cca12c2d63e3d62b670d901e8a2c95854feec" + revision = "a2144134853fc9a27a7b1e3eb4f19f1a76df13c9" [[projects]] branch = "master" @@ -75,7 +81,7 @@ "unix", "windows" ] - revision = "6c888cc515d3ed83fc103cf1d84468aad274b0a7" + revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4" [[projects]] name = "gopkg.in/eapache/channels.v1" @@ -86,12 +92,12 @@ [[projects]] name = "gopkg.in/ini.v1" packages = ["."] - revision = "06f5f3d67269ccec1fe5fe4134ba6e982984f7f5" - version = "v1.37.0" + revision = "358ee7663966325963d4e8b2e1fbd570c5195153" + version = "v1.38.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "c3c901e4e393a2df9e421924d3a4d85ec73642e36dcbc1ddca5fc13159220e86" + inputs-digest = "0ccb06b3617c87e75bd650f92adc99e55b93070e0b2a0bc71634270226e125fc" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index ffa7005ac4..c381b4d9c8 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -29,6 +29,10 @@ name = "github.com/go-chi/chi" version = "3.3.2" +[[constraint]] + name = "github.com/go-chi/cors" + version = "1.0.0" + [[constraint]] name = "github.com/go-chi/render" version = "1.0.1" @@ -51,7 +55,7 @@ [[constraint]] name = "gopkg.in/ini.v1" - version = "1.37.0" + version = "1.38.1" [prune] go-tests = true diff --git a/hub/server.go b/hub/server.go index 328e5108cb..5a6e7276b5 100644 --- a/hub/server.go +++ b/hub/server.go @@ -8,6 +8,7 @@ import ( T "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" + "github.com/go-chi/cors" "github.com/go-chi/render" log "github.com/sirupsen/logrus" ) @@ -20,6 +21,15 @@ type Traffic struct { func NewHub(addr string) { r := chi.NewRouter() + cors := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Content-Type"}, + MaxAge: 300, + }) + + r.Use(cors.Handler) + r.Get("/traffic", traffic) r.Get("/logs", getLogs) r.Mount("/configs", configRouter()) From 83891503186098418ddca005eda337534eb929c6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 26 Jul 2018 00:04:59 +0800 Subject: [PATCH 008/535] Improve: config convergent and add log-level --- adapters/local/http.go | 32 +++ {proxy/http => adapters/local}/https.go | 8 +- adapters/local/socks.go | 32 +++ adapters/local/util.go | 66 ++++++ adapters/{ => remote}/direct.go | 16 +- adapters/{ => remote}/reject.go | 5 - adapters/{ => remote}/selector.go | 0 adapters/{ => remote}/shadowsocks.go | 24 +- adapters/{ => remote}/urltest.go | 0 config/config.go | 296 ++++++++++++++++++++++++ config/general.go | 17 ++ {tunnel => config}/mode.go | 11 +- {tunnel => config}/utils.go | 2 +- constant/adapters.go | 3 - constant/addr.go | 8 +- constant/config.go | 11 - constant/log.go | 35 +++ hub/common.go | 4 +- hub/configs.go | 34 ++- hub/proxies.go | 8 +- hub/rules.go | 6 +- hub/server.go | 34 ++- main.go | 22 +- proxy/http/http.go | 77 ------ proxy/http/server.go | 36 +-- proxy/listener.go | 58 ++--- proxy/socks/tcp.go | 61 +---- tunnel/connection.go | 67 ++++++ tunnel/log.go | 40 +--- tunnel/tunnel.go | 223 ++++++------------ {adapters => tunnel}/util.go | 5 +- 31 files changed, 757 insertions(+), 484 deletions(-) create mode 100644 adapters/local/http.go rename {proxy/http => adapters/local}/https.go (72%) create mode 100644 adapters/local/socks.go create mode 100644 adapters/local/util.go rename adapters/{ => remote}/direct.go (68%) rename adapters/{ => remote}/reject.go (86%) rename adapters/{ => remote}/selector.go (100%) rename adapters/{ => remote}/shadowsocks.go (81%) rename adapters/{ => remote}/urltest.go (100%) create mode 100644 config/config.go create mode 100644 config/general.go rename {tunnel => config}/mode.go (58%) rename {tunnel => config}/utils.go (90%) create mode 100644 constant/log.go delete mode 100644 proxy/http/http.go create mode 100644 tunnel/connection.go rename {adapters => tunnel}/util.go (78%) diff --git a/adapters/local/http.go b/adapters/local/http.go new file mode 100644 index 0000000000..69712aa9c3 --- /dev/null +++ b/adapters/local/http.go @@ -0,0 +1,32 @@ +package adapters + +import ( + "net/http" + + C "github.com/Dreamacro/clash/constant" +) + +type HttpAdapter struct { + addr *C.Addr + R *http.Request + W http.ResponseWriter + done chan struct{} +} + +func (h *HttpAdapter) Close() { + h.done <- struct{}{} +} + +func (h *HttpAdapter) Addr() *C.Addr { + return h.addr +} + +func NewHttp(host string, w http.ResponseWriter, r *http.Request) (*HttpAdapter, chan struct{}) { + done := make(chan struct{}) + return &HttpAdapter{ + addr: parseHttpAddr(host), + R: r, + W: w, + done: done, + }, done +} diff --git a/proxy/http/https.go b/adapters/local/https.go similarity index 72% rename from proxy/http/https.go rename to adapters/local/https.go index 33eed177f9..da766609a9 100644 --- a/proxy/http/https.go +++ b/adapters/local/https.go @@ -1,8 +1,7 @@ -package http +package adapters import ( "bufio" - "io" "net" C "github.com/Dreamacro/clash/constant" @@ -22,9 +21,8 @@ func (h *HttpsAdapter) Addr() *C.Addr { return h.addr } -func (h *HttpsAdapter) Connect(proxy C.ProxyAdapter) { - go io.Copy(h.conn, proxy.ReadWriter()) - io.Copy(proxy.ReadWriter(), h.conn) +func (h *HttpsAdapter) Conn() net.Conn { + return h.conn } func NewHttps(host string, conn net.Conn) *HttpsAdapter { diff --git a/adapters/local/socks.go b/adapters/local/socks.go new file mode 100644 index 0000000000..dd1c46238c --- /dev/null +++ b/adapters/local/socks.go @@ -0,0 +1,32 @@ +package adapters + +import ( + "net" + + C "github.com/Dreamacro/clash/constant" + "github.com/riobard/go-shadowsocks2/socks" +) + +type SocksAdapter struct { + conn net.Conn + addr *C.Addr +} + +func (s *SocksAdapter) Close() { + s.conn.Close() +} + +func (s *SocksAdapter) Addr() *C.Addr { + return s.addr +} + +func (s *SocksAdapter) Conn() net.Conn { + return s.conn +} + +func NewSocks(target socks.Addr, conn net.Conn) *SocksAdapter { + return &SocksAdapter{ + conn: conn, + addr: parseSocksAddr(target), + } +} diff --git a/adapters/local/util.go b/adapters/local/util.go new file mode 100644 index 0000000000..0fb204300d --- /dev/null +++ b/adapters/local/util.go @@ -0,0 +1,66 @@ +package adapters + +import ( + "net" + "strconv" + + C "github.com/Dreamacro/clash/constant" + "github.com/riobard/go-shadowsocks2/socks" +) + +func parseSocksAddr(target socks.Addr) *C.Addr { + var host, port string + var ip net.IP + + switch target[0] { + case socks.AtypDomainName: + host = string(target[2 : 2+target[1]]) + port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) + ipAddr, err := net.ResolveIPAddr("ip", host) + if err == nil { + ip = ipAddr.IP + } + case socks.AtypIPv4: + ip = net.IP(target[1 : 1+net.IPv4len]) + port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) + case socks.AtypIPv6: + ip = net.IP(target[1 : 1+net.IPv6len]) + port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) + } + + return &C.Addr{ + NetWork: C.TCP, + AddrType: int(target[0]), + Host: host, + IP: &ip, + Port: port, + } +} + +func parseHttpAddr(target string) *C.Addr { + host, port, _ := net.SplitHostPort(target) + ipAddr, err := net.ResolveIPAddr("ip", host) + var resolveIP *net.IP + if err == nil { + resolveIP = &ipAddr.IP + } + + var addType int + ip := net.ParseIP(host) + switch { + case ip == nil: + addType = socks.AtypDomainName + case ip.To4() == nil: + addType = socks.AtypIPv6 + default: + addType = socks.AtypIPv4 + } + + return &C.Addr{ + NetWork: C.TCP, + AddrType: addType, + Host: host, + IP: resolveIP, + Port: port, + } +} diff --git a/adapters/direct.go b/adapters/remote/direct.go similarity index 68% rename from adapters/direct.go rename to adapters/remote/direct.go index 3df410da6d..ac46c4a703 100644 --- a/adapters/direct.go +++ b/adapters/remote/direct.go @@ -1,7 +1,6 @@ package adapters import ( - "io" "net" C "github.com/Dreamacro/clash/constant" @@ -12,11 +11,6 @@ type DirectAdapter struct { conn net.Conn } -// ReadWriter is used to handle network traffic -func (d *DirectAdapter) ReadWriter() io.ReadWriter { - return d.conn -} - // Close is used to close connection func (d *DirectAdapter) Close() { d.conn.Close() @@ -27,9 +21,7 @@ func (d *DirectAdapter) Conn() net.Conn { return d.conn } -type Direct struct { - traffic *C.Traffic -} +type Direct struct{} func (d *Direct) Name() string { return "Direct" @@ -45,9 +37,9 @@ func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { return } c.(*net.TCPConn).SetKeepAlive(true) - return &DirectAdapter{conn: NewTrafficTrack(c, d.traffic)}, nil + return &DirectAdapter{conn: c}, nil } -func NewDirect(traffic *C.Traffic) *Direct { - return &Direct{traffic: traffic} +func NewDirect() *Direct { + return &Direct{} } diff --git a/adapters/reject.go b/adapters/remote/reject.go similarity index 86% rename from adapters/reject.go rename to adapters/remote/reject.go index 91bdfd1ef4..7833e0047e 100644 --- a/adapters/reject.go +++ b/adapters/remote/reject.go @@ -11,11 +11,6 @@ import ( type RejectAdapter struct { } -// ReadWriter is used to handle network traffic -func (r *RejectAdapter) ReadWriter() io.ReadWriter { - return &NopRW{} -} - // Close is used to close connection func (r *RejectAdapter) Close() {} diff --git a/adapters/selector.go b/adapters/remote/selector.go similarity index 100% rename from adapters/selector.go rename to adapters/remote/selector.go diff --git a/adapters/shadowsocks.go b/adapters/remote/shadowsocks.go similarity index 81% rename from adapters/shadowsocks.go rename to adapters/remote/shadowsocks.go index 6884aacc56..6323a89703 100644 --- a/adapters/shadowsocks.go +++ b/adapters/remote/shadowsocks.go @@ -3,7 +3,6 @@ package adapters import ( "bytes" "fmt" - "io" "net" "net/url" "strconv" @@ -19,11 +18,6 @@ type ShadowsocksAdapter struct { conn net.Conn } -// ReadWriter is used to handle network traffic -func (ss *ShadowsocksAdapter) ReadWriter() io.ReadWriter { - return ss.conn -} - // Close is used to close connection func (ss *ShadowsocksAdapter) Close() { ss.conn.Close() @@ -34,10 +28,9 @@ func (ss *ShadowsocksAdapter) Conn() net.Conn { } type ShadowSocks struct { - server string - name string - cipher core.Cipher - traffic *C.Traffic + server string + name string + cipher core.Cipher } func (ss *ShadowSocks) Name() string { @@ -56,10 +49,10 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro c.(*net.TCPConn).SetKeepAlive(true) c = ss.cipher.StreamConn(c) _, err = c.Write(serializesSocksAddr(addr)) - return &ShadowsocksAdapter{conn: NewTrafficTrack(c, ss.traffic)}, err + return &ShadowsocksAdapter{conn: c}, err } -func NewShadowSocks(name string, ssURL string, traffic *C.Traffic) (*ShadowSocks, error) { +func NewShadowSocks(name string, ssURL string) (*ShadowSocks, error) { var key []byte server, cipher, password, _ := parseURL(ssURL) ciph, err := core.PickCipher(cipher, key, password) @@ -67,10 +60,9 @@ func NewShadowSocks(name string, ssURL string, traffic *C.Traffic) (*ShadowSocks return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } return &ShadowSocks{ - server: server, - name: name, - cipher: ciph, - traffic: traffic, + server: server, + name: name, + cipher: ciph, }, nil } diff --git a/adapters/urltest.go b/adapters/remote/urltest.go similarity index 100% rename from adapters/urltest.go rename to adapters/remote/urltest.go diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000000..c6bb4e945a --- /dev/null +++ b/config/config.go @@ -0,0 +1,296 @@ +package config + +import ( + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/Dreamacro/clash/adapters/remote" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/observable" + R "github.com/Dreamacro/clash/rules" + + "gopkg.in/ini.v1" +) + +var ( + config *Config + once sync.Once +) + +// Config is clash config manager +type Config struct { + general *General + rules []C.Rule + proxies map[string]C.Proxy + lastUpdate time.Time + + event chan<- interface{} + errCh chan interface{} + observable *observable.Observable +} + +// Event is event of clash config +type Event struct { + Type string + Payload interface{} +} + +// Subscribe config stream +func (c *Config) Subscribe() observable.Subscription { + sub, _ := c.observable.Subscribe() + return sub +} + +// Report return a channel for collecting error message +func (c *Config) Report() chan<- interface{} { + return c.errCh +} + +func (c *Config) readConfig() (*ini.File, error) { + if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) { + return nil, err + } + return ini.LoadSources( + ini.LoadOptions{AllowBooleanKeys: true}, + C.ConfigPath, + ) +} + +// Parse config +func (c *Config) Parse() error { + cfg, err := c.readConfig() + if err != nil { + return err + } + + if err := c.parseGeneral(cfg); err != nil { + return err + } + + if err := c.parseProxies(cfg); err != nil { + return err + } + + return c.parseRules(cfg) +} + +// Proxies return proxies of clash +func (c *Config) Proxies() map[string]C.Proxy { + return c.proxies +} + +// Rules return rules of clash +func (c *Config) Rules() []C.Rule { + return c.rules +} + +// SetMode change mode of clash +func (c *Config) SetMode(mode Mode) { + c.general.Mode = mode + c.event <- &Event{Type: "mode", Payload: mode} +} + +// General return clash general config +func (c *Config) General() General { + return *c.general +} + +// UpdateRules is a function for hot reload rules +func (c *Config) UpdateRules() error { + cfg, err := c.readConfig() + if err != nil { + return err + } + + return c.parseRules(cfg) +} + +func (c *Config) parseGeneral(cfg *ini.File) error { + general := cfg.Section("General") + + port := general.Key("port").RangeInt(C.DefalutHTTPPort, 1, 65535) + socksPort := general.Key("socks-port").RangeInt(C.DefalutSOCKSPort, 1, 65535) + allowLan := general.Key("allow-lan").MustBool() + logLevelString := general.Key("log-level").MustString(C.INFO.String()) + modeString := general.Key("mode").MustString(Rule.String()) + + mode, exist := ModeMapping[modeString] + if !exist { + return fmt.Errorf("General.mode value invalid") + } + + logLevel, exist := C.LogLevelMapping[logLevelString] + if !exist { + return fmt.Errorf("General.log-level value invalid") + } + + c.general = &General{ + Base: &Base{ + Port: &port, + SocketPort: &socksPort, + AllowLan: &allowLan, + }, + Mode: mode, + LogLevel: logLevel, + } + + if restAddr := general.Key("external-controller").String(); restAddr != "" { + c.event <- &Event{Type: "external-controller", Payload: restAddr} + } + + c.UpdateGeneral(*c.general) + return nil +} + +// UpdateGeneral dispatch update event +func (c *Config) UpdateGeneral(general General) { + c.event <- &Event{Type: "base", Payload: *general.Base} + c.event <- &Event{Type: "mode", Payload: general.Mode} + c.event <- &Event{Type: "log-level", Payload: general.LogLevel} +} + +func (c *Config) parseProxies(cfg *ini.File) error { + proxies := make(map[string]C.Proxy) + proxiesConfig := cfg.Section("Proxy") + groupsConfig := cfg.Section("Proxy Group") + + // parse proxy + for _, key := range proxiesConfig.Keys() { + proxy := key.Strings(",") + if len(proxy) == 0 { + continue + } + switch proxy[0] { + // ss, server, port, cipter, password + case "ss": + if len(proxy) < 5 { + continue + } + ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2]) + ss, err := adapters.NewShadowSocks(key.Name(), ssURL) + if err != nil { + return err + } + proxies[key.Name()] = ss + } + } + + // parse proxy group + for _, key := range groupsConfig.Keys() { + rule := strings.Split(key.Value(), ",") + rule = trimArr(rule) + switch rule[0] { + case "url-test": + if len(rule) < 4 { + return fmt.Errorf("URLTest need more than 4 param") + } + proxyNames := rule[1 : len(rule)-2] + delay, _ := strconv.Atoi(rule[len(rule)-1]) + url := rule[len(rule)-2] + var ps []C.Proxy + for _, name := range proxyNames { + if p, ok := proxies[name]; ok { + ps = append(ps, p) + } + } + + adapter, err := adapters.NewURLTest(key.Name(), ps, url, time.Duration(delay)*time.Second) + if err != nil { + return fmt.Errorf("Config error: %s", err.Error()) + } + proxies[key.Name()] = adapter + case "select": + if len(rule) < 3 { + return fmt.Errorf("Selector need more than 3 param") + } + proxyNames := rule[1:] + selectProxy := make(map[string]C.Proxy) + for _, name := range proxyNames { + proxy, exist := proxies[name] + if !exist { + return fmt.Errorf("Proxy %s not exist", name) + } + selectProxy[name] = proxy + } + selector, err := adapters.NewSelector(key.Name(), selectProxy) + if err != nil { + return fmt.Errorf("Selector create error: %s", err.Error()) + } + proxies[key.Name()] = selector + } + } + + // init proxy + proxies["DIRECT"] = adapters.NewDirect() + proxies["REJECT"] = adapters.NewReject() + + c.proxies = proxies + c.event <- &Event{Type: "proxies", Payload: proxies} + return nil +} + +func (c *Config) parseRules(cfg *ini.File) error { + rules := []C.Rule{} + + rulesConfig := cfg.Section("Rule") + // parse rules + for _, key := range rulesConfig.Keys() { + rule := strings.Split(key.Name(), ",") + if len(rule) < 3 { + continue + } + rule = trimArr(rule) + switch rule[0] { + case "DOMAIN-SUFFIX": + rules = append(rules, R.NewDomainSuffix(rule[1], rule[2])) + case "DOMAIN-KEYWORD": + rules = append(rules, R.NewDomainKeyword(rule[1], rule[2])) + case "GEOIP": + rules = append(rules, R.NewGEOIP(rule[1], rule[2])) + case "IP-CIDR", "IP-CIDR6": + rules = append(rules, R.NewIPCIDR(rule[1], rule[2])) + case "FINAL": + rules = append(rules, R.NewFinal(rule[2])) + } + } + + c.rules = rules + c.event <- &Event{Type: "rules", Payload: rules} + return nil +} + +func (c *Config) handleErrorMessage() { + for elm := range c.errCh { + event := elm.(Event) + switch event.Type { + case "base": + c.general.Base = event.Payload.(*Base) + } + } +} + +func newConfig() *Config { + event := make(chan interface{}) + config := &Config{ + general: &General{}, + proxies: make(map[string]C.Proxy), + rules: []C.Rule{}, + lastUpdate: time.Now(), + + event: event, + observable: observable.NewObservable(event), + } + go config.handleErrorMessage() + return config +} + +func Instance() *Config { + once.Do(func() { + config = newConfig() + }) + return config +} diff --git a/config/general.go b/config/general.go new file mode 100644 index 0000000000..121414476b --- /dev/null +++ b/config/general.go @@ -0,0 +1,17 @@ +package config + +import ( + C "github.com/Dreamacro/clash/constant" +) + +type General struct { + *Base + Mode Mode + LogLevel C.LogLevel +} + +type Base struct { + Port *int + SocketPort *int + AllowLan *bool +} diff --git a/tunnel/mode.go b/config/mode.go similarity index 58% rename from tunnel/mode.go rename to config/mode.go index d82a0aee28..b910eba183 100644 --- a/tunnel/mode.go +++ b/config/mode.go @@ -1,7 +1,16 @@ -package tunnel +package config type Mode int +var ( + // ModeMapping is a mapping for Mode enum + ModeMapping = map[string]Mode{ + "Global": Global, + "Rule": Rule, + "Direct": Direct, + } +) + const ( Global Mode = iota Rule diff --git a/tunnel/utils.go b/config/utils.go similarity index 90% rename from tunnel/utils.go rename to config/utils.go index 4e72f18dd7..cc2b99d47b 100644 --- a/tunnel/utils.go +++ b/config/utils.go @@ -1,4 +1,4 @@ -package tunnel +package config import ( "strings" diff --git a/constant/adapters.go b/constant/adapters.go index df265bdc53..1509570a65 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -1,7 +1,6 @@ package constant import ( - "io" "net" ) @@ -15,14 +14,12 @@ const ( ) type ProxyAdapter interface { - ReadWriter() io.ReadWriter Conn() net.Conn Close() } type ServerAdapter interface { Addr() *Addr - Connect(ProxyAdapter) Close() } diff --git a/constant/addr.go b/constant/addr.go index 6f498f98da..e404b48920 100644 --- a/constant/addr.go +++ b/constant/addr.go @@ -10,8 +10,11 @@ const ( AtypDomainName = 3 AtypIPv6 = 4 - TCP = iota + TCP NetWork = iota UDP + + HTTP SourceType = iota + SOCKS ) type NetWork int @@ -23,9 +26,12 @@ func (n *NetWork) String() string { return "udp" } +type SourceType int + // Addr is used to store connection address type Addr struct { NetWork NetWork + Source SourceType AddrType int Host string IP *net.IP diff --git a/constant/config.go b/constant/config.go index c915ae6c73..c12c208b02 100644 --- a/constant/config.go +++ b/constant/config.go @@ -11,7 +11,6 @@ import ( "strings" log "github.com/sirupsen/logrus" - "gopkg.in/ini.v1" ) const ( @@ -107,13 +106,3 @@ func downloadMMDB(path string) (err error) { return nil } - -func GetConfig() (*ini.File, error) { - if _, err := os.Stat(ConfigPath); os.IsNotExist(err) { - return nil, err - } - return ini.LoadSources( - ini.LoadOptions{AllowBooleanKeys: true}, - ConfigPath, - ) -} diff --git a/constant/log.go b/constant/log.go new file mode 100644 index 0000000000..4423322e9f --- /dev/null +++ b/constant/log.go @@ -0,0 +1,35 @@ +package constant + +var ( + // LogLevelMapping is a mapping for LogLevel enum + LogLevelMapping = map[string]LogLevel{ + "error": ERROR, + "warning": WARNING, + "info": INFO, + "debug": DEBUG, + } +) + +const ( + ERROR LogLevel = iota + WARNING + INFO + DEBUG +) + +type LogLevel int + +func (l LogLevel) String() string { + switch l { + case INFO: + return "info" + case WARNING: + return "warning" + case ERROR: + return "error" + case DEBUG: + return "debug" + default: + return "unknow" + } +} diff --git a/hub/common.go b/hub/common.go index bd900c2800..4898d2260b 100644 --- a/hub/common.go +++ b/hub/common.go @@ -1,12 +1,14 @@ package hub import ( + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/proxy" T "github.com/Dreamacro/clash/tunnel" ) var ( - tunnel = T.GetInstance() + tunnel = T.Instance() + cfg = config.Instance() listener = proxy.Instance() ) diff --git a/hub/configs.go b/hub/configs.go index 63d7fff4db..8601909317 100644 --- a/hub/configs.go +++ b/hub/configs.go @@ -4,9 +4,9 @@ import ( "fmt" "net/http" + "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/proxy" - T "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -19,17 +19,23 @@ func configRouter() http.Handler { return r } -var modeMapping = map[string]T.Mode{ - "Global": T.Global, - "Rule": T.Rule, - "Direct": T.Direct, +type configSchema struct { + Port int `json:"port"` + SocketPort int `json:"socket-port"` + AllowLan bool `json:"allow-lan"` + Mode string `json:"mode"` + LogLevel string `json:"log-level"` } func getConfigs(w http.ResponseWriter, r *http.Request) { - info := listener.Info() - mode := tunnel.GetMode().String() - info.Mode = &mode - render.JSON(w, r, info) + general := cfg.General() + render.JSON(w, r, configSchema{ + Port: *general.Port, + SocketPort: *general.SocketPort, + AllowLan: *general.AllowLan, + Mode: general.Mode.String(), + LogLevel: general.LogLevel.String(), + }) } func updateConfigs(w http.ResponseWriter, r *http.Request) { @@ -48,15 +54,19 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { // update proxy listener := proxy.Instance() - proxyErr = listener.Update(general.AllowLan, general.Port, general.SocksPort) + proxyErr = listener.Update(&config.Base{ + AllowLan: general.AllowLan, + Port: general.Port, + SocketPort: general.SocksPort, + }) // update mode if general.Mode != nil { - mode, ok := modeMapping[*general.Mode] + mode, ok := config.ModeMapping[*general.Mode] if !ok { modeErr = fmt.Errorf("Mode error") } else { - tunnel.SetMode(mode) + cfg.SetMode(mode) } } diff --git a/hub/proxies.go b/hub/proxies.go index b8d90e2ff9..42c1af4de5 100644 --- a/hub/proxies.go +++ b/hub/proxies.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - A "github.com/Dreamacro/clash/adapters" + A "github.com/Dreamacro/clash/adapters/remote" C "github.com/Dreamacro/clash/constant" "github.com/go-chi/chi" @@ -61,7 +61,7 @@ type GetProxiesResponse struct { } func getProxies(w http.ResponseWriter, r *http.Request) { - _, rawProxies := tunnel.Config() + rawProxies := cfg.Proxies() proxies := make(map[string]interface{}) for name, proxy := range rawProxies { proxies[name] = transformProxy(proxy) @@ -71,7 +71,7 @@ func getProxies(w http.ResponseWriter, r *http.Request) { func getProxy(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") - _, proxies := tunnel.Config() + proxies := cfg.Proxies() proxy, exist := proxies[name] if !exist { w.WriteHeader(http.StatusNotFound) @@ -98,7 +98,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { } name := chi.URLParam(r, "name") - _, proxies := tunnel.Config() + proxies := cfg.Proxies() proxy, exist := proxies[name] if !exist { w.WriteHeader(http.StatusNotFound) diff --git a/hub/rules.go b/hub/rules.go index 3f6dbe143c..b8780fc1e7 100644 --- a/hub/rules.go +++ b/hub/rules.go @@ -24,10 +24,10 @@ type GetRulesResponse struct { } func getRules(w http.ResponseWriter, r *http.Request) { - rulesCfg, _ := tunnel.Config() + rawRules := cfg.Rules() var rules []Rule - for _, rule := range rulesCfg { + for _, rule := range rawRules { rules = append(rules, Rule{ Name: rule.RuleType().String(), Payload: rule.Payload(), @@ -41,7 +41,7 @@ func getRules(w http.ResponseWriter, r *http.Request) { } func updateRules(w http.ResponseWriter, r *http.Request) { - err := tunnel.UpdateConfig() + err := cfg.UpdateRules() if err != nil { w.WriteHeader(http.StatusInternalServerError) render.JSON(w, r, Error{ diff --git a/hub/server.go b/hub/server.go index 5a6e7276b5..7d7a35d6f4 100644 --- a/hub/server.go +++ b/hub/server.go @@ -5,6 +5,8 @@ import ( "net/http" "time" + "github.com/Dreamacro/clash/config" + C "github.com/Dreamacro/clash/constant" T "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" @@ -18,7 +20,19 @@ type Traffic struct { Down int64 `json:"down"` } -func NewHub(addr string) { +func newHub(signal chan struct{}) { + var addr string + ch := config.Instance().Subscribe() + signal <- struct{}{} + for { + elm := <-ch + event := elm.(*config.Event) + if event.Type == "external-controller" { + addr = event.Payload.(string) + break + } + } + r := chi.NewRouter() cors := cors.New(cors.Options{ @@ -38,7 +52,7 @@ func NewHub(addr string) { err := http.ListenAndServe(addr, r) if err != nil { - log.Fatalf("External controller error: %s", err.Error()) + log.Errorf("External controller error: %s", err.Error()) } } @@ -75,14 +89,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) { req.Level = "info" } - mapping := map[string]T.LogLevel{ - "info": T.INFO, - "debug": T.DEBUG, - "error": T.ERROR, - "warning": T.WARNING, - } - - level, ok := mapping[req.Level] + level, ok := C.LogLevelMapping[req.Level] if !ok { w.WriteHeader(http.StatusBadRequest) render.JSON(w, r, Error{ @@ -117,3 +124,10 @@ func getLogs(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() } } + +// Run initial hub +func Run() { + signal := make(chan struct{}) + go newHub(signal) + <-signal +} diff --git a/main.go b/main.go index 309f1281e8..7bb6d8b6bc 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,7 @@ import ( "os/signal" "syscall" - C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/tunnel" @@ -14,23 +14,13 @@ import ( ) func main() { - if err := tunnel.GetInstance().UpdateConfig(); err != nil { - log.Fatalf("Parse config error: %s", err.Error()) - } - - if err := proxy.Instance().Run(); err != nil { - log.Fatalf("Proxy listen error: %s", err.Error()) - } + tunnel.Instance().Run() + proxy.Instance().Run() + hub.Run() - // Hub - cfg, err := C.GetConfig() + err := config.Instance().Parse() if err != nil { - log.Fatalf("Read config error: %s", err.Error()) - } - - section := cfg.Section("General") - if key, err := section.GetKey("external-controller"); err == nil { - go hub.NewHub(key.Value()) + log.Fatalf("Parse config error: %s", err.Error()) } sigCh := make(chan os.Signal, 1) diff --git a/proxy/http/http.go b/proxy/http/http.go deleted file mode 100644 index 8807c928ca..0000000000 --- a/proxy/http/http.go +++ /dev/null @@ -1,77 +0,0 @@ -package http - -import ( - "io" - "net" - "net/http" - "time" - - C "github.com/Dreamacro/clash/constant" -) - -type HttpAdapter struct { - addr *C.Addr - r *http.Request - w http.ResponseWriter - done chan struct{} -} - -func (h *HttpAdapter) Close() { - h.done <- struct{}{} -} - -func (h *HttpAdapter) Addr() *C.Addr { - return h.addr -} - -func (h *HttpAdapter) Connect(proxy C.ProxyAdapter) { - req := http.Transport{ - Dial: func(string, string) (net.Conn, error) { - return proxy.Conn(), nil - }, - // from http.DefaultTransport - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - resp, err := req.RoundTrip(h.r) - if err != nil { - return - } - defer resp.Body.Close() - - header := h.w.Header() - for k, vv := range resp.Header { - for _, v := range vv { - header.Add(k, v) - } - } - h.w.WriteHeader(resp.StatusCode) - var writer io.Writer = h.w - if len(resp.TransferEncoding) > 0 && resp.TransferEncoding[0] == "chunked" { - writer = ChunkWriter{Writer: h.w} - } - io.Copy(writer, resp.Body) -} - -type ChunkWriter struct { - io.Writer -} - -func (cw ChunkWriter) Write(b []byte) (int, error) { - n, err := cw.Writer.Write(b) - if err == nil { - cw.Writer.(http.Flusher).Flush() - } - return n, err -} - -func NewHttp(host string, w http.ResponseWriter, r *http.Request) (*HttpAdapter, chan struct{}) { - done := make(chan struct{}) - return &HttpAdapter{ - addr: parseHttpAddr(host), - r: r, - w: w, - done: done, - }, done -} diff --git a/proxy/http/server.go b/proxy/http/server.go index 47dbc5bc81..e7c1850cdc 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -6,15 +6,15 @@ import ( "net/http" "strings" + "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" - "github.com/riobard/go-shadowsocks2/socks" log "github.com/sirupsen/logrus" ) var ( - tun = tunnel.GetInstance() + tun = tunnel.Instance() ) func NewHttpProxy(addr string) (*C.ProxySignal, error) { @@ -61,7 +61,7 @@ func handleHTTP(w http.ResponseWriter, r *http.Request) { if !strings.Contains(addr, ":") { addr += ":80" } - req, done := NewHttp(addr, w, r) + req, done := adapters.NewHttp(addr, w, r) tun.Add(req) <-done } @@ -77,33 +77,5 @@ func handleTunneling(w http.ResponseWriter, r *http.Request) { } // w.WriteHeader(http.StatusOK) doesn't works in Safari conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) - tun.Add(NewHttps(r.Host, conn)) -} - -func parseHttpAddr(target string) *C.Addr { - host, port, _ := net.SplitHostPort(target) - ipAddr, err := net.ResolveIPAddr("ip", host) - var resolveIP *net.IP - if err == nil { - resolveIP = &ipAddr.IP - } - - var addType int - ip := net.ParseIP(host) - switch { - case ip == nil: - addType = socks.AtypDomainName - case ip.To4() == nil: - addType = socks.AtypIPv6 - default: - addType = socks.AtypIPv4 - } - - return &C.Addr{ - NetWork: C.TCP, - AddrType: addType, - Host: host, - IP: resolveIP, - Port: port, - } + tun.Add(adapters.NewHttps(r.Host, conn)) } diff --git a/proxy/listener.go b/proxy/listener.go index 853508cf41..d72ed6c652 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -4,11 +4,10 @@ import ( "fmt" "sync" + "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy/socks" - - log "github.com/sirupsen/logrus" ) var ( @@ -35,24 +34,24 @@ func (l *Listener) Info() (info C.General) { } } -func (l *Listener) Update(allowLan *bool, httpPort *int, socksPort *int) error { - if allowLan != nil { - l.allowLan = *allowLan +func (l *Listener) Update(base *config.Base) error { + if base.AllowLan != nil { + l.allowLan = *base.AllowLan } var socksErr, httpErr error - if allowLan != nil || httpPort != nil { + if base.AllowLan != nil || base.Port != nil { newHTTPPort := l.httpPort - if httpPort != nil { - newHTTPPort = *httpPort + if base.Port != nil { + newHTTPPort = *base.Port } httpErr = l.updateHTTP(newHTTPPort) } - if allowLan != nil || socksPort != nil { + if base.AllowLan != nil || base.SocketPort != nil { newSocksPort := l.socksPort - if socksPort != nil { - newSocksPort = *socksPort + if base.SocketPort != nil { + newSocksPort = *base.SocketPort } socksErr = l.updateSocks(newSocksPort) } @@ -112,29 +111,30 @@ func (l *Listener) genAddr(port int) string { return fmt.Sprintf("%s:%d", host, port) } -func (l *Listener) Run() error { - return l.Update(&l.allowLan, &l.httpPort, &l.socksPort) -} - -func newListener() *Listener { - cfg, err := C.GetConfig() - if err != nil { - log.Fatalf("Read config error: %s", err.Error()) +func (l *Listener) process(signal chan<- struct{}) { + sub := config.Instance().Subscribe() + signal <- struct{}{} + for elm := range sub { + event := elm.(*config.Event) + if event.Type == "base" { + base := event.Payload.(config.Base) + l.Update(&base) + } } +} - general := cfg.Section("General") - - port := general.Key("port").RangeInt(C.DefalutHTTPPort, 1, 65535) - socksPort := general.Key("socks-port").RangeInt(C.DefalutSOCKSPort, 1, 65535) - allowLan := general.Key("allow-lan").MustBool() +// Run ensure config monitoring +func (l *Listener) Run() { + signal := make(chan struct{}) + go l.process(signal) + <-signal +} - return &Listener{ - httpPort: port, - socksPort: socksPort, - allowLan: allowLan, - } +func newListener() *Listener { + return &Listener{} } +// Instance return singleton instance of Listener func Instance() *Listener { once.Do(func() { listener = newListener() diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index c6e863564b..0fe78e29ac 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -1,10 +1,9 @@ package socks import ( - "io" "net" - "strconv" + "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" @@ -13,7 +12,7 @@ import ( ) var ( - tun = tunnel.GetInstance() + tun = tunnel.Instance() ) func NewSocksProxy(addr string) (*C.ProxySignal, error) { @@ -60,59 +59,5 @@ func handleSocks(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(NewSocks(target, conn)) -} - -type SocksAdapter struct { - conn net.Conn - addr *C.Addr -} - -func (s *SocksAdapter) Close() { - s.conn.Close() -} - -func (s *SocksAdapter) Addr() *C.Addr { - return s.addr -} - -func (s *SocksAdapter) Connect(proxy C.ProxyAdapter) { - go io.Copy(s.conn, proxy.ReadWriter()) - io.Copy(proxy.ReadWriter(), s.conn) -} - -func parseSocksAddr(target socks.Addr) *C.Addr { - var host, port string - var ip net.IP - - switch target[0] { - case socks.AtypDomainName: - host = string(target[2 : 2+target[1]]) - port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) - ipAddr, err := net.ResolveIPAddr("ip", host) - if err == nil { - ip = ipAddr.IP - } - case socks.AtypIPv4: - ip = net.IP(target[1 : 1+net.IPv4len]) - port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) - case socks.AtypIPv6: - ip = net.IP(target[1 : 1+net.IPv6len]) - port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) - } - - return &C.Addr{ - NetWork: C.TCP, - AddrType: int(target[0]), - Host: host, - IP: &ip, - Port: port, - } -} - -func NewSocks(target socks.Addr, conn net.Conn) *SocksAdapter { - return &SocksAdapter{ - conn: conn, - addr: parseSocksAddr(target), - } + tun.Add(adapters.NewSocks(target, conn)) } diff --git a/tunnel/connection.go b/tunnel/connection.go new file mode 100644 index 0000000000..794e06b63b --- /dev/null +++ b/tunnel/connection.go @@ -0,0 +1,67 @@ +package tunnel + +import ( + "io" + "net" + "net/http" + "time" + + "github.com/Dreamacro/clash/adapters/local" + C "github.com/Dreamacro/clash/constant" +) + +func (t *Tunnel) handleHTTP(request *adapters.HttpAdapter, proxy C.ProxyAdapter) { + req := http.Transport{ + Dial: func(string, string) (net.Conn, error) { + conn := newTrafficTrack(proxy.Conn(), t.traffic) + return conn, nil + }, + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + resp, err := req.RoundTrip(request.R) + if err != nil { + return + } + defer resp.Body.Close() + + header := request.W.Header() + for k, vv := range resp.Header { + for _, v := range vv { + header.Add(k, v) + } + } + request.W.WriteHeader(resp.StatusCode) + var writer io.Writer = request.W + if len(resp.TransferEncoding) > 0 && resp.TransferEncoding[0] == "chunked" { + writer = ChunkWriter{Writer: request.W} + } + io.Copy(writer, resp.Body) +} + +func (t *Tunnel) handleHTTPS(request *adapters.HttpsAdapter, proxy C.ProxyAdapter) { + conn := newTrafficTrack(proxy.Conn(), t.traffic) + go io.Copy(request.Conn(), conn) + io.Copy(conn, request.Conn()) +} + +func (t *Tunnel) handleSOCKS(request *adapters.SocksAdapter, proxy C.ProxyAdapter) { + conn := newTrafficTrack(proxy.Conn(), t.traffic) + go io.Copy(request.Conn(), conn) + io.Copy(conn, request.Conn()) +} + +// ChunkWriter is a writer wrapper and used when TransferEncoding is chunked +type ChunkWriter struct { + io.Writer +} + +func (cw ChunkWriter) Write(b []byte) (int, error) { + n, err := cw.Writer.Write(b) + if err == nil { + cw.Writer.(http.Flusher).Flush() + } + return n, err +} diff --git a/tunnel/log.go b/tunnel/log.go index 2021a35420..f703d62c41 100644 --- a/tunnel/log.go +++ b/tunnel/log.go @@ -3,47 +3,29 @@ package tunnel import ( "fmt" - log "github.com/sirupsen/logrus" -) + C "github.com/Dreamacro/clash/constant" -const ( - ERROR LogLevel = iota - WARNING - INFO - DEBUG + log "github.com/sirupsen/logrus" ) -type LogLevel int - type Log struct { - LogLevel LogLevel + LogLevel C.LogLevel Payload string } func (l *Log) Type() string { - switch l.LogLevel { - case INFO: - return "Info" - case WARNING: - return "Warning" - case ERROR: - return "Error" - case DEBUG: - return "Debug" - default: - return "Unknow" - } + return l.LogLevel.String() } func print(data Log) { switch data.LogLevel { - case INFO: + case C.INFO: log.Infoln(data.Payload) - case WARNING: + case C.WARNING: log.Warnln(data.Payload) - case ERROR: + case C.ERROR: log.Errorln(data.Payload) - case DEBUG: + case C.DEBUG: log.Debugln(data.Payload) } } @@ -55,11 +37,13 @@ func (t *Tunnel) subscribeLogs() { } for elm := range sub { data := elm.(Log) - print(data) + if data.LogLevel <= t.logLevel { + print(data) + } } } -func newLog(logLevel LogLevel, format string, v ...interface{}) Log { +func newLog(logLevel C.LogLevel, format string, v ...interface{}) Log { return Log{ LogLevel: logLevel, Payload: fmt.Sprintf(format, v...), diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 16e436450a..8d84a9d631 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -1,16 +1,14 @@ package tunnel import ( - "fmt" - "strconv" - "strings" "sync" "time" - "github.com/Dreamacro/clash/adapters" + LocalAdapter "github.com/Dreamacro/clash/adapters/local" + RemoteAdapter "github.com/Dreamacro/clash/adapters/remote" + cfg "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/observable" - R "github.com/Dreamacro/clash/rules" "gopkg.in/eapache/channels.v1" ) @@ -20,168 +18,61 @@ var ( once sync.Once ) +// Tunnel handle proxy socket and HTTP/SOCKS socket type Tunnel struct { queue *channels.InfiniteChannel rules []C.Rule proxies map[string]C.Proxy - observable *observable.Observable - logCh chan interface{} configLock *sync.RWMutex traffic *C.Traffic - mode Mode - selector *adapters.Selector + + // Outbound Rule + mode cfg.Mode + selector *RemoteAdapter.Selector + + // Log + logCh chan interface{} + observable *observable.Observable + logLevel C.LogLevel } +// Add request to queue func (t *Tunnel) Add(req C.ServerAdapter) { t.queue.In() <- req } +// Traffic return traffic of all connections func (t *Tunnel) Traffic() *C.Traffic { return t.traffic } -func (t *Tunnel) Config() ([]C.Rule, map[string]C.Proxy) { - return t.rules, t.proxies -} - +// Log return clash log stream func (t *Tunnel) Log() *observable.Observable { return t.observable } -func (t *Tunnel) SetMode(mode Mode) { - t.mode = mode -} - -func (t *Tunnel) GetMode() Mode { - return t.mode -} - -func (t *Tunnel) UpdateConfig() (err error) { - cfg, err := C.GetConfig() - if err != nil { - return - } - - // empty proxies and rules - proxies := make(map[string]C.Proxy) - rules := []C.Rule{} - - proxiesConfig := cfg.Section("Proxy") - rulesConfig := cfg.Section("Rule") - groupsConfig := cfg.Section("Proxy Group") - - // parse proxy - for _, key := range proxiesConfig.Keys() { - proxy := key.Strings(",") - if len(proxy) == 0 { - continue - } - switch proxy[0] { - // ss, server, port, cipter, password - case "ss": - if len(proxy) < 5 { - continue - } - ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2]) - ss, err := adapters.NewShadowSocks(key.Name(), ssURL, t.traffic) - if err != nil { - return err - } - proxies[key.Name()] = ss - } - } - - // parse rules - for _, key := range rulesConfig.Keys() { - rule := strings.Split(key.Name(), ",") - if len(rule) < 3 { - continue - } - rule = trimArr(rule) - switch rule[0] { - case "DOMAIN-SUFFIX": - rules = append(rules, R.NewDomainSuffix(rule[1], rule[2])) - case "DOMAIN-KEYWORD": - rules = append(rules, R.NewDomainKeyword(rule[1], rule[2])) - case "GEOIP": - rules = append(rules, R.NewGEOIP(rule[1], rule[2])) - case "IP-CIDR", "IP-CIDR6": - rules = append(rules, R.NewIPCIDR(rule[1], rule[2])) - case "FINAL": - rules = append(rules, R.NewFinal(rule[2])) +func (t *Tunnel) configMonitor(signal chan<- struct{}) { + sub := cfg.Instance().Subscribe() + signal <- struct{}{} + for elm := range sub { + event := elm.(*cfg.Event) + switch event.Type { + case "proxies": + proxies := event.Payload.(map[string]C.Proxy) + t.configLock.Lock() + t.proxies = proxies + t.configLock.Unlock() + case "rules": + rules := event.Payload.([]C.Rule) + t.configLock.Lock() + t.rules = rules + t.configLock.Unlock() + case "mode": + t.mode = event.Payload.(cfg.Mode) + case "log-level": + t.logLevel = event.Payload.(C.LogLevel) } } - - // parse proxy groups - for _, key := range groupsConfig.Keys() { - rule := strings.Split(key.Value(), ",") - rule = trimArr(rule) - switch rule[0] { - case "url-test": - if len(rule) < 4 { - return fmt.Errorf("URLTest need more than 4 param") - } - proxyNames := rule[1 : len(rule)-2] - delay, _ := strconv.Atoi(rule[len(rule)-1]) - url := rule[len(rule)-2] - var ps []C.Proxy - for _, name := range proxyNames { - if p, ok := proxies[name]; ok { - ps = append(ps, p) - } - } - - adapter, err := adapters.NewURLTest(key.Name(), ps, url, time.Duration(delay)*time.Second) - if err != nil { - return fmt.Errorf("Config error: %s", err.Error()) - } - proxies[key.Name()] = adapter - case "select": - if len(rule) < 3 { - return fmt.Errorf("Selector need more than 3 param") - } - proxyNames := rule[1:] - selectProxy := make(map[string]C.Proxy) - for _, name := range proxyNames { - proxy, exist := proxies[name] - if !exist { - return fmt.Errorf("Proxy %s not exist", name) - } - selectProxy[name] = proxy - } - selector, err := adapters.NewSelector(key.Name(), selectProxy) - if err != nil { - return fmt.Errorf("Selector create error: %s", err.Error()) - } - proxies[key.Name()] = selector - } - } - - // init proxy - proxies["DIRECT"] = adapters.NewDirect(t.traffic) - proxies["REJECT"] = adapters.NewReject() - - t.configLock.Lock() - defer t.configLock.Unlock() - - // stop url-test - for _, elm := range t.proxies { - urlTest, ok := elm.(*adapters.URLTest) - if ok { - urlTest.Close() - } - } - - s, err := adapters.NewSelector("Proxy", proxies) - if err != nil { - return err - } - - t.proxies = proxies - t.rules = rules - t.selector = s - - return nil } func (t *Tunnel) process() { @@ -199,9 +90,9 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { var proxy C.Proxy switch t.mode { - case Direct: + case cfg.Direct: proxy = t.proxies["DIRECT"] - case Global: + case cfg.Global: proxy = t.selector // Rule default: @@ -209,12 +100,22 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } remoConn, err := proxy.Generator(addr) if err != nil { - t.logCh <- newLog(WARNING, "Proxy connect error: %s", err.Error()) + t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error()) return } defer remoConn.Close() - localConn.Connect(remoConn) + switch adapter := localConn.(type) { + case *LocalAdapter.HttpAdapter: + t.handleHTTP(adapter, remoConn) + break + case *LocalAdapter.HttpsAdapter: + t.handleHTTPS(adapter, remoConn) + break + case *LocalAdapter.SocksAdapter: + t.handleSOCKS(adapter, remoConn) + break + } } func (t *Tunnel) match(addr *C.Addr) C.Proxy { @@ -227,31 +128,39 @@ func (t *Tunnel) match(addr *C.Addr) C.Proxy { if !ok { continue } - t.logCh <- newLog(INFO, "%v match %s using %s", addr.String(), rule.RuleType().String(), rule.Adapter()) + t.logCh <- newLog(C.INFO, "%v match %s using %s", addr.String(), rule.RuleType().String(), rule.Adapter()) return a } } - t.logCh <- newLog(INFO, "%v doesn't match any rule using DIRECT", addr.String()) + t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", addr.String()) return t.proxies["DIRECT"] } +// Run initial task +func (t *Tunnel) Run() { + go t.process() + go t.subscribeLogs() + signal := make(chan struct{}) + go t.configMonitor(signal) + <-signal +} + func newTunnel() *Tunnel { logCh := make(chan interface{}) - tunnel := &Tunnel{ + return &Tunnel{ queue: channels.NewInfiniteChannel(), proxies: make(map[string]C.Proxy), observable: observable.NewObservable(logCh), logCh: logCh, configLock: &sync.RWMutex{}, traffic: C.NewTraffic(time.Second), - mode: Rule, + mode: cfg.Rule, + logLevel: C.INFO, } - go tunnel.process() - go tunnel.subscribeLogs() - return tunnel } -func GetInstance() *Tunnel { +// Instance return singleton instance of Tunnel +func Instance() *Tunnel { once.Do(func() { tunnel = newTunnel() }) diff --git a/adapters/util.go b/tunnel/util.go similarity index 78% rename from adapters/util.go rename to tunnel/util.go index 1f41c548ea..15c4d3dacc 100644 --- a/adapters/util.go +++ b/tunnel/util.go @@ -1,4 +1,4 @@ -package adapters +package tunnel import ( "net" @@ -6,6 +6,7 @@ import ( C "github.com/Dreamacro/clash/constant" ) +// TrafficTrack record traffic of net.Conn type TrafficTrack struct { net.Conn traffic *C.Traffic @@ -23,6 +24,6 @@ func (tt *TrafficTrack) Write(b []byte) (int, error) { return n, err } -func NewTrafficTrack(conn net.Conn, traffic *C.Traffic) *TrafficTrack { +func newTrafficTrack(conn net.Conn, traffic *C.Traffic) *TrafficTrack { return &TrafficTrack{traffic: traffic, Conn: conn} } From 7347c28f752773643e4bb8c763288267fdde2326 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 31 Jul 2018 17:53:39 +0800 Subject: [PATCH 009/535] Fix: io copy REJECT error --- adapters/remote/reject.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/adapters/remote/reject.go b/adapters/remote/reject.go index 7833e0047e..b4ce4d86a7 100644 --- a/adapters/remote/reject.go +++ b/adapters/remote/reject.go @@ -3,12 +3,14 @@ package adapters import ( "io" "net" + "time" C "github.com/Dreamacro/clash/constant" ) // RejectAdapter is a reject connected adapter type RejectAdapter struct { + conn net.Conn } // Close is used to close connection @@ -16,7 +18,7 @@ func (r *RejectAdapter) Close() {} // Conn is used to http request func (r *RejectAdapter) Conn() net.Conn { - return nil + return r.conn } type Reject struct { @@ -31,19 +33,37 @@ func (r *Reject) Type() C.AdapterType { } func (r *Reject) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { - return &RejectAdapter{}, nil + return &RejectAdapter{conn: &NopConn{}}, nil } func NewReject() *Reject { return &Reject{} } -type NopRW struct{} +type NopConn struct{} -func (rw *NopRW) Read(b []byte) (int, error) { +func (rw *NopConn) Read(b []byte) (int, error) { return len(b), nil } -func (rw *NopRW) Write(b []byte) (int, error) { +func (rw *NopConn) Write(b []byte) (int, error) { return 0, io.EOF } + +// Close is fake function for net.Conn +func (rw *NopConn) Close() error { return nil } + +// LocalAddr is fake function for net.Conn +func (rw *NopConn) LocalAddr() net.Addr { return nil } + +// RemoteAddr is fake function for net.Conn +func (rw *NopConn) RemoteAddr() net.Addr { return nil } + +// SetDeadline is fake function for net.Conn +func (rw *NopConn) SetDeadline(time.Time) error { return nil } + +// SetReadDeadline is fake function for net.Conn +func (rw *NopConn) SetReadDeadline(time.Time) error { return nil } + +// SetWriteDeadline is fake function for net.Conn +func (rw *NopConn) SetWriteDeadline(time.Time) error { return nil } From 295d649624193f9be1a4ee41853ae4787a1d470f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 31 Jul 2018 17:54:16 +0800 Subject: [PATCH 010/535] Update: Initialize the config file outside of the init function --- config/initial.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ constant/config.go | 57 ------------------------------------ main.go | 1 + 3 files changed, 73 insertions(+), 57 deletions(-) create mode 100644 config/initial.go diff --git a/config/initial.go b/config/initial.go new file mode 100644 index 0000000000..b13eefd0ae --- /dev/null +++ b/config/initial.go @@ -0,0 +1,72 @@ +package config + +import ( + "archive/tar" + "compress/gzip" + "io" + "net/http" + "os" + "strings" + + C "github.com/Dreamacro/clash/constant" + + log "github.com/sirupsen/logrus" +) + +func downloadMMDB(path string) (err error) { + resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz") + if err != nil { + return + } + defer resp.Body.Close() + + gr, err := gzip.NewReader(resp.Body) + if err != nil { + return + } + defer gr.Close() + + tr := tar.NewReader(gr) + for { + h, err := tr.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + + if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") { + continue + } + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, tr) + if err != nil { + return err + } + } + + return nil +} + +// Init prepare necessary files +func Init() { + // initial config.ini + if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) { + log.Info("Can't find config, create a empty file") + os.OpenFile(C.ConfigPath, os.O_CREATE|os.O_WRONLY, 0644) + } + + // initial mmdb + if _, err := os.Stat(C.MMDBPath); os.IsNotExist(err) { + log.Info("Can't find MMDB, start download") + err := downloadMMDB(C.MMDBPath) + if err != nil { + log.Fatalf("Can't download MMDB: %s", err.Error()) + } + } +} diff --git a/constant/config.go b/constant/config.go index c12c208b02..b090edfb01 100644 --- a/constant/config.go +++ b/constant/config.go @@ -1,14 +1,9 @@ package constant import ( - "archive/tar" - "compress/gzip" - "io" - "net/http" "os" "os/user" "path" - "strings" log "github.com/sirupsen/logrus" ) @@ -52,57 +47,5 @@ func init() { } ConfigPath = path.Join(dirPath, "config.ini") - if _, err := os.Stat(ConfigPath); os.IsNotExist(err) { - log.Info("Can't find config, create a empty file") - os.OpenFile(ConfigPath, os.O_CREATE|os.O_WRONLY, 0644) - } - MMDBPath = path.Join(dirPath, "Country.mmdb") - if _, err := os.Stat(MMDBPath); os.IsNotExist(err) { - log.Info("Can't find MMDB, start download") - err := downloadMMDB(MMDBPath) - if err != nil { - log.Fatalf("Can't download MMDB: %s", err.Error()) - } - } -} - -func downloadMMDB(path string) (err error) { - resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz") - if err != nil { - return - } - defer resp.Body.Close() - - gr, err := gzip.NewReader(resp.Body) - if err != nil { - return - } - defer gr.Close() - - tr := tar.NewReader(gr) - for { - h, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - - if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") { - continue - } - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, tr) - if err != nil { - return err - } - } - - return nil } diff --git a/main.go b/main.go index 7bb6d8b6bc..57e834c215 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ func main() { proxy.Instance().Run() hub.Run() + config.Init() err := config.Instance().Parse() if err != nil { log.Fatalf("Parse config error: %s", err.Error()) From a1a58c31aef1beb3ac5f9ad0bdebb6e91140edab Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 1 Aug 2018 00:18:29 +0800 Subject: [PATCH 011/535] Improve: lazy load mmdb --- rules/geoip.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/rules/geoip.go b/rules/geoip.go index 9ee8691e0b..f79f26f0a9 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -1,21 +1,18 @@ package rules import ( + "sync" + C "github.com/Dreamacro/clash/constant" "github.com/oschwald/geoip2-golang" log "github.com/sirupsen/logrus" ) -var mmdb *geoip2.Reader - -func init() { - var err error - mmdb, err = geoip2.Open(C.MMDBPath) - if err != nil { - log.Fatalf("Can't load mmdb: %s", err.Error()) - } -} +var ( + mmdb *geoip2.Reader + once sync.Once +) type GEOIP struct { country string @@ -43,6 +40,13 @@ func (g *GEOIP) Payload() string { } func NewGEOIP(country string, adapter string) *GEOIP { + once.Do(func() { + var err error + mmdb, err = geoip2.Open(C.MMDBPath) + if err != nil { + log.Fatalf("Can't load mmdb: %s", err.Error()) + } + }) return &GEOIP{ country: country, adapter: adapter, From 63c967b2b3edee45199afeea2b2b0813b0388c43 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 4 Aug 2018 23:04:16 +0800 Subject: [PATCH 012/535] Fixed: global mode and update log level --- config/config.go | 7 +++++++ constant/config.go | 1 + hub/configs.go | 17 ++++++++++++++--- tunnel/tunnel.go | 6 ++---- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/config/config.go b/config/config.go index c6bb4e945a..06e17a18dd 100644 --- a/config/config.go +++ b/config/config.go @@ -94,6 +94,12 @@ func (c *Config) SetMode(mode Mode) { c.event <- &Event{Type: "mode", Payload: mode} } +// SetLogLevel change log level of clash +func (c *Config) SetLogLevel(level C.LogLevel) { + c.general.LogLevel = level + c.event <- &Event{Type: "log-level", Payload: level} +} + // General return clash general config func (c *Config) General() General { return *c.general @@ -225,6 +231,7 @@ func (c *Config) parseProxies(cfg *ini.File) error { } // init proxy + proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", proxies) proxies["DIRECT"] = adapters.NewDirect() proxies["REJECT"] = adapters.NewReject() diff --git a/constant/config.go b/constant/config.go index b090edfb01..3b8016b852 100644 --- a/constant/config.go +++ b/constant/config.go @@ -25,6 +25,7 @@ type General struct { AllowLan *bool `json:"allow-lan,omitempty"` Port *int `json:"port,omitempty"` SocksPort *int `json:"socks-port,omitempty"` + LogLevel *string `json:"log-level,omitempty"` } func init() { diff --git a/hub/configs.go b/hub/configs.go index 8601909317..8082bcc7ca 100644 --- a/hub/configs.go +++ b/hub/configs.go @@ -50,7 +50,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } // update errors - var proxyErr, modeErr error + var proxyErr, modeErr, logLevelErr error // update proxy listener := proxy.Instance() @@ -70,9 +70,20 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } } + // update log-level + if general.LogLevel != nil { + level, ok := C.LogLevelMapping[*general.LogLevel] + if !ok { + logLevelErr = fmt.Errorf("Log Level error") + } else { + cfg.SetLogLevel(level) + } + } + hasError, errors := formatErrors(map[string]error{ - "proxy": proxyErr, - "mode": modeErr, + "proxy": proxyErr, + "mode": modeErr, + "log-level": logLevelErr, }) if hasError { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 8d84a9d631..1b591b8a58 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -5,7 +5,6 @@ import ( "time" LocalAdapter "github.com/Dreamacro/clash/adapters/local" - RemoteAdapter "github.com/Dreamacro/clash/adapters/remote" cfg "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/observable" @@ -27,8 +26,7 @@ type Tunnel struct { traffic *C.Traffic // Outbound Rule - mode cfg.Mode - selector *RemoteAdapter.Selector + mode cfg.Mode // Log logCh chan interface{} @@ -93,7 +91,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { case cfg.Direct: proxy = t.proxies["DIRECT"] case cfg.Global: - proxy = t.selector + proxy = t.proxies["GLOBAL"] // Rule default: proxy = t.match(addr) From 674f4be24db25e66b5e16875bb6e6d1666287b4a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 7 Aug 2018 14:45:16 +0800 Subject: [PATCH 013/535] Fix: add JSON Content-Type for stream api to avoid buffer in swift --- hub/server.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/hub/server.go b/hub/server.go index 7d7a35d6f4..009b122dda 100644 --- a/hub/server.go +++ b/hub/server.go @@ -44,8 +44,8 @@ func newHub(signal chan struct{}) { r.Use(cors.Handler) - r.Get("/traffic", traffic) - r.Get("/logs", getLogs) + r.With(jsonContentType).Get("/traffic", traffic) + r.With(jsonContentType).Get("/logs", getLogs) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) @@ -56,6 +56,14 @@ func newHub(signal chan struct{}) { } } +func jsonContentType(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} + func traffic(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) From 36f4ceafa167c63519ca8e68b5e6d35e3c261a3e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 7 Aug 2018 14:45:24 +0800 Subject: [PATCH 014/535] Fix: returns the sorted proxy name --- adapters/remote/selector.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adapters/remote/selector.go b/adapters/remote/selector.go index 42214e0eac..92326c1c11 100644 --- a/adapters/remote/selector.go +++ b/adapters/remote/selector.go @@ -2,6 +2,7 @@ package adapters import ( "errors" + "sort" C "github.com/Dreamacro/clash/constant" ) @@ -33,6 +34,7 @@ func (s *Selector) All() []string { for k := range s.proxies { all = append(all, k) } + sort.Strings(all) return all } From ea424a7694283c6dfbd5248197e2fe3ce543c290 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 8 Aug 2018 11:51:06 +0800 Subject: [PATCH 015/535] Add: delay test api for proxies --- adapters/remote/urltest.go | 70 +------------------------------ adapters/remote/util.go | 86 ++++++++++++++++++++++++++++++++++++++ hub/proxies.go | 64 ++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 68 deletions(-) create mode 100644 adapters/remote/util.go diff --git a/adapters/remote/urltest.go b/adapters/remote/urltest.go index 00e877876d..3a0fa664e5 100644 --- a/adapters/remote/urltest.go +++ b/adapters/remote/urltest.go @@ -1,10 +1,6 @@ package adapters import ( - "fmt" - "net" - "net/http" - "net/url" "sync" "time" @@ -14,9 +10,7 @@ import ( type URLTest struct { name string proxies []C.Proxy - url *url.URL rawURL string - addr *C.Addr fast C.Proxy delay time.Duration done chan struct{} @@ -65,7 +59,7 @@ func (u *URLTest) speedTest() { for _, p := range u.proxies { go func(p C.Proxy) { - err := getUrl(p, u.addr, u.rawURL) + _, err := DelayTest(p, u.rawURL) if err == nil { c <- p } @@ -89,76 +83,16 @@ func (u *URLTest) speedTest() { } } -func getUrl(proxy C.Proxy, addr *C.Addr, rawURL string) (err error) { - instance, err := proxy.Generator(addr) - if err != nil { - return - } - defer instance.Close() - transport := &http.Transport{ - Dial: func(string, string) (net.Conn, error) { - return instance.Conn(), nil - }, - // from http.DefaultTransport - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - client := http.Client{Transport: transport} - req, err := client.Get(rawURL) - if err != nil { - return - } - req.Body.Close() - return nil -} - -func selectFast(in chan interface{}) chan interface{} { - out := make(chan interface{}) - go func() { - p, open := <-in - if open { - out <- p - } - close(out) - for range in { - } - }() - - return out -} - func NewURLTest(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) { - u, err := url.Parse(rawURL) + _, err := urlToAddr(rawURL) if err != nil { return nil, err } - port := u.Port() - if port == "" { - if u.Scheme == "https" { - port = "443" - } else if u.Scheme == "http" { - port = "80" - } else { - return nil, fmt.Errorf("%s scheme not Support", rawURL) - } - } - - addr := &C.Addr{ - AddrType: C.AtypDomainName, - Host: u.Hostname(), - IP: nil, - Port: port, - } - urlTest := &URLTest{ name: name, proxies: proxies[:], rawURL: rawURL, - url: u, - addr: addr, fast: proxies[0], delay: delay, done: make(chan struct{}), diff --git a/adapters/remote/util.go b/adapters/remote/util.go new file mode 100644 index 0000000000..02c084bc5c --- /dev/null +++ b/adapters/remote/util.go @@ -0,0 +1,86 @@ +package adapters + +import ( + "fmt" + "net" + "net/http" + "net/url" + "time" + + C "github.com/Dreamacro/clash/constant" +) + +// DelayTest get the delay for the specified URL +func DelayTest(proxy C.Proxy, url string) (t int16, err error) { + addr, err := urlToAddr(url) + if err != nil { + return + } + + start := time.Now() + instance, err := proxy.Generator(&addr) + if err != nil { + return + } + defer instance.Close() + transport := &http.Transport{ + Dial: func(string, string) (net.Conn, error) { + return instance.Conn(), nil + }, + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + client := http.Client{Transport: transport} + req, err := client.Get(url) + if err != nil { + return + } + req.Body.Close() + t = int16(time.Since(start) / time.Millisecond) + return +} + +func urlToAddr(rawURL string) (addr C.Addr, err error) { + u, err := url.Parse(rawURL) + if err != nil { + return + } + + port := u.Port() + if port == "" { + if u.Scheme == "https" { + port = "443" + } else if u.Scheme == "http" { + port = "80" + } else { + err = fmt.Errorf("%s scheme not Support", rawURL) + return + } + } + + addr = C.Addr{ + AddrType: C.AtypDomainName, + Host: u.Hostname(), + IP: nil, + Port: port, + } + return +} + +func selectFast(in chan interface{}) chan interface{} { + out := make(chan interface{}) + go func() { + p, open := <-in + if open { + out <- p + } + close(out) + for range in { + } + }() + + return out +} diff --git a/hub/proxies.go b/hub/proxies.go index 42c1af4de5..10a8e8ccca 100644 --- a/hub/proxies.go +++ b/hub/proxies.go @@ -3,6 +3,8 @@ package hub import ( "fmt" "net/http" + "strconv" + "time" A "github.com/Dreamacro/clash/adapters/remote" C "github.com/Dreamacro/clash/constant" @@ -15,6 +17,7 @@ func proxyRouter() http.Handler { r := chi.NewRouter() r.Get("/", getProxies) r.Get("/{name}", getProxy) + r.Get("/{name}/delay", getProxyDelay) r.Put("/{name}", updateProxy) return r } @@ -127,3 +130,64 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } + +type GetProxyDelayRequest struct { + URL string `json:"url"` + Timeout int16 `json:"timeout"` +} + +type GetProxyDelayResponse struct { + Delay int16 `json:"delay"` +} + +func getProxyDelay(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + url := query.Get("url") + timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + render.JSON(w, r, Error{ + Error: "Format error", + }) + return + } + + name := chi.URLParam(r, "name") + proxies := cfg.Proxies() + proxy, exist := proxies[name] + if !exist { + w.WriteHeader(http.StatusNotFound) + render.JSON(w, r, Error{ + Error: "Proxy not found", + }) + return + } + + sigCh := make(chan int16) + go func() { + t, err := A.DelayTest(proxy, url) + if err != nil { + sigCh <- 0 + } + sigCh <- t + }() + + select { + case <-time.After(time.Millisecond * time.Duration(timeout)): + w.WriteHeader(http.StatusRequestTimeout) + render.JSON(w, r, Error{ + Error: "Proxy delay test timeout", + }) + case t := <-sigCh: + if t == 0 { + w.WriteHeader(http.StatusServiceUnavailable) + render.JSON(w, r, Error{ + Error: "An error occurred in the delay test", + }) + } else { + render.JSON(w, r, GetProxyDelayResponse{ + Delay: t, + }) + } + } +} From 410b272b50d76792ac7b8baaba01c0a421e53062 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 11 Aug 2018 22:51:30 +0800 Subject: [PATCH 016/535] Fix: issue #3 --- adapters/local/http.go | 41 ++++++++++++++++------ adapters/local/https.go | 33 ------------------ proxy/http/server.go | 62 ++++++++++++++++----------------- proxy/http/util.go | 77 +++++++++++++++++++++++++++++++++++++++++ tunnel/connection.go | 50 ++++---------------------- tunnel/tunnel.go | 3 -- 6 files changed, 143 insertions(+), 123 deletions(-) delete mode 100644 adapters/local/https.go create mode 100644 proxy/http/util.go diff --git a/adapters/local/http.go b/adapters/local/http.go index 69712aa9c3..cd335f92da 100644 --- a/adapters/local/http.go +++ b/adapters/local/http.go @@ -1,32 +1,51 @@ package adapters import ( - "net/http" + "net" C "github.com/Dreamacro/clash/constant" ) +type PeekedConn struct { + net.Conn + Peeked []byte +} + +func (c *PeekedConn) Read(p []byte) (n int, err error) { + if len(c.Peeked) > 0 { + n = copy(p, c.Peeked) + c.Peeked = c.Peeked[n:] + if len(c.Peeked) == 0 { + c.Peeked = nil + } + return n, nil + } + return c.Conn.Read(p) +} + type HttpAdapter struct { addr *C.Addr - R *http.Request - W http.ResponseWriter - done chan struct{} + conn *PeekedConn } func (h *HttpAdapter) Close() { - h.done <- struct{}{} + h.conn.Close() } func (h *HttpAdapter) Addr() *C.Addr { return h.addr } -func NewHttp(host string, w http.ResponseWriter, r *http.Request) (*HttpAdapter, chan struct{}) { - done := make(chan struct{}) +func (h *HttpAdapter) Conn() net.Conn { + return h.conn +} + +func NewHttp(host string, peeked []byte, conn net.Conn) *HttpAdapter { return &HttpAdapter{ addr: parseHttpAddr(host), - R: r, - W: w, - done: done, - }, done + conn: &PeekedConn{ + Peeked: peeked, + Conn: conn, + }, + } } diff --git a/adapters/local/https.go b/adapters/local/https.go deleted file mode 100644 index da766609a9..0000000000 --- a/adapters/local/https.go +++ /dev/null @@ -1,33 +0,0 @@ -package adapters - -import ( - "bufio" - "net" - - C "github.com/Dreamacro/clash/constant" -) - -type HttpsAdapter struct { - addr *C.Addr - conn net.Conn - rw *bufio.ReadWriter -} - -func (h *HttpsAdapter) Close() { - h.conn.Close() -} - -func (h *HttpsAdapter) Addr() *C.Addr { - return h.addr -} - -func (h *HttpsAdapter) Conn() net.Conn { - return h.conn -} - -func NewHttps(host string, conn net.Conn) *HttpsAdapter { - return &HttpsAdapter{ - addr: parseHttpAddr(host), - conn: conn, - } -} diff --git a/proxy/http/server.go b/proxy/http/server.go index e7c1850cdc..31c0856f9d 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -1,7 +1,7 @@ package http import ( - "context" + "bufio" "net" "net/http" "strings" @@ -30,24 +30,23 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) { Closed: closed, } - server := &http.Server{ - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodConnect { - handleTunneling(w, r) - } else { - handleHTTP(w, r) - } - }), - } - go func() { log.Infof("HTTP proxy listening at: %s", addr) - server.Serve(l) + for { + c, err := l.Accept() + if err != nil { + if _, open := <-done; !open { + break + } + continue + } + go handleConn(c) + } }() go func() { <-done - server.Shutdown(context.Background()) + close(done) l.Close() closed <- struct{}{} }() @@ -55,27 +54,26 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) { return signal, nil } -func handleHTTP(w http.ResponseWriter, r *http.Request) { - addr := r.Host - // padding default port - if !strings.Contains(addr, ":") { - addr += ":80" +func handleConn(conn net.Conn) { + br := bufio.NewReader(conn) + method, hostName := httpHostHeader(br) + if hostName == "" { + return } - req, done := adapters.NewHttp(addr, w, r) - tun.Add(req) - <-done -} -func handleTunneling(w http.ResponseWriter, r *http.Request) { - hijacker, ok := w.(http.Hijacker) - if !ok { - return + if !strings.Contains(hostName, ":") { + hostName += ":80" } - conn, _, err := hijacker.Hijack() - if err != nil { - return + + var peeked []byte + if method == http.MethodConnect { + _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) + if err != nil { + return + } + } else if n := br.Buffered(); n > 0 { + peeked, _ = br.Peek(br.Buffered()) } - // w.WriteHeader(http.StatusOK) doesn't works in Safari - conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) - tun.Add(adapters.NewHttps(r.Host, conn)) + + tun.Add(adapters.NewHttp(hostName, peeked, conn)) } diff --git a/proxy/http/util.go b/proxy/http/util.go new file mode 100644 index 0000000000..090ff7e677 --- /dev/null +++ b/proxy/http/util.go @@ -0,0 +1,77 @@ +package http + +import ( + "bufio" + "bytes" + "net/http" +) + +// httpHostHeader returns the HTTP Host header from br without +// consuming any of its bytes. It returns ""if it can't find one. +func httpHostHeader(br *bufio.Reader) (method, host string) { + const maxPeek = 4 << 10 + peekSize := 0 + for { + peekSize++ + if peekSize > maxPeek { + b, _ := br.Peek(br.Buffered()) + return method, httpHostHeaderFromBytes(b) + } + b, err := br.Peek(peekSize) + if n := br.Buffered(); n > peekSize { + b, _ = br.Peek(n) + peekSize = n + } + if len(b) > 0 { + if b[0] < 'A' || b[0] > 'Z' { + // Doesn't look like an HTTP verb + // (GET, POST, etc). + return + } + if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) + if err != nil { + return + } + if len(req.Header["Host"]) > 1 { + // TODO(bradfitz): what does + // ReadRequest do if there are + // multiple Host headers? + return + } + return req.Method, req.Host + } + } + if err != nil { + return method, httpHostHeaderFromBytes(b) + } + } +} + +var ( + lfHostColon = []byte("\nHost:") + lfhostColon = []byte("\nhost:") + crlf = []byte("\r\n") + lf = []byte("\n") + crlfcrlf = []byte("\r\n\r\n") + lflf = []byte("\n\n") +) + +func httpHostHeaderFromBytes(b []byte) string { + if i := bytes.Index(b, lfHostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) + } + if i := bytes.Index(b, lfhostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) + } + return "" +} + +// untilEOL returns v, truncated before the first '\n' byte, if any. +// The returned slice may include a '\r' at the end. +func untilEOL(v []byte) []byte { + if i := bytes.IndexByte(v, '\n'); i != -1 { + return v[:i] + } + return v +} diff --git a/tunnel/connection.go b/tunnel/connection.go index 794e06b63b..6fb27f187f 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -2,47 +2,22 @@ package tunnel import ( "io" - "net" - "net/http" - "time" "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" ) func (t *Tunnel) handleHTTP(request *adapters.HttpAdapter, proxy C.ProxyAdapter) { - req := http.Transport{ - Dial: func(string, string) (net.Conn, error) { - conn := newTrafficTrack(proxy.Conn(), t.traffic) - return conn, nil - }, - // from http.DefaultTransport - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - resp, err := req.RoundTrip(request.R) - if err != nil { - return - } - defer resp.Body.Close() + conn := newTrafficTrack(proxy.Conn(), t.traffic) - header := request.W.Header() - for k, vv := range resp.Header { - for _, v := range vv { - header.Add(k, v) + // Before we unwrap src and/or dst, copy any buffered data. + if wc, ok := request.Conn().(*adapters.PeekedConn); ok && len(wc.Peeked) > 0 { + if _, err := conn.Write(wc.Peeked); err != nil { + return } + wc.Peeked = nil } - request.W.WriteHeader(resp.StatusCode) - var writer io.Writer = request.W - if len(resp.TransferEncoding) > 0 && resp.TransferEncoding[0] == "chunked" { - writer = ChunkWriter{Writer: request.W} - } - io.Copy(writer, resp.Body) -} -func (t *Tunnel) handleHTTPS(request *adapters.HttpsAdapter, proxy C.ProxyAdapter) { - conn := newTrafficTrack(proxy.Conn(), t.traffic) go io.Copy(request.Conn(), conn) io.Copy(conn, request.Conn()) } @@ -52,16 +27,3 @@ func (t *Tunnel) handleSOCKS(request *adapters.SocksAdapter, proxy C.ProxyAdapte go io.Copy(request.Conn(), conn) io.Copy(conn, request.Conn()) } - -// ChunkWriter is a writer wrapper and used when TransferEncoding is chunked -type ChunkWriter struct { - io.Writer -} - -func (cw ChunkWriter) Write(b []byte) (int, error) { - n, err := cw.Writer.Write(b) - if err == nil { - cw.Writer.(http.Flusher).Flush() - } - return n, err -} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 1b591b8a58..07abb53202 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -107,9 +107,6 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { case *LocalAdapter.HttpAdapter: t.handleHTTP(adapter, remoConn) break - case *LocalAdapter.HttpsAdapter: - t.handleHTTPS(adapter, remoConn) - break case *LocalAdapter.SocksAdapter: t.handleSOCKS(adapter, remoConn) break From 0208e3293380eec4916dab2e04223685cf8b2d11 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 02:23:46 +0800 Subject: [PATCH 017/535] Fix: update proxy config api --- config/config.go | 83 +++++++++++++++++++++++++++++++++++----------- config/general.go | 17 ---------- config/utils.go | 8 +++++ constant/config.go | 4 +-- hub/configs.go | 40 +++++++++++----------- hub/server.go | 1 + proxy/listener.go | 80 +++++++++----------------------------------- 7 files changed, 109 insertions(+), 124 deletions(-) delete mode 100644 config/general.go diff --git a/config/config.go b/config/config.go index 06e17a18dd..f0a127a3a7 100644 --- a/config/config.go +++ b/config/config.go @@ -21,6 +21,22 @@ var ( once sync.Once ) +// General config +type General struct { + Port int + SocksPort int + AllowLan bool + Mode Mode + LogLevel C.LogLevel +} + +// ProxyConfig is update proxy schema +type ProxyConfig struct { + Port *int + SocksPort *int + AllowLan *bool +} + // Config is clash config manager type Config struct { general *General @@ -29,7 +45,7 @@ type Config struct { lastUpdate time.Time event chan<- interface{} - errCh chan interface{} + reportCh chan interface{} observable *observable.Observable } @@ -45,9 +61,9 @@ func (c *Config) Subscribe() observable.Subscription { return sub } -// Report return a channel for collecting error message +// Report return a channel for collecting report message func (c *Config) Report() chan<- interface{} { - return c.errCh + return c.reportCh } func (c *Config) readConfig() (*ini.File, error) { @@ -118,8 +134,8 @@ func (c *Config) UpdateRules() error { func (c *Config) parseGeneral(cfg *ini.File) error { general := cfg.Section("General") - port := general.Key("port").RangeInt(C.DefalutHTTPPort, 1, 65535) - socksPort := general.Key("socks-port").RangeInt(C.DefalutSOCKSPort, 1, 65535) + port := general.Key("port").RangeInt(0, 1, 65535) + socksPort := general.Key("socks-port").RangeInt(0, 1, 65535) allowLan := general.Key("allow-lan").MustBool() logLevelString := general.Key("log-level").MustString(C.INFO.String()) modeString := general.Key("mode").MustString(Rule.String()) @@ -135,13 +151,11 @@ func (c *Config) parseGeneral(cfg *ini.File) error { } c.general = &General{ - Base: &Base{ - Port: &port, - SocketPort: &socksPort, - AllowLan: &allowLan, - }, - Mode: mode, - LogLevel: logLevel, + Port: port, + SocksPort: socksPort, + AllowLan: allowLan, + Mode: mode, + LogLevel: logLevel, } if restAddr := general.Key("external-controller").String(); restAddr != "" { @@ -154,11 +168,32 @@ func (c *Config) parseGeneral(cfg *ini.File) error { // UpdateGeneral dispatch update event func (c *Config) UpdateGeneral(general General) { - c.event <- &Event{Type: "base", Payload: *general.Base} + c.UpdateProxy(ProxyConfig{ + Port: &general.Port, + SocksPort: &general.SocksPort, + AllowLan: &general.AllowLan, + }) c.event <- &Event{Type: "mode", Payload: general.Mode} c.event <- &Event{Type: "log-level", Payload: general.LogLevel} } +// UpdateProxy dispatch update proxy event +func (c *Config) UpdateProxy(pc ProxyConfig) { + if pc.AllowLan != nil { + c.general.AllowLan = *pc.AllowLan + } + + if (pc.AllowLan != nil || pc.Port != nil) && *pc.Port != 0 { + c.general.Port = *pc.Port + c.event <- &Event{Type: "http-addr", Payload: genAddr(*pc.Port, c.general.AllowLan)} + } + + if (pc.AllowLan != nil || pc.SocksPort != nil) && *pc.SocksPort != 0 { + c.general.SocksPort = *pc.SocksPort + c.event <- &Event{Type: "socks-addr", Payload: genAddr(*pc.SocksPort, c.general.AllowLan)} + } +} + func (c *Config) parseProxies(cfg *ini.File) error { proxies := make(map[string]C.Proxy) proxiesConfig := cfg.Section("Proxy") @@ -270,18 +305,27 @@ func (c *Config) parseRules(cfg *ini.File) error { return nil } -func (c *Config) handleErrorMessage() { - for elm := range c.errCh { - event := elm.(Event) +func (c *Config) handleResponseMessage() { + for elm := range c.reportCh { + event := elm.(*Event) switch event.Type { - case "base": - c.general.Base = event.Payload.(*Base) + case "http-addr": + if event.Payload.(bool) == false { + c.general.Port = 0 + } + break + case "socks-addr": + if event.Payload.(bool) == false { + c.general.SocksPort = 0 + } + break } } } func newConfig() *Config { event := make(chan interface{}) + reportCh := make(chan interface{}) config := &Config{ general: &General{}, proxies: make(map[string]C.Proxy), @@ -289,9 +333,10 @@ func newConfig() *Config { lastUpdate: time.Now(), event: event, + reportCh: reportCh, observable: observable.NewObservable(event), } - go config.handleErrorMessage() + go config.handleResponseMessage() return config } diff --git a/config/general.go b/config/general.go deleted file mode 100644 index 121414476b..0000000000 --- a/config/general.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -import ( - C "github.com/Dreamacro/clash/constant" -) - -type General struct { - *Base - Mode Mode - LogLevel C.LogLevel -} - -type Base struct { - Port *int - SocketPort *int - AllowLan *bool -} diff --git a/config/utils.go b/config/utils.go index cc2b99d47b..793fcfe78e 100644 --- a/config/utils.go +++ b/config/utils.go @@ -1,6 +1,7 @@ package config import ( + "fmt" "strings" ) @@ -10,3 +11,10 @@ func trimArr(arr []string) (r []string) { } return } + +func genAddr(port int, allowLan bool) string { + if allowLan { + return fmt.Sprintf(":%d", port) + } + return fmt.Sprintf("127.0.0.1:%d", port) +} diff --git a/constant/config.go b/constant/config.go index 3b8016b852..b09375877c 100644 --- a/constant/config.go +++ b/constant/config.go @@ -9,9 +9,7 @@ import ( ) const ( - Name = "clash" - DefalutHTTPPort = 7890 - DefalutSOCKSPort = 7891 + Name = "clash" ) var ( diff --git a/hub/configs.go b/hub/configs.go index 8082bcc7ca..2cbe2e3c65 100644 --- a/hub/configs.go +++ b/hub/configs.go @@ -6,7 +6,6 @@ import ( "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/proxy" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -20,21 +19,21 @@ func configRouter() http.Handler { } type configSchema struct { - Port int `json:"port"` - SocketPort int `json:"socket-port"` - AllowLan bool `json:"allow-lan"` - Mode string `json:"mode"` - LogLevel string `json:"log-level"` + Port int `json:"port"` + SocksPort int `json:"socket-port"` + AllowLan bool `json:"allow-lan"` + Mode string `json:"mode"` + LogLevel string `json:"log-level"` } func getConfigs(w http.ResponseWriter, r *http.Request) { general := cfg.General() render.JSON(w, r, configSchema{ - Port: *general.Port, - SocketPort: *general.SocketPort, - AllowLan: *general.AllowLan, - Mode: general.Mode.String(), - LogLevel: general.LogLevel.String(), + Port: general.Port, + SocksPort: general.SocksPort, + AllowLan: general.AllowLan, + Mode: general.Mode.String(), + LogLevel: general.LogLevel.String(), }) } @@ -50,15 +49,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } // update errors - var proxyErr, modeErr, logLevelErr error - - // update proxy - listener := proxy.Instance() - proxyErr = listener.Update(&config.Base{ - AllowLan: general.AllowLan, - Port: general.Port, - SocketPort: general.SocksPort, - }) + var modeErr, logLevelErr error // update mode if general.Mode != nil { @@ -81,7 +72,6 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } hasError, errors := formatErrors(map[string]error{ - "proxy": proxyErr, "mode": modeErr, "log-level": logLevelErr, }) @@ -91,5 +81,13 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, errors) return } + + // update proxy + cfg.UpdateProxy(config.ProxyConfig{ + AllowLan: general.AllowLan, + Port: general.Port, + SocksPort: general.SocksPort, + }) + w.WriteHeader(http.StatusNoContent) } diff --git a/hub/server.go b/hub/server.go index 009b122dda..2a4f789140 100644 --- a/hub/server.go +++ b/hub/server.go @@ -50,6 +50,7 @@ func newHub(signal chan struct{}) { r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) + log.Infof("RESTful API listening at: %s", addr) err := http.ListenAndServe(addr, r) if err != nil { log.Errorf("External controller error: %s", err.Error()) diff --git a/proxy/listener.go b/proxy/listener.go index d72ed6c652..91c1c92851 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -1,7 +1,6 @@ package proxy import ( - "fmt" "sync" "github.com/Dreamacro/clash/config" @@ -16,58 +15,12 @@ var ( ) type Listener struct { - httpPort int - socksPort int - allowLan bool - // signal for update httpSignal *C.ProxySignal socksSignal *C.ProxySignal } -// Info returns the proxies's current configuration -func (l *Listener) Info() (info C.General) { - return C.General{ - Port: &l.httpPort, - SocksPort: &l.socksPort, - AllowLan: &l.allowLan, - } -} - -func (l *Listener) Update(base *config.Base) error { - if base.AllowLan != nil { - l.allowLan = *base.AllowLan - } - - var socksErr, httpErr error - if base.AllowLan != nil || base.Port != nil { - newHTTPPort := l.httpPort - if base.Port != nil { - newHTTPPort = *base.Port - } - httpErr = l.updateHTTP(newHTTPPort) - } - - if base.AllowLan != nil || base.SocketPort != nil { - newSocksPort := l.socksPort - if base.SocketPort != nil { - newSocksPort = *base.SocketPort - } - socksErr = l.updateSocks(newSocksPort) - } - - if socksErr != nil && httpErr != nil { - return fmt.Errorf("%s\n%s", socksErr.Error(), httpErr.Error()) - } else if socksErr != nil { - return socksErr - } else if httpErr != nil { - return httpErr - } else { - return nil - } -} - -func (l *Listener) updateHTTP(port int) error { +func (l *Listener) updateHTTP(addr string) error { if l.httpSignal != nil { signal := l.httpSignal signal.Done <- struct{}{} @@ -75,17 +28,16 @@ func (l *Listener) updateHTTP(port int) error { l.httpSignal = nil } - signal, err := http.NewHttpProxy(l.genAddr(port)) + signal, err := http.NewHttpProxy(addr) if err != nil { return err } l.httpSignal = signal - l.httpPort = port return nil } -func (l *Listener) updateSocks(port int) error { +func (l *Listener) updateSocks(addr string) error { if l.socksSignal != nil { signal := l.socksSignal signal.Done <- struct{}{} @@ -93,32 +45,32 @@ func (l *Listener) updateSocks(port int) error { l.socksSignal = nil } - signal, err := socks.NewSocksProxy(l.genAddr(port)) + signal, err := socks.NewSocksProxy(addr) if err != nil { return err } l.socksSignal = signal - l.socksPort = port return nil } -func (l *Listener) genAddr(port int) string { - host := "127.0.0.1" - if l.allowLan { - host = "" - } - return fmt.Sprintf("%s:%d", host, port) -} - func (l *Listener) process(signal chan<- struct{}) { sub := config.Instance().Subscribe() signal <- struct{}{} + reportCH := config.Instance().Report() for elm := range sub { event := elm.(*config.Event) - if event.Type == "base" { - base := event.Payload.(config.Base) - l.Update(&base) + switch event.Type { + case "http-addr": + addr := event.Payload.(string) + err := l.updateHTTP(addr) + reportCH <- &config.Event{Type: "http-addr", Payload: err == nil} + break + case "socks-addr": + addr := event.Payload.(string) + err := l.updateSocks(addr) + reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil} + break } } } From 63308472ad5b66d1151cecf2468b8f566de72a04 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 04:00:34 +0800 Subject: [PATCH 018/535] New: redir proxy --- config/config.go | 19 +++++++++++ proxy/listener.go | 24 +++++++++++++ proxy/redir/tcp.go | 62 ++++++++++++++++++++++++++++++++++ proxy/redir/tcp_darwin.go | 58 +++++++++++++++++++++++++++++++ proxy/redir/tcp_linux.go | 51 ++++++++++++++++++++++++++++ proxy/redir/tcp_linux_386.go | 17 ++++++++++ proxy/redir/tcp_linux_other.go | 14 ++++++++ proxy/redir/tcp_windows.go | 9 +++++ 8 files changed, 254 insertions(+) create mode 100644 proxy/redir/tcp.go create mode 100644 proxy/redir/tcp_darwin.go create mode 100644 proxy/redir/tcp_linux.go create mode 100644 proxy/redir/tcp_linux_386.go create mode 100644 proxy/redir/tcp_linux_other.go create mode 100644 proxy/redir/tcp_windows.go diff --git a/config/config.go b/config/config.go index f0a127a3a7..e1bf695b00 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,7 @@ import ( "github.com/Dreamacro/clash/observable" R "github.com/Dreamacro/clash/rules" + log "github.com/sirupsen/logrus" "gopkg.in/ini.v1" ) @@ -25,6 +26,7 @@ var ( type General struct { Port int SocksPort int + RedirPort int AllowLan bool Mode Mode LogLevel C.LogLevel @@ -34,6 +36,7 @@ type General struct { type ProxyConfig struct { Port *int SocksPort *int + RedirPort *int AllowLan *bool } @@ -136,6 +139,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error { port := general.Key("port").RangeInt(0, 1, 65535) socksPort := general.Key("socks-port").RangeInt(0, 1, 65535) + redirPort := general.Key("redir-port").RangeInt(0, 1, 65535) allowLan := general.Key("allow-lan").MustBool() logLevelString := general.Key("log-level").MustString(C.INFO.String()) modeString := general.Key("mode").MustString(Rule.String()) @@ -153,6 +157,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error { c.general = &General{ Port: port, SocksPort: socksPort, + RedirPort: redirPort, AllowLan: allowLan, Mode: mode, LogLevel: logLevel, @@ -171,6 +176,7 @@ func (c *Config) UpdateGeneral(general General) { c.UpdateProxy(ProxyConfig{ Port: &general.Port, SocksPort: &general.SocksPort, + RedirPort: &general.RedirPort, AllowLan: &general.AllowLan, }) c.event <- &Event{Type: "mode", Payload: general.Mode} @@ -192,6 +198,11 @@ func (c *Config) UpdateProxy(pc ProxyConfig) { c.general.SocksPort = *pc.SocksPort c.event <- &Event{Type: "socks-addr", Payload: genAddr(*pc.SocksPort, c.general.AllowLan)} } + + if (pc.AllowLan != nil || pc.RedirPort != nil) && *pc.RedirPort != 0 { + c.general.RedirPort = *pc.RedirPort + c.event <- &Event{Type: "redir-addr", Payload: genAddr(*pc.RedirPort, c.general.AllowLan)} + } } func (c *Config) parseProxies(cfg *ini.File) error { @@ -311,14 +322,22 @@ func (c *Config) handleResponseMessage() { switch event.Type { case "http-addr": if event.Payload.(bool) == false { + log.Errorf("Listening HTTP proxy at %s error", c.general.Port) c.general.Port = 0 } break case "socks-addr": if event.Payload.(bool) == false { + log.Errorf("Listening SOCKS proxy at %s error", c.general.SocksPort) c.general.SocksPort = 0 } break + case "redir-addr": + if event.Payload.(bool) == false { + log.Errorf("Listening Redir proxy at %s error", c.general.RedirPort) + c.general.RedirPort = 0 + } + break } } } diff --git a/proxy/listener.go b/proxy/listener.go index 91c1c92851..e3f6863c43 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -6,6 +6,7 @@ import ( "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/socks" ) @@ -18,6 +19,7 @@ type Listener struct { // signal for update httpSignal *C.ProxySignal socksSignal *C.ProxySignal + redirSignal *C.ProxySignal } func (l *Listener) updateHTTP(addr string) error { @@ -54,6 +56,23 @@ func (l *Listener) updateSocks(addr string) error { return nil } +func (l *Listener) updateRedir(addr string) error { + if l.redirSignal != nil { + signal := l.redirSignal + signal.Done <- struct{}{} + <-signal.Closed + l.redirSignal = nil + } + + signal, err := redir.NewRedirProxy(addr) + if err != nil { + return err + } + + l.redirSignal = signal + return nil +} + func (l *Listener) process(signal chan<- struct{}) { sub := config.Instance().Subscribe() signal <- struct{}{} @@ -71,6 +90,11 @@ func (l *Listener) process(signal chan<- struct{}) { err := l.updateSocks(addr) reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil} break + case "redir-addr": + addr := event.Payload.(string) + err := l.updateRedir(addr) + reportCH <- &config.Event{Type: "redir-addr", Payload: err == nil} + break } } } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go new file mode 100644 index 0000000000..16ccb248a9 --- /dev/null +++ b/proxy/redir/tcp.go @@ -0,0 +1,62 @@ +package redir + +import ( + "net" + + "github.com/Dreamacro/clash/adapters/local" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" + + log "github.com/sirupsen/logrus" +) + +var ( + tun = tunnel.Instance() +) + +func NewRedirProxy(addr string) (*C.ProxySignal, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + done := make(chan struct{}) + closed := make(chan struct{}) + signal := &C.ProxySignal{ + Done: done, + Closed: closed, + } + + go func() { + log.Infof("Redir proxy listening at: %s", addr) + for { + c, err := l.Accept() + if err != nil { + if _, open := <-done; !open { + break + } + continue + } + go handleRedir(c) + } + }() + + go func() { + <-done + close(done) + l.Close() + closed <- struct{}{} + }() + + return signal, nil +} + +func handleRedir(conn net.Conn) { + target, err := parserPacket(conn) + if err != nil { + conn.Close() + return + } + conn.(*net.TCPConn).SetKeepAlive(true) + tun.Add(adapters.NewSocks(target, conn)) +} diff --git a/proxy/redir/tcp_darwin.go b/proxy/redir/tcp_darwin.go new file mode 100644 index 0000000000..3fe7803f5a --- /dev/null +++ b/proxy/redir/tcp_darwin.go @@ -0,0 +1,58 @@ +package redir + +import ( + "net" + "syscall" + "unsafe" + + "github.com/riobard/go-shadowsocks2/socks" +) + +func parserPacket(c net.Conn) (socks.Addr, error) { + const ( + PfInout = 0 + PfIn = 1 + PfOut = 2 + IOCOut = 0x40000000 + IOCIn = 0x80000000 + IOCInOut = IOCIn | IOCOut + IOCPARMMask = 0x1FFF + LEN = 4*16 + 4*4 + 4*1 + // #define _IOC(inout,group,num,len) (inout | ((len & IOCPARMMask) << 16) | ((group) << 8) | (num)) + // #define _IOWR(g,n,t) _IOC(IOCInOut, (g), (n), sizeof(t)) + // #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) + DIOCNATLOOK = IOCInOut | ((LEN & IOCPARMMask) << 16) | ('D' << 8) | 23 + ) + + fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY) + if err != nil { + return nil, err + } + defer syscall.Close(fd) + + nl := struct { // struct pfioc_natlook + saddr, daddr, rsaddr, rdaddr [16]byte + sxport, dxport, rsxport, rdxport [4]byte + af, proto, protoVariant, direction uint8 + }{ + af: syscall.AF_INET, + proto: syscall.IPPROTO_TCP, + direction: PfOut, + } + saddr := c.RemoteAddr().(*net.TCPAddr) + daddr := c.LocalAddr().(*net.TCPAddr) + copy(nl.saddr[:], saddr.IP) + copy(nl.daddr[:], daddr.IP) + nl.sxport[0], nl.sxport[1] = byte(saddr.Port>>8), byte(saddr.Port) + nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port) + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { + return nil, errno + } + + addr := make([]byte, 1+net.IPv4len+2) + addr[0] = socks.AtypIPv4 + copy(addr[1:1+net.IPv4len], nl.rdaddr[:4]) + copy(addr[1+net.IPv4len:], nl.rdxport[:2]) + return addr, nil +} diff --git a/proxy/redir/tcp_linux.go b/proxy/redir/tcp_linux.go new file mode 100644 index 0000000000..836b2e5d43 --- /dev/null +++ b/proxy/redir/tcp_linux.go @@ -0,0 +1,51 @@ +package redir + +import ( + "errors" + "net" + "syscall" + "unsafe" + + "github.com/riobard/go-shadowsocks2/socks" +) + +const ( + SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h + IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h +) + +func parserPacket(conn net.Conn) (socks.Addr, error) { + c, ok := conn.(*net.TCPConn) + if !ok { + return nil, errors.New("only work with TCP connection") + } + + rc, err := c.SyscallConn() + if err != nil { + return nil, err + } + + var addr socks.Addr + + rc.Control(func(fd uintptr) { + addr, err = getorigdst(fd) + }) + + return addr, err +} + +// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c +func getorigdst(fd uintptr) (socks.Addr, error) { + raw := syscall.RawSockaddrInet4{} + siz := unsafe.Sizeof(raw) + if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { + return nil, err + } + + addr := make([]byte, 1+net.IPv4len+2) + addr[0] = socks.AtypIPv4 + copy(addr[1:1+net.IPv4len], raw.Addr[:]) + port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian + addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] + return addr, nil +} diff --git a/proxy/redir/tcp_linux_386.go b/proxy/redir/tcp_linux_386.go new file mode 100644 index 0000000000..32f692df8e --- /dev/null +++ b/proxy/redir/tcp_linux_386.go @@ -0,0 +1,17 @@ +package redir + +import ( + "syscall" + "unsafe" +) + +const GETSOCKOPT = 15 // https://golang.org/src/syscall/syscall_linux_386.go#L183 + +func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { + var a [6]uintptr + a[0], a[1], a[2], a[3], a[4], a[5] = a0, a1, a2, a3, a4, a5 + if _, _, errno := syscall.Syscall6(syscall.SYS_SOCKETCALL, call, uintptr(unsafe.Pointer(&a)), 0, 0, 0, 0); errno != 0 { + return errno + } + return nil +} diff --git a/proxy/redir/tcp_linux_other.go b/proxy/redir/tcp_linux_other.go new file mode 100644 index 0000000000..95472823c1 --- /dev/null +++ b/proxy/redir/tcp_linux_other.go @@ -0,0 +1,14 @@ +// +build linux,!386 + +package redir + +import "syscall" + +const GETSOCKOPT = syscall.SYS_GETSOCKOPT + +func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) error { + if _, _, errno := syscall.Syscall6(call, a0, a1, a2, a3, a4, a5); errno != 0 { + return errno + } + return nil +} diff --git a/proxy/redir/tcp_windows.go b/proxy/redir/tcp_windows.go new file mode 100644 index 0000000000..075e5f27db --- /dev/null +++ b/proxy/redir/tcp_windows.go @@ -0,0 +1,9 @@ +package redir + +import ( + "errors" +) + +func parserPacket(conn net.Conn) (socks.Addr, error) { + return nil, errors.New("Windows not support yet") +} From 752944d3818bc4aad5444126e9d1a33e5ca89938 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 04:22:14 +0800 Subject: [PATCH 019/535] Update: README.md --- README.md | 27 ++++++++++++++++++++++----- config/config.go | 4 ++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d4be64ab4e..5b9273156e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ - @@ -66,6 +65,9 @@ Below is a simple demo configuration file: port = 7890 socks-port = 7891 +# redir proxy for Linux and macOS +redir-port = 7892 + # A RESTful API for clash external-controller = 127.0.0.1:8080 @@ -73,13 +75,18 @@ external-controller = 127.0.0.1:8080 # name = ss, server, port, cipher, password # The types of cipher are consistent with go-shadowsocks2 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 -Proxy1 = ss, server1, port, AEAD_CHACHA20_POLY1305, password -Proxy2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password +ss1 = ss, server1, port, AEAD_CHACHA20_POLY1305, password +ss2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password [Proxy Group] # url-test select which proxy will be used by benchmarking speed to a URL. # name = url-test, [proxies], url, interval(second) -Proxy = url-test, Proxy1, Proxy2, http://www.google.com/generate_204, 300 +auto = url-test, ss1, ss2, http://www.google.com/generate_204, 300 + +# select is used for selecting proxy or proxy group +# you can use RESTful API to switch proxy, is recommended for use in GUI. +# name = select, [proxies] +Proxy = select, ss1, ss2, auto [Rule] DOMAIN-SUFFIX,google.com,Proxy @@ -89,9 +96,19 @@ GEOIP,CN,DIRECT FINAL,,Proxy # note: there is two "," ``` +## Thanks + +[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) + +[google/tcpproxy](https://github.com/google/tcpproxy) + ## License + [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) ## TODO -- [ ] Complementing the necessary rule operators +- [x] Complementing the necessary rule operators +- [x] Redir proxy +- [ ] UDP support +- [ ] Connection manager diff --git a/config/config.go b/config/config.go index e1bf695b00..bcd332695c 100644 --- a/config/config.go +++ b/config/config.go @@ -256,8 +256,8 @@ func (c *Config) parseProxies(cfg *ini.File) error { } proxies[key.Name()] = adapter case "select": - if len(rule) < 3 { - return fmt.Errorf("Selector need more than 3 param") + if len(rule) < 2 { + return fmt.Errorf("Selector need more than 2 param") } proxyNames := rule[1:] selectProxy := make(map[string]C.Proxy) From 228d674d935a9072c6159688889a2ae4b04aae98 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 11:06:17 +0800 Subject: [PATCH 020/535] Fix: import package --- proxy/redir/tcp_windows.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proxy/redir/tcp_windows.go b/proxy/redir/tcp_windows.go index 075e5f27db..a749c11771 100644 --- a/proxy/redir/tcp_windows.go +++ b/proxy/redir/tcp_windows.go @@ -2,6 +2,9 @@ package redir import ( "errors" + "net" + + "github.com/riobard/go-shadowsocks2/socks" ) func parserPacket(conn net.Conn) (socks.Addr, error) { From 35e572406b51b3e5c5263286e2444d79f38f6850 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 12:14:59 +0800 Subject: [PATCH 021/535] Fix: firefox one socket to process multiple domains --- adapters/local/http.go | 99 +++++++++++++++++++++++++++++++++++++++++- proxy/http/server.go | 4 +- proxy/http/util.go | 77 -------------------------------- 3 files changed, 100 insertions(+), 80 deletions(-) delete mode 100644 proxy/http/util.go diff --git a/adapters/local/http.go b/adapters/local/http.go index cd335f92da..574f02bc68 100644 --- a/adapters/local/http.go +++ b/adapters/local/http.go @@ -1,7 +1,12 @@ package adapters import ( + "bufio" + "bytes" + "io" "net" + "net/http" + "strings" C "github.com/Dreamacro/clash/constant" ) @@ -9,6 +14,8 @@ import ( type PeekedConn struct { net.Conn Peeked []byte + host string + isHTTP bool } func (c *PeekedConn) Read(p []byte) (n int, err error) { @@ -20,6 +27,23 @@ func (c *PeekedConn) Read(p []byte) (n int, err error) { } return n, nil } + + // Sometimes firefox just open a socket to process multiple domains in HTTP + // The temporary solution is to return io.EOF when encountering different HOST + if c.isHTTP { + br := bufio.NewReader(bytes.NewReader(p)) + _, hostName := ParserHTTPHostHeader(br) + if hostName != "" { + if !strings.Contains(hostName, ":") { + hostName += ":80" + } + + if hostName != c.host { + return 0, io.EOF + } + } + } + return c.Conn.Read(p) } @@ -40,12 +64,85 @@ func (h *HttpAdapter) Conn() net.Conn { return h.conn } -func NewHttp(host string, peeked []byte, conn net.Conn) *HttpAdapter { +func NewHttp(host string, peeked []byte, isHTTP bool, conn net.Conn) *HttpAdapter { return &HttpAdapter{ addr: parseHttpAddr(host), conn: &PeekedConn{ Peeked: peeked, Conn: conn, + host: host, + isHTTP: isHTTP, }, } } + +// ParserHTTPHostHeader returns the HTTP Host header from br without +// consuming any of its bytes. It returns "" if it can't find one. +func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { + // br := bufio.NewReader(bytes.NewReader(data)) + const maxPeek = 4 << 10 + peekSize := 0 + for { + peekSize++ + if peekSize > maxPeek { + b, _ := br.Peek(br.Buffered()) + return method, httpHostHeaderFromBytes(b) + } + b, err := br.Peek(peekSize) + if n := br.Buffered(); n > peekSize { + b, _ = br.Peek(n) + peekSize = n + } + if len(b) > 0 { + if b[0] < 'A' || b[0] > 'Z' { + // Doesn't look like an HTTP verb + // (GET, POST, etc). + return + } + if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) + if err != nil { + return + } + if len(req.Header["Host"]) > 1 { + // TODO(bradfitz): what does + // ReadRequest do if there are + // multiple Host headers? + return + } + return req.Method, req.Host + } + } + if err != nil { + return method, httpHostHeaderFromBytes(b) + } + } +} + +var ( + lfHostColon = []byte("\nHost:") + lfhostColon = []byte("\nhost:") + crlf = []byte("\r\n") + lf = []byte("\n") + crlfcrlf = []byte("\r\n\r\n") + lflf = []byte("\n\n") +) + +func httpHostHeaderFromBytes(b []byte) string { + if i := bytes.Index(b, lfHostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) + } + if i := bytes.Index(b, lfhostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) + } + return "" +} + +// untilEOL returns v, truncated before the first '\n' byte, if any. +// The returned slice may include a '\r' at the end. +func untilEOL(v []byte) []byte { + if i := bytes.IndexByte(v, '\n'); i != -1 { + return v[:i] + } + return v +} diff --git a/proxy/http/server.go b/proxy/http/server.go index 31c0856f9d..d015a0857c 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -56,7 +56,7 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) { func handleConn(conn net.Conn) { br := bufio.NewReader(conn) - method, hostName := httpHostHeader(br) + method, hostName := adapters.ParserHTTPHostHeader(br) if hostName == "" { return } @@ -75,5 +75,5 @@ func handleConn(conn net.Conn) { peeked, _ = br.Peek(br.Buffered()) } - tun.Add(adapters.NewHttp(hostName, peeked, conn)) + tun.Add(adapters.NewHttp(hostName, peeked, method != http.MethodConnect, conn)) } diff --git a/proxy/http/util.go b/proxy/http/util.go deleted file mode 100644 index 090ff7e677..0000000000 --- a/proxy/http/util.go +++ /dev/null @@ -1,77 +0,0 @@ -package http - -import ( - "bufio" - "bytes" - "net/http" -) - -// httpHostHeader returns the HTTP Host header from br without -// consuming any of its bytes. It returns ""if it can't find one. -func httpHostHeader(br *bufio.Reader) (method, host string) { - const maxPeek = 4 << 10 - peekSize := 0 - for { - peekSize++ - if peekSize > maxPeek { - b, _ := br.Peek(br.Buffered()) - return method, httpHostHeaderFromBytes(b) - } - b, err := br.Peek(peekSize) - if n := br.Buffered(); n > peekSize { - b, _ = br.Peek(n) - peekSize = n - } - if len(b) > 0 { - if b[0] < 'A' || b[0] > 'Z' { - // Doesn't look like an HTTP verb - // (GET, POST, etc). - return - } - if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { - req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) - if err != nil { - return - } - if len(req.Header["Host"]) > 1 { - // TODO(bradfitz): what does - // ReadRequest do if there are - // multiple Host headers? - return - } - return req.Method, req.Host - } - } - if err != nil { - return method, httpHostHeaderFromBytes(b) - } - } -} - -var ( - lfHostColon = []byte("\nHost:") - lfhostColon = []byte("\nhost:") - crlf = []byte("\r\n") - lf = []byte("\n") - crlfcrlf = []byte("\r\n\r\n") - lflf = []byte("\n\n") -) - -func httpHostHeaderFromBytes(b []byte) string { - if i := bytes.Index(b, lfHostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) - } - if i := bytes.Index(b, lfhostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) - } - return "" -} - -// untilEOL returns v, truncated before the first '\n' byte, if any. -// The returned slice may include a '\r' at the end. -func untilEOL(v []byte) []byte { - if i := bytes.IndexByte(v, '\n'); i != -1 { - return v[:i] - } - return v -} From 2b87b907aebc814ba82493e62032f616ac573eae Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 13:50:54 +0800 Subject: [PATCH 022/535] New: custom socks5 proxy support --- README.md | 3 ++ adapters/remote/socks5.go | 91 +++++++++++++++++++++++++++++++++++++++ config/config.go | 11 +++-- constant/adapters.go | 3 ++ proxy/listener.go | 3 -- tunnel/tunnel.go | 2 - 6 files changed, 105 insertions(+), 8 deletions(-) create mode 100644 adapters/remote/socks5.go diff --git a/README.md b/README.md index 5b9273156e..b0d330f57b 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ external-controller = 127.0.0.1:8080 ss1 = ss, server1, port, AEAD_CHACHA20_POLY1305, password ss2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password +# name = socks5, server, port +socks = socks5, server1, port + [Proxy Group] # url-test select which proxy will be used by benchmarking speed to a URL. # name = url-test, [proxies], url, interval(second) diff --git a/adapters/remote/socks5.go b/adapters/remote/socks5.go new file mode 100644 index 0000000000..dec9adde68 --- /dev/null +++ b/adapters/remote/socks5.go @@ -0,0 +1,91 @@ +package adapters + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + + C "github.com/Dreamacro/clash/constant" + + "github.com/riobard/go-shadowsocks2/socks" +) + +// Socks5Adapter is a shadowsocks adapter +type Socks5Adapter struct { + conn net.Conn +} + +// Close is used to close connection +func (ss *Socks5Adapter) Close() { + ss.conn.Close() +} + +func (ss *Socks5Adapter) Conn() net.Conn { + return ss.conn +} + +type Socks5 struct { + addr string + name string +} + +func (ss *Socks5) Name() string { + return ss.name +} + +func (ss *Socks5) Type() C.AdapterType { + return C.Socks5 +} + +func (ss *Socks5) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { + c, err := net.Dial("tcp", ss.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error", ss.addr) + } + c.(*net.TCPConn).SetKeepAlive(true) + + if err := ss.sharkHand(addr, c); err != nil { + return nil, err + } + return &Socks5Adapter{conn: c}, nil +} + +func (ss *Socks5) sharkHand(addr *C.Addr, rw io.ReadWriter) error { + buf := make([]byte, socks.MaxAddrLen) + + // VER, CMD, RSV + _, err := rw.Write([]byte{5, 1, 0}) + if err != nil { + return err + } + + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return err + } + + if buf[0] != 5 { + return errors.New("SOCKS version error") + } else if buf[1] != 0 { + return errors.New("SOCKS need auth") + } + + // VER, CMD, RSV, ADDR + if _, err := rw.Write(bytes.Join([][]byte{[]byte{5, 1, 0}, serializesSocksAddr(addr)}, []byte(""))); err != nil { + return err + } + + if _, err := io.ReadFull(rw, buf[:10]); err != nil { + return err + } + + return nil +} + +func NewSocks5(name, addr string) *Socks5 { + return &Socks5{ + addr: addr, + name: name, + } +} diff --git a/config/config.go b/config/config.go index bcd332695c..d77acc616b 100644 --- a/config/config.go +++ b/config/config.go @@ -228,6 +228,14 @@ func (c *Config) parseProxies(cfg *ini.File) error { return err } proxies[key.Name()] = ss + // socks5, server, port + case "socks5": + if len(proxy) < 3 { + continue + } + addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2]) + socks5 := adapters.NewSocks5(key.Name(), addr) + proxies[key.Name()] = socks5 } } @@ -325,19 +333,16 @@ func (c *Config) handleResponseMessage() { log.Errorf("Listening HTTP proxy at %s error", c.general.Port) c.general.Port = 0 } - break case "socks-addr": if event.Payload.(bool) == false { log.Errorf("Listening SOCKS proxy at %s error", c.general.SocksPort) c.general.SocksPort = 0 } - break case "redir-addr": if event.Payload.(bool) == false { log.Errorf("Listening Redir proxy at %s error", c.general.RedirPort) c.general.RedirPort = 0 } - break } } } diff --git a/constant/adapters.go b/constant/adapters.go index 1509570a65..a899d54c91 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -10,6 +10,7 @@ const ( Reject Selector Shadowsocks + Socks5 URLTest ) @@ -42,6 +43,8 @@ func (at AdapterType) String() string { return "Selector" case Shadowsocks: return "Shadowsocks" + case Socks5: + return "Socks5" case URLTest: return "URLTest" default: diff --git a/proxy/listener.go b/proxy/listener.go index e3f6863c43..06e658a327 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -84,17 +84,14 @@ func (l *Listener) process(signal chan<- struct{}) { addr := event.Payload.(string) err := l.updateHTTP(addr) reportCH <- &config.Event{Type: "http-addr", Payload: err == nil} - break case "socks-addr": addr := event.Payload.(string) err := l.updateSocks(addr) reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil} - break case "redir-addr": addr := event.Payload.(string) err := l.updateRedir(addr) reportCH <- &config.Event{Type: "redir-addr", Payload: err == nil} - break } } } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 07abb53202..f83d71bb2f 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -106,10 +106,8 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { switch adapter := localConn.(type) { case *LocalAdapter.HttpAdapter: t.handleHTTP(adapter, remoConn) - break case *LocalAdapter.SocksAdapter: t.handleSOCKS(adapter, remoConn) - break } } From fc4f119049c47b991d63a51b7a65b83fb2b9cb6a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 14:06:50 +0800 Subject: [PATCH 023/535] Fix: Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 87ae04aea5..622afd3242 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME=clash BINDIR=bin GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' -all: linux macos +all: linux macos win64 linux: GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ From ebe1cee6dc3f062fc2703eacb08a4d74a1ad4e46 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 16:18:58 +0800 Subject: [PATCH 024/535] Improve: clean code --- adapters/local/http.go | 92 ++++++----------------------------------- adapters/local/socks.go | 5 +++ adapters/local/util.go | 76 +++++++++++++++++++++++++++++++++- config/config.go | 1 + proxy/http/server.go | 2 +- tunnel/connection.go | 2 +- tunnel/tunnel.go | 2 +- 7 files changed, 97 insertions(+), 83 deletions(-) diff --git a/adapters/local/http.go b/adapters/local/http.go index 574f02bc68..e9d3f62287 100644 --- a/adapters/local/http.go +++ b/adapters/local/http.go @@ -5,12 +5,12 @@ import ( "bytes" "io" "net" - "net/http" "strings" C "github.com/Dreamacro/clash/constant" ) +// PeekedConn handle http connection and buffed HTTP data type PeekedConn struct { net.Conn Peeked []byte @@ -47,26 +47,31 @@ func (c *PeekedConn) Read(p []byte) (n int, err error) { return c.Conn.Read(p) } -type HttpAdapter struct { +// HTTPAdapter is a adapter for HTTP connection +type HTTPAdapter struct { addr *C.Addr conn *PeekedConn } -func (h *HttpAdapter) Close() { +// Close HTTP connection +func (h *HTTPAdapter) Close() { h.conn.Close() } -func (h *HttpAdapter) Addr() *C.Addr { +// Addr return destination address +func (h *HTTPAdapter) Addr() *C.Addr { return h.addr } -func (h *HttpAdapter) Conn() net.Conn { +// Conn return raw net.Conn of HTTP +func (h *HTTPAdapter) Conn() net.Conn { return h.conn } -func NewHttp(host string, peeked []byte, isHTTP bool, conn net.Conn) *HttpAdapter { - return &HttpAdapter{ - addr: parseHttpAddr(host), +// NewHTTP is HTTPAdapter generator +func NewHTTP(host string, peeked []byte, isHTTP bool, conn net.Conn) *HTTPAdapter { + return &HTTPAdapter{ + addr: parseHTTPAddr(host), conn: &PeekedConn{ Peeked: peeked, Conn: conn, @@ -75,74 +80,3 @@ func NewHttp(host string, peeked []byte, isHTTP bool, conn net.Conn) *HttpAdapte }, } } - -// ParserHTTPHostHeader returns the HTTP Host header from br without -// consuming any of its bytes. It returns "" if it can't find one. -func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { - // br := bufio.NewReader(bytes.NewReader(data)) - const maxPeek = 4 << 10 - peekSize := 0 - for { - peekSize++ - if peekSize > maxPeek { - b, _ := br.Peek(br.Buffered()) - return method, httpHostHeaderFromBytes(b) - } - b, err := br.Peek(peekSize) - if n := br.Buffered(); n > peekSize { - b, _ = br.Peek(n) - peekSize = n - } - if len(b) > 0 { - if b[0] < 'A' || b[0] > 'Z' { - // Doesn't look like an HTTP verb - // (GET, POST, etc). - return - } - if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { - req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) - if err != nil { - return - } - if len(req.Header["Host"]) > 1 { - // TODO(bradfitz): what does - // ReadRequest do if there are - // multiple Host headers? - return - } - return req.Method, req.Host - } - } - if err != nil { - return method, httpHostHeaderFromBytes(b) - } - } -} - -var ( - lfHostColon = []byte("\nHost:") - lfhostColon = []byte("\nhost:") - crlf = []byte("\r\n") - lf = []byte("\n") - crlfcrlf = []byte("\r\n\r\n") - lflf = []byte("\n\n") -) - -func httpHostHeaderFromBytes(b []byte) string { - if i := bytes.Index(b, lfHostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) - } - if i := bytes.Index(b, lfhostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) - } - return "" -} - -// untilEOL returns v, truncated before the first '\n' byte, if any. -// The returned slice may include a '\r' at the end. -func untilEOL(v []byte) []byte { - if i := bytes.IndexByte(v, '\n'); i != -1 { - return v[:i] - } - return v -} diff --git a/adapters/local/socks.go b/adapters/local/socks.go index dd1c46238c..2f31cdc59f 100644 --- a/adapters/local/socks.go +++ b/adapters/local/socks.go @@ -7,23 +7,28 @@ import ( "github.com/riobard/go-shadowsocks2/socks" ) +// SocksAdapter is a adapter for socks and redir connection type SocksAdapter struct { conn net.Conn addr *C.Addr } +// Close socks and redir connection func (s *SocksAdapter) Close() { s.conn.Close() } +// Addr return destination address func (s *SocksAdapter) Addr() *C.Addr { return s.addr } +// Conn return raw net.Conn func (s *SocksAdapter) Conn() net.Conn { return s.conn } +// NewSocks is SocksAdapter generator func NewSocks(target socks.Addr, conn net.Conn) *SocksAdapter { return &SocksAdapter{ conn: conn, diff --git a/adapters/local/util.go b/adapters/local/util.go index 0fb204300d..90e9c472da 100644 --- a/adapters/local/util.go +++ b/adapters/local/util.go @@ -1,7 +1,10 @@ package adapters import ( + "bufio" + "bytes" "net" + "net/http" "strconv" C "github.com/Dreamacro/clash/constant" @@ -37,7 +40,7 @@ func parseSocksAddr(target socks.Addr) *C.Addr { } } -func parseHttpAddr(target string) *C.Addr { +func parseHTTPAddr(target string) *C.Addr { host, port, _ := net.SplitHostPort(target) ipAddr, err := net.ResolveIPAddr("ip", host) var resolveIP *net.IP @@ -64,3 +67,74 @@ func parseHttpAddr(target string) *C.Addr { Port: port, } } + +// ParserHTTPHostHeader returns the HTTP Host header from br without +// consuming any of its bytes. It returns "" if it can't find one. +func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { + // br := bufio.NewReader(bytes.NewReader(data)) + const maxPeek = 4 << 10 + peekSize := 0 + for { + peekSize++ + if peekSize > maxPeek { + b, _ := br.Peek(br.Buffered()) + return method, httpHostHeaderFromBytes(b) + } + b, err := br.Peek(peekSize) + if n := br.Buffered(); n > peekSize { + b, _ = br.Peek(n) + peekSize = n + } + if len(b) > 0 { + if b[0] < 'A' || b[0] > 'Z' { + // Doesn't look like an HTTP verb + // (GET, POST, etc). + return + } + if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) + if err != nil { + return + } + if len(req.Header["Host"]) > 1 { + // TODO(bradfitz): what does + // ReadRequest do if there are + // multiple Host headers? + return + } + return req.Method, req.Host + } + } + if err != nil { + return method, httpHostHeaderFromBytes(b) + } + } +} + +var ( + lfHostColon = []byte("\nHost:") + lfhostColon = []byte("\nhost:") + crlf = []byte("\r\n") + lf = []byte("\n") + crlfcrlf = []byte("\r\n\r\n") + lflf = []byte("\n\n") +) + +func httpHostHeaderFromBytes(b []byte) string { + if i := bytes.Index(b, lfHostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) + } + if i := bytes.Index(b, lfhostColon); i != -1 { + return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) + } + return "" +} + +// untilEOL returns v, truncated before the first '\n' byte, if any. +// The returned slice may include a '\r' at the end. +func untilEOL(v []byte) []byte { + if i := bytes.IndexByte(v, '\n'); i != -1 { + return v[:i] + } + return v +} diff --git a/config/config.go b/config/config.go index d77acc616b..94e1a872c0 100644 --- a/config/config.go +++ b/config/config.go @@ -364,6 +364,7 @@ func newConfig() *Config { return config } +// Instance return singleton instance of Config func Instance() *Config { once.Do(func() { config = newConfig() diff --git a/proxy/http/server.go b/proxy/http/server.go index d015a0857c..5dae396070 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -75,5 +75,5 @@ func handleConn(conn net.Conn) { peeked, _ = br.Peek(br.Buffered()) } - tun.Add(adapters.NewHttp(hostName, peeked, method != http.MethodConnect, conn)) + tun.Add(adapters.NewHTTP(hostName, peeked, method != http.MethodConnect, conn)) } diff --git a/tunnel/connection.go b/tunnel/connection.go index 6fb27f187f..ea3d42fd26 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -7,7 +7,7 @@ import ( C "github.com/Dreamacro/clash/constant" ) -func (t *Tunnel) handleHTTP(request *adapters.HttpAdapter, proxy C.ProxyAdapter) { +func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) { conn := newTrafficTrack(proxy.Conn(), t.traffic) // Before we unwrap src and/or dst, copy any buffered data. diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index f83d71bb2f..8d3e577362 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -104,7 +104,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer remoConn.Close() switch adapter := localConn.(type) { - case *LocalAdapter.HttpAdapter: + case *LocalAdapter.HTTPAdapter: t.handleHTTP(adapter, remoConn) case *LocalAdapter.SocksAdapter: t.handleSOCKS(adapter, remoConn) From 26618901d426ca4902237f97dc396c3f9391a8cb Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 Aug 2018 19:35:13 +0800 Subject: [PATCH 025/535] Fix: log api query --- hub/server.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/hub/server.go b/hub/server.go index 2a4f789140..cb15006737 100644 --- a/hub/server.go +++ b/hub/server.go @@ -82,23 +82,18 @@ func traffic(w http.ResponseWriter, r *http.Request) { } } -type GetLogs struct { - Level string `json:"level"` -} - type Log struct { Type string `json:"type"` Payload string `json:"payload"` } func getLogs(w http.ResponseWriter, r *http.Request) { - req := &GetLogs{} - render.DecodeJSON(r.Body, req) - if req.Level == "" { - req.Level = "info" + levelText := r.URL.Query().Get("level") + if levelText == "" { + levelText = "info" } - level, ok := C.LogLevelMapping[req.Level] + level, ok := C.LogLevelMapping[levelText] if !ok { w.WriteHeader(http.StatusBadRequest) render.JSON(w, r, Error{ From ad25a89dd29e4f87d3034afd57aabeba779bfeb3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 14 Aug 2018 18:06:56 +0800 Subject: [PATCH 026/535] Update: README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b0d330f57b..48690e4d33 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,11 @@ - HTTP/HTTPS and SOCKS proxy - Surge like configuration - GeoIP rule support +- Support for Netfilter TCP redirect + +## Discussion + +[Telegram Group](https://t.me/clash_discuss) ## Install From 2a2e61652f789ad267ed2d613c6cff8de285e70e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 26 Aug 2018 22:43:38 +0800 Subject: [PATCH 027/535] Fix: updateConfig api crash --- config/config.go | 18 +++++++++--------- config/utils.go | 9 +++++++++ constant/config.go | 1 + hub/configs.go | 3 +++ 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/config/config.go b/config/config.go index 94e1a872c0..20f7f1d810 100644 --- a/config/config.go +++ b/config/config.go @@ -189,19 +189,19 @@ func (c *Config) UpdateProxy(pc ProxyConfig) { c.general.AllowLan = *pc.AllowLan } - if (pc.AllowLan != nil || pc.Port != nil) && *pc.Port != 0 { - c.general.Port = *pc.Port - c.event <- &Event{Type: "http-addr", Payload: genAddr(*pc.Port, c.general.AllowLan)} + c.general.Port = *or(pc.Port, &c.general.Port) + if c.general.Port != 0 && (pc.AllowLan != nil || pc.Port != nil) { + c.event <- &Event{Type: "http-addr", Payload: genAddr(c.general.Port, c.general.AllowLan)} } - if (pc.AllowLan != nil || pc.SocksPort != nil) && *pc.SocksPort != 0 { - c.general.SocksPort = *pc.SocksPort - c.event <- &Event{Type: "socks-addr", Payload: genAddr(*pc.SocksPort, c.general.AllowLan)} + c.general.SocksPort = *or(pc.SocksPort, &c.general.SocksPort) + if c.general.SocksPort != 0 && (pc.AllowLan != nil || pc.SocksPort != nil) { + c.event <- &Event{Type: "socks-addr", Payload: genAddr(c.general.SocksPort, c.general.AllowLan)} } - if (pc.AllowLan != nil || pc.RedirPort != nil) && *pc.RedirPort != 0 { - c.general.RedirPort = *pc.RedirPort - c.event <- &Event{Type: "redir-addr", Payload: genAddr(*pc.RedirPort, c.general.AllowLan)} + c.general.RedirPort = *or(pc.RedirPort, &c.general.RedirPort) + if c.general.RedirPort != 0 && (pc.AllowLan != nil || pc.RedirPort != nil) { + c.event <- &Event{Type: "redir-addr", Payload: genAddr(c.general.RedirPort, c.general.AllowLan)} } } diff --git a/config/utils.go b/config/utils.go index 793fcfe78e..e196394bbe 100644 --- a/config/utils.go +++ b/config/utils.go @@ -18,3 +18,12 @@ func genAddr(port int, allowLan bool) string { } return fmt.Sprintf("127.0.0.1:%d", port) } + +func or(pointers ...*int) *int { + for _, p := range pointers { + if p != nil { + return p + } + } + return pointers[len(pointers)-1] +} diff --git a/constant/config.go b/constant/config.go index b09375877c..90df19ba63 100644 --- a/constant/config.go +++ b/constant/config.go @@ -23,6 +23,7 @@ type General struct { AllowLan *bool `json:"allow-lan,omitempty"` Port *int `json:"port,omitempty"` SocksPort *int `json:"socks-port,omitempty"` + RedirPort *int `json:"redir-port,omitempty"` LogLevel *string `json:"log-level,omitempty"` } diff --git a/hub/configs.go b/hub/configs.go index 2cbe2e3c65..499648371c 100644 --- a/hub/configs.go +++ b/hub/configs.go @@ -21,6 +21,7 @@ func configRouter() http.Handler { type configSchema struct { Port int `json:"port"` SocksPort int `json:"socket-port"` + RedirPort int `json:"redir-port"` AllowLan bool `json:"allow-lan"` Mode string `json:"mode"` LogLevel string `json:"log-level"` @@ -31,6 +32,7 @@ func getConfigs(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, configSchema{ Port: general.Port, SocksPort: general.SocksPort, + RedirPort: general.RedirPort, AllowLan: general.AllowLan, Mode: general.Mode.String(), LogLevel: general.LogLevel.String(), @@ -87,6 +89,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { AllowLan: general.AllowLan, Port: general.Port, SocksPort: general.SocksPort, + RedirPort: general.RedirPort, }) w.WriteHeader(http.StatusNoContent) From 8ec025b56a91a3aebf6d79df8bcd5c7123900f90 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 27 Aug 2018 00:06:40 +0800 Subject: [PATCH 028/535] Improve: HTTP proxy server handler --- README.md | 2 - adapters/local/http.go | 80 ++++++++++---------------- adapters/local/https.go | 14 +++++ adapters/local/{socks.go => socket.go} | 16 +++--- adapters/local/util.go | 10 +++- proxy/http/server.go | 19 +++--- proxy/redir/tcp.go | 2 +- proxy/socks/tcp.go | 2 +- tunnel/connection.go | 47 ++++++++++++--- tunnel/tunnel.go | 2 +- 10 files changed, 109 insertions(+), 85 deletions(-) create mode 100644 adapters/local/https.go rename adapters/local/{socks.go => socket.go} (51%) diff --git a/README.md b/README.md index 48690e4d33..fc1600e4d0 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,6 @@ FINAL,,Proxy # note: there is two "," [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) -[google/tcpproxy](https://github.com/google/tcpproxy) - ## License [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) diff --git a/adapters/local/http.go b/adapters/local/http.go index e9d3f62287..ddd846118b 100644 --- a/adapters/local/http.go +++ b/adapters/local/http.go @@ -1,56 +1,18 @@ package adapters import ( - "bufio" - "bytes" - "io" "net" + "net/http" "strings" C "github.com/Dreamacro/clash/constant" ) -// PeekedConn handle http connection and buffed HTTP data -type PeekedConn struct { - net.Conn - Peeked []byte - host string - isHTTP bool -} - -func (c *PeekedConn) Read(p []byte) (n int, err error) { - if len(c.Peeked) > 0 { - n = copy(p, c.Peeked) - c.Peeked = c.Peeked[n:] - if len(c.Peeked) == 0 { - c.Peeked = nil - } - return n, nil - } - - // Sometimes firefox just open a socket to process multiple domains in HTTP - // The temporary solution is to return io.EOF when encountering different HOST - if c.isHTTP { - br := bufio.NewReader(bytes.NewReader(p)) - _, hostName := ParserHTTPHostHeader(br) - if hostName != "" { - if !strings.Contains(hostName, ":") { - hostName += ":80" - } - - if hostName != c.host { - return 0, io.EOF - } - } - } - - return c.Conn.Read(p) -} - // HTTPAdapter is a adapter for HTTP connection type HTTPAdapter struct { addr *C.Addr - conn *PeekedConn + conn net.Conn + R *http.Request } // Close HTTP connection @@ -69,14 +31,34 @@ func (h *HTTPAdapter) Conn() net.Conn { } // NewHTTP is HTTPAdapter generator -func NewHTTP(host string, peeked []byte, isHTTP bool, conn net.Conn) *HTTPAdapter { +func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { return &HTTPAdapter{ - addr: parseHTTPAddr(host), - conn: &PeekedConn{ - Peeked: peeked, - Conn: conn, - host: host, - isHTTP: isHTTP, - }, + addr: parseHTTPAddr(request), + R: request, + conn: conn, + } +} + +// RemoveHopByHopHeaders remove hop-by-hop header +func RemoveHopByHopHeaders(header http.Header) { + // Strip hop-by-hop header based on RFC: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 + // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do + + header.Del("Proxy-Connection") + header.Del("Proxy-Authenticate") + header.Del("Proxy-Authorization") + header.Del("TE") + header.Del("Trailers") + header.Del("Transfer-Encoding") + header.Del("Upgrade") + + connections := header.Get("Connection") + header.Del("Connection") + if len(connections) == 0 { + return + } + for _, h := range strings.Split(connections, ",") { + header.Del(strings.TrimSpace(h)) } } diff --git a/adapters/local/https.go b/adapters/local/https.go new file mode 100644 index 0000000000..61d3125586 --- /dev/null +++ b/adapters/local/https.go @@ -0,0 +1,14 @@ +package adapters + +import ( + "net" + "net/http" +) + +// NewHTTPS is HTTPAdapter generator +func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { + return &SocketAdapter{ + addr: parseHTTPAddr(request), + conn: conn, + } +} diff --git a/adapters/local/socks.go b/adapters/local/socket.go similarity index 51% rename from adapters/local/socks.go rename to adapters/local/socket.go index 2f31cdc59f..4143e74641 100644 --- a/adapters/local/socks.go +++ b/adapters/local/socket.go @@ -7,30 +7,30 @@ import ( "github.com/riobard/go-shadowsocks2/socks" ) -// SocksAdapter is a adapter for socks and redir connection -type SocksAdapter struct { +// SocketAdapter is a adapter for socks and redir connection +type SocketAdapter struct { conn net.Conn addr *C.Addr } // Close socks and redir connection -func (s *SocksAdapter) Close() { +func (s *SocketAdapter) Close() { s.conn.Close() } // Addr return destination address -func (s *SocksAdapter) Addr() *C.Addr { +func (s *SocketAdapter) Addr() *C.Addr { return s.addr } // Conn return raw net.Conn -func (s *SocksAdapter) Conn() net.Conn { +func (s *SocketAdapter) Conn() net.Conn { return s.conn } -// NewSocks is SocksAdapter generator -func NewSocks(target socks.Addr, conn net.Conn) *SocksAdapter { - return &SocksAdapter{ +// NewSocket is SocketAdapter generator +func NewSocket(target socks.Addr, conn net.Conn) *SocketAdapter { + return &SocketAdapter{ conn: conn, addr: parseSocksAddr(target), } diff --git a/adapters/local/util.go b/adapters/local/util.go index 90e9c472da..37e47db453 100644 --- a/adapters/local/util.go +++ b/adapters/local/util.go @@ -40,8 +40,12 @@ func parseSocksAddr(target socks.Addr) *C.Addr { } } -func parseHTTPAddr(target string) *C.Addr { - host, port, _ := net.SplitHostPort(target) +func parseHTTPAddr(request *http.Request) *C.Addr { + host := request.URL.Hostname() + port := request.URL.Port() + if port == "" { + port = "80" + } ipAddr, err := net.ResolveIPAddr("ip", host) var resolveIP *net.IP if err == nil { @@ -92,6 +96,7 @@ func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { return } if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { + println(string(b)) req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) if err != nil { return @@ -102,6 +107,7 @@ func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { // multiple Host headers? return } + println(req.Host) return req.Method, req.Host } } diff --git a/proxy/http/server.go b/proxy/http/server.go index 5dae396070..eb11c648a0 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -4,7 +4,6 @@ import ( "bufio" "net" "net/http" - "strings" "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" @@ -56,24 +55,20 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) { func handleConn(conn net.Conn) { br := bufio.NewReader(conn) - method, hostName := adapters.ParserHTTPHostHeader(br) - if hostName == "" { + request, err := http.ReadRequest(br) + if err != nil { + conn.Close() return } - if !strings.Contains(hostName, ":") { - hostName += ":80" - } - - var peeked []byte - if method == http.MethodConnect { + if request.Method == http.MethodConnect { _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) if err != nil { return } - } else if n := br.Buffered(); n > 0 { - peeked, _ = br.Peek(br.Buffered()) + tun.Add(adapters.NewHTTPS(request, conn)) + return } - tun.Add(adapters.NewHTTP(hostName, peeked, method != http.MethodConnect, conn)) + tun.Add(adapters.NewHTTP(request, conn)) } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index 16ccb248a9..3f653dc929 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -58,5 +58,5 @@ func handleRedir(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocks(target, conn)) + tun.Add(adapters.NewSocket(target, conn)) } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 0fe78e29ac..5aa8ccc1a9 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -59,5 +59,5 @@ func handleSocks(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocks(target, conn)) + tun.Add(adapters.NewSocket(target, conn)) } diff --git a/tunnel/connection.go b/tunnel/connection.go index ea3d42fd26..581ca1ace0 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -1,7 +1,9 @@ package tunnel import ( + "bufio" "io" + "net/http" "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" @@ -9,20 +11,47 @@ import ( func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) { conn := newTrafficTrack(proxy.Conn(), t.traffic) + req := request.R + host := req.Host - // Before we unwrap src and/or dst, copy any buffered data. - if wc, ok := request.Conn().(*adapters.PeekedConn); ok && len(wc.Peeked) > 0 { - if _, err := conn.Write(wc.Peeked); err != nil { - return + for { + req.Header.Set("Connection", "close") + req.RequestURI = "" + adapters.RemoveHopByHopHeaders(req.Header) + err := req.Write(conn) + if err != nil { + break } - wc.Peeked = nil - } + br := bufio.NewReader(conn) + resp, err := http.ReadResponse(br, req) + if err != nil { + break + } + adapters.RemoveHopByHopHeaders(resp.Header) + if resp.ContentLength >= 0 { + resp.Header.Set("Proxy-Connection", "keep-alive") + resp.Header.Set("Connection", "keep-alive") + resp.Header.Set("Keep-Alive", "timeout=4") + resp.Close = false + } else { + resp.Close = true + } + resp.Write(request.Conn()) - go io.Copy(request.Conn(), conn) - io.Copy(conn, request.Conn()) + req, err = http.ReadRequest(bufio.NewReader(request.Conn())) + if err != nil { + break + } + + // Sometimes firefox just open a socket to process multiple domains in HTTP + // The temporary solution is close connection when encountering different HOST + if req.Host != host { + break + } + } } -func (t *Tunnel) handleSOCKS(request *adapters.SocksAdapter, proxy C.ProxyAdapter) { +func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) { conn := newTrafficTrack(proxy.Conn(), t.traffic) go io.Copy(request.Conn(), conn) io.Copy(conn, request.Conn()) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 8d3e577362..a3d29f688e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -106,7 +106,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { switch adapter := localConn.(type) { case *LocalAdapter.HTTPAdapter: t.handleHTTP(adapter, remoConn) - case *LocalAdapter.SocksAdapter: + case *LocalAdapter.SocketAdapter: t.handleSOCKS(adapter, remoConn) } } From 613a0c36dd18d4451726d65819ecca40939bd88a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 27 Aug 2018 00:07:57 +0800 Subject: [PATCH 029/535] Update: travis ci with golang 1.11 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4714980bd2..a6adfd5c48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go sudo: false go: - - "1.10" + - "1.11" before_install: - go get -u github.com/golang/dep/cmd/dep install: From a5f2bd3152b93246dcde61793225427649c0181b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 27 Aug 2018 08:50:27 +0800 Subject: [PATCH 030/535] Fix: log format type --- config/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index 20f7f1d810..88c7d86591 100644 --- a/config/config.go +++ b/config/config.go @@ -330,17 +330,17 @@ func (c *Config) handleResponseMessage() { switch event.Type { case "http-addr": if event.Payload.(bool) == false { - log.Errorf("Listening HTTP proxy at %s error", c.general.Port) + log.Errorf("Listening HTTP proxy at %d error", c.general.Port) c.general.Port = 0 } case "socks-addr": if event.Payload.(bool) == false { - log.Errorf("Listening SOCKS proxy at %s error", c.general.SocksPort) + log.Errorf("Listening SOCKS proxy at %d error", c.general.SocksPort) c.general.SocksPort = 0 } case "redir-addr": if event.Payload.(bool) == false { - log.Errorf("Listening Redir proxy at %s error", c.general.RedirPort) + log.Errorf("Listening Redir proxy at %d error", c.general.RedirPort) c.general.RedirPort = 0 } } From f4c51cdb0ef32fb982126f44046c3399ac6379f1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 29 Aug 2018 15:00:12 +0800 Subject: [PATCH 031/535] Improve: Better handling of TCP connections --- adapters/remote/direct.go | 2 +- adapters/remote/shadowsocks.go | 2 +- adapters/remote/socks5.go | 3 +-- adapters/remote/util.go | 7 +++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/adapters/remote/direct.go b/adapters/remote/direct.go index ac46c4a703..9d22f9487f 100644 --- a/adapters/remote/direct.go +++ b/adapters/remote/direct.go @@ -36,7 +36,7 @@ func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { if err != nil { return } - c.(*net.TCPConn).SetKeepAlive(true) + tcpKeepAlive(c) return &DirectAdapter{conn: c}, nil } diff --git a/adapters/remote/shadowsocks.go b/adapters/remote/shadowsocks.go index 6323a89703..07590c9d83 100644 --- a/adapters/remote/shadowsocks.go +++ b/adapters/remote/shadowsocks.go @@ -46,7 +46,7 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro if err != nil { return nil, fmt.Errorf("%s connect error", ss.server) } - c.(*net.TCPConn).SetKeepAlive(true) + tcpKeepAlive(c) c = ss.cipher.StreamConn(c) _, err = c.Write(serializesSocksAddr(addr)) return &ShadowsocksAdapter{conn: c}, err diff --git a/adapters/remote/socks5.go b/adapters/remote/socks5.go index dec9adde68..8d82f18380 100644 --- a/adapters/remote/socks5.go +++ b/adapters/remote/socks5.go @@ -44,8 +44,7 @@ func (ss *Socks5) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { if err != nil { return nil, fmt.Errorf("%s connect error", ss.addr) } - c.(*net.TCPConn).SetKeepAlive(true) - + tcpKeepAlive(c) if err := ss.sharkHand(addr, c); err != nil { return nil, err } diff --git a/adapters/remote/util.go b/adapters/remote/util.go index 02c084bc5c..e8a3500642 100644 --- a/adapters/remote/util.go +++ b/adapters/remote/util.go @@ -84,3 +84,10 @@ func selectFast(in chan interface{}) chan interface{} { return out } + +func tcpKeepAlive(c net.Conn) { + if tcp, ok := c.(*net.TCPConn); ok { + tcp.SetKeepAlive(true) + tcp.SetKeepAlivePeriod(3 * time.Minute) + } +} From f2dbabeaa06a5405062418cfe865290612409427 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 31 Aug 2018 21:24:10 +0800 Subject: [PATCH 032/535] Fix: close connection when response closed --- tunnel/connection.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 581ca1ace0..27a24a4226 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -36,7 +36,10 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) } else { resp.Close = true } - resp.Write(request.Conn()) + err = resp.Write(request.Conn()) + if err != nil || resp.Close { + break + } req, err = http.ReadRequest(bufio.NewReader(request.Conn())) if err != nil { From af13acc17151159c839c1f28900185990f435d5e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 3 Sep 2018 23:52:16 +0800 Subject: [PATCH 033/535] Clean: unused code --- adapters/local/util.go | 75 ------------------------------------------ 1 file changed, 75 deletions(-) diff --git a/adapters/local/util.go b/adapters/local/util.go index 37e47db453..c0c86d1fa6 100644 --- a/adapters/local/util.go +++ b/adapters/local/util.go @@ -1,8 +1,6 @@ package adapters import ( - "bufio" - "bytes" "net" "net/http" "strconv" @@ -71,76 +69,3 @@ func parseHTTPAddr(request *http.Request) *C.Addr { Port: port, } } - -// ParserHTTPHostHeader returns the HTTP Host header from br without -// consuming any of its bytes. It returns "" if it can't find one. -func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) { - // br := bufio.NewReader(bytes.NewReader(data)) - const maxPeek = 4 << 10 - peekSize := 0 - for { - peekSize++ - if peekSize > maxPeek { - b, _ := br.Peek(br.Buffered()) - return method, httpHostHeaderFromBytes(b) - } - b, err := br.Peek(peekSize) - if n := br.Buffered(); n > peekSize { - b, _ = br.Peek(n) - peekSize = n - } - if len(b) > 0 { - if b[0] < 'A' || b[0] > 'Z' { - // Doesn't look like an HTTP verb - // (GET, POST, etc). - return - } - if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 { - println(string(b)) - req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b))) - if err != nil { - return - } - if len(req.Header["Host"]) > 1 { - // TODO(bradfitz): what does - // ReadRequest do if there are - // multiple Host headers? - return - } - println(req.Host) - return req.Method, req.Host - } - } - if err != nil { - return method, httpHostHeaderFromBytes(b) - } - } -} - -var ( - lfHostColon = []byte("\nHost:") - lfhostColon = []byte("\nhost:") - crlf = []byte("\r\n") - lf = []byte("\n") - crlfcrlf = []byte("\r\n\r\n") - lflf = []byte("\n\n") -) - -func httpHostHeaderFromBytes(b []byte) string { - if i := bytes.Index(b, lfHostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):]))) - } - if i := bytes.Index(b, lfhostColon); i != -1 { - return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):]))) - } - return "" -} - -// untilEOL returns v, truncated before the first '\n' byte, if any. -// The returned slice may include a '\r' at the end. -func untilEOL(v []byte) []byte { - if i := bytes.IndexByte(v, '\n'); i != -1 { - return v[:i] - } - return v -} From 834baa9e270f01dfd8337d0b1ffd160602e8dcf9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 6 Sep 2018 10:53:29 +0800 Subject: [PATCH 034/535] =?UTF-8?q?Feature:=20=E2=9C=A8=20add=20vmess=20su?= =?UTF-8?q?pport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gopkg.lock | 70 ++++++++++--- Gopkg.toml | 16 ++- adapters/remote/vmess.go | 93 +++++++++++++++++ common/vmess/aead.go | 115 +++++++++++++++++++++ common/vmess/chunk.go | 103 +++++++++++++++++++ common/vmess/conn.go | 213 +++++++++++++++++++++++++++++++++++++++ common/vmess/user.go | 54 ++++++++++ common/vmess/vmess.go | 106 +++++++++++++++++++ config/config.go | 15 +++ constant/adapters.go | 3 + 10 files changed, 772 insertions(+), 16 deletions(-) create mode 100644 adapters/remote/vmess.go create mode 100644 common/vmess/aead.go create mode 100644 common/vmess/chunk.go create mode 100644 common/vmess/conn.go create mode 100644 common/vmess/user.go create mode 100644 common/vmess/vmess.go diff --git a/Gopkg.lock b/Gopkg.lock index 4dab52be38..638a039aa4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,65 +3,92 @@ [[projects]] branch = "master" + digest = "1:8fa55a6e302771a90a86ceae1ca3c0df4ef15d21092198e8313f61dde9eea963" name = "github.com/Yawning/chacha20" packages = ["."] + pruneopts = "UT" revision = "e3b1f968fc6397b51d963fee8ec8711a47bc0ce8" [[projects]] + digest = "1:444b82bfe35c83bbcaf84e310fb81a1f9ece03edfed586483c869e2c046aef69" name = "github.com/eapache/queue" packages = ["."] + pruneopts = "UT" revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98" version = "v1.1.0" [[projects]] + digest = "1:b9914f85d95a0968bafd1be1908ba29e2eafafd88d6fd13696be42bf5368c380" name = "github.com/go-chi/chi" packages = ["."] - revision = "e83ac2304db3c50cf03d96a2fcd39009d458bc35" - version = "v3.3.2" + pruneopts = "UT" + revision = "b5294d10673813fac8558e7f47242bc9e61b4c25" + version = "v3.3.3" [[projects]] + digest = "1:dfa416a1bb8139f30832543340f972f65c0db9932034cb6a1b42c5ac615a3fb8" name = "github.com/go-chi/cors" packages = ["."] + pruneopts = "UT" revision = "dba6525398619dead495962a916728e7ee2ca322" version = "v1.0.0" [[projects]] + digest = "1:54d7b4b9ab2bb2bae35b55eea900bc8fe15cba05a95fc78bf7fd7b82a9a07afa" name = "github.com/go-chi/render" packages = ["."] + pruneopts = "UT" revision = "3215478343fbc559bd3fc08f7031bb134d6bdad5" version = "v1.0.1" [[projects]] + digest = "1:ce579162ae1341f3e5ab30c0dce767f28b1eb6a81359aad01723f1ba6b4becdf" + name = "github.com/gofrs/uuid" + packages = ["."] + pruneopts = "UT" + revision = "370558f003bfe29580cd0f698d8640daccdcc45c" + version = "v3.1.1" + +[[projects]] + digest = "1:2e8bdbc8a11d716dab1bf66d326285d7e5c92fa4c996c1574ba1153e57534b85" name = "github.com/oschwald/geoip2-golang" packages = ["."] + pruneopts = "UT" revision = "7118115686e16b77967cdbf55d1b944fe14ad312" version = "v1.2.1" [[projects]] + digest = "1:07e8589503b7ec22430dae6eed6f2c17e4249ab245574f4fd0ba8fc9c597d138" name = "github.com/oschwald/maxminddb-golang" packages = ["."] + pruneopts = "UT" revision = "c5bec84d1963260297932a1b7a1753c8420717a7" version = "v1.3.0" [[projects]] + digest = "1:2bfa1a73654feb90893014b04779ce5205f3e19e843c0e32a89ea051d31f12d5" name = "github.com/riobard/go-shadowsocks2" packages = [ "core", "shadowaead", "shadowstream", - "socks" + "socks", ] + pruneopts = "UT" revision = "8346403248229fc7e10d7a259de8e9352a9d8830" version = "v0.1.0" [[projects]] + digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc" name = "github.com/sirupsen/logrus" packages = ["."] - revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" - version = "v1.0.5" + pruneopts = "UT" + revision = "3e01752db0189b9157070a0e1668a620f9a85da2" + version = "v1.0.6" [[projects]] branch = "master" + digest = "1:d33888518d56c3f0cc9009594f56be4faf33ffff358fe10ff8e7e8cccf0e6617" name = "golang.org/x/crypto" packages = [ "chacha20poly1305", @@ -69,35 +96,54 @@ "internal/chacha20", "internal/subtle", "poly1305", - "ssh/terminal" + "ssh/terminal", ] - revision = "a2144134853fc9a27a7b1e3eb4f19f1a76df13c9" + pruneopts = "UT" + revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4" [[projects]] branch = "master" + digest = "1:576f8d82185dc836ec6d10c0e5568dc4ff94e4d9f101d33ed5d6bae0cbba65b2" name = "golang.org/x/sys" packages = [ "cpu", "unix", - "windows" + "windows", ] - revision = "ac767d655b305d4e9612f5f6e33120b9176c4ad4" + pruneopts = "UT" + revision = "ebe1bf3edb3325c393447059974de898d5133eb8" [[projects]] + digest = "1:975a4480c40f2d0b95e1f83d3ec1aa29a2774e80179e08a9a4ba2aab86721b23" name = "gopkg.in/eapache/channels.v1" packages = ["."] + pruneopts = "UT" revision = "47238d5aae8c0fefd518ef2bee46290909cf8263" version = "v1.1.0" [[projects]] + digest = "1:5abd6a22805b1919f6a6bca0ae58b13cef1f3412812f38569978f43ef02743d4" name = "gopkg.in/ini.v1" packages = ["."] - revision = "358ee7663966325963d4e8b2e1fbd570c5195153" - version = "v1.38.1" + pruneopts = "UT" + revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e" + version = "v1.38.2" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "0ccb06b3617c87e75bd650f92adc99e55b93070e0b2a0bc71634270226e125fc" + input-imports = [ + "github.com/go-chi/chi", + "github.com/go-chi/cors", + "github.com/go-chi/render", + "github.com/gofrs/uuid", + "github.com/oschwald/geoip2-golang", + "github.com/riobard/go-shadowsocks2/core", + "github.com/riobard/go-shadowsocks2/socks", + "github.com/sirupsen/logrus", + "golang.org/x/crypto/chacha20poly1305", + "gopkg.in/eapache/channels.v1", + "gopkg.in/ini.v1", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index c381b4d9c8..9b9d252d9b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,6 +1,6 @@ # Gopkg.toml example # -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] @@ -27,7 +27,7 @@ [[constraint]] name = "github.com/go-chi/chi" - version = "3.3.2" + version = "3.3.3" [[constraint]] name = "github.com/go-chi/cors" @@ -37,6 +37,10 @@ name = "github.com/go-chi/render" version = "1.0.1" +[[constraint]] + name = "github.com/gofrs/uuid" + version = "3.1.1" + [[constraint]] name = "github.com/oschwald/geoip2-golang" version = "1.2.1" @@ -47,7 +51,11 @@ [[constraint]] name = "github.com/sirupsen/logrus" - version = "1.0.5" + version = "1.0.6" + +[[constraint]] + branch = "master" + name = "golang.org/x/crypto" [[constraint]] name = "gopkg.in/eapache/channels.v1" @@ -55,7 +63,7 @@ [[constraint]] name = "gopkg.in/ini.v1" - version = "1.38.1" + version = "1.38.2" [prune] go-tests = true diff --git a/adapters/remote/vmess.go b/adapters/remote/vmess.go new file mode 100644 index 0000000000..fafe2ca63f --- /dev/null +++ b/adapters/remote/vmess.go @@ -0,0 +1,93 @@ +package adapters + +import ( + "fmt" + "net" + "strconv" + "strings" + + "github.com/Dreamacro/clash/common/vmess" + C "github.com/Dreamacro/clash/constant" +) + +// VmessAdapter is a vmess adapter +type VmessAdapter struct { + conn net.Conn +} + +// Close is used to close connection +func (v *VmessAdapter) Close() { + v.conn.Close() +} + +func (v *VmessAdapter) Conn() net.Conn { + return v.conn +} + +type Vmess struct { + name string + server string + client *vmess.Client +} + +func (ss *Vmess) Name() string { + return ss.name +} + +func (ss *Vmess) Type() C.AdapterType { + return C.Vmess +} + +func (ss *Vmess) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { + c, err := net.Dial("tcp", ss.server) + if err != nil { + return nil, fmt.Errorf("%s connect error", ss.server) + } + tcpKeepAlive(c) + c = ss.client.New(c, parseVmessAddr(addr)) + return &VmessAdapter{conn: c}, err +} + +func NewVmess(name string, server string, uuid string, alterID uint16, security string) (*Vmess, error) { + security = strings.ToLower(security) + client, err := vmess.NewClient(vmess.Config{ + UUID: uuid, + AlterID: alterID, + Security: security, + }) + if err != nil { + return nil, err + } + return &Vmess{ + name: name, + server: server, + client: client, + }, nil +} + +func parseVmessAddr(info *C.Addr) *vmess.DstAddr { + var addrType byte + var addr []byte + switch info.AddrType { + case C.AtypIPv4: + addrType = byte(vmess.AtypIPv4) + addr = make([]byte, net.IPv4len) + copy(addr[:], info.IP.To4()) + case C.AtypIPv6: + addrType = byte(vmess.AtypIPv6) + addr = make([]byte, net.IPv6len) + copy(addr[:], info.IP.To16()) + case C.AtypDomainName: + addrType = byte(vmess.AtypDomainName) + addr = make([]byte, len(info.Host)+1) + addr[0] = byte(len(info.Host)) + copy(addr[1:], []byte(info.Host)) + } + + port, _ := strconv.Atoi(info.Port) + return &vmess.DstAddr{ + AddrType: addrType, + Addr: addr, + Port: uint(port), + } +} diff --git a/common/vmess/aead.go b/common/vmess/aead.go new file mode 100644 index 0000000000..342f373e0b --- /dev/null +++ b/common/vmess/aead.go @@ -0,0 +1,115 @@ +package vmess + +import ( + "crypto/cipher" + "encoding/binary" + "errors" + "io" +) + +type aeadWriter struct { + io.Writer + cipher.AEAD + nonce [32]byte + count uint16 + iv []byte +} + +func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter { + return &aeadWriter{Writer: w, AEAD: aead, iv: iv} +} + +func (w *aeadWriter) Write(b []byte) (n int, err error) { + buf := bufPool.Get().([]byte) + defer bufPool.Put(buf[:cap(buf)]) + length := len(b) + for { + if length == 0 { + break + } + readLen := chunkSize - w.Overhead() + if length < readLen { + readLen = length + } + payloadBuf := buf[lenSize : lenSize+chunkSize-w.Overhead()] + copy(payloadBuf, b[n:n+readLen]) + + binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen+w.Overhead())) + binary.BigEndian.PutUint16(w.nonce[:2], w.count) + copy(w.nonce[2:], w.iv[2:12]) + + w.Seal(payloadBuf[:0], w.nonce[:w.NonceSize()], payloadBuf[:readLen], nil) + w.count++ + + _, err = w.Writer.Write(buf[:lenSize+readLen+w.Overhead()]) + if err != nil { + break + } + n += readLen + length -= readLen + } + return +} + +type aeadReader struct { + io.Reader + cipher.AEAD + nonce [32]byte + buf []byte + offset int + iv []byte + sizeBuf []byte + count uint16 +} + +func newAEADReader(r io.Reader, aead cipher.AEAD, iv []byte) *aeadReader { + return &aeadReader{Reader: r, AEAD: aead, iv: iv, sizeBuf: make([]byte, lenSize)} +} + +func (r *aeadReader) Read(b []byte) (int, error) { + if r.buf != nil { + n := copy(b, r.buf[r.offset:]) + r.offset += n + if r.offset == len(r.buf) { + bufPool.Put(r.buf[:cap(r.buf)]) + r.buf = nil + } + return n, nil + } + + _, err := io.ReadFull(r.Reader, r.sizeBuf) + if err != nil { + return 0, err + } + + size := int(binary.BigEndian.Uint16(r.sizeBuf)) + if size > maxSize { + return 0, errors.New("Buffer is larger than standard") + } + + buf := bufPool.Get().([]byte) + _, err = io.ReadFull(r.Reader, buf[:size]) + if err != nil { + bufPool.Put(buf[:cap(buf)]) + return 0, err + } + + binary.BigEndian.PutUint16(r.nonce[:2], r.count) + copy(r.nonce[2:], r.iv[2:12]) + + _, err = r.Open(buf[:0], r.nonce[:r.NonceSize()], buf[:size], nil) + r.count++ + if err != nil { + return 0, err + } + realLen := size - r.Overhead() + n := copy(b, buf[:realLen]) + if len(b) >= realLen { + bufPool.Put(buf[:cap(buf)]) + return n, nil + } + + r.offset = n + r.buf = buf[:realLen] + return n, nil +} diff --git a/common/vmess/chunk.go b/common/vmess/chunk.go new file mode 100644 index 0000000000..b396fdd6e6 --- /dev/null +++ b/common/vmess/chunk.go @@ -0,0 +1,103 @@ +package vmess + +import ( + "encoding/binary" + "errors" + "io" + "sync" +) + +const ( + lenSize = 2 + chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 + maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead() +) + +var bufPool = sync.Pool{New: func() interface{} { return make([]byte, maxSize) }} + +type chunkReader struct { + io.Reader + buf []byte + sizeBuf []byte + offset int +} + +func newChunkReader(reader io.Reader) *chunkReader { + return &chunkReader{Reader: reader, sizeBuf: make([]byte, lenSize)} +} + +func newChunkWriter(writer io.WriteCloser) *chunkWriter { + return &chunkWriter{Writer: writer} +} + +func (cr *chunkReader) Read(b []byte) (int, error) { + if cr.buf != nil { + n := copy(b, cr.buf[cr.offset:]) + cr.offset += n + if cr.offset == len(cr.buf) { + bufPool.Put(cr.buf[:cap(cr.buf)]) + cr.buf = nil + } + return n, nil + } + + _, err := io.ReadFull(cr.Reader, cr.sizeBuf) + if err != nil { + return 0, err + } + + size := int(binary.BigEndian.Uint16(cr.sizeBuf)) + if size > maxSize { + return 0, errors.New("Buffer is larger than standard") + } + + if len(b) >= size { + _, err := io.ReadFull(cr.Reader, b[:size]) + if err != nil { + return 0, err + } + + return size, nil + } + + buf := bufPool.Get().([]byte) + _, err = io.ReadFull(cr.Reader, buf[:size]) + if err != nil { + bufPool.Put(buf[:cap(buf)]) + return 0, err + } + n := copy(b, cr.buf[:]) + cr.offset = n + cr.buf = buf[:size] + return n, nil +} + +type chunkWriter struct { + io.Writer +} + +func (cw *chunkWriter) Write(b []byte) (n int, err error) { + buf := bufPool.Get().([]byte) + defer bufPool.Put(buf[:cap(buf)]) + length := len(b) + for { + if length == 0 { + break + } + readLen := chunkSize + if length < chunkSize { + readLen = length + } + payloadBuf := buf[lenSize : lenSize+chunkSize] + copy(payloadBuf, b[n:n+readLen]) + + binary.BigEndian.PutUint16(buf[:lenSize], uint16(readLen)) + _, err = cw.Writer.Write(buf[:lenSize+readLen]) + if err != nil { + break + } + n += readLen + length -= readLen + } + return +} diff --git a/common/vmess/conn.go b/common/vmess/conn.go new file mode 100644 index 0000000000..8a284b2884 --- /dev/null +++ b/common/vmess/conn.go @@ -0,0 +1,213 @@ +package vmess + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/md5" + "encoding/binary" + "errors" + "hash/fnv" + "io" + "math/rand" + "net" + "time" + + "golang.org/x/crypto/chacha20poly1305" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// Conn wrapper a net.Conn with vmess protocol +type Conn struct { + net.Conn + reader io.Reader + writer io.Writer + dst *DstAddr + id *ID + reqBodyIV []byte + reqBodyKey []byte + respBodyIV []byte + respBodyKey []byte + respV byte + security byte + + sent bool + received bool +} + +func (vc *Conn) Write(b []byte) (int, error) { + if vc.sent { + return vc.writer.Write(b) + } + + if err := vc.sendRequest(); err != nil { + return 0, err + } + + vc.sent = true + return vc.writer.Write(b) +} + +func (vc *Conn) Read(b []byte) (int, error) { + if vc.received { + return vc.reader.Read(b) + } + + if err := vc.recvResponse(); err != nil { + return 0, err + } + vc.received = true + return vc.reader.Read(b) +} + +func (vc *Conn) sendRequest() error { + timestamp := make([]byte, 8) + binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UTC().Unix())) + + h := hmac.New(md5.New, vc.id.UUID.Bytes()) + h.Write(timestamp) + _, err := vc.Conn.Write(h.Sum(nil)) + if err != nil { + return err + } + + buf := &bytes.Buffer{} + + // Ver IV Key V Opt + buf.WriteByte(Version) + buf.Write(vc.reqBodyIV[:]) + buf.Write(vc.reqBodyKey[:]) + buf.WriteByte(vc.respV) + buf.WriteByte(OptionChunkStream) + + p := rand.Intn(16) + // P Sec Reserve Cmd + buf.WriteByte(byte(p<<4) | byte(vc.security)) + buf.WriteByte(0) + buf.WriteByte(CommandTCP) + + // Port AddrType Addr + binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port)) + buf.WriteByte(vc.dst.AddrType) + buf.Write(vc.dst.Addr) + + // padding + if p > 0 { + padding := make([]byte, p) + rand.Read(padding) + buf.Write(padding) + } + + fnv1a := fnv.New32a() + fnv1a.Write(buf.Bytes()) + buf.Write(fnv1a.Sum(nil)) + + block, err := aes.NewCipher(vc.id.CmdKey) + if err != nil { + return err + } + + stream := cipher.NewCFBEncrypter(block, hashTimestamp(time.Now().UTC())) + stream.XORKeyStream(buf.Bytes(), buf.Bytes()) + _, err = vc.Conn.Write(buf.Bytes()) + return err +} + +func (vc *Conn) recvResponse() error { + block, err := aes.NewCipher(vc.respBodyKey[:]) + if err != nil { + return err + } + + stream := cipher.NewCFBDecrypter(block, vc.respBodyIV[:]) + buf := make([]byte, 4) + _, err = io.ReadFull(vc.Conn, buf) + if err != nil { + return err + } + stream.XORKeyStream(buf, buf) + + if buf[0] != vc.respV { + return errors.New("unexpected response header") + } + + if buf[2] != 0 { + return errors.New("dynamic port is not supported now") + } + + return nil +} + +func hashTimestamp(t time.Time) []byte { + md5hash := md5.New() + ts := make([]byte, 8) + binary.BigEndian.PutUint64(ts, uint64(t.UTC().Unix())) + md5hash.Write(ts) + md5hash.Write(ts) + md5hash.Write(ts) + md5hash.Write(ts) + return md5hash.Sum(nil) +} + +// newConn return a Conn instance +func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn { + randBytes := make([]byte, 33) + rand.Read(randBytes) + reqBodyIV := make([]byte, 16) + reqBodyKey := make([]byte, 16) + copy(reqBodyIV[:], randBytes[:16]) + copy(reqBodyKey[:], randBytes[16:32]) + respV := randBytes[32] + + respBodyKey := md5.Sum(reqBodyKey[:]) + respBodyIV := md5.Sum(reqBodyIV[:]) + + var writer io.Writer + var reader io.Reader + switch security { + case SecurityNone: + reader = newChunkReader(conn) + writer = newChunkWriter(conn) + case SecurityAES128GCM: + block, _ := aes.NewCipher(reqBodyKey[:]) + aead, _ := cipher.NewGCM(block) + writer = newAEADWriter(conn, aead, reqBodyIV[:]) + + block, _ = aes.NewCipher(respBodyKey[:]) + aead, _ = cipher.NewGCM(block) + reader = newAEADReader(conn, aead, respBodyIV[:]) + case SecurityCHACHA20POLY1305: + key := make([]byte, 32) + t := md5.Sum(reqBodyKey[:]) + copy(key, t[:]) + t = md5.Sum(key[:16]) + copy(key[16:], t[:]) + aead, _ := chacha20poly1305.New(key) + writer = newAEADWriter(conn, aead, reqBodyIV[:]) + + t = md5.Sum(respBodyKey[:]) + copy(key, t[:]) + t = md5.Sum(key[:16]) + copy(key[16:], t[:]) + aead, _ = chacha20poly1305.New(key) + reader = newAEADReader(conn, aead, respBodyIV[:]) + } + + return &Conn{ + Conn: conn, + id: id, + dst: dst, + reqBodyIV: reqBodyIV, + reqBodyKey: reqBodyKey, + respV: respV, + respBodyIV: respBodyIV[:], + respBodyKey: respBodyKey[:], + reader: reader, + writer: writer, + security: security, + } +} diff --git a/common/vmess/user.go b/common/vmess/user.go new file mode 100644 index 0000000000..a1a3f3c25e --- /dev/null +++ b/common/vmess/user.go @@ -0,0 +1,54 @@ +package vmess + +import ( + "bytes" + "crypto/md5" + + "github.com/gofrs/uuid" +) + +// ID cmdKey length +const ( + IDBytesLen = 16 +) + +// The ID of en entity, in the form of a UUID. +type ID struct { + UUID *uuid.UUID + CmdKey []byte +} + +// newID returns an ID with given UUID. +func newID(uuid *uuid.UUID) *ID { + id := &ID{UUID: uuid, CmdKey: make([]byte, IDBytesLen)} + md5hash := md5.New() + md5hash.Write(uuid.Bytes()) + md5hash.Write([]byte("c48619fe-8f02-49e0-b9e9-edf763e17e21")) + md5hash.Sum(id.CmdKey[:0]) + return id +} + +func nextID(u *uuid.UUID) *uuid.UUID { + md5hash := md5.New() + md5hash.Write(u.Bytes()) + md5hash.Write([]byte("16167dc8-16b6-4e6d-b8bb-65dd68113a81")) + var newid uuid.UUID + for { + md5hash.Sum(newid[:0]) + if !bytes.Equal(newid.Bytes(), u.Bytes()) { + return &newid + } + md5hash.Write([]byte("533eff8a-4113-4b10-b5ce-0f5d76b98cd2")) + } +} + +func newAlterIDs(primary *ID, alterIDCount uint16) []*ID { + alterIDs := make([]*ID, alterIDCount) + prevID := primary.UUID + for idx := range alterIDs { + newid := nextID(prevID) + alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]} + prevID = newid + } + return alterIDs +} diff --git a/common/vmess/vmess.go b/common/vmess/vmess.go new file mode 100644 index 0000000000..464b6c66a0 --- /dev/null +++ b/common/vmess/vmess.go @@ -0,0 +1,106 @@ +package vmess + +import ( + "fmt" + "math/rand" + "net" + "runtime" + + "github.com/gofrs/uuid" +) + +// Version of vmess +const Version byte = 1 + +// Request Options +const ( + OptionChunkStream byte = 1 + OptionChunkMasking byte = 4 +) + +// Security type vmess +type Security = byte + +// Cipher types +const ( + SecurityAES128GCM Security = 3 + SecurityCHACHA20POLY1305 Security = 4 + SecurityNone Security = 5 +) + +// CipherMapping return +var CipherMapping = map[string]byte{ + "none": SecurityNone, + "aes-128-gcm": SecurityAES128GCM, + "chacha20-poly1305": SecurityCHACHA20POLY1305, +} + +// Command types +const ( + CommandTCP byte = 1 + CommandUDP byte = 2 +) + +// Addr types +const ( + AtypIPv4 byte = 1 + AtypDomainName byte = 2 + AtypIPv6 byte = 3 +) + +// DstAddr store destination address +type DstAddr struct { + AddrType byte + Addr []byte + Port uint +} + +// Client is vmess connection generator +type Client struct { + user []*ID + uuid *uuid.UUID + security Security +} + +// Config of vmess +type Config struct { + UUID string + AlterID uint16 + Security string +} + +// New return a Conn with net.Conn and DstAddr +func (c *Client) New(conn net.Conn, dst *DstAddr) net.Conn { + r := rand.Intn(len(c.user)) + return newConn(conn, c.user[r], dst, c.security) +} + +// NewClient return Client instance +func NewClient(config Config) (*Client, error) { + uid, err := uuid.FromString(config.UUID) + if err != nil { + return nil, err + } + + var security Security + switch config.Security { + case "aes-128-gcm": + security = SecurityAES128GCM + case "chacha20-poly1305": + security = SecurityCHACHA20POLY1305 + case "none": + security = SecurityNone + case "auto": + security = SecurityCHACHA20POLY1305 + if runtime.GOARCH == "amd64" || runtime.GOARCH == "s390x" || runtime.GOARCH == "arm64" { + security = SecurityAES128GCM + } + default: + return nil, fmt.Errorf("Unknown security type: %s", config.Security) + } + return &Client{ + user: newAlterIDs(newID(&uid), config.AlterID), + uuid: &uid, + security: security, + }, nil +} diff --git a/config/config.go b/config/config.go index 88c7d86591..e40f8b0303 100644 --- a/config/config.go +++ b/config/config.go @@ -236,6 +236,21 @@ func (c *Config) parseProxies(cfg *ini.File) error { addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2]) socks5 := adapters.NewSocks5(key.Name(), addr) proxies[key.Name()] = socks5 + // vmess, server, port, uuid, alterId, security + case "vmess": + if len(proxy) < 6 { + continue + } + addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2]) + alterID, err := strconv.Atoi(proxy[4]) + if err != nil { + return err + } + vmess, err := adapters.NewVmess(key.Name(), addr, proxy[3], uint16(alterID), proxy[5]) + if err != nil { + return err + } + proxies[key.Name()] = vmess } } diff --git a/constant/adapters.go b/constant/adapters.go index a899d54c91..a7fd1557de 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -12,6 +12,7 @@ const ( Shadowsocks Socks5 URLTest + Vmess ) type ProxyAdapter interface { @@ -47,6 +48,8 @@ func (at AdapterType) String() string { return "Socks5" case URLTest: return "URLTest" + case Vmess: + return "Vmess" default: return "Unknow" } From fcb46e77069445ab00e12eb870837464f151abe1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 8 Sep 2018 19:53:24 +0800 Subject: [PATCH 035/535] Feature: support vmess tls mode --- adapters/remote/vmess.go | 4 +++- common/vmess/vmess.go | 11 +++++++++++ config/config.go | 3 ++- config/utils.go | 17 +++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/adapters/remote/vmess.go b/adapters/remote/vmess.go index fafe2ca63f..e98828c330 100644 --- a/adapters/remote/vmess.go +++ b/adapters/remote/vmess.go @@ -48,16 +48,18 @@ func (ss *Vmess) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { return &VmessAdapter{conn: c}, err } -func NewVmess(name string, server string, uuid string, alterID uint16, security string) (*Vmess, error) { +func NewVmess(name string, server string, uuid string, alterID uint16, security string, option map[string]string) (*Vmess, error) { security = strings.ToLower(security) client, err := vmess.NewClient(vmess.Config{ UUID: uuid, AlterID: alterID, Security: security, + TLS: option["tls"] == "true", }) if err != nil { return nil, err } + return &Vmess{ name: name, server: server, diff --git a/common/vmess/vmess.go b/common/vmess/vmess.go index 464b6c66a0..8b294b94b5 100644 --- a/common/vmess/vmess.go +++ b/common/vmess/vmess.go @@ -1,6 +1,7 @@ package vmess import ( + "crypto/tls" "fmt" "math/rand" "net" @@ -35,6 +36,10 @@ var CipherMapping = map[string]byte{ "chacha20-poly1305": SecurityCHACHA20POLY1305, } +var tlsConfig = &tls.Config{ + InsecureSkipVerify: true, +} + // Command types const ( CommandTCP byte = 1 @@ -60,6 +65,7 @@ type Client struct { user []*ID uuid *uuid.UUID security Security + tls bool } // Config of vmess @@ -67,11 +73,15 @@ type Config struct { UUID string AlterID uint16 Security string + TLS bool } // New return a Conn with net.Conn and DstAddr func (c *Client) New(conn net.Conn, dst *DstAddr) net.Conn { r := rand.Intn(len(c.user)) + if c.tls { + conn = tls.Client(conn, tlsConfig) + } return newConn(conn, c.user[r], dst, c.security) } @@ -102,5 +112,6 @@ func NewClient(config Config) (*Client, error) { user: newAlterIDs(newID(&uid), config.AlterID), uuid: &uid, security: security, + tls: config.TLS, }, nil } diff --git a/config/config.go b/config/config.go index e40f8b0303..02a9c105f1 100644 --- a/config/config.go +++ b/config/config.go @@ -246,7 +246,8 @@ func (c *Config) parseProxies(cfg *ini.File) error { if err != nil { return err } - vmess, err := adapters.NewVmess(key.Name(), addr, proxy[3], uint16(alterID), proxy[5]) + option := parseOptions(6, proxy...) + vmess, err := adapters.NewVmess(key.Name(), addr, proxy[3], uint16(alterID), proxy[5], option) if err != nil { return err } diff --git a/config/utils.go b/config/utils.go index e196394bbe..b0cec2453e 100644 --- a/config/utils.go +++ b/config/utils.go @@ -27,3 +27,20 @@ func or(pointers ...*int) *int { } return pointers[len(pointers)-1] } + +func parseOptions(startIdx int, params ...string) map[string]string { + mapping := make(map[string]string) + if len(params) <= startIdx { + return mapping + } + + for _, option := range params[startIdx:] { + pair := strings.SplitN(option, "=", 2) + if len(pair) != 2 { + continue + } + + mapping[strings.Trim(pair[0], " ")] = strings.Trim(pair[1], " ") + } + return mapping +} From 4082a2c7000105c5b965e06a2a6846f0df795ca1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 9 Sep 2018 15:01:46 +0800 Subject: [PATCH 036/535] Improve: add DOMAIN rule --- config/config.go | 2 ++ constant/rule.go | 5 ++++- rules/domian.go | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 rules/domian.go diff --git a/config/config.go b/config/config.go index 02a9c105f1..847c232703 100644 --- a/config/config.go +++ b/config/config.go @@ -322,6 +322,8 @@ func (c *Config) parseRules(cfg *ini.File) error { } rule = trimArr(rule) switch rule[0] { + case "DOMAIN": + rules = append(rules, R.NewDomain(rule[1], rule[2])) case "DOMAIN-SUFFIX": rules = append(rules, R.NewDomainSuffix(rule[1], rule[2])) case "DOMAIN-KEYWORD": diff --git a/constant/rule.go b/constant/rule.go index 0acdcc3917..db449c7e64 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -2,7 +2,8 @@ package constant // Rule Type const ( - DomainSuffix RuleType = iota + Domain RuleType = iota + DomainSuffix DomainKeyword GEOIP IPCIDR @@ -13,6 +14,8 @@ type RuleType int func (rt RuleType) String() string { switch rt { + case Domain: + return "Domain" case DomainSuffix: return "DomainSuffix" case DomainKeyword: diff --git a/rules/domian.go b/rules/domian.go new file mode 100644 index 0000000000..a836ca67b3 --- /dev/null +++ b/rules/domian.go @@ -0,0 +1,36 @@ +package rules + +import ( + C "github.com/Dreamacro/clash/constant" +) + +type Domain struct { + domain string + adapter string +} + +func (d *Domain) RuleType() C.RuleType { + return C.Domain +} + +func (d *Domain) IsMatch(addr *C.Addr) bool { + if addr.AddrType != C.AtypDomainName { + return false + } + return addr.Host == d.domain +} + +func (d *Domain) Adapter() string { + return d.adapter +} + +func (d *Domain) Payload() string { + return d.domain +} + +func NewDomain(domain string, adapter string) *Domain { + return &Domain{ + domain: domain, + adapter: adapter, + } +} From 5d34cba6815cdc6df98097d0c96e2bb63f985eb0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 16 Sep 2018 23:02:32 +0800 Subject: [PATCH 037/535] Feature: add shadowsocks simple-obfs support --- adapters/remote/shadowsocks.go | 37 +++++-- common/simple-obfs/http.go | 88 +++++++++++++++ common/simple-obfs/tls.go | 190 +++++++++++++++++++++++++++++++++ config/config.go | 3 +- 4 files changed, 310 insertions(+), 8 deletions(-) create mode 100644 common/simple-obfs/http.go create mode 100644 common/simple-obfs/tls.go diff --git a/adapters/remote/shadowsocks.go b/adapters/remote/shadowsocks.go index 07590c9d83..47035c6056 100644 --- a/adapters/remote/shadowsocks.go +++ b/adapters/remote/shadowsocks.go @@ -9,6 +9,7 @@ import ( C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/common/simple-obfs" "github.com/riobard/go-shadowsocks2/core" "github.com/riobard/go-shadowsocks2/socks" ) @@ -28,9 +29,11 @@ func (ss *ShadowsocksAdapter) Conn() net.Conn { } type ShadowSocks struct { - server string - name string - cipher core.Cipher + server string + name string + obfs string + obfsHost string + cipher core.Cipher } func (ss *ShadowSocks) Name() string { @@ -47,22 +50,42 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro return nil, fmt.Errorf("%s connect error", ss.server) } tcpKeepAlive(c) + switch ss.obfs { + case "tls": + c = obfs.NewTLSObfs(c, ss.obfsHost) + case "http": + _, port, _ := net.SplitHostPort(ss.server) + c = obfs.NewHTTPObfs(c, ss.obfsHost, port) + } c = ss.cipher.StreamConn(c) _, err = c.Write(serializesSocksAddr(addr)) return &ShadowsocksAdapter{conn: c}, err } -func NewShadowSocks(name string, ssURL string) (*ShadowSocks, error) { +func NewShadowSocks(name string, ssURL string, option map[string]string) (*ShadowSocks, error) { var key []byte server, cipher, password, _ := parseURL(ssURL) ciph, err := core.PickCipher(cipher, key, password) if err != nil { return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } + + obfs := "" + obfsHost := "bing.com" + if value, ok := option["obfs"]; ok { + obfs = value + } + + if value, ok := option["obfs-host"]; ok { + obfsHost = value + } + return &ShadowSocks{ - server: server, - name: name, - cipher: ciph, + server: server, + name: name, + cipher: ciph, + obfs: obfs, + obfsHost: obfsHost, }, nil } diff --git a/common/simple-obfs/http.go b/common/simple-obfs/http.go new file mode 100644 index 0000000000..4c7c60dd64 --- /dev/null +++ b/common/simple-obfs/http.go @@ -0,0 +1,88 @@ +package obfs + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "math/rand" + "net" + "net/http" +) + +// HTTPObfs is shadowsocks http simple-obfs implementation +type HTTPObfs struct { + net.Conn + host string + port string + buf []byte + offset int + firstRequest bool + firstResponse bool +} + +func (ho *HTTPObfs) Read(b []byte) (int, error) { + if ho.buf != nil { + n := copy(b, ho.buf[ho.offset:]) + ho.offset += n + if ho.offset == len(ho.buf) { + ho.buf = nil + } + return n, nil + } + + if ho.firstResponse { + buf := bufPool.Get().([]byte) + n, err := ho.Conn.Read(buf) + if err != nil { + bufPool.Put(buf[:cap(buf)]) + return 0, err + } + idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) + if idx == -1 { + bufPool.Put(buf[:cap(buf)]) + return 0, io.EOF + } + ho.firstResponse = false + length := n - (idx + 4) + n = copy(b, buf[idx+4:n]) + if length > n { + ho.buf = buf[:idx+4+length] + ho.offset = idx + 4 + n + } else { + bufPool.Put(buf[:cap(buf)]) + } + return n, nil + } + return ho.Conn.Read(b) +} + +func (ho *HTTPObfs) Write(b []byte) (int, error) { + if ho.firstRequest { + randBytes := make([]byte, 16) + rand.Read(randBytes) + req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:])) + req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2)) + req.Header.Set("Upgrade", "websocket") + req.Header.Set("Connection", "Upgrade") + req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) + req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) + req.ContentLength = int64(len(b)) + err := req.Write(ho.Conn) + ho.firstRequest = false + return len(b), err + } + + return ho.Conn.Write(b) +} + +// NewHTTPObfs return a HTTPObfs +func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn { + return &HTTPObfs{ + Conn: conn, + firstRequest: true, + firstResponse: true, + host: host, + port: port, + } +} diff --git a/common/simple-obfs/tls.go b/common/simple-obfs/tls.go new file mode 100644 index 0000000000..6cb526276c --- /dev/null +++ b/common/simple-obfs/tls.go @@ -0,0 +1,190 @@ +package obfs + +import ( + "bytes" + "encoding/binary" + "io" + "math/rand" + "net" + "sync" + "time" +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +var bufPool = sync.Pool{New: func() interface{} { return make([]byte, 2048) }} + +// TLSObfs is shadowsocks tls simple-obfs implementation +type TLSObfs struct { + net.Conn + server string + remain int + firstRequest bool + firstResponse bool +} + +func (to *TLSObfs) read(b []byte, discardN int) (int, error) { + buf := bufPool.Get().([]byte) + _, err := io.ReadFull(to.Conn, buf[:discardN]) + if err != nil { + return 0, err + } + bufPool.Put(buf[:cap(buf)]) + + sizeBuf := make([]byte, 2) + _, err = io.ReadFull(to.Conn, sizeBuf) + if err != nil { + return 0, nil + } + + length := int(binary.BigEndian.Uint16(sizeBuf)) + if length > len(b) { + n, err := to.Conn.Read(b) + if err != nil { + return n, err + } + to.remain = length - n + return n, nil + } + + return io.ReadFull(to.Conn, b[:length]) +} + +func (to *TLSObfs) Read(b []byte) (int, error) { + if to.remain > 0 { + length := to.remain + if length > len(b) { + length = len(b) + } + + n, err := io.ReadFull(to.Conn, b[:length]) + to.remain -= n + return n, err + } + + if to.firstResponse { + // type + ver + lensize + 91 = 96 + // type + ver + lensize + 1 = 6 + // type + ver = 3 + to.firstResponse = false + return to.read(b, 105) + } + + // type + ver = 3 + return to.read(b, 3) +} + +func (to *TLSObfs) Write(b []byte) (int, error) { + if to.firstRequest { + helloMsg := makeClientHelloMsg(b, to.server) + _, err := to.Conn.Write(helloMsg) + to.firstRequest = false + return len(b), err + } + + size := bufPool.Get().([]byte) + binary.BigEndian.PutUint16(size[:2], uint16(len(b))) + + buf := &bytes.Buffer{} + buf.Write([]byte{0x17, 0x03, 0x03}) + buf.Write(size[:2]) + buf.Write(b) + _, err := to.Conn.Write(buf.Bytes()) + bufPool.Put(size[:cap(size)]) + return len(b), err +} + +// NewTLSObfs return a SimpleObfs +func NewTLSObfs(conn net.Conn, server string) net.Conn { + return &TLSObfs{ + Conn: conn, + server: server, + firstRequest: true, + firstResponse: true, + } +} + +func makeClientHelloMsg(data []byte, server string) []byte { + random := make([]byte, 32) + sessionID := make([]byte, 32) + size := make([]byte, 2) + rand.Read(random) + rand.Read(sessionID) + + buf := &bytes.Buffer{} + + // handshake, TLS 1.0 version, length + buf.WriteByte(22) + buf.Write([]byte{0x03, 0x01}) + length := uint16(212 + len(data) + len(server)) + buf.WriteByte(byte(length >> 8)) + buf.WriteByte(byte(length & 0xff)) + + // clientHello, length, TLS 1.2 version + buf.WriteByte(1) + binary.BigEndian.PutUint16(size, uint16(208+len(data)+len(server))) + buf.WriteByte(0) + buf.Write(size) + buf.Write([]byte{0x03, 0x03}) + + // random, sid len, sid + buf.Write(random) + buf.WriteByte(32) + buf.Write(sessionID) + + // cipher suites + buf.Write([]byte{0x00, 0x38}) + buf.Write([]byte{ + 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, + 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, + 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, + 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff, + }) + + // compression + buf.Write([]byte{0x01, 0x00}) + + // extension length + binary.BigEndian.PutUint16(size, uint16(79+len(data)+len(server))) + buf.Write(size) + + // session ticket + buf.Write([]byte{0x00, 0x23}) + binary.BigEndian.PutUint16(size, uint16(len(data))) + buf.Write(size) + buf.Write(data) + + // server name + buf.Write([]byte{0x00, 0x00}) + binary.BigEndian.PutUint16(size, uint16(len(server)+5)) + buf.Write(size) + binary.BigEndian.PutUint16(size, uint16(len(server)+3)) + buf.Write(size) + buf.WriteByte(0) + binary.BigEndian.PutUint16(size, uint16(len(server))) + buf.Write(size) + buf.Write([]byte(server)) + + // ec_point + buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02}) + + // groups + buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18}) + + // signature + buf.Write([]byte{ + 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, + 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, + 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, + }) + + // encrypt then mac + buf.Write([]byte{0x00, 0x16, 0x00, 0x00}) + + // extended master secret + buf.Write([]byte{0x00, 0x17, 0x00, 0x00}) + + return buf.Bytes() +} diff --git a/config/config.go b/config/config.go index 847c232703..47d68935e2 100644 --- a/config/config.go +++ b/config/config.go @@ -223,7 +223,8 @@ func (c *Config) parseProxies(cfg *ini.File) error { continue } ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2]) - ss, err := adapters.NewShadowSocks(key.Name(), ssURL) + option := parseOptions(5, proxy...) + ss, err := adapters.NewShadowSocks(key.Name(), ssURL, option) if err != nil { return err } From 0724d4ea527225a1961a60f16d37586decd6058d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 16 Sep 2018 23:08:04 +0800 Subject: [PATCH 038/535] Update: README.md --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fc1600e4d0..c2153e9115 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ - HTTP/HTTPS and SOCKS proxy - Surge like configuration - GeoIP rule support +- Support Vmess/Shadowsocks/Socks5 - Support for Netfilter TCP redirect ## Discussion @@ -77,14 +78,18 @@ redir-port = 7892 external-controller = 127.0.0.1:8080 [Proxy] -# name = ss, server, port, cipher, password +# name = ss, server, port, cipher, password(, obfs=tls/http, obfs-host=bing.com) # The types of cipher are consistent with go-shadowsocks2 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 ss1 = ss, server1, port, AEAD_CHACHA20_POLY1305, password ss2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password +# name = vmess, server, port, uuid, alterId, cipher(, tls=true) +# cipher support auto/aes-128-gcm/chacha20-poly1305/none +vmess1 = vmess, server, port, uuid, 32, auto, tls=true + # name = socks5, server, port -socks = socks5, server1, port +socks = socks5, server, port [Proxy Group] # url-test select which proxy will be used by benchmarking speed to a URL. @@ -108,6 +113,8 @@ FINAL,,Proxy # note: there is two "," [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) +[v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) + ## License [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) From 1d0d5667fd6b2626acc395fa8696bcfc88d66f76 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 16 Sep 2018 23:35:37 +0800 Subject: [PATCH 039/535] Fix: Rule with ipv6 address --- config/config.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index 47d68935e2..ea83c4d613 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config import ( + "bufio" "fmt" "os" "strconv" @@ -74,7 +75,10 @@ func (c *Config) readConfig() (*ini.File, error) { return nil, err } return ini.LoadSources( - ini.LoadOptions{AllowBooleanKeys: true}, + ini.LoadOptions{ + AllowBooleanKeys: true, + UnparseableSections: []string{"Rule"}, + }, C.ConfigPath, ) } @@ -316,8 +320,14 @@ func (c *Config) parseRules(cfg *ini.File) error { rulesConfig := cfg.Section("Rule") // parse rules - for _, key := range rulesConfig.Keys() { - rule := strings.Split(key.Name(), ",") + reader := bufio.NewReader(strings.NewReader(rulesConfig.Body())) + for { + line, _, err := reader.ReadLine() + if err != nil { + break + } + + rule := strings.Split(string(line), ",") if len(rule) < 3 { continue } From 0caa8e05a32e3c92b8b09070485817221bfa6af5 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 17 Sep 2018 00:15:58 +0800 Subject: [PATCH 040/535] Improve: better relay copies --- tunnel/connection.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 27a24a4226..87d85d3f31 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -3,6 +3,7 @@ package tunnel import ( "bufio" "io" + "net" "net/http" "github.com/Dreamacro/clash/adapters/local" @@ -56,6 +57,18 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) { conn := newTrafficTrack(proxy.Conn(), t.traffic) - go io.Copy(request.Conn(), conn) - io.Copy(conn, request.Conn()) + relay(request.Conn(), conn) +} + +// relay copies between left and right bidirectionally. +func relay(leftConn, rightConn net.Conn) { + ch := make(chan error) + + go func() { + _, err := io.Copy(leftConn, rightConn) + ch <- err + }() + + io.Copy(rightConn, leftConn) + <-ch } From eb778ad6e28f35c1b035f771b953d85944a18b22 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 21 Sep 2018 11:33:29 +0800 Subject: [PATCH 041/535] Improve: cleanup code --- adapters/remote/shadowsocks.go | 7 +++---- adapters/remote/socks5.go | 6 +++--- config/config.go | 9 +++++++-- tunnel/connection.go | 3 +++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/adapters/remote/shadowsocks.go b/adapters/remote/shadowsocks.go index 47035c6056..919a93930f 100644 --- a/adapters/remote/shadowsocks.go +++ b/adapters/remote/shadowsocks.go @@ -7,9 +7,9 @@ import ( "net/url" "strconv" + "github.com/Dreamacro/clash/common/simple-obfs" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/common/simple-obfs" "github.com/riobard/go-shadowsocks2/core" "github.com/riobard/go-shadowsocks2/socks" ) @@ -63,9 +63,8 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro } func NewShadowSocks(name string, ssURL string, option map[string]string) (*ShadowSocks, error) { - var key []byte server, cipher, password, _ := parseURL(ssURL) - ciph, err := core.PickCipher(cipher, key, password) + ciph, err := core.PickCipher(cipher, nil, password) if err != nil { return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } @@ -120,5 +119,5 @@ func serializesSocksAddr(addr *C.Addr) []byte { host := addr.IP.To16() buf = [][]byte{{aType}, host, port} } - return bytes.Join(buf, []byte("")) + return bytes.Join(buf, nil) } diff --git a/adapters/remote/socks5.go b/adapters/remote/socks5.go index 8d82f18380..5be0638782 100644 --- a/adapters/remote/socks5.go +++ b/adapters/remote/socks5.go @@ -45,13 +45,13 @@ func (ss *Socks5) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { return nil, fmt.Errorf("%s connect error", ss.addr) } tcpKeepAlive(c) - if err := ss.sharkHand(addr, c); err != nil { + if err := ss.shakeHand(addr, c); err != nil { return nil, err } return &Socks5Adapter{conn: c}, nil } -func (ss *Socks5) sharkHand(addr *C.Addr, rw io.ReadWriter) error { +func (ss *Socks5) shakeHand(addr *C.Addr, rw io.ReadWriter) error { buf := make([]byte, socks.MaxAddrLen) // VER, CMD, RSV @@ -71,7 +71,7 @@ func (ss *Socks5) sharkHand(addr *C.Addr, rw io.ReadWriter) error { } // VER, CMD, RSV, ADDR - if _, err := rw.Write(bytes.Join([][]byte{[]byte{5, 1, 0}, serializesSocksAddr(addr)}, []byte(""))); err != nil { + if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(addr)}, []byte(""))); err != nil { return err } diff --git a/config/config.go b/config/config.go index ea83c4d613..0fb589a277 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,7 @@ package config import ( "bufio" "fmt" + "net/url" "os" "strconv" "strings" @@ -226,9 +227,13 @@ func (c *Config) parseProxies(cfg *ini.File) error { if len(proxy) < 5 { continue } - ssURL := fmt.Sprintf("ss://%s:%s@%s:%s", proxy[3], proxy[4], proxy[1], proxy[2]) + ssURL := url.URL{ + Scheme: "ss", + User: url.UserPassword(proxy[3], proxy[4]), + Host: fmt.Sprintf("%s:%s", proxy[1], proxy[2]), + } option := parseOptions(5, proxy...) - ss, err := adapters.NewShadowSocks(key.Name(), ssURL, option) + ss, err := adapters.NewShadowSocks(key.Name(), ssURL.String(), option) if err != nil { return err } diff --git a/tunnel/connection.go b/tunnel/connection.go index 87d85d3f31..842927a12d 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -5,6 +5,7 @@ import ( "io" "net" "net/http" + "time" "github.com/Dreamacro/clash/adapters/local" C "github.com/Dreamacro/clash/constant" @@ -66,9 +67,11 @@ func relay(leftConn, rightConn net.Conn) { go func() { _, err := io.Copy(leftConn, rightConn) + leftConn.SetReadDeadline(time.Now()) ch <- err }() io.Copy(rightConn, leftConn) + rightConn.SetReadDeadline(time.Now()) <-ch } From 3e68faecb2c0c73f3cd78ca57307e76e38f07065 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 21 Sep 2018 15:27:51 +0800 Subject: [PATCH 042/535] Improve: add rc4-md5 and chacha20 supported with the fork --- Gopkg.lock | 36 +++++++++++++++++----------------- Gopkg.toml | 8 ++++---- adapters/local/socket.go | 2 +- adapters/local/util.go | 2 +- adapters/remote/shadowsocks.go | 4 ++-- adapters/remote/socks5.go | 2 +- proxy/redir/tcp_darwin.go | 2 +- proxy/redir/tcp_linux.go | 2 +- proxy/redir/tcp_windows.go | 2 +- proxy/socks/tcp.go | 2 +- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 638a039aa4..394ed3adf6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,19 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + digest = "1:d21e998147c1c8cd727bd34148f373d3faa46fd8669c389b53c4eaabbe4eb2b3" + name = "github.com/Dreamacro/go-shadowsocks2" + packages = [ + "core", + "shadowaead", + "shadowstream", + "socks", + ] + pruneopts = "UT" + revision = "1c1fd6c192eb76261ea3ccd80e3b141b25f20db4" + version = "v0.1.1" + [[projects]] branch = "master" digest = "1:8fa55a6e302771a90a86ceae1ca3c0df4ef15d21092198e8313f61dde9eea963" @@ -65,19 +78,6 @@ revision = "c5bec84d1963260297932a1b7a1753c8420717a7" version = "v1.3.0" -[[projects]] - digest = "1:2bfa1a73654feb90893014b04779ce5205f3e19e843c0e32a89ea051d31f12d5" - name = "github.com/riobard/go-shadowsocks2" - packages = [ - "core", - "shadowaead", - "shadowstream", - "socks", - ] - pruneopts = "UT" - revision = "8346403248229fc7e10d7a259de8e9352a9d8830" - version = "v0.1.0" - [[projects]] digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc" name = "github.com/sirupsen/logrus" @@ -99,11 +99,11 @@ "ssh/terminal", ] pruneopts = "UT" - revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4" + revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" [[projects]] branch = "master" - digest = "1:576f8d82185dc836ec6d10c0e5568dc4ff94e4d9f101d33ed5d6bae0cbba65b2" + digest = "1:ddbafa32d1899456edbf7a64aec7afe5aa287b840e6a12b996f8a8425c1d9a6a" name = "golang.org/x/sys" packages = [ "cpu", @@ -111,7 +111,7 @@ "windows", ] pruneopts = "UT" - revision = "ebe1bf3edb3325c393447059974de898d5133eb8" + revision = "d641721ec2dead6fe5ca284096fe4b1fcd49e427" [[projects]] digest = "1:975a4480c40f2d0b95e1f83d3ec1aa29a2774e80179e08a9a4ba2aab86721b23" @@ -133,13 +133,13 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/Dreamacro/go-shadowsocks2/core", + "github.com/Dreamacro/go-shadowsocks2/socks", "github.com/go-chi/chi", "github.com/go-chi/cors", "github.com/go-chi/render", "github.com/gofrs/uuid", "github.com/oschwald/geoip2-golang", - "github.com/riobard/go-shadowsocks2/core", - "github.com/riobard/go-shadowsocks2/socks", "github.com/sirupsen/logrus", "golang.org/x/crypto/chacha20poly1305", "gopkg.in/eapache/channels.v1", diff --git a/Gopkg.toml b/Gopkg.toml index 9b9d252d9b..799ede7d16 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -25,6 +25,10 @@ # unused-packages = true +[[constraint]] + name = "github.com/Dreamacro/go-shadowsocks2" + version = "0.1.1" + [[constraint]] name = "github.com/go-chi/chi" version = "3.3.3" @@ -45,10 +49,6 @@ name = "github.com/oschwald/geoip2-golang" version = "1.2.1" -[[constraint]] - name = "github.com/riobard/go-shadowsocks2" - version = "0.1.0" - [[constraint]] name = "github.com/sirupsen/logrus" version = "1.0.6" diff --git a/adapters/local/socket.go b/adapters/local/socket.go index 4143e74641..ff9b09c3e1 100644 --- a/adapters/local/socket.go +++ b/adapters/local/socket.go @@ -4,7 +4,7 @@ import ( "net" C "github.com/Dreamacro/clash/constant" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/socks" ) // SocketAdapter is a adapter for socks and redir connection diff --git a/adapters/local/util.go b/adapters/local/util.go index c0c86d1fa6..0a713421e5 100644 --- a/adapters/local/util.go +++ b/adapters/local/util.go @@ -6,7 +6,7 @@ import ( "strconv" C "github.com/Dreamacro/clash/constant" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/socks" ) func parseSocksAddr(target socks.Addr) *C.Addr { diff --git a/adapters/remote/shadowsocks.go b/adapters/remote/shadowsocks.go index 919a93930f..8c0a0987a3 100644 --- a/adapters/remote/shadowsocks.go +++ b/adapters/remote/shadowsocks.go @@ -10,8 +10,8 @@ import ( "github.com/Dreamacro/clash/common/simple-obfs" C "github.com/Dreamacro/clash/constant" - "github.com/riobard/go-shadowsocks2/core" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/core" + "github.com/Dreamacro/go-shadowsocks2/socks" ) // ShadowsocksAdapter is a shadowsocks adapter diff --git a/adapters/remote/socks5.go b/adapters/remote/socks5.go index 5be0638782..5464dd981d 100644 --- a/adapters/remote/socks5.go +++ b/adapters/remote/socks5.go @@ -9,7 +9,7 @@ import ( C "github.com/Dreamacro/clash/constant" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/socks" ) // Socks5Adapter is a shadowsocks adapter diff --git a/proxy/redir/tcp_darwin.go b/proxy/redir/tcp_darwin.go index 3fe7803f5a..f032a03d1e 100644 --- a/proxy/redir/tcp_darwin.go +++ b/proxy/redir/tcp_darwin.go @@ -5,7 +5,7 @@ import ( "syscall" "unsafe" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/socks" ) func parserPacket(c net.Conn) (socks.Addr, error) { diff --git a/proxy/redir/tcp_linux.go b/proxy/redir/tcp_linux.go index 836b2e5d43..1cd5a3ee54 100644 --- a/proxy/redir/tcp_linux.go +++ b/proxy/redir/tcp_linux.go @@ -6,7 +6,7 @@ import ( "syscall" "unsafe" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/socks" ) const ( diff --git a/proxy/redir/tcp_windows.go b/proxy/redir/tcp_windows.go index a749c11771..cce61c079b 100644 --- a/proxy/redir/tcp_windows.go +++ b/proxy/redir/tcp_windows.go @@ -4,7 +4,7 @@ import ( "errors" "net" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/socks" ) func parserPacket(conn net.Conn) (socks.Addr, error) { diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 5aa8ccc1a9..da9a85bde0 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -7,7 +7,7 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" - "github.com/riobard/go-shadowsocks2/socks" + "github.com/Dreamacro/go-shadowsocks2/socks" log "github.com/sirupsen/logrus" ) From 220e4f06080339f26f5529b614107973feb3c256 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 26 Sep 2018 00:34:15 +0800 Subject: [PATCH 043/535] Add: fallback policy group --- adapters/remote/fallback.go | 128 ++++++++++++++++++++++++++++++++++++ config/config.go | 28 ++++++++ constant/adapters.go | 3 + hub/proxies.go | 10 +++ 4 files changed, 169 insertions(+) create mode 100644 adapters/remote/fallback.go diff --git a/adapters/remote/fallback.go b/adapters/remote/fallback.go new file mode 100644 index 0000000000..5fe7a77380 --- /dev/null +++ b/adapters/remote/fallback.go @@ -0,0 +1,128 @@ +package adapters + +import ( + "errors" + "sync" + "time" + + C "github.com/Dreamacro/clash/constant" +) + +type proxy struct { + RawProxy C.Proxy + Valid bool +} + +type Fallback struct { + name string + proxies []*proxy + rawURL string + delay time.Duration + done chan struct{} +} + +func (f *Fallback) Name() string { + return f.name +} + +func (f *Fallback) Type() C.AdapterType { + return C.Fallback +} + +func (f *Fallback) Now() string { + _, proxy := f.findNextValidProxy(0) + if proxy != nil { + return proxy.RawProxy.Name() + } + return f.proxies[0].RawProxy.Name() +} + +func (f *Fallback) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { + idx := 0 + var proxy *proxy + for { + idx, proxy = f.findNextValidProxy(idx) + if proxy == nil { + break + } + adapter, err = proxy.RawProxy.Generator(addr) + if err != nil { + proxy.Valid = false + idx++ + continue + } + return + } + return nil, errors.New("There are no valid proxy") +} + +func (f *Fallback) Close() { + f.done <- struct{}{} +} + +func (f *Fallback) loop() { + tick := time.NewTicker(f.delay) + go f.validTest() +Loop: + for { + select { + case <-tick.C: + go f.validTest() + case <-f.done: + break Loop + } + } +} + +func (f *Fallback) findNextValidProxy(start int) (int, *proxy) { + for i := start; i < len(f.proxies); i++ { + if f.proxies[i].Valid { + return i, f.proxies[i] + } + } + return -1, nil +} + +func (f *Fallback) validTest() { + wg := sync.WaitGroup{} + wg.Add(len(f.proxies)) + + for _, p := range f.proxies { + go func(p *proxy) { + _, err := DelayTest(p.RawProxy, f.rawURL) + p.Valid = err == nil + wg.Done() + }(p) + } + + wg.Wait() +} + +func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*Fallback, error) { + _, err := urlToAddr(rawURL) + if err != nil { + return nil, err + } + + if len(proxies) < 1 { + return nil, errors.New("The number of proxies cannot be 0") + } + + warpperProxies := make([]*proxy, len(proxies)) + for idx := range proxies { + warpperProxies[idx] = &proxy{ + RawProxy: proxies[idx], + Valid: true, + } + } + + Fallback := &Fallback{ + name: name, + proxies: warpperProxies, + rawURL: rawURL, + delay: delay, + done: make(chan struct{}), + } + go Fallback.loop() + return Fallback, nil +} diff --git a/config/config.go b/config/config.go index 0fb589a277..fc8e0a2727 100644 --- a/config/config.go +++ b/config/config.go @@ -307,6 +307,25 @@ func (c *Config) parseProxies(cfg *ini.File) error { return fmt.Errorf("Selector create error: %s", err.Error()) } proxies[key.Name()] = selector + case "fallback": + if len(rule) < 4 { + return fmt.Errorf("URLTest need more than 4 param") + } + proxyNames := rule[1 : len(rule)-2] + delay, _ := strconv.Atoi(rule[len(rule)-1]) + url := rule[len(rule)-2] + var ps []C.Proxy + for _, name := range proxyNames { + if p, ok := proxies[name]; ok { + ps = append(ps, p) + } + } + + adapter, err := adapters.NewFallback(key.Name(), ps, url, time.Duration(delay)*time.Second) + if err != nil { + return fmt.Errorf("Config error: %s", err.Error()) + } + proxies[key.Name()] = adapter } } @@ -315,6 +334,15 @@ func (c *Config) parseProxies(cfg *ini.File) error { proxies["DIRECT"] = adapters.NewDirect() proxies["REJECT"] = adapters.NewReject() + // close old goroutine + for _, proxy := range c.proxies { + switch raw := proxy.(type) { + case *adapters.URLTest: + raw.Close() + case *adapters.Fallback: + raw.Close() + } + } c.proxies = proxies c.event <- &Event{Type: "proxies", Payload: proxies} return nil diff --git a/constant/adapters.go b/constant/adapters.go index a7fd1557de..9f2e040661 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -7,6 +7,7 @@ import ( // Adapter Type const ( Direct AdapterType = iota + Fallback Reject Selector Shadowsocks @@ -38,6 +39,8 @@ func (at AdapterType) String() string { switch at { case Direct: return "Direct" + case Fallback: + return "Fallback" case Reject: return "Reject" case Selector: diff --git a/hub/proxies.go b/hub/proxies.go index 10a8e8ccca..19bccca424 100644 --- a/hub/proxies.go +++ b/hub/proxies.go @@ -37,6 +37,11 @@ type URLTest struct { Now string `json:"now"` } +type Fallback struct { + Type string `json:"type"` + Now string `json:"now"` +} + func transformProxy(proxy C.Proxy) interface{} { t := proxy.Type() switch t { @@ -52,6 +57,11 @@ func transformProxy(proxy C.Proxy) interface{} { Type: t.String(), Now: proxy.(*A.URLTest).Now(), } + case C.Fallback: + return Fallback{ + Type: t.String(), + Now: proxy.(*A.Fallback).Now(), + } default: return SampleProxy{ Type: proxy.Type().String(), From 2fd59cb31c01caccead1876814795f3ed6f91a5f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 30 Sep 2018 12:25:52 +0800 Subject: [PATCH 044/535] Chore: make the code more semantic --- adapters/{local => inbound}/http.go | 18 +++++++-------- adapters/{local => inbound}/https.go | 4 ++-- adapters/{local => inbound}/socket.go | 14 ++++++------ adapters/{local => inbound}/util.go | 8 +++---- adapters/{remote => outbound}/direct.go | 4 ++-- adapters/{remote => outbound}/fallback.go | 6 ++--- adapters/{remote => outbound}/reject.go | 2 +- adapters/{remote => outbound}/selector.go | 4 ++-- adapters/{remote => outbound}/shadowsocks.go | 22 +++++++++---------- adapters/{remote => outbound}/socks5.go | 8 +++---- adapters/{remote => outbound}/urltest.go | 6 ++--- adapters/{remote => outbound}/util.go | 6 ++--- adapters/{remote => outbound}/vmess.go | 22 +++++++++---------- {observable => common/observable}/iterable.go | 0 .../observable}/observable.go | 0 .../observable}/observable_test.go | 0 .../observable}/subscriber.go | 0 {common => component}/simple-obfs/http.go | 0 {common => component}/simple-obfs/tls.go | 0 {common => component}/vmess/aead.go | 0 {common => component}/vmess/chunk.go | 0 {common => component}/vmess/conn.go | 0 {common => component}/vmess/user.go | 0 {common => component}/vmess/vmess.go | 0 config/config.go | 4 ++-- constant/adapters.go | 4 ++-- constant/{addr.go => metadata.go} | 6 ++--- constant/rule.go | 2 +- hub/proxies.go | 2 +- proxy/http/server.go | 2 +- proxy/redir/tcp.go | 2 +- proxy/socks/tcp.go | 2 +- rules/domain_keyword.go | 6 ++--- rules/domain_suffix.go | 6 ++--- rules/domian.go | 6 ++--- rules/final.go | 2 +- rules/geoip.go | 6 ++--- rules/ipcidr.go | 6 ++--- tunnel/connection.go | 2 +- tunnel/tunnel.go | 22 +++++++++---------- 40 files changed, 102 insertions(+), 102 deletions(-) rename adapters/{local => inbound}/http.go (82%) rename adapters/{local => inbound}/https.go (77%) rename adapters/{local => inbound}/socket.go (72%) rename adapters/{local => inbound}/util.go (90%) rename adapters/{remote => outbound}/direct.go (78%) rename adapters/{remote => outbound}/fallback.go (92%) rename adapters/{remote => outbound}/reject.go (94%) rename adapters/{remote => outbound}/selector.go (89%) rename adapters/{remote => outbound}/shadowsocks.go (82%) rename adapters/{remote => outbound}/socks5.go (83%) rename adapters/{remote => outbound}/urltest.go (90%) rename adapters/{remote => outbound}/util.go (93%) rename adapters/{remote => outbound}/vmess.go (74%) rename {observable => common/observable}/iterable.go (100%) rename {observable => common/observable}/observable.go (100%) rename {observable => common/observable}/observable_test.go (100%) rename {observable => common/observable}/subscriber.go (100%) rename {common => component}/simple-obfs/http.go (100%) rename {common => component}/simple-obfs/tls.go (100%) rename {common => component}/vmess/aead.go (100%) rename {common => component}/vmess/chunk.go (100%) rename {common => component}/vmess/conn.go (100%) rename {common => component}/vmess/user.go (100%) rename {common => component}/vmess/vmess.go (100%) rename constant/{addr.go => metadata.go} (81%) diff --git a/adapters/local/http.go b/adapters/inbound/http.go similarity index 82% rename from adapters/local/http.go rename to adapters/inbound/http.go index ddd846118b..01aa14b8e2 100644 --- a/adapters/local/http.go +++ b/adapters/inbound/http.go @@ -10,9 +10,9 @@ import ( // HTTPAdapter is a adapter for HTTP connection type HTTPAdapter struct { - addr *C.Addr - conn net.Conn - R *http.Request + metadata *C.Metadata + conn net.Conn + R *http.Request } // Close HTTP connection @@ -20,9 +20,9 @@ func (h *HTTPAdapter) Close() { h.conn.Close() } -// Addr return destination address -func (h *HTTPAdapter) Addr() *C.Addr { - return h.addr +// Metadata return destination metadata +func (h *HTTPAdapter) Metadata() *C.Metadata { + return h.metadata } // Conn return raw net.Conn of HTTP @@ -33,9 +33,9 @@ func (h *HTTPAdapter) Conn() net.Conn { // NewHTTP is HTTPAdapter generator func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { return &HTTPAdapter{ - addr: parseHTTPAddr(request), - R: request, - conn: conn, + metadata: parseHTTPAddr(request), + R: request, + conn: conn, } } diff --git a/adapters/local/https.go b/adapters/inbound/https.go similarity index 77% rename from adapters/local/https.go rename to adapters/inbound/https.go index 61d3125586..6e36180b7c 100644 --- a/adapters/local/https.go +++ b/adapters/inbound/https.go @@ -8,7 +8,7 @@ import ( // NewHTTPS is HTTPAdapter generator func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { return &SocketAdapter{ - addr: parseHTTPAddr(request), - conn: conn, + metadata: parseHTTPAddr(request), + conn: conn, } } diff --git a/adapters/local/socket.go b/adapters/inbound/socket.go similarity index 72% rename from adapters/local/socket.go rename to adapters/inbound/socket.go index ff9b09c3e1..8d701ad6e9 100644 --- a/adapters/local/socket.go +++ b/adapters/inbound/socket.go @@ -9,8 +9,8 @@ import ( // SocketAdapter is a adapter for socks and redir connection type SocketAdapter struct { - conn net.Conn - addr *C.Addr + conn net.Conn + metadata *C.Metadata } // Close socks and redir connection @@ -18,9 +18,9 @@ func (s *SocketAdapter) Close() { s.conn.Close() } -// Addr return destination address -func (s *SocketAdapter) Addr() *C.Addr { - return s.addr +// Metadata return destination metadata +func (s *SocketAdapter) Metadata() *C.Metadata { + return s.metadata } // Conn return raw net.Conn @@ -31,7 +31,7 @@ func (s *SocketAdapter) Conn() net.Conn { // NewSocket is SocketAdapter generator func NewSocket(target socks.Addr, conn net.Conn) *SocketAdapter { return &SocketAdapter{ - conn: conn, - addr: parseSocksAddr(target), + conn: conn, + metadata: parseSocksAddr(target), } } diff --git a/adapters/local/util.go b/adapters/inbound/util.go similarity index 90% rename from adapters/local/util.go rename to adapters/inbound/util.go index 0a713421e5..06499f0474 100644 --- a/adapters/local/util.go +++ b/adapters/inbound/util.go @@ -9,7 +9,7 @@ import ( "github.com/Dreamacro/go-shadowsocks2/socks" ) -func parseSocksAddr(target socks.Addr) *C.Addr { +func parseSocksAddr(target socks.Addr) *C.Metadata { var host, port string var ip net.IP @@ -29,7 +29,7 @@ func parseSocksAddr(target socks.Addr) *C.Addr { port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) } - return &C.Addr{ + return &C.Metadata{ NetWork: C.TCP, AddrType: int(target[0]), Host: host, @@ -38,7 +38,7 @@ func parseSocksAddr(target socks.Addr) *C.Addr { } } -func parseHTTPAddr(request *http.Request) *C.Addr { +func parseHTTPAddr(request *http.Request) *C.Metadata { host := request.URL.Hostname() port := request.URL.Port() if port == "" { @@ -61,7 +61,7 @@ func parseHTTPAddr(request *http.Request) *C.Addr { addType = socks.AtypIPv4 } - return &C.Addr{ + return &C.Metadata{ NetWork: C.TCP, AddrType: addType, Host: host, diff --git a/adapters/remote/direct.go b/adapters/outbound/direct.go similarity index 78% rename from adapters/remote/direct.go rename to adapters/outbound/direct.go index 9d22f9487f..70a1d64805 100644 --- a/adapters/remote/direct.go +++ b/adapters/outbound/direct.go @@ -31,8 +31,8 @@ func (d *Direct) Type() C.AdapterType { return C.Direct } -func (d *Direct) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { - c, err := net.Dial("tcp", net.JoinHostPort(addr.String(), addr.Port)) +func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { + c, err := net.Dial("tcp", net.JoinHostPort(metadata.String(), metadata.Port)) if err != nil { return } diff --git a/adapters/remote/fallback.go b/adapters/outbound/fallback.go similarity index 92% rename from adapters/remote/fallback.go rename to adapters/outbound/fallback.go index 5fe7a77380..435a2e8354 100644 --- a/adapters/remote/fallback.go +++ b/adapters/outbound/fallback.go @@ -37,7 +37,7 @@ func (f *Fallback) Now() string { return f.proxies[0].RawProxy.Name() } -func (f *Fallback) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { +func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { idx := 0 var proxy *proxy for { @@ -45,7 +45,7 @@ func (f *Fallback) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { if proxy == nil { break } - adapter, err = proxy.RawProxy.Generator(addr) + adapter, err = proxy.RawProxy.Generator(metadata) if err != nil { proxy.Valid = false idx++ @@ -99,7 +99,7 @@ func (f *Fallback) validTest() { } func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*Fallback, error) { - _, err := urlToAddr(rawURL) + _, err := urlToMetadata(rawURL) if err != nil { return nil, err } diff --git a/adapters/remote/reject.go b/adapters/outbound/reject.go similarity index 94% rename from adapters/remote/reject.go rename to adapters/outbound/reject.go index b4ce4d86a7..3bc8bdf30a 100644 --- a/adapters/remote/reject.go +++ b/adapters/outbound/reject.go @@ -32,7 +32,7 @@ func (r *Reject) Type() C.AdapterType { return C.Reject } -func (r *Reject) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { +func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { return &RejectAdapter{conn: &NopConn{}}, nil } diff --git a/adapters/remote/selector.go b/adapters/outbound/selector.go similarity index 89% rename from adapters/remote/selector.go rename to adapters/outbound/selector.go index 92326c1c11..719335f871 100644 --- a/adapters/remote/selector.go +++ b/adapters/outbound/selector.go @@ -21,8 +21,8 @@ func (s *Selector) Type() C.AdapterType { return C.Selector } -func (s *Selector) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { - return s.selected.Generator(addr) +func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { + return s.selected.Generator(metadata) } func (s *Selector) Now() string { diff --git a/adapters/remote/shadowsocks.go b/adapters/outbound/shadowsocks.go similarity index 82% rename from adapters/remote/shadowsocks.go rename to adapters/outbound/shadowsocks.go index 8c0a0987a3..3a46c034a8 100644 --- a/adapters/remote/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -7,7 +7,7 @@ import ( "net/url" "strconv" - "github.com/Dreamacro/clash/common/simple-obfs" + "github.com/Dreamacro/clash/component/simple-obfs" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/go-shadowsocks2/core" @@ -44,7 +44,7 @@ func (ss *ShadowSocks) Type() C.AdapterType { return C.Shadowsocks } -func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { +func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { c, err := net.Dial("tcp", ss.server) if err != nil { return nil, fmt.Errorf("%s connect error", ss.server) @@ -58,7 +58,7 @@ func (ss *ShadowSocks) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err erro c = obfs.NewHTTPObfs(c, ss.obfsHost, port) } c = ss.cipher.StreamConn(c) - _, err = c.Write(serializesSocksAddr(addr)) + _, err = c.Write(serializesSocksAddr(metadata)) return &ShadowsocksAdapter{conn: c}, err } @@ -102,21 +102,21 @@ func parseURL(s string) (addr, cipher, password string, err error) { return } -func serializesSocksAddr(addr *C.Addr) []byte { +func serializesSocksAddr(metadata *C.Metadata) []byte { var buf [][]byte - aType := uint8(addr.AddrType) - p, _ := strconv.Atoi(addr.Port) + aType := uint8(metadata.AddrType) + p, _ := strconv.Atoi(metadata.Port) port := []byte{uint8(p >> 8), uint8(p & 0xff)} - switch addr.AddrType { + switch metadata.AddrType { case socks.AtypDomainName: - len := uint8(len(addr.Host)) - host := []byte(addr.Host) + len := uint8(len(metadata.Host)) + host := []byte(metadata.Host) buf = [][]byte{{aType, len}, host, port} case socks.AtypIPv4: - host := addr.IP.To4() + host := metadata.IP.To4() buf = [][]byte{{aType}, host, port} case socks.AtypIPv6: - host := addr.IP.To16() + host := metadata.IP.To16() buf = [][]byte{{aType}, host, port} } return bytes.Join(buf, nil) diff --git a/adapters/remote/socks5.go b/adapters/outbound/socks5.go similarity index 83% rename from adapters/remote/socks5.go rename to adapters/outbound/socks5.go index 5464dd981d..c9a6149408 100644 --- a/adapters/remote/socks5.go +++ b/adapters/outbound/socks5.go @@ -39,19 +39,19 @@ func (ss *Socks5) Type() C.AdapterType { return C.Socks5 } -func (ss *Socks5) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { +func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { c, err := net.Dial("tcp", ss.addr) if err != nil { return nil, fmt.Errorf("%s connect error", ss.addr) } tcpKeepAlive(c) - if err := ss.shakeHand(addr, c); err != nil { + if err := ss.shakeHand(metadata, c); err != nil { return nil, err } return &Socks5Adapter{conn: c}, nil } -func (ss *Socks5) shakeHand(addr *C.Addr, rw io.ReadWriter) error { +func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { buf := make([]byte, socks.MaxAddrLen) // VER, CMD, RSV @@ -71,7 +71,7 @@ func (ss *Socks5) shakeHand(addr *C.Addr, rw io.ReadWriter) error { } // VER, CMD, RSV, ADDR - if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(addr)}, []byte(""))); err != nil { + if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(metadata)}, []byte(""))); err != nil { return err } diff --git a/adapters/remote/urltest.go b/adapters/outbound/urltest.go similarity index 90% rename from adapters/remote/urltest.go rename to adapters/outbound/urltest.go index 3a0fa664e5..751a7edf5a 100644 --- a/adapters/remote/urltest.go +++ b/adapters/outbound/urltest.go @@ -28,8 +28,8 @@ func (u *URLTest) Now() string { return u.fast.Name() } -func (u *URLTest) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { - return u.fast.Generator(addr) +func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { + return u.fast.Generator(metadata) } func (u *URLTest) Close() { @@ -84,7 +84,7 @@ func (u *URLTest) speedTest() { } func NewURLTest(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) { - _, err := urlToAddr(rawURL) + _, err := urlToMetadata(rawURL) if err != nil { return nil, err } diff --git a/adapters/remote/util.go b/adapters/outbound/util.go similarity index 93% rename from adapters/remote/util.go rename to adapters/outbound/util.go index e8a3500642..1073c94e6e 100644 --- a/adapters/remote/util.go +++ b/adapters/outbound/util.go @@ -12,7 +12,7 @@ import ( // DelayTest get the delay for the specified URL func DelayTest(proxy C.Proxy, url string) (t int16, err error) { - addr, err := urlToAddr(url) + addr, err := urlToMetadata(url) if err != nil { return } @@ -43,7 +43,7 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) { return } -func urlToAddr(rawURL string) (addr C.Addr, err error) { +func urlToMetadata(rawURL string) (addr C.Metadata, err error) { u, err := url.Parse(rawURL) if err != nil { return @@ -61,7 +61,7 @@ func urlToAddr(rawURL string) (addr C.Addr, err error) { } } - addr = C.Addr{ + addr = C.Metadata{ AddrType: C.AtypDomainName, Host: u.Hostname(), IP: nil, diff --git a/adapters/remote/vmess.go b/adapters/outbound/vmess.go similarity index 74% rename from adapters/remote/vmess.go rename to adapters/outbound/vmess.go index e98828c330..4bb0817bf4 100644 --- a/adapters/remote/vmess.go +++ b/adapters/outbound/vmess.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/Dreamacro/clash/common/vmess" + "github.com/Dreamacro/clash/component/vmess" C "github.com/Dreamacro/clash/constant" ) @@ -38,13 +38,13 @@ func (ss *Vmess) Type() C.AdapterType { return C.Vmess } -func (ss *Vmess) Generator(addr *C.Addr) (adapter C.ProxyAdapter, err error) { +func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { c, err := net.Dial("tcp", ss.server) if err != nil { return nil, fmt.Errorf("%s connect error", ss.server) } tcpKeepAlive(c) - c = ss.client.New(c, parseVmessAddr(addr)) + c = ss.client.New(c, parseVmessAddr(metadata)) return &VmessAdapter{conn: c}, err } @@ -67,26 +67,26 @@ func NewVmess(name string, server string, uuid string, alterID uint16, security }, nil } -func parseVmessAddr(info *C.Addr) *vmess.DstAddr { +func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { var addrType byte var addr []byte - switch info.AddrType { + switch metadata.AddrType { case C.AtypIPv4: addrType = byte(vmess.AtypIPv4) addr = make([]byte, net.IPv4len) - copy(addr[:], info.IP.To4()) + copy(addr[:], metadata.IP.To4()) case C.AtypIPv6: addrType = byte(vmess.AtypIPv6) addr = make([]byte, net.IPv6len) - copy(addr[:], info.IP.To16()) + copy(addr[:], metadata.IP.To16()) case C.AtypDomainName: addrType = byte(vmess.AtypDomainName) - addr = make([]byte, len(info.Host)+1) - addr[0] = byte(len(info.Host)) - copy(addr[1:], []byte(info.Host)) + addr = make([]byte, len(metadata.Host)+1) + addr[0] = byte(len(metadata.Host)) + copy(addr[1:], []byte(metadata.Host)) } - port, _ := strconv.Atoi(info.Port) + port, _ := strconv.Atoi(metadata.Port) return &vmess.DstAddr{ AddrType: addrType, Addr: addr, diff --git a/observable/iterable.go b/common/observable/iterable.go similarity index 100% rename from observable/iterable.go rename to common/observable/iterable.go diff --git a/observable/observable.go b/common/observable/observable.go similarity index 100% rename from observable/observable.go rename to common/observable/observable.go diff --git a/observable/observable_test.go b/common/observable/observable_test.go similarity index 100% rename from observable/observable_test.go rename to common/observable/observable_test.go diff --git a/observable/subscriber.go b/common/observable/subscriber.go similarity index 100% rename from observable/subscriber.go rename to common/observable/subscriber.go diff --git a/common/simple-obfs/http.go b/component/simple-obfs/http.go similarity index 100% rename from common/simple-obfs/http.go rename to component/simple-obfs/http.go diff --git a/common/simple-obfs/tls.go b/component/simple-obfs/tls.go similarity index 100% rename from common/simple-obfs/tls.go rename to component/simple-obfs/tls.go diff --git a/common/vmess/aead.go b/component/vmess/aead.go similarity index 100% rename from common/vmess/aead.go rename to component/vmess/aead.go diff --git a/common/vmess/chunk.go b/component/vmess/chunk.go similarity index 100% rename from common/vmess/chunk.go rename to component/vmess/chunk.go diff --git a/common/vmess/conn.go b/component/vmess/conn.go similarity index 100% rename from common/vmess/conn.go rename to component/vmess/conn.go diff --git a/common/vmess/user.go b/component/vmess/user.go similarity index 100% rename from common/vmess/user.go rename to component/vmess/user.go diff --git a/common/vmess/vmess.go b/component/vmess/vmess.go similarity index 100% rename from common/vmess/vmess.go rename to component/vmess/vmess.go diff --git a/config/config.go b/config/config.go index fc8e0a2727..c72ffcb2f2 100644 --- a/config/config.go +++ b/config/config.go @@ -10,9 +10,9 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/adapters/remote" + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/common/observable" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/observable" R "github.com/Dreamacro/clash/rules" log "github.com/sirupsen/logrus" diff --git a/constant/adapters.go b/constant/adapters.go index 9f2e040661..69a00ba17a 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -22,14 +22,14 @@ type ProxyAdapter interface { } type ServerAdapter interface { - Addr() *Addr + Metadata() *Metadata Close() } type Proxy interface { Name() string Type() AdapterType - Generator(addr *Addr) (ProxyAdapter, error) + Generator(metadata *Metadata) (ProxyAdapter, error) } // AdapterType is enum of adapter type diff --git a/constant/addr.go b/constant/metadata.go similarity index 81% rename from constant/addr.go rename to constant/metadata.go index e404b48920..dfc274e725 100644 --- a/constant/addr.go +++ b/constant/metadata.go @@ -28,8 +28,8 @@ func (n *NetWork) String() string { type SourceType int -// Addr is used to store connection address -type Addr struct { +// Metadata is used to store connection address +type Metadata struct { NetWork NetWork Source SourceType AddrType int @@ -38,7 +38,7 @@ type Addr struct { Port string } -func (addr *Addr) String() string { +func (addr *Metadata) String() string { if addr.Host == "" { return addr.IP.String() } diff --git a/constant/rule.go b/constant/rule.go index db449c7e64..1a8f5f97c1 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -33,7 +33,7 @@ func (rt RuleType) String() string { type Rule interface { RuleType() RuleType - IsMatch(addr *Addr) bool + IsMatch(metadata *Metadata) bool Adapter() string Payload() string } diff --git a/hub/proxies.go b/hub/proxies.go index 19bccca424..c0fe062bae 100644 --- a/hub/proxies.go +++ b/hub/proxies.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - A "github.com/Dreamacro/clash/adapters/remote" + A "github.com/Dreamacro/clash/adapters/outbound" C "github.com/Dreamacro/clash/constant" "github.com/go-chi/chi" diff --git a/proxy/http/server.go b/proxy/http/server.go index eb11c648a0..598300589c 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -5,7 +5,7 @@ import ( "net" "net/http" - "github.com/Dreamacro/clash/adapters/local" + "github.com/Dreamacro/clash/adapters/inbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index 3f653dc929..f4b47f4b74 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -3,7 +3,7 @@ package redir import ( "net" - "github.com/Dreamacro/clash/adapters/local" + "github.com/Dreamacro/clash/adapters/inbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index da9a85bde0..57645dc314 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -3,7 +3,7 @@ package socks import ( "net" - "github.com/Dreamacro/clash/adapters/local" + "github.com/Dreamacro/clash/adapters/inbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" diff --git a/rules/domain_keyword.go b/rules/domain_keyword.go index 19ca2e2240..f2b1dd1c75 100644 --- a/rules/domain_keyword.go +++ b/rules/domain_keyword.go @@ -15,11 +15,11 @@ func (dk *DomainKeyword) RuleType() C.RuleType { return C.DomainKeyword } -func (dk *DomainKeyword) IsMatch(addr *C.Addr) bool { - if addr.AddrType != C.AtypDomainName { +func (dk *DomainKeyword) IsMatch(metadata *C.Metadata) bool { + if metadata.AddrType != C.AtypDomainName { return false } - domain := addr.Host + domain := metadata.Host return strings.Contains(domain, dk.keyword) } diff --git a/rules/domain_suffix.go b/rules/domain_suffix.go index d6dd9f970d..c29ab06e82 100644 --- a/rules/domain_suffix.go +++ b/rules/domain_suffix.go @@ -15,11 +15,11 @@ func (ds *DomainSuffix) RuleType() C.RuleType { return C.DomainSuffix } -func (ds *DomainSuffix) IsMatch(addr *C.Addr) bool { - if addr.AddrType != C.AtypDomainName { +func (ds *DomainSuffix) IsMatch(metadata *C.Metadata) bool { + if metadata.AddrType != C.AtypDomainName { return false } - domain := addr.Host + domain := metadata.Host return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix } diff --git a/rules/domian.go b/rules/domian.go index a836ca67b3..181fc1d2a6 100644 --- a/rules/domian.go +++ b/rules/domian.go @@ -13,11 +13,11 @@ func (d *Domain) RuleType() C.RuleType { return C.Domain } -func (d *Domain) IsMatch(addr *C.Addr) bool { - if addr.AddrType != C.AtypDomainName { +func (d *Domain) IsMatch(metadata *C.Metadata) bool { + if metadata.AddrType != C.AtypDomainName { return false } - return addr.Host == d.domain + return metadata.Host == d.domain } func (d *Domain) Adapter() string { diff --git a/rules/final.go b/rules/final.go index f07fbab162..1cc3b88851 100644 --- a/rules/final.go +++ b/rules/final.go @@ -12,7 +12,7 @@ func (f *Final) RuleType() C.RuleType { return C.FINAL } -func (f *Final) IsMatch(addr *C.Addr) bool { +func (f *Final) IsMatch(metadata *C.Metadata) bool { return true } diff --git a/rules/geoip.go b/rules/geoip.go index f79f26f0a9..ff52ff36a7 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -23,11 +23,11 @@ func (g *GEOIP) RuleType() C.RuleType { return C.GEOIP } -func (g *GEOIP) IsMatch(addr *C.Addr) bool { - if addr.IP == nil { +func (g *GEOIP) IsMatch(metadata *C.Metadata) bool { + if metadata.IP == nil { return false } - record, _ := mmdb.Country(*addr.IP) + record, _ := mmdb.Country(*metadata.IP) return record.Country.IsoCode == g.country } diff --git a/rules/ipcidr.go b/rules/ipcidr.go index baf4475797..fe1a9d9cd6 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -15,12 +15,12 @@ func (i *IPCIDR) RuleType() C.RuleType { return C.IPCIDR } -func (i *IPCIDR) IsMatch(addr *C.Addr) bool { - if addr.IP == nil { +func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { + if metadata.IP == nil { return false } - return i.ipnet.Contains(*addr.IP) + return i.ipnet.Contains(*metadata.IP) } func (i *IPCIDR) Adapter() string { diff --git a/tunnel/connection.go b/tunnel/connection.go index 842927a12d..3b69e50628 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -7,7 +7,7 @@ import ( "net/http" "time" - "github.com/Dreamacro/clash/adapters/local" + "github.com/Dreamacro/clash/adapters/inbound" C "github.com/Dreamacro/clash/constant" ) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index a3d29f688e..fb43255408 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -4,10 +4,10 @@ import ( "sync" "time" - LocalAdapter "github.com/Dreamacro/clash/adapters/local" + InboundAdapter "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/common/observable" cfg "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/observable" "gopkg.in/eapache/channels.v1" ) @@ -84,7 +84,7 @@ func (t *Tunnel) process() { func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer localConn.Close() - addr := localConn.Addr() + metadata := localConn.Metadata() var proxy C.Proxy switch t.mode { @@ -94,9 +94,9 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { proxy = t.proxies["GLOBAL"] // Rule default: - proxy = t.match(addr) + proxy = t.match(metadata) } - remoConn, err := proxy.Generator(addr) + remoConn, err := proxy.Generator(metadata) if err != nil { t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error()) return @@ -104,28 +104,28 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer remoConn.Close() switch adapter := localConn.(type) { - case *LocalAdapter.HTTPAdapter: + case *InboundAdapter.HTTPAdapter: t.handleHTTP(adapter, remoConn) - case *LocalAdapter.SocketAdapter: + case *InboundAdapter.SocketAdapter: t.handleSOCKS(adapter, remoConn) } } -func (t *Tunnel) match(addr *C.Addr) C.Proxy { +func (t *Tunnel) match(metadata *C.Metadata) C.Proxy { t.configLock.RLock() defer t.configLock.RUnlock() for _, rule := range t.rules { - if rule.IsMatch(addr) { + if rule.IsMatch(metadata) { a, ok := t.proxies[rule.Adapter()] if !ok { continue } - t.logCh <- newLog(C.INFO, "%v match %s using %s", addr.String(), rule.RuleType().String(), rule.Adapter()) + t.logCh <- newLog(C.INFO, "%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) return a } } - t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", addr.String()) + t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", metadata.String()) return t.proxies["DIRECT"] } From 85a67988b99d9e9ae643a44508d6c833da7080ff Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 30 Sep 2018 16:30:11 +0800 Subject: [PATCH 045/535] Feat: add structure helper --- common/structure/structure.go | 160 +++++++++++++++++++++++++++++ common/structure/structure_test.go | 141 +++++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 common/structure/structure.go create mode 100644 common/structure/structure_test.go diff --git a/common/structure/structure.go b/common/structure/structure.go new file mode 100644 index 0000000000..ac824a0719 --- /dev/null +++ b/common/structure/structure.go @@ -0,0 +1,160 @@ +package structure + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +// Option is the configuration that is used to create a new decoder +type Option struct { + TagName string + WeaklyTypedInput bool +} + +// Decoder is the core of structure +type Decoder struct { + option *Option +} + +// NewDecoder return a Decoder by Option +func NewDecoder(option Option) *Decoder { + if option.TagName == "" { + option.TagName = "structure" + } + return &Decoder{option: &option} +} + +// Decode transform a map[string]interface{} to a struct +func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error { + if reflect.TypeOf(dst).Kind() != reflect.Ptr { + return fmt.Errorf("Decode must recive a ptr struct") + } + t := reflect.TypeOf(dst).Elem() + v := reflect.ValueOf(dst).Elem() + for idx := 0; idx < v.NumField(); idx++ { + field := t.Field(idx) + + tag := field.Tag.Get(d.option.TagName) + str := strings.SplitN(tag, ",", 2) + key := str[0] + omitempty := false + if len(str) > 1 { + omitempty = str[1] == "omitempty" + } + + value, ok := src[key] + if !ok { + if omitempty { + continue + } + return fmt.Errorf("key %s missing", key) + } + + err := d.decode(key, value, v.Field(idx)) + if err != nil { + return err + } + } + return nil +} + +func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error { + switch val.Kind() { + case reflect.Int: + return d.decodeInt(name, data, val) + case reflect.String: + return d.decodeString(name, data, val) + case reflect.Bool: + return d.decodeBool(name, data, val) + case reflect.Slice: + return d.decodeSlice(name, data, val) + default: + return fmt.Errorf("type %s not support", val.Kind().String()) + } +} + +func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) (err error) { + dataVal := reflect.ValueOf(data) + kind := dataVal.Kind() + switch { + case kind == reflect.Int: + val.SetInt(dataVal.Int()) + case kind == reflect.String && d.option.WeaklyTypedInput: + var i int64 + i, err = strconv.ParseInt(dataVal.String(), 0, val.Type().Bits()) + if err == nil { + val.SetInt(i) + } else { + err = fmt.Errorf("cannot parse '%s' as int: %s", name, err) + } + default: + err = fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type(), + ) + } + return err +} + +func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) (err error) { + dataVal := reflect.ValueOf(data) + kind := dataVal.Kind() + switch { + case kind == reflect.String: + val.SetString(dataVal.String()) + case kind == reflect.Int && d.option.WeaklyTypedInput: + val.SetString(strconv.FormatInt(dataVal.Int(), 10)) + default: + err = fmt.Errorf( + "'%s' expected type'%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type(), + ) + } + return err +} + +func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) (err error) { + dataVal := reflect.ValueOf(data) + kind := dataVal.Kind() + switch { + case kind == reflect.Bool: + val.SetBool(dataVal.Bool()) + case kind == reflect.Int && d.option.WeaklyTypedInput: + val.SetBool(dataVal.Int() != 0) + default: + err = fmt.Errorf( + "'%s' expected type'%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type(), + ) + } + return err +} + +func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + valType := val.Type() + valElemType := valType.Elem() + + if dataVal.Kind() != reflect.Slice { + return fmt.Errorf("'%s' is not a slice", name) + } + + valSlice := val + for i := 0; i < dataVal.Len(); i++ { + currentData := dataVal.Index(i).Interface() + for valSlice.Len() <= i { + valSlice = reflect.Append(valSlice, reflect.Zero(valElemType)) + } + currentField := valSlice.Index(i) + + fieldName := fmt.Sprintf("%s[%d]", name, i) + if err := d.decode(fieldName, currentData, currentField); err != nil { + return err + } + } + + val.Set(valSlice) + return nil +} diff --git a/common/structure/structure_test.go b/common/structure/structure_test.go new file mode 100644 index 0000000000..6e3e6290d2 --- /dev/null +++ b/common/structure/structure_test.go @@ -0,0 +1,141 @@ +package structure + +import ( + "reflect" + "testing" +) + +var decoder = NewDecoder(Option{TagName: "test"}) +var weakTypeDecoder = NewDecoder(Option{TagName: "test", WeaklyTypedInput: true}) + +type Baz struct { + Foo int `test:"foo"` + Bar string `test:"bar"` +} + +type BazSlice struct { + Foo int `test:"foo"` + Bar []string `test:"bar"` +} + +type BazOptional struct { + Foo int `test:"foo,omitempty"` + Bar string `test:"bar,omitempty"` +} + +func TestStructure_Basic(t *testing.T) { + rawMap := map[string]interface{}{ + "foo": 1, + "bar": "test", + "extra": false, + } + + goal := &Baz{ + Foo: 1, + Bar: "test", + } + + s := &Baz{} + err := decoder.Decode(rawMap, s) + if err != nil { + t.Fatal(err.Error()) + } + if !reflect.DeepEqual(s, goal) { + t.Fatalf("bad: %#v", s) + } +} + +func TestStructure_Slice(t *testing.T) { + rawMap := map[string]interface{}{ + "foo": 1, + "bar": []string{"one", "two"}, + } + + goal := &BazSlice{ + Foo: 1, + Bar: []string{"one", "two"}, + } + + s := &BazSlice{} + err := decoder.Decode(rawMap, s) + if err != nil { + t.Fatal(err.Error()) + } + if !reflect.DeepEqual(s, goal) { + t.Fatalf("bad: %#v", s) + } +} + +func TestStructure_Optional(t *testing.T) { + rawMap := map[string]interface{}{ + "foo": 1, + } + + goal := &BazOptional{ + Foo: 1, + } + + s := &BazOptional{} + err := decoder.Decode(rawMap, s) + if err != nil { + t.Fatal(err.Error()) + } + if !reflect.DeepEqual(s, goal) { + t.Fatalf("bad: %#v", s) + } +} + +func TestStructure_MissingKey(t *testing.T) { + rawMap := map[string]interface{}{ + "foo": 1, + } + + s := &Baz{} + err := decoder.Decode(rawMap, s) + if err == nil { + t.Fatalf("should throw error: %#v", s) + } +} + +func TestStructure_ParamError(t *testing.T) { + rawMap := map[string]interface{}{} + s := Baz{} + err := decoder.Decode(rawMap, s) + if err == nil { + t.Fatalf("should throw error: %#v", s) + } +} + +func TestStructure_SliceTypeError(t *testing.T) { + rawMap := map[string]interface{}{ + "foo": 1, + "bar": []int{1, 2}, + } + + s := &BazSlice{} + err := decoder.Decode(rawMap, s) + if err == nil { + t.Fatalf("should throw error: %#v", s) + } +} + +func TestStructure_WeakType(t *testing.T) { + rawMap := map[string]interface{}{ + "foo": "1", + "bar": []int{1}, + } + + goal := &BazSlice{ + Foo: 1, + Bar: []string{"1"}, + } + + s := &BazSlice{} + err := weakTypeDecoder.Decode(rawMap, s) + if err != nil { + t.Fatal(err.Error()) + } + if !reflect.DeepEqual(s, goal) { + t.Fatalf("bad: %#v", s) + } +} From 04e05c6a859c71b8b7bb33d50a1ddeb936868212 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 1 Oct 2018 19:38:54 +0800 Subject: [PATCH 046/535] Feature: repalce dep with go module --- .travis.yml | 5 +- Dockerfile | 8 ++- Gopkg.lock | 149 ---------------------------------------------------- Gopkg.toml | 70 ------------------------ go.mod | 17 ++++++ go.sum | 33 ++++++++++++ 6 files changed, 55 insertions(+), 227 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 go.mod create mode 100644 go.sum diff --git a/.travis.yml b/.travis.yml index a6adfd5c48..6fa211d4b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,14 +2,13 @@ language: go sudo: false go: - "1.11" -before_install: - - go get -u github.com/golang/dep/cmd/dep install: - - "$GOPATH/bin/dep ensure" + - "go mod download" env: global: - NAME=clash - BINDIR=bin + - GO111MODULE=on script: - go test ./... before_deploy: make -j releases diff --git a/Dockerfile b/Dockerfile index 3cada3f834..06dc565b6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,9 @@ FROM golang:latest as builder RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \ tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \ cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb -RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh && \ - mkdir -p /go/src/github.com/Dreamacro/clash -WORKDIR /go/src/github.com/Dreamacro/clash -COPY . /go/src/github.com/Dreamacro/clash -RUN dep ensure && \ +WORKDIR /clash-src +COPY . /clash-src +RUN go mod download && \ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash && \ chmod +x /clash diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 394ed3adf6..0000000000 --- a/Gopkg.lock +++ /dev/null @@ -1,149 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:d21e998147c1c8cd727bd34148f373d3faa46fd8669c389b53c4eaabbe4eb2b3" - name = "github.com/Dreamacro/go-shadowsocks2" - packages = [ - "core", - "shadowaead", - "shadowstream", - "socks", - ] - pruneopts = "UT" - revision = "1c1fd6c192eb76261ea3ccd80e3b141b25f20db4" - version = "v0.1.1" - -[[projects]] - branch = "master" - digest = "1:8fa55a6e302771a90a86ceae1ca3c0df4ef15d21092198e8313f61dde9eea963" - name = "github.com/Yawning/chacha20" - packages = ["."] - pruneopts = "UT" - revision = "e3b1f968fc6397b51d963fee8ec8711a47bc0ce8" - -[[projects]] - digest = "1:444b82bfe35c83bbcaf84e310fb81a1f9ece03edfed586483c869e2c046aef69" - name = "github.com/eapache/queue" - packages = ["."] - pruneopts = "UT" - revision = "44cc805cf13205b55f69e14bcb69867d1ae92f98" - version = "v1.1.0" - -[[projects]] - digest = "1:b9914f85d95a0968bafd1be1908ba29e2eafafd88d6fd13696be42bf5368c380" - name = "github.com/go-chi/chi" - packages = ["."] - pruneopts = "UT" - revision = "b5294d10673813fac8558e7f47242bc9e61b4c25" - version = "v3.3.3" - -[[projects]] - digest = "1:dfa416a1bb8139f30832543340f972f65c0db9932034cb6a1b42c5ac615a3fb8" - name = "github.com/go-chi/cors" - packages = ["."] - pruneopts = "UT" - revision = "dba6525398619dead495962a916728e7ee2ca322" - version = "v1.0.0" - -[[projects]] - digest = "1:54d7b4b9ab2bb2bae35b55eea900bc8fe15cba05a95fc78bf7fd7b82a9a07afa" - name = "github.com/go-chi/render" - packages = ["."] - pruneopts = "UT" - revision = "3215478343fbc559bd3fc08f7031bb134d6bdad5" - version = "v1.0.1" - -[[projects]] - digest = "1:ce579162ae1341f3e5ab30c0dce767f28b1eb6a81359aad01723f1ba6b4becdf" - name = "github.com/gofrs/uuid" - packages = ["."] - pruneopts = "UT" - revision = "370558f003bfe29580cd0f698d8640daccdcc45c" - version = "v3.1.1" - -[[projects]] - digest = "1:2e8bdbc8a11d716dab1bf66d326285d7e5c92fa4c996c1574ba1153e57534b85" - name = "github.com/oschwald/geoip2-golang" - packages = ["."] - pruneopts = "UT" - revision = "7118115686e16b77967cdbf55d1b944fe14ad312" - version = "v1.2.1" - -[[projects]] - digest = "1:07e8589503b7ec22430dae6eed6f2c17e4249ab245574f4fd0ba8fc9c597d138" - name = "github.com/oschwald/maxminddb-golang" - packages = ["."] - pruneopts = "UT" - revision = "c5bec84d1963260297932a1b7a1753c8420717a7" - version = "v1.3.0" - -[[projects]] - digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc" - name = "github.com/sirupsen/logrus" - packages = ["."] - pruneopts = "UT" - revision = "3e01752db0189b9157070a0e1668a620f9a85da2" - version = "v1.0.6" - -[[projects]] - branch = "master" - digest = "1:d33888518d56c3f0cc9009594f56be4faf33ffff358fe10ff8e7e8cccf0e6617" - name = "golang.org/x/crypto" - packages = [ - "chacha20poly1305", - "hkdf", - "internal/chacha20", - "internal/subtle", - "poly1305", - "ssh/terminal", - ] - pruneopts = "UT" - revision = "0e37d006457bf46f9e6692014ba72ef82c33022c" - -[[projects]] - branch = "master" - digest = "1:ddbafa32d1899456edbf7a64aec7afe5aa287b840e6a12b996f8a8425c1d9a6a" - name = "golang.org/x/sys" - packages = [ - "cpu", - "unix", - "windows", - ] - pruneopts = "UT" - revision = "d641721ec2dead6fe5ca284096fe4b1fcd49e427" - -[[projects]] - digest = "1:975a4480c40f2d0b95e1f83d3ec1aa29a2774e80179e08a9a4ba2aab86721b23" - name = "gopkg.in/eapache/channels.v1" - packages = ["."] - pruneopts = "UT" - revision = "47238d5aae8c0fefd518ef2bee46290909cf8263" - version = "v1.1.0" - -[[projects]] - digest = "1:5abd6a22805b1919f6a6bca0ae58b13cef1f3412812f38569978f43ef02743d4" - name = "gopkg.in/ini.v1" - packages = ["."] - pruneopts = "UT" - revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e" - version = "v1.38.2" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/Dreamacro/go-shadowsocks2/core", - "github.com/Dreamacro/go-shadowsocks2/socks", - "github.com/go-chi/chi", - "github.com/go-chi/cors", - "github.com/go-chi/render", - "github.com/gofrs/uuid", - "github.com/oschwald/geoip2-golang", - "github.com/sirupsen/logrus", - "golang.org/x/crypto/chacha20poly1305", - "gopkg.in/eapache/channels.v1", - "gopkg.in/ini.v1", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 799ede7d16..0000000000 --- a/Gopkg.toml +++ /dev/null @@ -1,70 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/Dreamacro/go-shadowsocks2" - version = "0.1.1" - -[[constraint]] - name = "github.com/go-chi/chi" - version = "3.3.3" - -[[constraint]] - name = "github.com/go-chi/cors" - version = "1.0.0" - -[[constraint]] - name = "github.com/go-chi/render" - version = "1.0.1" - -[[constraint]] - name = "github.com/gofrs/uuid" - version = "3.1.1" - -[[constraint]] - name = "github.com/oschwald/geoip2-golang" - version = "1.2.1" - -[[constraint]] - name = "github.com/sirupsen/logrus" - version = "1.0.6" - -[[constraint]] - branch = "master" - name = "golang.org/x/crypto" - -[[constraint]] - name = "gopkg.in/eapache/channels.v1" - version = "1.1.0" - -[[constraint]] - name = "gopkg.in/ini.v1" - version = "1.38.2" - -[prune] - go-tests = true - unused-packages = true diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..4cb65dd509 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/Dreamacro/clash + +require ( + github.com/Dreamacro/go-shadowsocks2 v0.1.1 + github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect + github.com/eapache/queue v1.1.0 // indirect + github.com/go-chi/chi v3.3.3+incompatible + github.com/go-chi/cors v1.0.0 + github.com/go-chi/render v1.0.1 + github.com/gofrs/uuid v3.1.0+incompatible + github.com/oschwald/geoip2-golang v1.2.1 + github.com/oschwald/maxminddb-golang v1.3.0 // indirect + github.com/sirupsen/logrus v1.1.0 + golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 + gopkg.in/eapache/channels.v1 v1.1.0 + gopkg.in/ini.v1 v1.38.3 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000..2797031941 --- /dev/null +++ b/go.sum @@ -0,0 +1,33 @@ +github.com/Dreamacro/go-shadowsocks2 v0.1.1 h1:Z6Z1ZQFtIKqB3ZghASl4taaJmL7SOw+KpJ+QZpax+TI= +github.com/Dreamacro/go-shadowsocks2 v0.1.1/go.mod h1:6Fuc8zRHwXqCV9Xaw9qNfrh6OUYpDGrlPVPW4oQ34Gs= +github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= +github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8= +github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= +github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= +github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= +github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= +github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= +github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= +github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU= +github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= +gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= +gopkg.in/ini.v1 v1.38.3 h1:ourkRZgR6qjJYoec9lYhX4+nuN1tEbV34dQEQ3IRk9U= +gopkg.in/ini.v1 v1.38.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= From 16c94454590f320538ad78293cd9160072ababa0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 1 Oct 2018 19:42:15 +0800 Subject: [PATCH 047/535] Chore: adjust the keep-alive time --- adapters/outbound/util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 1073c94e6e..5b8c774e8d 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -88,6 +88,6 @@ func selectFast(in chan interface{}) chan interface{} { func tcpKeepAlive(c net.Conn) { if tcp, ok := c.(*net.TCPConn); ok { tcp.SetKeepAlive(true) - tcp.SetKeepAlivePeriod(3 * time.Minute) + tcp.SetKeepAlivePeriod(30 * time.Second) } } From 5c7fa6b18bf88504a6133ae2cd2b396256e2aafe Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 2 Oct 2018 15:26:36 +0800 Subject: [PATCH 048/535] Break Change: use yml, which is easier to parse, as the config format --- adapters/outbound/fallback.go | 16 +- adapters/outbound/selector.go | 5 + adapters/outbound/shadowsocks.go | 43 +++--- adapters/outbound/socks5.go | 12 +- adapters/outbound/urltest.go | 20 ++- adapters/outbound/util.go | 4 +- adapters/outbound/vmess.go | 24 ++- config/config.go | 247 ++++++++++++++++--------------- config/utils.go | 17 --- constant/config.go | 2 +- go.mod | 2 +- go.sum | 10 +- 12 files changed, 219 insertions(+), 183 deletions(-) diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 435a2e8354..224bf91fc7 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -21,6 +21,13 @@ type Fallback struct { done chan struct{} } +type FallbackOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` + URL string `proxy:"url"` + Delay int `proxy:"delay"` +} + func (f *Fallback) Name() string { return f.name } @@ -98,8 +105,8 @@ func (f *Fallback) validTest() { wg.Wait() } -func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*Fallback, error) { - _, err := urlToMetadata(rawURL) +func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { + _, err := urlToMetadata(option.URL) if err != nil { return nil, err } @@ -108,6 +115,7 @@ func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Durat return nil, errors.New("The number of proxies cannot be 0") } + delay := time.Duration(option.Delay) * time.Second warpperProxies := make([]*proxy, len(proxies)) for idx := range proxies { warpperProxies[idx] = &proxy{ @@ -117,9 +125,9 @@ func NewFallback(name string, proxies []C.Proxy, rawURL string, delay time.Durat } Fallback := &Fallback{ - name: name, + name: option.Name, proxies: warpperProxies, - rawURL: rawURL, + rawURL: option.URL, delay: delay, done: make(chan struct{}), } diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index 719335f871..ee5473445c 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -13,6 +13,11 @@ type Selector struct { proxies map[string]C.Proxy } +type SelectorOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` +} + func (s *Selector) Name() string { return s.name } diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 3a46c034a8..55056359f4 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "net" - "net/url" "strconv" "github.com/Dreamacro/clash/component/simple-obfs" @@ -36,6 +35,16 @@ type ShadowSocks struct { cipher core.Cipher } +type ShadowSocksOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Password string `proxy:"password"` + Cipher string `proxy:"cipher"` + Obfs string `proxy:"obfs,omitempty"` + ObfsHost string `proxy:"obfs-host,omitempty"` +} + func (ss *ShadowSocks) Name() string { return ss.name } @@ -62,46 +71,30 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, return &ShadowsocksAdapter{conn: c}, err } -func NewShadowSocks(name string, ssURL string, option map[string]string) (*ShadowSocks, error) { - server, cipher, password, _ := parseURL(ssURL) +func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { + server := fmt.Sprintf("%s:%d", option.Server, option.Port) + cipher := option.Cipher + password := option.Password ciph, err := core.PickCipher(cipher, nil, password) if err != nil { return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } - obfs := "" + obfs := option.Obfs obfsHost := "bing.com" - if value, ok := option["obfs"]; ok { - obfs = value - } - - if value, ok := option["obfs-host"]; ok { - obfsHost = value + if option.ObfsHost != "" { + obfsHost = option.ObfsHost } return &ShadowSocks{ server: server, - name: name, + name: option.Name, cipher: ciph, obfs: obfs, obfsHost: obfsHost, }, nil } -func parseURL(s string) (addr, cipher, password string, err error) { - u, err := url.Parse(s) - if err != nil { - return - } - - addr = u.Host - if u.User != nil { - cipher = u.User.Username() - password, _ = u.User.Password() - } - return -} - func serializesSocksAddr(metadata *C.Metadata) []byte { var buf [][]byte aType := uint8(metadata.AddrType) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index c9a6149408..1261f810ed 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -31,6 +31,12 @@ type Socks5 struct { name string } +type Socks5Option struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` +} + func (ss *Socks5) Name() string { return ss.name } @@ -82,9 +88,9 @@ func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { return nil } -func NewSocks5(name, addr string) *Socks5 { +func NewSocks5(option Socks5Option) *Socks5 { return &Socks5{ - addr: addr, - name: name, + addr: fmt.Sprintf("%s:%d", option.Server, option.Port), + name: option.Name, } } diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 751a7edf5a..c536e612f4 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -1,6 +1,7 @@ package adapters import ( + "errors" "sync" "time" @@ -16,6 +17,13 @@ type URLTest struct { done chan struct{} } +type URLTestOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` + URL string `proxy:"url"` + Delay int `proxy:"delay"` +} + func (u *URLTest) Name() string { return u.name } @@ -83,16 +91,20 @@ func (u *URLTest) speedTest() { } } -func NewURLTest(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) { - _, err := urlToMetadata(rawURL) +func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { + _, err := urlToMetadata(option.URL) if err != nil { return nil, err } + if len(proxies) < 1 { + return nil, errors.New("The number of proxies cannot be 0") + } + delay := time.Duration(option.Delay) * time.Second urlTest := &URLTest{ - name: name, + name: option.Name, proxies: proxies[:], - rawURL: rawURL, + rawURL: option.URL, fast: proxies[0], delay: delay, done: make(chan struct{}), diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 5b8c774e8d..ea8d695885 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -34,11 +34,11 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) { ExpectContinueTimeout: 1 * time.Second, } client := http.Client{Transport: transport} - req, err := client.Get(url) + resp, err := client.Get(url) if err != nil { return } - req.Body.Close() + resp.Body.Close() t = int16(time.Since(start) / time.Millisecond) return } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 4bb0817bf4..075a9fc0ae 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -30,6 +30,16 @@ type Vmess struct { client *vmess.Client } +type VmessOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + AlterID int `proxy:"alterId"` + Cipher string `proxy:"cipher"` + TLS bool `proxy:"tls,omitempty"` +} + func (ss *Vmess) Name() string { return ss.name } @@ -48,21 +58,21 @@ func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er return &VmessAdapter{conn: c}, err } -func NewVmess(name string, server string, uuid string, alterID uint16, security string, option map[string]string) (*Vmess, error) { - security = strings.ToLower(security) +func NewVmess(option VmessOption) (*Vmess, error) { + security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: uuid, - AlterID: alterID, + UUID: option.UUID, + AlterID: uint16(option.AlterID), Security: security, - TLS: option["tls"] == "true", + TLS: option.TLS, }) if err != nil { return nil, err } return &Vmess{ - name: name, - server: server, + name: option.Name, + server: fmt.Sprintf("%s:%d", option.Server, option.Port), client: client, }, nil } diff --git a/config/config.go b/config/config.go index c72ffcb2f2..ac78af2484 100644 --- a/config/config.go +++ b/config/config.go @@ -1,22 +1,21 @@ package config import ( - "bufio" "fmt" - "net/url" + "io/ioutil" "os" - "strconv" "strings" "sync" "time" "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/common/observable" + "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" R "github.com/Dreamacro/clash/rules" log "github.com/sirupsen/logrus" - "gopkg.in/ini.v1" + yaml "gopkg.in/yaml.v2" ) var ( @@ -42,6 +41,21 @@ type ProxyConfig struct { AllowLan *bool } +// RawConfig is raw config struct +type RawConfig struct { + Port int `yaml:"port"` + SocksPort int `yaml:"socks-port"` + RedirPort int `yaml:"redir-port"` + AllowLan bool `yaml:"allow-lan"` + Mode string `yaml:"mode"` + LogLevel string `yaml:"log-level"` + ExternalController string `yaml:"external-controller"` + + Proxy []map[string]interface{} `yaml:"Proxy"` + ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` + Rule []string `yaml:"Rule"` +} + // Config is clash config manager type Config struct { general *General @@ -71,17 +85,25 @@ func (c *Config) Report() chan<- interface{} { return c.reportCh } -func (c *Config) readConfig() (*ini.File, error) { +func (c *Config) readConfig() (*RawConfig, error) { if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) { return nil, err } - return ini.LoadSources( - ini.LoadOptions{ - AllowBooleanKeys: true, - UnparseableSections: []string{"Rule"}, - }, - C.ConfigPath, - ) + data, err := ioutil.ReadFile(C.ConfigPath) + if err != nil { + return nil, err + } + // config with some default value + rawConfig := &RawConfig{ + AllowLan: false, + Mode: Rule.String(), + LogLevel: C.INFO.String(), + Rule: []string{}, + Proxy: []map[string]interface{}{}, + ProxyGroup: []map[string]interface{}{}, + } + err = yaml.Unmarshal([]byte(data), &rawConfig) + return rawConfig, err } // Parse config @@ -139,15 +161,13 @@ func (c *Config) UpdateRules() error { return c.parseRules(cfg) } -func (c *Config) parseGeneral(cfg *ini.File) error { - general := cfg.Section("General") - - port := general.Key("port").RangeInt(0, 1, 65535) - socksPort := general.Key("socks-port").RangeInt(0, 1, 65535) - redirPort := general.Key("redir-port").RangeInt(0, 1, 65535) - allowLan := general.Key("allow-lan").MustBool() - logLevelString := general.Key("log-level").MustString(C.INFO.String()) - modeString := general.Key("mode").MustString(Rule.String()) +func (c *Config) parseGeneral(cfg *RawConfig) error { + port := cfg.Port + socksPort := cfg.SocksPort + redirPort := cfg.RedirPort + allowLan := cfg.AllowLan + logLevelString := cfg.LogLevel + modeString := cfg.Mode mode, exist := ModeMapping[modeString] if !exist { @@ -168,7 +188,7 @@ func (c *Config) parseGeneral(cfg *ini.File) error { LogLevel: logLevel, } - if restAddr := general.Key("external-controller").String(); restAddr != "" { + if restAddr := cfg.ExternalController; restAddr != "" { c.event <- &Event{Type: "external-controller", Payload: restAddr} } @@ -210,129 +230,128 @@ func (c *Config) UpdateProxy(pc ProxyConfig) { } } -func (c *Config) parseProxies(cfg *ini.File) error { +func (c *Config) parseProxies(cfg *RawConfig) error { proxies := make(map[string]C.Proxy) - proxiesConfig := cfg.Section("Proxy") - groupsConfig := cfg.Section("Proxy Group") + proxiesConfig := cfg.Proxy + groupsConfig := cfg.ProxyGroup + + decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) + + proxies["DIRECT"] = adapters.NewDirect() + proxies["REJECT"] = adapters.NewReject() // parse proxy - for _, key := range proxiesConfig.Keys() { - proxy := key.Strings(",") - if len(proxy) == 0 { - continue + for idx, mapping := range proxiesConfig { + proxyType, existType := mapping["type"].(string) + proxyName, existName := mapping["name"].(string) + if !existType && existName { + return fmt.Errorf("Proxy %d missing type or name", idx) } - switch proxy[0] { - // ss, server, port, cipter, password + + if _, exist := proxies[proxyName]; exist { + return fmt.Errorf("Proxy %s is the duplicate name", proxyName) + } + var proxy C.Proxy + var err error + switch proxyType { case "ss": - if len(proxy) < 5 { - continue - } - ssURL := url.URL{ - Scheme: "ss", - User: url.UserPassword(proxy[3], proxy[4]), - Host: fmt.Sprintf("%s:%s", proxy[1], proxy[2]), - } - option := parseOptions(5, proxy...) - ss, err := adapters.NewShadowSocks(key.Name(), ssURL.String(), option) + ssOption := &adapters.ShadowSocksOption{} + err = decoder.Decode(mapping, ssOption) if err != nil { - return err + break } - proxies[key.Name()] = ss - // socks5, server, port + proxy, err = adapters.NewShadowSocks(*ssOption) case "socks5": - if len(proxy) < 3 { - continue - } - addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2]) - socks5 := adapters.NewSocks5(key.Name(), addr) - proxies[key.Name()] = socks5 - // vmess, server, port, uuid, alterId, security - case "vmess": - if len(proxy) < 6 { - continue - } - addr := fmt.Sprintf("%s:%s", proxy[1], proxy[2]) - alterID, err := strconv.Atoi(proxy[4]) + socksOption := &adapters.Socks5Option{} + err = decoder.Decode(mapping, socksOption) if err != nil { - return err + break } - option := parseOptions(6, proxy...) - vmess, err := adapters.NewVmess(key.Name(), addr, proxy[3], uint16(alterID), proxy[5], option) + proxy = adapters.NewSocks5(*socksOption) + case "vmess": + vmessOption := &adapters.VmessOption{} + err = decoder.Decode(mapping, vmessOption) if err != nil { - return err + break } - proxies[key.Name()] = vmess + proxy, err = adapters.NewVmess(*vmessOption) + default: + return fmt.Errorf("Unsupport proxy type: %s", proxyType) } + if err != nil { + return fmt.Errorf("Proxy %s: %s", proxyName, err.Error()) + } + proxies[proxyName] = proxy } // parse proxy group - for _, key := range groupsConfig.Keys() { - rule := strings.Split(key.Value(), ",") - rule = trimArr(rule) - switch rule[0] { + for idx, mapping := range groupsConfig { + groupType, existType := mapping["type"].(string) + groupName, existName := mapping["name"].(string) + if !existType && existName { + return fmt.Errorf("ProxyGroup %d: missing type or name", idx) + } + + if _, exist := proxies[groupName]; exist { + return fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) + } + var group C.Proxy + var err error + switch groupType { case "url-test": - if len(rule) < 4 { - return fmt.Errorf("URLTest need more than 4 param") + urlTestOption := &adapters.URLTestOption{} + err = decoder.Decode(mapping, urlTestOption) + if err != nil { + break } - proxyNames := rule[1 : len(rule)-2] - delay, _ := strconv.Atoi(rule[len(rule)-1]) - url := rule[len(rule)-2] + var ps []C.Proxy - for _, name := range proxyNames { - if p, ok := proxies[name]; ok { - ps = append(ps, p) + for _, name := range urlTestOption.Proxies { + p, ok := proxies[name] + if !ok { + return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) } + ps = append(ps, p) } - - adapter, err := adapters.NewURLTest(key.Name(), ps, url, time.Duration(delay)*time.Second) - if err != nil { - return fmt.Errorf("Config error: %s", err.Error()) - } - proxies[key.Name()] = adapter + group, err = adapters.NewURLTest(*urlTestOption, ps) case "select": - if len(rule) < 2 { - return fmt.Errorf("Selector need more than 2 param") + selectorOption := &adapters.SelectorOption{} + err = decoder.Decode(mapping, selectorOption) + if err != nil { + break } - proxyNames := rule[1:] selectProxy := make(map[string]C.Proxy) - for _, name := range proxyNames { + for _, name := range selectorOption.Proxies { proxy, exist := proxies[name] if !exist { - return fmt.Errorf("Proxy %s not exist", name) + return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) } selectProxy[name] = proxy } - selector, err := adapters.NewSelector(key.Name(), selectProxy) - if err != nil { - return fmt.Errorf("Selector create error: %s", err.Error()) - } - proxies[key.Name()] = selector + group, err = adapters.NewSelector(selectorOption.Name, selectProxy) case "fallback": - if len(rule) < 4 { - return fmt.Errorf("URLTest need more than 4 param") + fallbackOption := &adapters.FallbackOption{} + err = decoder.Decode(mapping, fallbackOption) + if err != nil { + break } - proxyNames := rule[1 : len(rule)-2] - delay, _ := strconv.Atoi(rule[len(rule)-1]) - url := rule[len(rule)-2] var ps []C.Proxy - for _, name := range proxyNames { - if p, ok := proxies[name]; ok { - ps = append(ps, p) + for _, name := range fallbackOption.Proxies { + p, ok := proxies[name] + if !ok { + return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) } + ps = append(ps, p) } - - adapter, err := adapters.NewFallback(key.Name(), ps, url, time.Duration(delay)*time.Second) - if err != nil { - return fmt.Errorf("Config error: %s", err.Error()) - } - proxies[key.Name()] = adapter + group, err = adapters.NewFallback(*fallbackOption, ps) + } + if err != nil { + return fmt.Errorf("Proxy %s: %s", groupName, err.Error()) } + proxies[groupName] = group } - // init proxy proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", proxies) - proxies["DIRECT"] = adapters.NewDirect() - proxies["REJECT"] = adapters.NewReject() // close old goroutine for _, proxy := range c.proxies { @@ -348,19 +367,13 @@ func (c *Config) parseProxies(cfg *ini.File) error { return nil } -func (c *Config) parseRules(cfg *ini.File) error { +func (c *Config) parseRules(cfg *RawConfig) error { rules := []C.Rule{} - rulesConfig := cfg.Section("Rule") + rulesConfig := cfg.Rule // parse rules - reader := bufio.NewReader(strings.NewReader(rulesConfig.Body())) - for { - line, _, err := reader.ReadLine() - if err != nil { - break - } - - rule := strings.Split(string(line), ",") + for _, line := range rulesConfig { + rule := strings.Split(line, ",") if len(rule) < 3 { continue } diff --git a/config/utils.go b/config/utils.go index b0cec2453e..e196394bbe 100644 --- a/config/utils.go +++ b/config/utils.go @@ -27,20 +27,3 @@ func or(pointers ...*int) *int { } return pointers[len(pointers)-1] } - -func parseOptions(startIdx int, params ...string) map[string]string { - mapping := make(map[string]string) - if len(params) <= startIdx { - return mapping - } - - for _, option := range params[startIdx:] { - pair := strings.SplitN(option, "=", 2) - if len(pair) != 2 { - continue - } - - mapping[strings.Trim(pair[0], " ")] = strings.Trim(pair[1], " ") - } - return mapping -} diff --git a/constant/config.go b/constant/config.go index 90df19ba63..0c76091903 100644 --- a/constant/config.go +++ b/constant/config.go @@ -46,6 +46,6 @@ func init() { } } - ConfigPath = path.Join(dirPath, "config.ini") + ConfigPath = path.Join(dirPath, "config.yml") MMDBPath = path.Join(dirPath, "Country.mmdb") } diff --git a/go.mod b/go.mod index 4cb65dd509..5192bf8e52 100644 --- a/go.mod +++ b/go.mod @@ -13,5 +13,5 @@ require ( github.com/sirupsen/logrus v1.1.0 golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 gopkg.in/eapache/channels.v1 v1.1.0 - gopkg.in/ini.v1 v1.38.3 + gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 2797031941..c5fdb2d5eb 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,7 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.1 h1:Z6Z1ZQFtIKqB3ZghASl4taaJmL7SOw+Kp github.com/Dreamacro/go-shadowsocks2 v0.1.1/go.mod h1:6Fuc8zRHwXqCV9Xaw9qNfrh6OUYpDGrlPVPW4oQ34Gs= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= +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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -13,21 +14,26 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= +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/sirupsen/logrus v1.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU= github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= -gopkg.in/ini.v1 v1.38.3 h1:ourkRZgR6qjJYoec9lYhX4+nuN1tEbV34dQEQ3IRk9U= -gopkg.in/ini.v1 v1.38.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From ea9cd41197788f520d15dabfb2a6bae7aab53f75 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 3 Oct 2018 12:25:24 +0800 Subject: [PATCH 049/535] Chore: update README.md --- README.md | 72 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index c2153e9115..2d640d74c0 100644 --- a/README.md +++ b/README.md @@ -62,51 +62,69 @@ If you have Docker installed, you can run clash directly using `docker-compose`. ## Config -Configuration file at `$HOME/.config/clash/config.ini` +**NOTE: after v0.7.1, clash using yaml as configuration file** + +Configuration file at `$HOME/.config/clash/config.yml` Below is a simple demo configuration file: -```ini -[General] -port = 7890 -socks-port = 7891 +```yml +# port of HTTP +port: 7890 + +# port of SOCKS5 +socks-port: 7891 # redir proxy for Linux and macOS -redir-port = 7892 +redir-port: 7892 + +allow-lan: false + +# Rule / Global/ Direct +mode: Rule + +# set log level to stdout (default is info) +# info / warning / error / debug +log-level: info # A RESTful API for clash -external-controller = 127.0.0.1:8080 +external-controller: 127.0.0.1:9090 + +Proxy: -[Proxy] -# name = ss, server, port, cipher, password(, obfs=tls/http, obfs-host=bing.com) +# shadowsocks # The types of cipher are consistent with go-shadowsocks2 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 -ss1 = ss, server1, port, AEAD_CHACHA20_POLY1305, password -ss2 = ss, server2, port, AEAD_CHACHA20_POLY1305, password +# after v0.7.1 clash support chacha20 rc4-md5 +- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" } +- { name: "ss2", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", obfs: tls, obfs-host: bing.com } -# name = vmess, server, port, uuid, alterId, cipher(, tls=true) +# vmess # cipher support auto/aes-128-gcm/chacha20-poly1305/none -vmess1 = vmess, server, port, uuid, 32, auto, tls=true +- { name: "vmess1", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto } +- { name: "vmess2", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } -# name = socks5, server, port -socks = socks5, server, port +# socks5 +- { name: "socks", type: socks5, server: server, port: 443 } -[Proxy Group] +Proxy Group: # url-test select which proxy will be used by benchmarking speed to a URL. -# name = url-test, [proxies], url, interval(second) -auto = url-test, ss1, ss2, http://www.google.com/generate_204, 300 +- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, delay: 300 } + +# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. +- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, delay: 300 } # select is used for selecting proxy or proxy group # you can use RESTful API to switch proxy, is recommended for use in GUI. -# name = select, [proxies] -Proxy = select, ss1, ss2, auto - -[Rule] -DOMAIN-SUFFIX,google.com,Proxy -DOMAIN-KEYWORD,google,Proxy -DOMAIN-SUFFIX,ad.com,REJECT -GEOIP,CN,DIRECT -FINAL,,Proxy # note: there is two "," +- { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] } + +Rule: +- DOMAIN-SUFFIX,google.com,Proxy +- DOMAIN-KEYWORD,google,Proxy +- DOMAIN-SUFFIX,ad.com,REJECT +- GEOIP,CN,DIRECT +# note: there is two "," +- FINAL,,Proxy ``` ## Thanks From 8fff0968bfaadcf04adbe7c978d67eb24ccb42a3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 6 Oct 2018 13:15:02 +0800 Subject: [PATCH 050/535] Feature: add authorization for API --- config/config.go | 4 +++- hub/server.go | 41 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index ac78af2484..355b3f7c29 100644 --- a/config/config.go +++ b/config/config.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/adapters/outbound" + adapters "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/common/observable" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" @@ -50,6 +50,7 @@ type RawConfig struct { Mode string `yaml:"mode"` LogLevel string `yaml:"log-level"` ExternalController string `yaml:"external-controller"` + Secret string `yaml:"secret"` Proxy []map[string]interface{} `yaml:"Proxy"` ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` @@ -190,6 +191,7 @@ func (c *Config) parseGeneral(cfg *RawConfig) error { if restAddr := cfg.ExternalController; restAddr != "" { c.event <- &Event{Type: "external-controller", Payload: restAddr} + c.event <- &Event{Type: "secret", Payload: cfg.Secret} } c.UpdateGeneral(*c.general) diff --git a/hub/server.go b/hub/server.go index cb15006737..b85b9c2fa1 100644 --- a/hub/server.go +++ b/hub/server.go @@ -3,6 +3,7 @@ package hub import ( "encoding/json" "net/http" + "strings" "time" "github.com/Dreamacro/clash/config" @@ -15,6 +16,8 @@ import ( log "github.com/sirupsen/logrus" ) +var secret = "" + type Traffic struct { Up int64 `json:"up"` Down int64 `json:"down"` @@ -24,11 +27,19 @@ func newHub(signal chan struct{}) { var addr string ch := config.Instance().Subscribe() signal <- struct{}{} + count := 0 for { elm := <-ch event := elm.(*config.Event) - if event.Type == "external-controller" { + switch event.Type { + case "external-controller": addr = event.Payload.(string) + count++ + case "secret": + secret = event.Payload.(string) + count++ + } + if count == 2 { break } } @@ -38,11 +49,11 @@ func newHub(signal chan struct{}) { cors := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, - AllowedHeaders: []string{"Content-Type"}, + AllowedHeaders: []string{"Content-Type", "Authorization"}, MaxAge: 300, }) - r.Use(cors.Handler) + r.Use(cors.Handler, authentication) r.With(jsonContentType).Get("/traffic", traffic) r.With(jsonContentType).Get("/logs", getLogs) @@ -65,6 +76,30 @@ func jsonContentType(next http.Handler) http.Handler { return http.HandlerFunc(fn) } +func authentication(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + header := r.Header.Get("Authorization") + text := strings.SplitN(header, " ", 2) + + if secret == "" { + next.ServeHTTP(w, r) + return + } + + hasUnvalidHeader := text[0] != "Bearer" + hasUnvalidSecret := len(text) == 2 && text[1] != secret + if hasUnvalidHeader || hasUnvalidSecret { + w.WriteHeader(http.StatusUnauthorized) + render.JSON(w, r, Error{ + Error: "Authentication failed", + }) + return + } + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} + func traffic(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) From 7b803778495bba11ff8ed6c0f9f45a56ec789937 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 6 Oct 2018 14:17:50 +0800 Subject: [PATCH 051/535] Fix: return the first proxy when no proxy valid --- adapters/outbound/fallback.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 224bf91fc7..7057d0238c 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -60,7 +60,7 @@ func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err } return } - return nil, errors.New("There are no valid proxy") + return f.proxies[0].RawProxy.Generator(metadata) } func (f *Fallback) Close() { From a0f077b0911f52042c9289505af997d193f80b95 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 6 Oct 2018 14:19:06 +0800 Subject: [PATCH 052/535] Improve: get /rules return proxy now --- hub/rules.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hub/rules.go b/hub/rules.go index b8780fc1e7..96ddc26c19 100644 --- a/hub/rules.go +++ b/hub/rules.go @@ -17,6 +17,7 @@ func ruleRouter() http.Handler { type Rule struct { Name string `json:"name"` Payload string `json:"type"` + Proxy string `json:"proxy"` } type GetRulesResponse struct { @@ -31,6 +32,7 @@ func getRules(w http.ResponseWriter, r *http.Request) { rules = append(rules, Rule{ Name: rule.RuleType().String(), Payload: rule.Payload(), + Proxy: rule.Adapter(), }) } From 40a94be2089dc75da45088bae218fe672f4ab1da Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 6 Oct 2018 15:13:44 +0800 Subject: [PATCH 053/535] Fix: rename delay --> interval --- README.md | 4 ++-- adapters/outbound/fallback.go | 32 ++++++++++++++--------------- adapters/outbound/urltest.go | 38 +++++++++++++++++------------------ 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 2d640d74c0..48d0537dbb 100644 --- a/README.md +++ b/README.md @@ -109,10 +109,10 @@ Proxy: Proxy Group: # url-test select which proxy will be used by benchmarking speed to a URL. -- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, delay: 300 } +- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. -- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, delay: 300 } +- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } # select is used for selecting proxy or proxy group # you can use RESTful API to switch proxy, is recommended for use in GUI. diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 7057d0238c..e858d0ab09 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -14,18 +14,18 @@ type proxy struct { } type Fallback struct { - name string - proxies []*proxy - rawURL string - delay time.Duration - done chan struct{} + name string + proxies []*proxy + rawURL string + interval time.Duration + done chan struct{} } type FallbackOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` - URL string `proxy:"url"` - Delay int `proxy:"delay"` + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` + URL string `proxy:"url"` + Interval int `proxy:"interval"` } func (f *Fallback) Name() string { @@ -68,7 +68,7 @@ func (f *Fallback) Close() { } func (f *Fallback) loop() { - tick := time.NewTicker(f.delay) + tick := time.NewTicker(f.interval) go f.validTest() Loop: for { @@ -115,7 +115,7 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { return nil, errors.New("The number of proxies cannot be 0") } - delay := time.Duration(option.Delay) * time.Second + interval := time.Duration(option.Interval) * time.Second warpperProxies := make([]*proxy, len(proxies)) for idx := range proxies { warpperProxies[idx] = &proxy{ @@ -125,11 +125,11 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { } Fallback := &Fallback{ - name: option.Name, - proxies: warpperProxies, - rawURL: option.URL, - delay: delay, - done: make(chan struct{}), + name: option.Name, + proxies: warpperProxies, + rawURL: option.URL, + interval: interval, + done: make(chan struct{}), } go Fallback.loop() return Fallback, nil diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index c536e612f4..df2be060d2 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -9,19 +9,19 @@ import ( ) type URLTest struct { - name string - proxies []C.Proxy - rawURL string - fast C.Proxy - delay time.Duration - done chan struct{} + name string + proxies []C.Proxy + rawURL string + fast C.Proxy + interval time.Duration + done chan struct{} } type URLTestOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` - URL string `proxy:"url"` - Delay int `proxy:"delay"` + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` + URL string `proxy:"url"` + Interval int `proxy:"interval"` } func (u *URLTest) Name() string { @@ -45,7 +45,7 @@ func (u *URLTest) Close() { } func (u *URLTest) loop() { - tick := time.NewTicker(u.delay) + tick := time.NewTicker(u.interval) go u.speedTest() Loop: for { @@ -63,7 +63,7 @@ func (u *URLTest) speedTest() { wg.Add(len(u.proxies)) c := make(chan interface{}) fast := selectFast(c) - timer := time.NewTimer(u.delay) + timer := time.NewTimer(u.interval) for _, p := range u.proxies { go func(p C.Proxy) { @@ -100,14 +100,14 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { return nil, errors.New("The number of proxies cannot be 0") } - delay := time.Duration(option.Delay) * time.Second + interval := time.Duration(option.Interval) * time.Second urlTest := &URLTest{ - name: option.Name, - proxies: proxies[:], - rawURL: option.URL, - fast: proxies[0], - delay: delay, - done: make(chan struct{}), + name: option.Name, + proxies: proxies[:], + rawURL: option.URL, + fast: proxies[0], + interval: interval, + done: make(chan struct{}), } go urlTest.loop() return urlTest, nil From 64e37916543f20bccda7e98c2b91ef70fedceda1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 10 Oct 2018 12:05:02 +0800 Subject: [PATCH 054/535] Improve: better chacha20 implementation --- go.mod | 5 ++--- go.sum | 14 ++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 5192bf8e52..9bcc35c089 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,7 @@ module github.com/Dreamacro/clash require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.1 - github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect + github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181010040219-acb54ea41fbd github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v3.3.3+incompatible github.com/go-chi/cors v1.0.0 @@ -11,7 +10,7 @@ require ( github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/sirupsen/logrus v1.1.0 - golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 + golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index c5fdb2d5eb..a79997327f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.1 h1:Z6Z1ZQFtIKqB3ZghASl4taaJmL7SOw+KpJ+QZpax+TI= -github.com/Dreamacro/go-shadowsocks2 v0.1.1/go.mod h1:6Fuc8zRHwXqCV9Xaw9qNfrh6OUYpDGrlPVPW4oQ34Gs= -github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= -github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= +github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181010040219-acb54ea41fbd h1:6HucPDdF32vx1b9Z+XVC8cNJuCCkh0eTS20GmKdwOec= +github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181010040219-acb54ea41fbd/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= @@ -27,10 +27,12 @@ github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= -golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= From 381f76450760df3d2e5d7220ff05cf0ab7bb27b1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 14 Oct 2018 21:22:58 +0800 Subject: [PATCH 055/535] Chore: refactoring code of config path --- config/config.go | 9 +++++++-- config/initial.go | 15 ++++++++++---- constant/config.go | 41 -------------------------------------- constant/path.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ rules/geoip.go | 2 +- 5 files changed, 68 insertions(+), 48 deletions(-) create mode 100644 constant/path.go diff --git a/config/config.go b/config/config.go index 355b3f7c29..4d6b87fafd 100644 --- a/config/config.go +++ b/config/config.go @@ -87,13 +87,18 @@ func (c *Config) Report() chan<- interface{} { } func (c *Config) readConfig() (*RawConfig, error) { - if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) { + if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { return nil, err } - data, err := ioutil.ReadFile(C.ConfigPath) + data, err := ioutil.ReadFile(C.Path.Config()) if err != nil { return nil, err } + + if len(data) == 0 { + return nil, fmt.Errorf("Configuration file %s is empty", C.Path.Config()) + } + // config with some default value rawConfig := &RawConfig{ AllowLan: false, diff --git a/config/initial.go b/config/initial.go index b13eefd0ae..62f6357003 100644 --- a/config/initial.go +++ b/config/initial.go @@ -55,16 +55,23 @@ func downloadMMDB(path string) (err error) { // Init prepare necessary files func Init() { + // initial homedir + if _, err := os.Stat(C.Path.HomeDir()); os.IsNotExist(err) { + if err := os.MkdirAll(C.Path.HomeDir(), 0777); err != nil { + log.Fatalf("Can't create config directory %s: %s", C.Path.HomeDir(), err.Error()) + } + } + // initial config.ini - if _, err := os.Stat(C.ConfigPath); os.IsNotExist(err) { + if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { log.Info("Can't find config, create a empty file") - os.OpenFile(C.ConfigPath, os.O_CREATE|os.O_WRONLY, 0644) + os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) } // initial mmdb - if _, err := os.Stat(C.MMDBPath); os.IsNotExist(err) { + if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { log.Info("Can't find MMDB, start download") - err := downloadMMDB(C.MMDBPath) + err := downloadMMDB(C.Path.MMDB()) if err != nil { log.Fatalf("Can't download MMDB: %s", err.Error()) } diff --git a/constant/config.go b/constant/config.go index 0c76091903..f0b93fe236 100644 --- a/constant/config.go +++ b/constant/config.go @@ -1,23 +1,5 @@ package constant -import ( - "os" - "os/user" - "path" - - log "github.com/sirupsen/logrus" -) - -const ( - Name = "clash" -) - -var ( - HomeDir string - ConfigPath string - MMDBPath string -) - type General struct { Mode *string `json:"mode,omitempty"` AllowLan *bool `json:"allow-lan,omitempty"` @@ -26,26 +8,3 @@ type General struct { RedirPort *int `json:"redir-port,omitempty"` LogLevel *string `json:"log-level,omitempty"` } - -func init() { - currentUser, err := user.Current() - if err != nil { - dir := os.Getenv("HOME") - if dir == "" { - log.Fatalf("Can't get current user: %s", err.Error()) - } - HomeDir = dir - } else { - HomeDir = currentUser.HomeDir - } - - dirPath := path.Join(HomeDir, ".config", Name) - if _, err := os.Stat(dirPath); os.IsNotExist(err) { - if err := os.MkdirAll(dirPath, 0777); err != nil { - log.Fatalf("Can't create config directory %s: %s", dirPath, err.Error()) - } - } - - ConfigPath = path.Join(dirPath, "config.yml") - MMDBPath = path.Join(dirPath, "Country.mmdb") -} diff --git a/constant/path.go b/constant/path.go new file mode 100644 index 0000000000..ad7b847ded --- /dev/null +++ b/constant/path.go @@ -0,0 +1,49 @@ +package constant + +import ( + "os" + "os/user" + P "path" +) + +const Name = "clash" + +// Path is used to get the configuration path +var Path *path + +type path struct { + homedir string +} + +func init() { + currentUser, err := user.Current() + var homedir string + if err != nil { + dir := os.Getenv("HOME") + if dir == "" { + dir, _ = os.Getwd() + } + homedir = dir + } else { + homedir = currentUser.HomeDir + } + homedir = P.Join(homedir, ".config", Name) + Path = &path{homedir: homedir} +} + +// SetHomeDir is used to set the configuration path +func SetHomeDir(root string) { + Path = &path{homedir: root} +} + +func (p *path) HomeDir() string { + return p.homedir +} + +func (p *path) Config() string { + return P.Join(p.homedir, "config.yml") +} + +func (p *path) MMDB() string { + return P.Join(p.homedir, "Country.mmdb") +} diff --git a/rules/geoip.go b/rules/geoip.go index ff52ff36a7..5fb9a1f7f8 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -42,7 +42,7 @@ func (g *GEOIP) Payload() string { func NewGEOIP(country string, adapter string) *GEOIP { once.Do(func() { var err error - mmdb, err = geoip2.Open(C.MMDBPath) + mmdb, err = geoip2.Open(C.Path.MMDB()) if err != nil { log.Fatalf("Can't load mmdb: %s", err.Error()) } From b68af433894e0abdf9965b47ba2310b76cc5ed73 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 16 Oct 2018 13:12:36 +0800 Subject: [PATCH 056/535] Improve: support custom configuration directory --- main.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/main.go b/main.go index 57e834c215..7273c4d592 100644 --- a/main.go +++ b/main.go @@ -4,20 +4,40 @@ import ( "os" "os/signal" "syscall" + "flag" + "path" "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/tunnel" + C "github.com/Dreamacro/clash/constant" log "github.com/sirupsen/logrus" ) +var ( + homedir string +) + +func init() { + flag.StringVar(&homedir, "d", "", "set configuration directory") + flag.Parse() +} + func main() { tunnel.Instance().Run() proxy.Instance().Run() hub.Run() + if (homedir != "") { + if !path.IsAbs(homedir) { + currentDir, _ := os.Getwd() + homedir = path.Join(currentDir, homedir) + } + C.SetHomeDir(homedir) + } + config.Init() err := config.Instance().Parse() if err != nil { From e863c66ad978a5bf5063ce2a0daf48ebb4e712ed Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 15 Oct 2018 09:42:34 +0800 Subject: [PATCH 057/535] Fix: a typo error --- rules/{domian.go => domain.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rules/{domian.go => domain.go} (100%) diff --git a/rules/domian.go b/rules/domain.go similarity index 100% rename from rules/domian.go rename to rules/domain.go From 0fd2f8e5ee7631688499af22a0ecdebbfce2f9c8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 16 Oct 2018 21:29:29 +0800 Subject: [PATCH 058/535] Chore: update ss-lib for xchacha20-ietf-poly1305 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9bcc35c089..837abb9373 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/Dreamacro/clash require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181010040219-acb54ea41fbd + github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181016063207-89bf7cffdaf4 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v3.3.3+incompatible github.com/go-chi/cors v1.0.0 diff --git a/go.sum b/go.sum index a79997327f..a23ffeb771 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181010040219-acb54ea41fbd h1:6HucPDdF32vx1b9Z+XVC8cNJuCCkh0eTS20GmKdwOec= -github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181010040219-acb54ea41fbd/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps= +github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181016063207-89bf7cffdaf4 h1:n+F4LoHdFCwsGohVN+4dwfmmx0h2uvdPEeiwdhFeH8g= +github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181016063207-89bf7cffdaf4/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= From ce07eda42837f79a1f1a5e742e099bcf7fd37975 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 17 Oct 2018 00:51:04 +0800 Subject: [PATCH 059/535] Chore: update README.md --- README.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 48d0537dbb..7593daad6a 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,19 @@ If you have Docker installed, you can run clash directly using `docker-compose`. ## Config -**NOTE: after v0.7.1, clash using yaml as configuration file** +**NOTE: after v0.8.0, clash using yaml as configuration file** -Configuration file at `$HOME/.config/clash/config.yml` +The default configuration directory is `$HOME/.config/clash` + +The name of the configuration file is `config.yml` + +If you want to use another directory, you can use `-d` to control the configuration directory + +For example, you can use the current directory as the configuration directory + +```sh +clash -d . +``` Below is a simple demo configuration file: @@ -76,11 +86,11 @@ port: 7890 socks-port: 7891 # redir proxy for Linux and macOS -redir-port: 7892 +# redir-port: 7892 allow-lan: false -# Rule / Global/ Direct +# Rule / Global/ Direct (default is Rule) mode: Rule # set log level to stdout (default is info) @@ -90,12 +100,15 @@ log-level: info # A RESTful API for clash external-controller: 127.0.0.1:9090 +# Secret for RESTful API (Optional) +secret: "" + Proxy: # shadowsocks # The types of cipher are consistent with go-shadowsocks2 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 -# after v0.7.1 clash support chacha20 rc4-md5 +# In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305 - { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" } - { name: "ss2", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", obfs: tls, obfs-host: bing.com } From 94d1972782867966fc5c707e4c45c506533cca9b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 18 Oct 2018 23:24:04 +0800 Subject: [PATCH 060/535] Fix: selector no longer randomly selects --- adapters/outbound/selector.go | 11 +++++----- config/config.go | 41 +++++++++++++++-------------------- config/utils.go | 14 ++++++++++++ 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index ee5473445c..1f920ae1c7 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -52,21 +52,20 @@ func (s *Selector) Set(name string) error { return nil } -func NewSelector(name string, proxies map[string]C.Proxy) (*Selector, error) { +func NewSelector(name string, proxies []C.Proxy) (*Selector, error) { if len(proxies) == 0 { return nil, errors.New("Provide at least one proxy") } mapping := make(map[string]C.Proxy) - var init string - for k, v := range proxies { - mapping[k] = v - init = k + for _, proxy := range proxies { + mapping[proxy.Name()] = proxy } + s := &Selector{ name: name, proxies: mapping, - selected: proxies[init], + selected: proxies[0], } return s, nil } diff --git a/config/config.go b/config/config.go index 4d6b87fafd..40e27c3ca6 100644 --- a/config/config.go +++ b/config/config.go @@ -312,13 +312,9 @@ func (c *Config) parseProxies(cfg *RawConfig) error { break } - var ps []C.Proxy - for _, name := range urlTestOption.Proxies { - p, ok := proxies[name] - if !ok { - return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) - } - ps = append(ps, p) + ps, err := getProxies(proxies, urlTestOption.Proxies) + if err != nil { + return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } group, err = adapters.NewURLTest(*urlTestOption, ps) case "select": @@ -327,28 +323,22 @@ func (c *Config) parseProxies(cfg *RawConfig) error { if err != nil { break } - selectProxy := make(map[string]C.Proxy) - for _, name := range selectorOption.Proxies { - proxy, exist := proxies[name] - if !exist { - return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) - } - selectProxy[name] = proxy + + ps, err := getProxies(proxies, selectorOption.Proxies) + if err != nil { + return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } - group, err = adapters.NewSelector(selectorOption.Name, selectProxy) + group, err = adapters.NewSelector(selectorOption.Name, ps) case "fallback": fallbackOption := &adapters.FallbackOption{} err = decoder.Decode(mapping, fallbackOption) if err != nil { break } - var ps []C.Proxy - for _, name := range fallbackOption.Proxies { - p, ok := proxies[name] - if !ok { - return fmt.Errorf("ProxyGroup %s: proxy or proxy group '%s' not found", groupName, name) - } - ps = append(ps, p) + + ps, err := getProxies(proxies, fallbackOption.Proxies) + if err != nil { + return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } group, err = adapters.NewFallback(*fallbackOption, ps) } @@ -358,7 +348,12 @@ func (c *Config) parseProxies(cfg *RawConfig) error { proxies[groupName] = group } - proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", proxies) + var ps []C.Proxy + for _, v := range proxies { + ps = append(ps, v) + } + + proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps) // close old goroutine for _, proxy := range c.proxies { diff --git a/config/utils.go b/config/utils.go index e196394bbe..ca5a772331 100644 --- a/config/utils.go +++ b/config/utils.go @@ -3,6 +3,8 @@ package config import ( "fmt" "strings" + + C "github.com/Dreamacro/clash/constant" ) func trimArr(arr []string) (r []string) { @@ -19,6 +21,18 @@ func genAddr(port int, allowLan bool) string { return fmt.Sprintf("127.0.0.1:%d", port) } +func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { + var ps []C.Proxy + for _, name := range list { + p, ok := mapping[name] + if !ok { + return nil, fmt.Errorf("'%s' not found", name) + } + ps = append(ps, p) + } + return ps, nil +} + func or(pointers ...*int) *int { for _, p := range pointers { if p != nil { From 82343c70e9943a0c602cd8a7e3db773057e7ded2 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 19 Oct 2018 19:11:26 +0800 Subject: [PATCH 061/535] Fix: chacha20 cipher in shadowsocks --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 837abb9373..f3a32a09ed 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/Dreamacro/clash require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181016063207-89bf7cffdaf4 + github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v3.3.3+incompatible github.com/go-chi/cors v1.0.0 diff --git a/go.sum b/go.sum index a23ffeb771..a8a3132c1a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181016063207-89bf7cffdaf4 h1:n+F4LoHdFCwsGohVN+4dwfmmx0h2uvdPEeiwdhFeH8g= -github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181016063207-89bf7cffdaf4/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps= +github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270 h1:ugkI+Yw5ArFnhF8KTbJxyWIyvxMCa8jWyUF+wIAulhM= +github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= From 1235c9a939e2a0c8c1608afb3f036a314b4bd174 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 19 Oct 2018 20:28:19 +0800 Subject: [PATCH 062/535] Fix: Direct & Reject name --- adapters/outbound/direct.go | 2 +- adapters/outbound/reject.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 70a1d64805..4a8a10e34c 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -24,7 +24,7 @@ func (d *DirectAdapter) Conn() net.Conn { type Direct struct{} func (d *Direct) Name() string { - return "Direct" + return "DIRECT" } func (d *Direct) Type() C.AdapterType { diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index 3bc8bdf30a..deb36e7147 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -25,7 +25,7 @@ type Reject struct { } func (r *Reject) Name() string { - return "Reject" + return "REJECT" } func (r *Reject) Type() C.AdapterType { From 4895bcefca18ef7d4fd85e88ba521364bcd40985 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 21 Oct 2018 20:28:40 +0800 Subject: [PATCH 063/535] Optimization: reduce the memory of each TCP relay --- tunnel/connection.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 3b69e50628..29061eb39c 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -5,12 +5,22 @@ import ( "io" "net" "net/http" + "sync" "time" "github.com/Dreamacro/clash/adapters/inbound" C "github.com/Dreamacro/clash/constant" ) +const ( + // io.Copy default buffer size is 32 KiB + // but the maximum packet size of vmess/shadowsocks is about 16 KiB + // so define a buffer of 20 KiB to reduce the memory of each TCP relay + bufferSize = 20 * 1024 +) + +var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} + func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) { conn := newTrafficTrack(proxy.Conn(), t.traffic) req := request.R @@ -66,12 +76,16 @@ func relay(leftConn, rightConn net.Conn) { ch := make(chan error) go func() { - _, err := io.Copy(leftConn, rightConn) + buf := bufPool.Get().([]byte) + _, err := io.CopyBuffer(leftConn, rightConn, buf) + bufPool.Put(buf[:cap(buf)]) leftConn.SetReadDeadline(time.Now()) ch <- err }() - io.Copy(rightConn, leftConn) + buf := bufPool.Get().([]byte) + io.CopyBuffer(rightConn, leftConn, buf) + bufPool.Put(buf[:cap(buf)]) rightConn.SetReadDeadline(time.Now()) <-ch } From 082d3bbf04677f9d148778766c2fccd181f3c45a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 22 Oct 2018 21:14:22 +0800 Subject: [PATCH 064/535] Chore: adjust dial tcp timeout --- adapters/outbound/direct.go | 2 +- adapters/outbound/shadowsocks.go | 2 +- adapters/outbound/socks5.go | 2 +- adapters/outbound/util.go | 4 ++++ adapters/outbound/vmess.go | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 4a8a10e34c..70a3f9eb51 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -32,7 +32,7 @@ func (d *Direct) Type() C.AdapterType { } func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - c, err := net.Dial("tcp", net.JoinHostPort(metadata.String(), metadata.Port)) + c, err := net.DialTimeout("tcp", net.JoinHostPort(metadata.String(), metadata.Port), tcpTimeout) if err != nil { return } diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 55056359f4..3943c060f7 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -54,7 +54,7 @@ func (ss *ShadowSocks) Type() C.AdapterType { } func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - c, err := net.Dial("tcp", ss.server) + c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", ss.server) } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 1261f810ed..4fe1cfe1d4 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -46,7 +46,7 @@ func (ss *Socks5) Type() C.AdapterType { } func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - c, err := net.Dial("tcp", ss.addr) + c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", ss.addr) } diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index ea8d695885..f1292546dd 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -10,6 +10,10 @@ import ( C "github.com/Dreamacro/clash/constant" ) +const ( + tcpTimeout = 5 * time.Second +) + // DelayTest get the delay for the specified URL func DelayTest(proxy C.Proxy, url string) (t int16, err error) { addr, err := urlToMetadata(url) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 075a9fc0ae..ea2bce5d87 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -49,7 +49,7 @@ func (ss *Vmess) Type() C.AdapterType { } func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - c, err := net.Dial("tcp", ss.server) + c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", ss.server) } From 1f556d4ae5f242dd0547fa5bd89c0f3ff14ea8fa Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 23 Oct 2018 11:28:41 +0800 Subject: [PATCH 065/535] Fix: vmess alterId can be 0 --- component/vmess/user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/component/vmess/user.go b/component/vmess/user.go index a1a3f3c25e..c098389e57 100644 --- a/component/vmess/user.go +++ b/component/vmess/user.go @@ -50,5 +50,6 @@ func newAlterIDs(primary *ID, alterIDCount uint16) []*ID { alterIDs[idx] = &ID{UUID: newid, CmdKey: primary.CmdKey[:]} prevID = newid } + alterIDs = append(alterIDs, primary) return alterIDs } From f943f9284d0a25b8817bc1b35661ed673ebbdd5a Mon Sep 17 00:00:00 2001 From: gruebel Date: Mon, 22 Oct 2018 22:44:11 +0200 Subject: [PATCH 066/535] Chore: clean up Dockerfile --- Dockerfile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 06dc565b6f..8f72c2e4ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,23 @@ FROM golang:latest as builder + RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \ tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \ - cp /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb + mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb + WORKDIR /clash-src + COPY . /clash-src + RUN go mod download && \ - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash && \ - chmod +x /clash + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash FROM alpine:latest -RUN apk --no-cache add ca-certificates && \ - mkdir -p /root/.config/clash + +RUN apk add --no-cache ca-certificates + COPY --from=builder /Country.mmdb /root/.config/clash/ -COPY --from=builder /clash . +COPY --from=builder /clash / + EXPOSE 7890 7891 + ENTRYPOINT ["/clash"] From 03c563a58e993e2b19d5df8a89d724a9ded07940 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Wed, 24 Oct 2018 17:06:08 +0800 Subject: [PATCH 067/535] Improve: url-test will automatically speed test when the connection fails --- adapters/outbound/urltest.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index df2be060d2..74402e48f3 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -3,6 +3,7 @@ package adapters import ( "errors" "sync" + "sync/atomic" "time" C "github.com/Dreamacro/clash/constant" @@ -15,6 +16,7 @@ type URLTest struct { fast C.Proxy interval time.Duration done chan struct{} + once int32 } type URLTestOption struct { @@ -37,7 +39,11 @@ func (u *URLTest) Now() string { } func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - return u.fast.Generator(metadata) + a, err := u.fast.Generator(metadata) + if err != nil { + go u.speedTest() + } + return a, err } func (u *URLTest) Close() { @@ -59,6 +65,11 @@ Loop: } func (u *URLTest) speedTest() { + if atomic.AddInt32(&u.once, 1) != 1 { + return + } + defer atomic.StoreInt32(&u.once, 0) + wg := sync.WaitGroup{} wg.Add(len(u.proxies)) c := make(chan interface{}) @@ -108,6 +119,7 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { fast: proxies[0], interval: interval, done: make(chan struct{}), + once: 0, } go urlTest.loop() return urlTest, nil From 990bba4a05eb50f21722c754a7d9058333d7fd92 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 25 Oct 2018 00:09:55 +0800 Subject: [PATCH 068/535] Fix: GET /rules format --- hub/rules.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hub/rules.go b/hub/rules.go index 96ddc26c19..c91120e8f1 100644 --- a/hub/rules.go +++ b/hub/rules.go @@ -15,8 +15,8 @@ func ruleRouter() http.Handler { } type Rule struct { - Name string `json:"name"` - Payload string `json:"type"` + Type string `json:"type"` + Payload string `json:"payload"` Proxy string `json:"proxy"` } @@ -30,7 +30,7 @@ func getRules(w http.ResponseWriter, r *http.Request) { var rules []Rule for _, rule := range rawRules { rules = append(rules, Rule{ - Name: rule.RuleType().String(), + Type: rule.RuleType().String(), Payload: rule.Payload(), Proxy: rule.Adapter(), }) From e12d46f619fe97aef1d90521cc8686c818166474 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 27 Oct 2018 12:36:33 +0800 Subject: [PATCH 069/535] Fix: unescape proxy name in /proixes --- hub/proxies.go | 26 ++++++++++++++++++++------ hub/server.go | 6 ++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/hub/proxies.go b/hub/proxies.go index c0fe062bae..3a54ed8bf2 100644 --- a/hub/proxies.go +++ b/hub/proxies.go @@ -1,8 +1,10 @@ package hub import ( + "context" "fmt" "net/http" + "net/url" "strconv" "time" @@ -16,12 +18,24 @@ import ( func proxyRouter() http.Handler { r := chi.NewRouter() r.Get("/", getProxies) - r.Get("/{name}", getProxy) - r.Get("/{name}/delay", getProxyDelay) - r.Put("/{name}", updateProxy) + r.With(parseProxyName).Get("/{name}", getProxy) + r.With(parseProxyName).Get("/{name}/delay", getProxyDelay) + r.With(parseProxyName).Put("/{name}", updateProxy) return r } +// When name is composed of a partial escape string, Golang does not unescape it +func parseProxyName(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + name := chi.URLParam(r, "name") + if newName, err := url.PathUnescape(name); err == nil { + name = newName + } + ctx := context.WithValue(r.Context(), contextKey("proxy name"), name) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + type SampleProxy struct { Type string `json:"type"` } @@ -83,7 +97,7 @@ func getProxies(w http.ResponseWriter, r *http.Request) { } func getProxy(w http.ResponseWriter, r *http.Request) { - name := chi.URLParam(r, "name") + name := r.Context().Value(contextKey("proxy name")).(string) proxies := cfg.Proxies() proxy, exist := proxies[name] if !exist { @@ -110,7 +124,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { return } - name := chi.URLParam(r, "name") + name := r.Context().Value(contextKey("proxy name")).(string) proxies := cfg.Proxies() proxy, exist := proxies[name] if !exist { @@ -162,7 +176,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { return } - name := chi.URLParam(r, "name") + name := r.Context().Value(contextKey("proxy name")).(string) proxies := cfg.Proxies() proxy, exist := proxies[name] if !exist { diff --git a/hub/server.go b/hub/server.go index b85b9c2fa1..304f991e84 100644 --- a/hub/server.go +++ b/hub/server.go @@ -100,6 +100,12 @@ func authentication(next http.Handler) http.Handler { return http.HandlerFunc(fn) } +type contextKey string + +func (c contextKey) String() string { + return "clash context key " + string(c) +} + func traffic(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) From 19cbe5245616a0968fd032dcf2eef88141ae9dbb Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 27 Oct 2018 12:57:56 +0800 Subject: [PATCH 070/535] Fix: weak type proxy name --- config/config.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/config/config.go b/config/config.go index 40e27c3ca6..ff784fef72 100644 --- a/config/config.go +++ b/config/config.go @@ -250,14 +250,10 @@ func (c *Config) parseProxies(cfg *RawConfig) error { // parse proxy for idx, mapping := range proxiesConfig { proxyType, existType := mapping["type"].(string) - proxyName, existName := mapping["name"].(string) - if !existType && existName { - return fmt.Errorf("Proxy %d missing type or name", idx) + if !existType { + return fmt.Errorf("Proxy %d missing type", idx) } - if _, exist := proxies[proxyName]; exist { - return fmt.Errorf("Proxy %s is the duplicate name", proxyName) - } var proxy C.Proxy var err error switch proxyType { @@ -285,10 +281,15 @@ func (c *Config) parseProxies(cfg *RawConfig) error { default: return fmt.Errorf("Unsupport proxy type: %s", proxyType) } + if err != nil { - return fmt.Errorf("Proxy %s: %s", proxyName, err.Error()) + return fmt.Errorf("Proxy [%d]: %s", idx, err.Error()) + } + + if _, exist := proxies[proxy.Name()]; exist { + return fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) } - proxies[proxyName] = proxy + proxies[proxy.Name()] = proxy } // parse proxy group From bcba14e05e979f2d6140d6daaaf3d9bd705249ee Mon Sep 17 00:00:00 2001 From: changx <620665+changx@users.noreply.github.com> Date: Sun, 28 Oct 2018 19:46:49 +0800 Subject: [PATCH 071/535] Improve: add tls, sni options to socks5 outbound adapter --- adapters/outbound/socks5.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 4fe1cfe1d4..a1495e908c 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -2,6 +2,7 @@ package adapters import ( "bytes" + "crypto/tls" "errors" "fmt" "io" @@ -29,12 +30,16 @@ func (ss *Socks5Adapter) Conn() net.Conn { type Socks5 struct { addr string name string + tls bool + sni bool } type Socks5Option struct { Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` + TLS bool `proxy:"tls"` + SNI bool `proxy:"sni"` } func (ss *Socks5) Name() string { @@ -47,6 +52,15 @@ func (ss *Socks5) Type() C.AdapterType { func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) + + if err == nil && ss.tls { + tlsConfig := tls.Config{ + InsecureSkipVerify: ss.sni, + MaxVersion: tls.VersionTLS12, + } + c = tls.Client(c, &tlsConfig) + } + if err != nil { return nil, fmt.Errorf("%s connect error", ss.addr) } @@ -92,5 +106,7 @@ func NewSocks5(option Socks5Option) *Socks5 { return &Socks5{ addr: fmt.Sprintf("%s:%d", option.Server, option.Port), name: option.Name, + tls: option.TLS, + sni: option.SNI, } } From d2174149c11aecaf43000e843417ceeee76301d0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 28 Oct 2018 23:46:32 +0800 Subject: [PATCH 072/535] Feature: vmess add websocket support --- adapters/outbound/vmess.go | 15 ++++-- component/vmess/vmess.go | 83 ++++++++++++++++++++++++------ component/vmess/websocket.go | 99 ++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 5 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 component/vmess/websocket.go diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index ea2bce5d87..e45844ae9a 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -38,6 +38,8 @@ type VmessOption struct { AlterID int `proxy:"alterId"` Cipher string `proxy:"cipher"` TLS bool `proxy:"tls,omitempty"` + Network string `proxy:"network,omitempty"` + WSPath string `proxy:"ws-path,omitempty"` } func (ss *Vmess) Name() string { @@ -54,17 +56,20 @@ func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er return nil, fmt.Errorf("%s connect error", ss.server) } tcpKeepAlive(c) - c = ss.client.New(c, parseVmessAddr(metadata)) + c, err = ss.client.New(c, parseVmessAddr(metadata)) return &VmessAdapter{conn: c}, err } func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: option.UUID, - AlterID: uint16(option.AlterID), - Security: security, - TLS: option.TLS, + UUID: option.UUID, + AlterID: uint16(option.AlterID), + Security: security, + TLS: option.TLS, + Host: fmt.Sprintf("%s:%d", option.Server, option.Port), + NetWork: option.Network, + WebSocketPath: option.WSPath, }) if err != nil { return nil, err diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 8b294b94b5..23dfc8f525 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -5,9 +5,12 @@ import ( "fmt" "math/rand" "net" + "net/url" "runtime" + "time" "github.com/gofrs/uuid" + "github.com/gorilla/websocket" ) // Version of vmess @@ -62,27 +65,69 @@ type DstAddr struct { // Client is vmess connection generator type Client struct { - user []*ID - uuid *uuid.UUID - security Security - tls bool + user []*ID + uuid *uuid.UUID + security Security + tls bool + host string + websocket bool + websocketPath string } // Config of vmess type Config struct { - UUID string - AlterID uint16 - Security string - TLS bool + UUID string + AlterID uint16 + Security string + TLS bool + Host string + NetWork string + WebSocketPath string } // New return a Conn with net.Conn and DstAddr -func (c *Client) New(conn net.Conn, dst *DstAddr) net.Conn { +func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { r := rand.Intn(len(c.user)) - if c.tls { + if c.websocket { + dialer := &websocket.Dialer{ + NetDial: func(network, addr string) (net.Conn, error) { + return conn, nil + }, + ReadBufferSize: 4 * 1024, + WriteBufferSize: 4 * 1024, + HandshakeTimeout: time.Second * 8, + } + scheme := "ws" + if c.tls { + scheme = "wss" + } + + host, port, err := net.SplitHostPort(c.host) + if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { + host = c.host + } + + uri := url.URL{ + Scheme: scheme, + Host: host, + Path: c.websocketPath, + } + + wsConn, resp, err := dialer.Dial(uri.String(), nil) + if err != nil { + var reason string + if resp != nil { + reason = resp.Status + } + println(uri.String(), err.Error()) + return nil, fmt.Errorf("Dial %s error: %s", host, reason) + } + + conn = newWebsocketConn(wsConn, conn.RemoteAddr()) + } else if c.tls { conn = tls.Client(conn, tlsConfig) } - return newConn(conn, c.user[r], dst, c.security) + return newConn(conn, c.user[r], dst, c.security), nil } // NewClient return Client instance @@ -108,10 +153,18 @@ func NewClient(config Config) (*Client, error) { default: return nil, fmt.Errorf("Unknown security type: %s", config.Security) } + + if config.NetWork != "" && config.NetWork != "ws" { + return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) + } + return &Client{ - user: newAlterIDs(newID(&uid), config.AlterID), - uuid: &uid, - security: security, - tls: config.TLS, + user: newAlterIDs(newID(&uid), config.AlterID), + uuid: &uid, + security: security, + tls: config.TLS, + host: config.Host, + websocket: config.NetWork == "ws", + websocketPath: config.WebSocketPath, }, nil } diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go new file mode 100644 index 0000000000..21e458140d --- /dev/null +++ b/component/vmess/websocket.go @@ -0,0 +1,99 @@ +package vmess + +import ( + "fmt" + "io" + "net" + "strings" + "time" + + "github.com/gorilla/websocket" +) + +type websocketConn struct { + conn *websocket.Conn + reader io.Reader + remoteAddr net.Addr +} + +// Read implements net.Conn.Read() +func (wsc *websocketConn) Read(b []byte) (int, error) { + for { + reader, err := wsc.getReader() + if err != nil { + return 0, err + } + + nBytes, err := reader.Read(b) + if err == io.EOF { + wsc.reader = nil + continue + } + return nBytes, err + } +} + +// Write implements io.Writer. +func (wsc *websocketConn) Write(b []byte) (int, error) { + if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil { + return 0, err + } + return len(b), nil +} + +func (wsc *websocketConn) Close() error { + var errors []string + if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil { + errors = append(errors, err.Error()) + } + if err := wsc.conn.Close(); err != nil { + errors = append(errors, err.Error()) + } + if len(errors) > 0 { + return fmt.Errorf("Failed to close connection: %s", strings.Join(errors, ",")) + } + return nil +} + +func (wsc *websocketConn) getReader() (io.Reader, error) { + if wsc.reader != nil { + return wsc.reader, nil + } + + _, reader, err := wsc.conn.NextReader() + if err != nil { + return nil, err + } + wsc.reader = reader + return reader, nil +} + +func (wsc *websocketConn) LocalAddr() net.Addr { + return wsc.conn.LocalAddr() +} + +func (wsc *websocketConn) RemoteAddr() net.Addr { + return wsc.remoteAddr +} + +func (wsc *websocketConn) SetDeadline(t time.Time) error { + if err := wsc.SetReadDeadline(t); err != nil { + return err + } + return wsc.SetWriteDeadline(t) +} + +func (wsc *websocketConn) SetReadDeadline(t time.Time) error { + return wsc.conn.SetReadDeadline(t) +} + +func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { + return wsc.conn.SetWriteDeadline(t) +} + +func newWebsocketConn(conn *websocket.Conn, remoteAddr net.Addr) net.Conn { + return &websocketConn{ + conn: conn, + remoteAddr: remoteAddr, + } +} diff --git a/go.mod b/go.mod index f3a32a09ed..2e536bf593 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.1.0+incompatible + github.com/gorilla/websocket v1.4.0 github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/sirupsen/logrus v1.1.0 diff --git a/go.sum b/go.sum index a8a3132c1a..185a7fdfc0 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= From ce7cb138d4d81214c7710380559034c49a99c404 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 29 Oct 2018 20:16:43 +0800 Subject: [PATCH 073/535] Chore: unified naming "skip-cert-verify" --- adapters/outbound/socks5.go | 28 ++++++++++++------------- adapters/outbound/vmess.go | 34 +++++++++++++++--------------- component/vmess/vmess.go | 41 ++++++++++++++++++++----------------- 3 files changed, 54 insertions(+), 49 deletions(-) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index a1495e908c..42389c34dd 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -28,18 +28,18 @@ func (ss *Socks5Adapter) Conn() net.Conn { } type Socks5 struct { - addr string - name string - tls bool - sni bool + addr string + name string + tls bool + skipCertVerify bool } type Socks5Option struct { - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - TLS bool `proxy:"tls"` - SNI bool `proxy:"sni"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + TLS bool `proxy:"tls,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } func (ss *Socks5) Name() string { @@ -55,7 +55,7 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e if err == nil && ss.tls { tlsConfig := tls.Config{ - InsecureSkipVerify: ss.sni, + InsecureSkipVerify: ss.skipCertVerify, MaxVersion: tls.VersionTLS12, } c = tls.Client(c, &tlsConfig) @@ -104,9 +104,9 @@ func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func NewSocks5(option Socks5Option) *Socks5 { return &Socks5{ - addr: fmt.Sprintf("%s:%d", option.Server, option.Port), - name: option.Name, - tls: option.TLS, - sni: option.SNI, + addr: fmt.Sprintf("%s:%d", option.Server, option.Port), + name: option.Name, + tls: option.TLS, + skipCertVerify: option.SkipCertVerify, } } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index e45844ae9a..9ea20acae7 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -31,15 +31,16 @@ type Vmess struct { } type VmessOption struct { - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - UUID string `proxy:"uuid"` - AlterID int `proxy:"alterId"` - Cipher string `proxy:"cipher"` - TLS bool `proxy:"tls,omitempty"` - Network string `proxy:"network,omitempty"` - WSPath string `proxy:"ws-path,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + AlterID int `proxy:"alterId"` + Cipher string `proxy:"cipher"` + TLS bool `proxy:"tls,omitempty"` + Network string `proxy:"network,omitempty"` + WSPath string `proxy:"ws-path,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } func (ss *Vmess) Name() string { @@ -63,13 +64,14 @@ func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: option.UUID, - AlterID: uint16(option.AlterID), - Security: security, - TLS: option.TLS, - Host: fmt.Sprintf("%s:%d", option.Server, option.Port), - NetWork: option.Network, - WebSocketPath: option.WSPath, + UUID: option.UUID, + AlterID: uint16(option.AlterID), + Security: security, + TLS: option.TLS, + Host: fmt.Sprintf("%s:%d", option.Server, option.Port), + NetWork: option.Network, + WebSocketPath: option.WSPath, + SkipCertVerify: option.SkipCertVerify, }) if err != nil { return nil, err diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 23dfc8f525..ece51d76c3 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -39,10 +39,6 @@ var CipherMapping = map[string]byte{ "chacha20-poly1305": SecurityCHACHA20POLY1305, } -var tlsConfig = &tls.Config{ - InsecureSkipVerify: true, -} - // Command types const ( CommandTCP byte = 1 @@ -65,24 +61,26 @@ type DstAddr struct { // Client is vmess connection generator type Client struct { - user []*ID - uuid *uuid.UUID - security Security - tls bool - host string - websocket bool - websocketPath string + user []*ID + uuid *uuid.UUID + security Security + tls bool + host string + websocket bool + websocketPath string + skipCertVerify bool } // Config of vmess type Config struct { - UUID string - AlterID uint16 - Security string - TLS bool - Host string - NetWork string - WebSocketPath string + UUID string + AlterID uint16 + Security string + TLS bool + Host string + NetWork string + WebSocketPath string + SkipCertVerify bool } // New return a Conn with net.Conn and DstAddr @@ -100,6 +98,9 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { scheme := "ws" if c.tls { scheme = "wss" + dialer.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: c.skipCertVerify, + } } host, port, err := net.SplitHostPort(c.host) @@ -125,7 +126,9 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { conn = newWebsocketConn(wsConn, conn.RemoteAddr()) } else if c.tls { - conn = tls.Client(conn, tlsConfig) + conn = tls.Client(conn, &tls.Config{ + InsecureSkipVerify: c.skipCertVerify, + }) } return newConn(conn, c.user[r], dst, c.security), nil } From 370bc769d5235ccadbc8a3a7b0512287f68f0d5b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 29 Oct 2018 20:25:13 +0800 Subject: [PATCH 074/535] Update: README.md --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7593daad6a..5d79488be9 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ log-level: info external-controller: 127.0.0.1:9090 # Secret for RESTful API (Optional) -secret: "" +# secret: "" Proxy: @@ -114,11 +114,22 @@ Proxy: # vmess # cipher support auto/aes-128-gcm/chacha20-poly1305/none -- { name: "vmess1", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto } -- { name: "vmess2", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } +- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto } +# with tls +- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } +# with tls and skip-cert-verify +- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true } +# with ws +- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path } +# with ws + tls +- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true } # socks5 - { name: "socks", type: socks5, server: server, port: 443 } +# with tls +- { name: "socks", type: socks5, server: server, port: 443, tls: true } +# with tls and skip-cert-verify +- { name: "socks", type: socks5, server: server, port: 443, tls: true, skip-cert-verify: true } Proxy Group: # url-test select which proxy will be used by benchmarking speed to a URL. @@ -134,7 +145,9 @@ Proxy Group: Rule: - DOMAIN-SUFFIX,google.com,Proxy - DOMAIN-KEYWORD,google,Proxy +- DOMAIN,google.com,Proxy - DOMAIN-SUFFIX,ad.com,REJECT +- IP-CIDR,127.0.0.0/8,DIRECT - GEOIP,CN,DIRECT # note: there is two "," - FINAL,,Proxy From c5757a9b110ef782775527e372bed18da42fbd4f Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Tue, 30 Oct 2018 10:50:57 +0800 Subject: [PATCH 075/535] Chore: delete redundant print --- component/vmess/vmess.go | 1 - 1 file changed, 1 deletion(-) diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index ece51d76c3..be1f02dc74 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -120,7 +120,6 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { if resp != nil { reason = resp.Status } - println(uri.String(), err.Error()) return nil, fmt.Errorf("Dial %s error: %s", host, reason) } From fd63707399363fee886aa39d9f899bc574fc9744 Mon Sep 17 00:00:00 2001 From: changx <620665+changx@users.noreply.github.com> Date: Thu, 1 Nov 2018 11:54:45 +0800 Subject: [PATCH 076/535] Optimization: use client session cache for TLS connection (#26) --- adapters/outbound/socks5.go | 21 ++++++++++++---- adapters/outbound/util.go | 14 +++++++++++ adapters/outbound/vmess.go | 1 + component/vmess/vmess.go | 50 ++++++++++++++++++++++++++----------- 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 42389c34dd..eae6109060 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -32,6 +32,7 @@ type Socks5 struct { name string tls bool skipCertVerify bool + tlsConfig *tls.Config } type Socks5Option struct { @@ -54,11 +55,9 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) if err == nil && ss.tls { - tlsConfig := tls.Config{ - InsecureSkipVerify: ss.skipCertVerify, - MaxVersion: tls.VersionTLS12, - } - c = tls.Client(c, &tlsConfig) + cc := tls.Client(c, ss.tlsConfig) + err = cc.Handshake() + c = cc } if err != nil { @@ -103,10 +102,22 @@ func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { } func NewSocks5(option Socks5Option) *Socks5 { + var tlsConfig *tls.Config + if option.TLS { + tlsConfig = &tls.Config{ + InsecureSkipVerify: option.SkipCertVerify, + ClientSessionCache: getClientSessionCache(), + MinVersion: tls.VersionTLS11, + MaxVersion: tls.VersionTLS12, + ServerName: option.Server, + } + } + return &Socks5{ addr: fmt.Sprintf("%s:%d", option.Server, option.Port), name: option.Name, tls: option.TLS, skipCertVerify: option.SkipCertVerify, + tlsConfig: tlsConfig, } } diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index f1292546dd..f38b55e662 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -1,10 +1,12 @@ package adapters import ( + "crypto/tls" "fmt" "net" "net/http" "net/url" + "sync" "time" C "github.com/Dreamacro/clash/constant" @@ -14,6 +16,11 @@ const ( tcpTimeout = 5 * time.Second ) +var ( + globalClientSessionCache tls.ClientSessionCache + once sync.Once +) + // DelayTest get the delay for the specified URL func DelayTest(proxy C.Proxy, url string) (t int16, err error) { addr, err := urlToMetadata(url) @@ -95,3 +102,10 @@ func tcpKeepAlive(c net.Conn) { tcp.SetKeepAlivePeriod(30 * time.Second) } } + +func getClientSessionCache() tls.ClientSessionCache { + once.Do(func() { + globalClientSessionCache = tls.NewLRUClientSessionCache(128) + }) + return globalClientSessionCache +} diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 9ea20acae7..68e655098f 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -72,6 +72,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { NetWork: option.Network, WebSocketPath: option.WSPath, SkipCertVerify: option.SkipCertVerify, + SessionCacahe: getClientSessionCache(), }) if err != nil { return nil, err diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index be1f02dc74..f9cd27ec74 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -7,6 +7,7 @@ import ( "net" "net/url" "runtime" + "sync" "time" "github.com/gofrs/uuid" @@ -39,6 +40,11 @@ var CipherMapping = map[string]byte{ "chacha20-poly1305": SecurityCHACHA20POLY1305, } +var ( + clientSessionCache tls.ClientSessionCache + once sync.Once +) + // Command types const ( CommandTCP byte = 1 @@ -61,14 +67,14 @@ type DstAddr struct { // Client is vmess connection generator type Client struct { - user []*ID - uuid *uuid.UUID - security Security - tls bool - host string - websocket bool - websocketPath string - skipCertVerify bool + user []*ID + uuid *uuid.UUID + security Security + tls bool + host string + websocket bool + websocketPath string + tlsConfig *tls.Config } // Config of vmess @@ -81,6 +87,7 @@ type Config struct { NetWork string WebSocketPath string SkipCertVerify bool + SessionCacahe tls.ClientSessionCache } // New return a Conn with net.Conn and DstAddr @@ -98,9 +105,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { scheme := "ws" if c.tls { scheme = "wss" - dialer.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: c.skipCertVerify, - } + dialer.TLSClientConfig = c.tlsConfig } host, port, err := net.SplitHostPort(c.host) @@ -125,9 +130,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { conn = newWebsocketConn(wsConn, conn.RemoteAddr()) } else if c.tls { - conn = tls.Client(conn, &tls.Config{ - InsecureSkipVerify: c.skipCertVerify, - }) + conn = tls.Client(conn, c.tlsConfig) } return newConn(conn, c.user[r], dst, c.security), nil } @@ -160,6 +163,17 @@ func NewClient(config Config) (*Client, error) { return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) } + var tlsConfig *tls.Config + if config.TLS { + tlsConfig = &tls.Config{ + InsecureSkipVerify: config.SkipCertVerify, + ClientSessionCache: config.SessionCacahe, + } + if tlsConfig.ClientSessionCache == nil { + tlsConfig.ClientSessionCache = getClientSessionCache() + } + } + return &Client{ user: newAlterIDs(newID(&uid), config.AlterID), uuid: &uid, @@ -168,5 +182,13 @@ func NewClient(config Config) (*Client, error) { host: config.Host, websocket: config.NetWork == "ws", websocketPath: config.WebSocketPath, + tlsConfig: tlsConfig, }, nil } + +func getClientSessionCache() tls.ClientSessionCache { + once.Do(func() { + clientSessionCache = tls.NewLRUClientSessionCache(128) + }) + return clientSessionCache +} From 10e0231bc1f7d7997cc60390d3852737ed7b1bb8 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sun, 4 Nov 2018 21:12:16 +0800 Subject: [PATCH 077/535] Fix: dial IPv6 host (#29) --- adapters/outbound/shadowsocks.go | 2 +- adapters/outbound/socks5.go | 3 ++- adapters/outbound/vmess.go | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 3943c060f7..f743f4df69 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -72,7 +72,7 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, } func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { - server := fmt.Sprintf("%s:%d", option.Server, option.Port) + server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) cipher := option.Cipher password := option.Password ciph, err := core.PickCipher(cipher, nil, password) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index eae6109060..d3a3f64b02 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net" + "strconv" C "github.com/Dreamacro/clash/constant" @@ -114,7 +115,7 @@ func NewSocks5(option Socks5Option) *Socks5 { } return &Socks5{ - addr: fmt.Sprintf("%s:%d", option.Server, option.Port), + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), name: option.Name, tls: option.TLS, skipCertVerify: option.SkipCertVerify, diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 68e655098f..6969c3c4a2 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -68,7 +68,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { AlterID: uint16(option.AlterID), Security: security, TLS: option.TLS, - Host: fmt.Sprintf("%s:%d", option.Server, option.Port), + Host: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), NetWork: option.Network, WebSocketPath: option.WSPath, SkipCertVerify: option.SkipCertVerify, @@ -80,7 +80,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { return &Vmess{ name: option.Name, - server: fmt.Sprintf("%s:%d", option.Server, option.Port), + server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), client: client, }, nil } From cc6d496143ecaeaf0049825e481954114e9006c3 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sun, 4 Nov 2018 21:36:20 +0800 Subject: [PATCH 078/535] Chore: optimize code structure in vmess websocket (#28) * Chore: move conn process of ws to websocket.go * Chore: some routine adjustment --- component/vmess/vmess.go | 81 +++++++++++++----------------------- component/vmess/websocket.go | 52 +++++++++++++++++++++-- 2 files changed, 76 insertions(+), 57 deletions(-) diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index f9cd27ec74..3356d8bc31 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -5,13 +5,10 @@ import ( "fmt" "math/rand" "net" - "net/url" "runtime" "sync" - "time" "github.com/gofrs/uuid" - "github.com/gorilla/websocket" ) // Version of vmess @@ -67,14 +64,13 @@ type DstAddr struct { // Client is vmess connection generator type Client struct { - user []*ID - uuid *uuid.UUID - security Security - tls bool - host string - websocket bool - websocketPath string - tlsConfig *tls.Config + user []*ID + uuid *uuid.UUID + security Security + tls bool + host string + wsConfig *websocketConfig + tlsConfig *tls.Config } // Config of vmess @@ -92,43 +88,13 @@ type Config struct { // New return a Conn with net.Conn and DstAddr func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { + var err error r := rand.Intn(len(c.user)) - if c.websocket { - dialer := &websocket.Dialer{ - NetDial: func(network, addr string) (net.Conn, error) { - return conn, nil - }, - ReadBufferSize: 4 * 1024, - WriteBufferSize: 4 * 1024, - HandshakeTimeout: time.Second * 8, - } - scheme := "ws" - if c.tls { - scheme = "wss" - dialer.TLSClientConfig = c.tlsConfig - } - - host, port, err := net.SplitHostPort(c.host) - if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { - host = c.host - } - - uri := url.URL{ - Scheme: scheme, - Host: host, - Path: c.websocketPath, - } - - wsConn, resp, err := dialer.Dial(uri.String(), nil) + if c.wsConfig != nil { + conn, err = newWebsocketConn(conn, c.wsConfig) if err != nil { - var reason string - if resp != nil { - reason = resp.Status - } - return nil, fmt.Errorf("Dial %s error: %s", host, reason) + return nil, err } - - conn = newWebsocketConn(wsConn, conn.RemoteAddr()) } else if c.tls { conn = tls.Client(conn, c.tlsConfig) } @@ -174,15 +140,24 @@ func NewClient(config Config) (*Client, error) { } } + var wsConfig *websocketConfig + if config.NetWork == "ws" { + wsConfig = &websocketConfig{ + host: config.Host, + path: config.WebSocketPath, + tls: config.TLS, + tlsConfig: tlsConfig, + } + } + return &Client{ - user: newAlterIDs(newID(&uid), config.AlterID), - uuid: &uid, - security: security, - tls: config.TLS, - host: config.Host, - websocket: config.NetWork == "ws", - websocketPath: config.WebSocketPath, - tlsConfig: tlsConfig, + user: newAlterIDs(newID(&uid), config.AlterID), + uuid: &uid, + security: security, + tls: config.TLS, + host: config.Host, + wsConfig: wsConfig, + tlsConfig: tlsConfig, }, nil } diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index 21e458140d..7bef5e559c 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -1,9 +1,11 @@ package vmess import ( + "crypto/tls" "fmt" "io" "net" + "net/url" "strings" "time" @@ -16,6 +18,13 @@ type websocketConn struct { remoteAddr net.Addr } +type websocketConfig struct { + host string + path string + tls bool + tlsConfig *tls.Config +} + // Read implements net.Conn.Read() func (wsc *websocketConn) Read(b []byte) (int, error) { for { @@ -91,9 +100,44 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { return wsc.conn.SetWriteDeadline(t) } -func newWebsocketConn(conn *websocket.Conn, remoteAddr net.Addr) net.Conn { - return &websocketConn{ - conn: conn, - remoteAddr: remoteAddr, +func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) { + dialer := &websocket.Dialer{ + NetDial: func(network, addr string) (net.Conn, error) { + return conn, nil + }, + ReadBufferSize: 4 * 1024, + WriteBufferSize: 4 * 1024, + HandshakeTimeout: time.Second * 8, + } + + scheme := "ws" + if c.tls { + scheme = "wss" + dialer.TLSClientConfig = c.tlsConfig + } + + host, port, err := net.SplitHostPort(c.host) + if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { + host = c.host } + + uri := url.URL{ + Scheme: scheme, + Host: host, + Path: c.path, + } + + wsConn, resp, err := dialer.Dial(uri.String(), nil) + if err != nil { + var reason string + if resp != nil { + reason = resp.Status + } + return nil, fmt.Errorf("Dial %s error: %s", host, reason) + } + + return &websocketConn{ + conn: wsConn, + remoteAddr: conn.RemoteAddr(), + }, nil } From 502aa61c0e82bfdd66eb5a73ddc37807103766f9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 6 Nov 2018 17:34:19 +0800 Subject: [PATCH 079/535] Fix: vmess small probability invalid auth --- component/vmess/conn.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/component/vmess/conn.go b/component/vmess/conn.go index 8a284b2884..8efe7ffcc3 100644 --- a/component/vmess/conn.go +++ b/component/vmess/conn.go @@ -65,11 +65,10 @@ func (vc *Conn) Read(b []byte) (int, error) { } func (vc *Conn) sendRequest() error { - timestamp := make([]byte, 8) - binary.BigEndian.PutUint64(timestamp, uint64(time.Now().UTC().Unix())) + timestamp := time.Now() h := hmac.New(md5.New, vc.id.UUID.Bytes()) - h.Write(timestamp) + binary.Write(h, binary.BigEndian, uint64(timestamp.Unix())) _, err := vc.Conn.Write(h.Sum(nil)) if err != nil { return err @@ -111,7 +110,7 @@ func (vc *Conn) sendRequest() error { return err } - stream := cipher.NewCFBEncrypter(block, hashTimestamp(time.Now().UTC())) + stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp)) stream.XORKeyStream(buf.Bytes(), buf.Bytes()) _, err = vc.Conn.Write(buf.Bytes()) return err @@ -145,7 +144,7 @@ func (vc *Conn) recvResponse() error { func hashTimestamp(t time.Time) []byte { md5hash := md5.New() ts := make([]byte, 8) - binary.BigEndian.PutUint64(ts, uint64(t.UTC().Unix())) + binary.BigEndian.PutUint64(ts, uint64(t.Unix())) md5hash.Write(ts) md5hash.Write(ts) md5hash.Write(ts) From da391356dd2996f30b58d58af0cfab4ea52b2d25 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 7 Nov 2018 16:57:21 +0800 Subject: [PATCH 080/535] Fix: simple-obfs tls --- component/simple-obfs/tls.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/component/simple-obfs/tls.go b/component/simple-obfs/tls.go index 6cb526276c..e725944f1f 100644 --- a/component/simple-obfs/tls.go +++ b/component/simple-obfs/tls.go @@ -107,9 +107,8 @@ func NewTLSObfs(conn net.Conn, server string) net.Conn { } func makeClientHelloMsg(data []byte, server string) []byte { - random := make([]byte, 32) + random := make([]byte, 28) sessionID := make([]byte, 32) - size := make([]byte, 2) rand.Read(random) rand.Read(sessionID) @@ -124,12 +123,12 @@ func makeClientHelloMsg(data []byte, server string) []byte { // clientHello, length, TLS 1.2 version buf.WriteByte(1) - binary.BigEndian.PutUint16(size, uint16(208+len(data)+len(server))) buf.WriteByte(0) - buf.Write(size) + binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server))) buf.Write([]byte{0x03, 0x03}) - // random, sid len, sid + // random with timestamp, sid len, sid + binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) buf.Write(random) buf.WriteByte(32) buf.Write(sessionID) @@ -147,24 +146,19 @@ func makeClientHelloMsg(data []byte, server string) []byte { buf.Write([]byte{0x01, 0x00}) // extension length - binary.BigEndian.PutUint16(size, uint16(79+len(data)+len(server))) - buf.Write(size) + binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server))) // session ticket buf.Write([]byte{0x00, 0x23}) - binary.BigEndian.PutUint16(size, uint16(len(data))) - buf.Write(size) + binary.Write(buf, binary.BigEndian, uint16(len(data))) buf.Write(data) // server name buf.Write([]byte{0x00, 0x00}) - binary.BigEndian.PutUint16(size, uint16(len(server)+5)) - buf.Write(size) - binary.BigEndian.PutUint16(size, uint16(len(server)+3)) - buf.Write(size) + binary.Write(buf, binary.BigEndian, uint16(len(server)+5)) + binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) buf.WriteByte(0) - binary.BigEndian.PutUint16(size, uint16(len(server))) - buf.Write(size) + binary.Write(buf, binary.BigEndian, uint16(len(server))) buf.Write([]byte(server)) // ec_point From 09cd34ec07d9ddb94b5976171f6f38b0ad82ab03 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 8 Nov 2018 20:14:57 +0800 Subject: [PATCH 081/535] Chore: update README.md --- README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5d79488be9..efb29d43e5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
-

A rule based proxy in Go.

+

A rule based tunnel in Go.

@@ -22,16 +22,12 @@ ## Features -- HTTP/HTTPS and SOCKS proxy +- HTTP/HTTPS and SOCKS protocol - Surge like configuration - GeoIP rule support - Support Vmess/Shadowsocks/Socks5 - Support for Netfilter TCP redirect -## Discussion - -[Telegram Group](https://t.me/clash_discuss) - ## Install You can build from source: @@ -85,7 +81,7 @@ port: 7890 # port of SOCKS5 socks-port: 7891 -# redir proxy for Linux and macOS +# redir port for Linux and macOS # redir-port: 7892 allow-lan: false From b0e062dc7ce01e4c91300bc5a03845e3a731e25a Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 9 Nov 2018 17:36:30 +0800 Subject: [PATCH 082/535] Feature: SOCKS5 authentication support (#34) * Feature: socks5 auth support * Chore: make code unified * Fix: auth buffer length --- adapters/outbound/socks5.go | 38 +++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index d3a3f64b02..b814efb3e8 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -31,6 +31,8 @@ func (ss *Socks5Adapter) Conn() net.Conn { type Socks5 struct { addr string name string + user string + pass string tls bool skipCertVerify bool tlsConfig *tls.Config @@ -40,6 +42,8 @@ type Socks5Option struct { Name string `proxy:"name"` Server string `proxy:"server"` Port int `proxy:"port"` + UserName string `proxy:"username,omitempty"` + Password string `proxy:"password,omitempty"` TLS bool `proxy:"tls,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } @@ -73,19 +77,47 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { buf := make([]byte, socks.MaxAddrLen) + var err error - // VER, CMD, RSV - _, err := rw.Write([]byte{5, 1, 0}) + // VER, NMETHODS, METHODS + if len(ss.user) > 0 { + _, err = rw.Write([]byte{5, 1, 2}) + } else { + _, err = rw.Write([]byte{5, 1, 0}) + } if err != nil { return err } + // VER, METHOD if _, err := io.ReadFull(rw, buf[:2]); err != nil { return err } if buf[0] != 5 { return errors.New("SOCKS version error") + } + + if buf[1] == 2 { + // password protocol version + authMsg := &bytes.Buffer{} + authMsg.WriteByte(1) + authMsg.WriteByte(uint8(len(ss.user))) + authMsg.WriteString(ss.user) + authMsg.WriteByte(uint8(len(ss.pass))) + authMsg.WriteString(ss.pass) + + if _, err := rw.Write(authMsg.Bytes()); err != nil { + return err + } + + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return err + } + + if buf[1] != 0 { + return errors.New("rejected username/password") + } } else if buf[1] != 0 { return errors.New("SOCKS need auth") } @@ -117,6 +149,8 @@ func NewSocks5(option Socks5Option) *Socks5 { return &Socks5{ addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), name: option.Name, + user: option.UserName, + pass: option.Password, tls: option.TLS, skipCertVerify: option.SkipCertVerify, tlsConfig: tlsConfig, From 91e35f2f6a4a006e87682f91cf5e151b86c57fc0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 14 Nov 2018 20:58:10 +0800 Subject: [PATCH 083/535] Fix: resolve path in windows --- main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index 7273c4d592..b48106251e 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,17 @@ package main import ( + "flag" "os" "os/signal" + "path/filepath" "syscall" - "flag" - "path" "github.com/Dreamacro/clash/config" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/proxy" "github.com/Dreamacro/clash/tunnel" - C "github.com/Dreamacro/clash/constant" log "github.com/sirupsen/logrus" ) @@ -30,10 +30,10 @@ func main() { proxy.Instance().Run() hub.Run() - if (homedir != "") { - if !path.IsAbs(homedir) { + if homedir != "" { + if !filepath.IsAbs(homedir) { currentDir, _ := os.Getwd() - homedir = path.Join(currentDir, homedir) + homedir = filepath.Join(currentDir, homedir) } C.SetHomeDir(homedir) } From 01a477bd3d503f1c1100360d84326c62b0a75023 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 21 Nov 2018 13:47:46 +0800 Subject: [PATCH 084/535] Chore: improve code architecture --- adapters/outbound/direct.go | 7 + adapters/outbound/fallback.go | 13 ++ adapters/outbound/reject.go | 7 + adapters/outbound/selector.go | 17 +- adapters/outbound/shadowsocks.go | 7 + adapters/outbound/socks5.go | 7 + adapters/outbound/urltest.go | 15 ++ adapters/outbound/vmess.go | 21 ++- config/config.go | 288 ++++++++----------------------- config/initial.go | 14 +- config/mode.go | 31 ---- config/utils.go | 7 - constant/adapters.go | 1 + constant/config.go | 10 -- constant/log.go | 35 ---- constant/proxy.go | 7 - hub/common.go | 33 ---- hub/configs.go | 96 ----------- hub/executor/executor.go | 66 +++++++ hub/hub.go | 21 +++ hub/proxies.go | 217 ----------------------- hub/route/configs.go | 69 ++++++++ hub/route/ctxkeys.go | 12 ++ hub/route/errors.go | 22 +++ hub/route/proxies.go | 137 +++++++++++++++ hub/{ => route}/rules.go | 27 +-- hub/{ => route}/server.go | 86 +++------ log/level.go | 76 ++++++++ log/log.go | 85 +++++++++ main.go | 14 +- proxy/http/server.go | 17 +- proxy/listener.go | 186 ++++++++++++-------- proxy/redir/tcp.go | 12 +- proxy/socks/tcp.go | 12 +- tunnel/log.go | 51 ------ tunnel/mode.go | 53 ++++++ tunnel/tunnel.go | 91 +++++----- 37 files changed, 903 insertions(+), 967 deletions(-) delete mode 100644 config/mode.go delete mode 100644 constant/config.go delete mode 100644 constant/log.go delete mode 100644 constant/proxy.go delete mode 100644 hub/common.go delete mode 100644 hub/configs.go create mode 100644 hub/executor/executor.go create mode 100644 hub/hub.go delete mode 100644 hub/proxies.go create mode 100644 hub/route/configs.go create mode 100644 hub/route/ctxkeys.go create mode 100644 hub/route/errors.go create mode 100644 hub/route/proxies.go rename hub/{ => route}/rules.go (56%) rename hub/{ => route}/server.go (62%) create mode 100644 log/level.go create mode 100644 log/log.go delete mode 100644 tunnel/log.go create mode 100644 tunnel/mode.go diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 70a3f9eb51..1a6a1144ca 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -1,6 +1,7 @@ package adapters import ( + "encoding/json" "net" C "github.com/Dreamacro/clash/constant" @@ -40,6 +41,12 @@ func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er return &DirectAdapter{conn: c}, nil } +func (d *Direct) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": d.Type().String(), + }) +} + func NewDirect() *Direct { return &Direct{} } diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index e858d0ab09..0acaabbb7c 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -1,6 +1,7 @@ package adapters import ( + "encoding/json" "errors" "sync" "time" @@ -63,6 +64,18 @@ func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err return f.proxies[0].RawProxy.Generator(metadata) } +func (f *Fallback) MarshalJSON() ([]byte, error) { + var all []string + for _, proxy := range f.proxies { + all = append(all, proxy.RawProxy.Name()) + } + return json.Marshal(map[string]interface{}{ + "type": f.Type().String(), + "now": f.Now(), + "all": all, + }) +} + func (f *Fallback) Close() { f.done <- struct{}{} } diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index deb36e7147..ae26022dc9 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -1,6 +1,7 @@ package adapters import ( + "encoding/json" "io" "net" "time" @@ -36,6 +37,12 @@ func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er return &RejectAdapter{conn: &NopConn{}}, nil } +func (r *Reject) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": r.Type().String(), + }) +} + func NewReject() *Reject { return &Reject{} } diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index 1f920ae1c7..e4dbd6d92a 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -1,6 +1,7 @@ package adapters import ( + "encoding/json" "errors" "sort" @@ -30,17 +31,21 @@ func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err return s.selected.Generator(metadata) } -func (s *Selector) Now() string { - return s.selected.Name() -} - -func (s *Selector) All() []string { +func (s *Selector) MarshalJSON() ([]byte, error) { var all []string for k := range s.proxies { all = append(all, k) } sort.Strings(all) - return all + return json.Marshal(map[string]interface{}{ + "type": s.Type().String(), + "now": s.Now(), + "all": all, + }) +} + +func (s *Selector) Now() string { + return s.selected.Name() } func (s *Selector) Set(name string) error { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index f743f4df69..3135608420 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -2,6 +2,7 @@ package adapters import ( "bytes" + "encoding/json" "fmt" "net" "strconv" @@ -71,6 +72,12 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, return &ShadowsocksAdapter{conn: c}, err } +func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": ss.Type().String(), + }) +} + func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) cipher := option.Cipher diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index b814efb3e8..dc958235d7 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -3,6 +3,7 @@ package adapters import ( "bytes" "crypto/tls" + "encoding/json" "errors" "fmt" "io" @@ -75,6 +76,12 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e return &Socks5Adapter{conn: c}, nil } +func (ss *Socks5) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": ss.Type().String(), + }) +} + func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { buf := make([]byte, socks.MaxAddrLen) var err error diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 74402e48f3..1466c2771c 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -1,7 +1,9 @@ package adapters import ( + "encoding/json" "errors" + "sort" "sync" "sync/atomic" "time" @@ -46,6 +48,19 @@ func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e return a, err } +func (u *URLTest) MarshalJSON() ([]byte, error) { + var all []string + for _, proxy := range u.proxies { + all = append(all, proxy.Name()) + } + sort.Strings(all) + return json.Marshal(map[string]interface{}{ + "type": u.Type().String(), + "now": u.Now(), + "all": all, + }) +} + func (u *URLTest) Close() { u.done <- struct{}{} } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 6969c3c4a2..04f4b110f0 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -1,6 +1,7 @@ package adapters import ( + "encoding/json" "fmt" "net" "strconv" @@ -43,24 +44,30 @@ type VmessOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (ss *Vmess) Name() string { - return ss.name +func (v *Vmess) Name() string { + return v.name } -func (ss *Vmess) Type() C.AdapterType { +func (v *Vmess) Type() C.AdapterType { return C.Vmess } -func (ss *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) +func (v *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { + c, err := net.DialTimeout("tcp", v.server, tcpTimeout) if err != nil { - return nil, fmt.Errorf("%s connect error", ss.server) + return nil, fmt.Errorf("%s connect error", v.server) } tcpKeepAlive(c) - c, err = ss.client.New(c, parseVmessAddr(metadata)) + c, err = v.client.New(c, parseVmessAddr(metadata)) return &VmessAdapter{conn: c}, err } +func (v *Vmess) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "type": v.Type().String(), + }) +} + func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ diff --git a/config/config.go b/config/config.go index ff784fef72..b62ba9d076 100644 --- a/config/config.go +++ b/config/config.go @@ -5,44 +5,30 @@ import ( "io/ioutil" "os" "strings" - "sync" - "time" adapters "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/common/observable" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" R "github.com/Dreamacro/clash/rules" + T "github.com/Dreamacro/clash/tunnel" - log "github.com/sirupsen/logrus" yaml "gopkg.in/yaml.v2" ) -var ( - config *Config - once sync.Once -) - // General config type General struct { - Port int - SocksPort int - RedirPort int - AllowLan bool - Mode Mode - LogLevel C.LogLevel -} - -// ProxyConfig is update proxy schema -type ProxyConfig struct { - Port *int - SocksPort *int - RedirPort *int - AllowLan *bool + Port int `json:"port"` + SocksPort int `json:"socks-port"` + RedirPort int `json:"redir-port"` + AllowLan bool `json:"allow-lan"` + Mode T.Mode `json:"mode"` + LogLevel log.LogLevel `json:"log-level"` + ExternalController string `json:"external-controller,omitempty"` + Secret string `json:"secret,omitempty"` } -// RawConfig is raw config struct -type RawConfig struct { +type rawConfig struct { Port int `yaml:"port"` SocksPort int `yaml:"socks-port"` RedirPort int `yaml:"redir-port"` @@ -59,38 +45,16 @@ type RawConfig struct { // Config is clash config manager type Config struct { - general *General - rules []C.Rule - proxies map[string]C.Proxy - lastUpdate time.Time - - event chan<- interface{} - reportCh chan interface{} - observable *observable.Observable -} - -// Event is event of clash config -type Event struct { - Type string - Payload interface{} -} - -// Subscribe config stream -func (c *Config) Subscribe() observable.Subscription { - sub, _ := c.observable.Subscribe() - return sub -} - -// Report return a channel for collecting report message -func (c *Config) Report() chan<- interface{} { - return c.reportCh + General *General + Rules []C.Rule + Proxies map[string]C.Proxy } -func (c *Config) readConfig() (*RawConfig, error) { - if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { +func readConfig(path string) (*rawConfig, error) { + if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err } - data, err := ioutil.ReadFile(C.Path.Config()) + data, err := ioutil.ReadFile(path) if err != nil { return nil, err } @@ -100,10 +64,10 @@ func (c *Config) readConfig() (*RawConfig, error) { } // config with some default value - rawConfig := &RawConfig{ + rawConfig := &rawConfig{ AllowLan: false, - Mode: Rule.String(), - LogLevel: C.INFO.String(), + Mode: T.Rule.String(), + LogLevel: log.INFO.String(), Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, @@ -113,131 +77,69 @@ func (c *Config) readConfig() (*RawConfig, error) { } // Parse config -func (c *Config) Parse() error { - cfg, err := c.readConfig() +func Parse(path string) (*Config, error) { + config := &Config{} + + rawCfg, err := readConfig(path) if err != nil { - return err + return nil, err } - if err := c.parseGeneral(cfg); err != nil { - return err + general, err := parseGeneral(rawCfg) + if err != nil { + return nil, err } + config.General = general - if err := c.parseProxies(cfg); err != nil { - return err + proxies, err := parseProxies(rawCfg) + if err != nil { + return nil, err } + config.Proxies = proxies - return c.parseRules(cfg) -} - -// Proxies return proxies of clash -func (c *Config) Proxies() map[string]C.Proxy { - return c.proxies -} - -// Rules return rules of clash -func (c *Config) Rules() []C.Rule { - return c.rules -} - -// SetMode change mode of clash -func (c *Config) SetMode(mode Mode) { - c.general.Mode = mode - c.event <- &Event{Type: "mode", Payload: mode} -} - -// SetLogLevel change log level of clash -func (c *Config) SetLogLevel(level C.LogLevel) { - c.general.LogLevel = level - c.event <- &Event{Type: "log-level", Payload: level} -} - -// General return clash general config -func (c *Config) General() General { - return *c.general -} - -// UpdateRules is a function for hot reload rules -func (c *Config) UpdateRules() error { - cfg, err := c.readConfig() + rules, err := parseRules(rawCfg) if err != nil { - return err + return nil, err } + config.Rules = rules - return c.parseRules(cfg) + return config, nil } -func (c *Config) parseGeneral(cfg *RawConfig) error { +func parseGeneral(cfg *rawConfig) (*General, error) { port := cfg.Port socksPort := cfg.SocksPort redirPort := cfg.RedirPort allowLan := cfg.AllowLan logLevelString := cfg.LogLevel modeString := cfg.Mode + externalController := cfg.ExternalController + secret := cfg.Secret - mode, exist := ModeMapping[modeString] + mode, exist := T.ModeMapping[modeString] if !exist { - return fmt.Errorf("General.mode value invalid") + return nil, fmt.Errorf("General.mode value invalid") } - logLevel, exist := C.LogLevelMapping[logLevelString] + logLevel, exist := log.LogLevelMapping[logLevelString] if !exist { - return fmt.Errorf("General.log-level value invalid") - } - - c.general = &General{ - Port: port, - SocksPort: socksPort, - RedirPort: redirPort, - AllowLan: allowLan, - Mode: mode, - LogLevel: logLevel, + return nil, fmt.Errorf("General.log-level value invalid") } - if restAddr := cfg.ExternalController; restAddr != "" { - c.event <- &Event{Type: "external-controller", Payload: restAddr} - c.event <- &Event{Type: "secret", Payload: cfg.Secret} + general := &General{ + Port: port, + SocksPort: socksPort, + RedirPort: redirPort, + AllowLan: allowLan, + Mode: mode, + LogLevel: logLevel, + ExternalController: externalController, + Secret: secret, } - - c.UpdateGeneral(*c.general) - return nil + return general, nil } -// UpdateGeneral dispatch update event -func (c *Config) UpdateGeneral(general General) { - c.UpdateProxy(ProxyConfig{ - Port: &general.Port, - SocksPort: &general.SocksPort, - RedirPort: &general.RedirPort, - AllowLan: &general.AllowLan, - }) - c.event <- &Event{Type: "mode", Payload: general.Mode} - c.event <- &Event{Type: "log-level", Payload: general.LogLevel} -} - -// UpdateProxy dispatch update proxy event -func (c *Config) UpdateProxy(pc ProxyConfig) { - if pc.AllowLan != nil { - c.general.AllowLan = *pc.AllowLan - } - - c.general.Port = *or(pc.Port, &c.general.Port) - if c.general.Port != 0 && (pc.AllowLan != nil || pc.Port != nil) { - c.event <- &Event{Type: "http-addr", Payload: genAddr(c.general.Port, c.general.AllowLan)} - } - - c.general.SocksPort = *or(pc.SocksPort, &c.general.SocksPort) - if c.general.SocksPort != 0 && (pc.AllowLan != nil || pc.SocksPort != nil) { - c.event <- &Event{Type: "socks-addr", Payload: genAddr(c.general.SocksPort, c.general.AllowLan)} - } - - c.general.RedirPort = *or(pc.RedirPort, &c.general.RedirPort) - if c.general.RedirPort != 0 && (pc.AllowLan != nil || pc.RedirPort != nil) { - c.event <- &Event{Type: "redir-addr", Payload: genAddr(c.general.RedirPort, c.general.AllowLan)} - } -} - -func (c *Config) parseProxies(cfg *RawConfig) error { +func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { proxies := make(map[string]C.Proxy) proxiesConfig := cfg.Proxy groupsConfig := cfg.ProxyGroup @@ -251,7 +153,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error { for idx, mapping := range proxiesConfig { proxyType, existType := mapping["type"].(string) if !existType { - return fmt.Errorf("Proxy %d missing type", idx) + return nil, fmt.Errorf("Proxy %d missing type", idx) } var proxy C.Proxy @@ -279,15 +181,15 @@ func (c *Config) parseProxies(cfg *RawConfig) error { } proxy, err = adapters.NewVmess(*vmessOption) default: - return fmt.Errorf("Unsupport proxy type: %s", proxyType) + return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) } if err != nil { - return fmt.Errorf("Proxy [%d]: %s", idx, err.Error()) + return nil, fmt.Errorf("Proxy [%d]: %s", idx, err.Error()) } if _, exist := proxies[proxy.Name()]; exist { - return fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) + return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) } proxies[proxy.Name()] = proxy } @@ -297,11 +199,11 @@ func (c *Config) parseProxies(cfg *RawConfig) error { groupType, existType := mapping["type"].(string) groupName, existName := mapping["name"].(string) if !existType && existName { - return fmt.Errorf("ProxyGroup %d: missing type or name", idx) + return nil, fmt.Errorf("ProxyGroup %d: missing type or name", idx) } if _, exist := proxies[groupName]; exist { - return fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) + return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) } var group C.Proxy var err error @@ -315,7 +217,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error { ps, err := getProxies(proxies, urlTestOption.Proxies) if err != nil { - return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) + return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } group, err = adapters.NewURLTest(*urlTestOption, ps) case "select": @@ -327,7 +229,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error { ps, err := getProxies(proxies, selectorOption.Proxies) if err != nil { - return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) + return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } group, err = adapters.NewSelector(selectorOption.Name, ps) case "fallback": @@ -339,12 +241,12 @@ func (c *Config) parseProxies(cfg *RawConfig) error { ps, err := getProxies(proxies, fallbackOption.Proxies) if err != nil { - return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) + return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } group, err = adapters.NewFallback(*fallbackOption, ps) } if err != nil { - return fmt.Errorf("Proxy %s: %s", groupName, err.Error()) + return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) } proxies[groupName] = group } @@ -357,7 +259,7 @@ func (c *Config) parseProxies(cfg *RawConfig) error { proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps) // close old goroutine - for _, proxy := range c.proxies { + for _, proxy := range proxies { switch raw := proxy.(type) { case *adapters.URLTest: raw.Close() @@ -365,12 +267,10 @@ func (c *Config) parseProxies(cfg *RawConfig) error { raw.Close() } } - c.proxies = proxies - c.event <- &Event{Type: "proxies", Payload: proxies} - return nil + return proxies, nil } -func (c *Config) parseRules(cfg *RawConfig) error { +func parseRules(cfg *rawConfig) ([]C.Rule, error) { rules := []C.Rule{} rulesConfig := cfg.Rule @@ -397,55 +297,5 @@ func (c *Config) parseRules(cfg *RawConfig) error { } } - c.rules = rules - c.event <- &Event{Type: "rules", Payload: rules} - return nil -} - -func (c *Config) handleResponseMessage() { - for elm := range c.reportCh { - event := elm.(*Event) - switch event.Type { - case "http-addr": - if event.Payload.(bool) == false { - log.Errorf("Listening HTTP proxy at %d error", c.general.Port) - c.general.Port = 0 - } - case "socks-addr": - if event.Payload.(bool) == false { - log.Errorf("Listening SOCKS proxy at %d error", c.general.SocksPort) - c.general.SocksPort = 0 - } - case "redir-addr": - if event.Payload.(bool) == false { - log.Errorf("Listening Redir proxy at %d error", c.general.RedirPort) - c.general.RedirPort = 0 - } - } - } -} - -func newConfig() *Config { - event := make(chan interface{}) - reportCh := make(chan interface{}) - config := &Config{ - general: &General{}, - proxies: make(map[string]C.Proxy), - rules: []C.Rule{}, - lastUpdate: time.Now(), - - event: event, - reportCh: reportCh, - observable: observable.NewObservable(event), - } - go config.handleResponseMessage() - return config -} - -// Instance return singleton instance of Config -func Instance() *Config { - once.Do(func() { - config = newConfig() - }) - return config + return rules, nil } diff --git a/config/initial.go b/config/initial.go index 62f6357003..fe13c66da3 100644 --- a/config/initial.go +++ b/config/initial.go @@ -3,6 +3,7 @@ package config import ( "archive/tar" "compress/gzip" + "fmt" "io" "net/http" "os" @@ -54,15 +55,15 @@ func downloadMMDB(path string) (err error) { } // Init prepare necessary files -func Init() { +func Init(dir string) error { // initial homedir - if _, err := os.Stat(C.Path.HomeDir()); os.IsNotExist(err) { - if err := os.MkdirAll(C.Path.HomeDir(), 0777); err != nil { - log.Fatalf("Can't create config directory %s: %s", C.Path.HomeDir(), err.Error()) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, 0777); err != nil { + return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error()) } } - // initial config.ini + // initial config.yml if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { log.Info("Can't find config, create a empty file") os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) @@ -73,7 +74,8 @@ func Init() { log.Info("Can't find MMDB, start download") err := downloadMMDB(C.Path.MMDB()) if err != nil { - log.Fatalf("Can't download MMDB: %s", err.Error()) + return fmt.Errorf("Can't download MMDB: %s", err.Error()) } } + return nil } diff --git a/config/mode.go b/config/mode.go deleted file mode 100644 index b910eba183..0000000000 --- a/config/mode.go +++ /dev/null @@ -1,31 +0,0 @@ -package config - -type Mode int - -var ( - // ModeMapping is a mapping for Mode enum - ModeMapping = map[string]Mode{ - "Global": Global, - "Rule": Rule, - "Direct": Direct, - } -) - -const ( - Global Mode = iota - Rule - Direct -) - -func (m Mode) String() string { - switch m { - case Global: - return "Global" - case Rule: - return "Rule" - case Direct: - return "Direct" - default: - return "Unknow" - } -} diff --git a/config/utils.go b/config/utils.go index ca5a772331..54e205f8f8 100644 --- a/config/utils.go +++ b/config/utils.go @@ -14,13 +14,6 @@ func trimArr(arr []string) (r []string) { return } -func genAddr(port int, allowLan bool) string { - if allowLan { - return fmt.Sprintf(":%d", port) - } - return fmt.Sprintf("127.0.0.1:%d", port) -} - func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { var ps []C.Proxy for _, name := range list { diff --git a/constant/adapters.go b/constant/adapters.go index 69a00ba17a..fa0526d1b9 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -30,6 +30,7 @@ type Proxy interface { Name() string Type() AdapterType Generator(metadata *Metadata) (ProxyAdapter, error) + MarshalJSON() ([]byte, error) } // AdapterType is enum of adapter type diff --git a/constant/config.go b/constant/config.go deleted file mode 100644 index f0b93fe236..0000000000 --- a/constant/config.go +++ /dev/null @@ -1,10 +0,0 @@ -package constant - -type General struct { - Mode *string `json:"mode,omitempty"` - AllowLan *bool `json:"allow-lan,omitempty"` - Port *int `json:"port,omitempty"` - SocksPort *int `json:"socks-port,omitempty"` - RedirPort *int `json:"redir-port,omitempty"` - LogLevel *string `json:"log-level,omitempty"` -} diff --git a/constant/log.go b/constant/log.go deleted file mode 100644 index 4423322e9f..0000000000 --- a/constant/log.go +++ /dev/null @@ -1,35 +0,0 @@ -package constant - -var ( - // LogLevelMapping is a mapping for LogLevel enum - LogLevelMapping = map[string]LogLevel{ - "error": ERROR, - "warning": WARNING, - "info": INFO, - "debug": DEBUG, - } -) - -const ( - ERROR LogLevel = iota - WARNING - INFO - DEBUG -) - -type LogLevel int - -func (l LogLevel) String() string { - switch l { - case INFO: - return "info" - case WARNING: - return "warning" - case ERROR: - return "error" - case DEBUG: - return "debug" - default: - return "unknow" - } -} diff --git a/constant/proxy.go b/constant/proxy.go deleted file mode 100644 index 302e92c328..0000000000 --- a/constant/proxy.go +++ /dev/null @@ -1,7 +0,0 @@ -package constant - -// ProxySignal is used to handle graceful shutdown of proxy -type ProxySignal struct { - Done chan<- struct{} - Closed <-chan struct{} -} diff --git a/hub/common.go b/hub/common.go deleted file mode 100644 index 4898d2260b..0000000000 --- a/hub/common.go +++ /dev/null @@ -1,33 +0,0 @@ -package hub - -import ( - "github.com/Dreamacro/clash/config" - "github.com/Dreamacro/clash/proxy" - T "github.com/Dreamacro/clash/tunnel" -) - -var ( - tunnel = T.Instance() - cfg = config.Instance() - listener = proxy.Instance() -) - -type Error struct { - Error string `json:"error"` -} - -type Errors struct { - Errors map[string]string `json:"errors"` -} - -func formatErrors(errorsMap map[string]error) (bool, Errors) { - errors := make(map[string]string) - hasError := false - for key, err := range errorsMap { - if err != nil { - errors[key] = err.Error() - hasError = true - } - } - return hasError, Errors{Errors: errors} -} diff --git a/hub/configs.go b/hub/configs.go deleted file mode 100644 index 499648371c..0000000000 --- a/hub/configs.go +++ /dev/null @@ -1,96 +0,0 @@ -package hub - -import ( - "fmt" - "net/http" - - "github.com/Dreamacro/clash/config" - C "github.com/Dreamacro/clash/constant" - - "github.com/go-chi/chi" - "github.com/go-chi/render" -) - -func configRouter() http.Handler { - r := chi.NewRouter() - r.Get("/", getConfigs) - r.Put("/", updateConfigs) - return r -} - -type configSchema struct { - Port int `json:"port"` - SocksPort int `json:"socket-port"` - RedirPort int `json:"redir-port"` - AllowLan bool `json:"allow-lan"` - Mode string `json:"mode"` - LogLevel string `json:"log-level"` -} - -func getConfigs(w http.ResponseWriter, r *http.Request) { - general := cfg.General() - render.JSON(w, r, configSchema{ - Port: general.Port, - SocksPort: general.SocksPort, - RedirPort: general.RedirPort, - AllowLan: general.AllowLan, - Mode: general.Mode.String(), - LogLevel: general.LogLevel.String(), - }) -} - -func updateConfigs(w http.ResponseWriter, r *http.Request) { - general := &C.General{} - err := render.DecodeJSON(r.Body, general) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, Error{ - Error: "Format error", - }) - return - } - - // update errors - var modeErr, logLevelErr error - - // update mode - if general.Mode != nil { - mode, ok := config.ModeMapping[*general.Mode] - if !ok { - modeErr = fmt.Errorf("Mode error") - } else { - cfg.SetMode(mode) - } - } - - // update log-level - if general.LogLevel != nil { - level, ok := C.LogLevelMapping[*general.LogLevel] - if !ok { - logLevelErr = fmt.Errorf("Log Level error") - } else { - cfg.SetLogLevel(level) - } - } - - hasError, errors := formatErrors(map[string]error{ - "mode": modeErr, - "log-level": logLevelErr, - }) - - if hasError { - w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, errors) - return - } - - // update proxy - cfg.UpdateProxy(config.ProxyConfig{ - AllowLan: general.AllowLan, - Port: general.Port, - SocksPort: general.SocksPort, - RedirPort: general.RedirPort, - }) - - w.WriteHeader(http.StatusNoContent) -} diff --git a/hub/executor/executor.go b/hub/executor/executor.go new file mode 100644 index 0000000000..541c782d82 --- /dev/null +++ b/hub/executor/executor.go @@ -0,0 +1,66 @@ +package executor + +import ( + "github.com/Dreamacro/clash/config" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + P "github.com/Dreamacro/clash/proxy" + T "github.com/Dreamacro/clash/tunnel" +) + +// Parse config with default config path +func Parse() (*config.Config, error) { + return ParseWithPath(C.Path.Config()) +} + +// ParseWithPath parse config with custom config path +func ParseWithPath(path string) (*config.Config, error) { + return config.Parse(path) +} + +// ApplyConfig dispatch configure to all parts +func ApplyConfig(cfg *config.Config) { + updateProxies(cfg.Proxies) + updateRules(cfg.Rules) + updateGeneral(cfg.General) +} + +func GetGeneral() *config.General { + ports := P.GetPorts() + return &config.General{ + Port: ports.Port, + SocksPort: ports.SocksPort, + RedirPort: ports.RedirPort, + AllowLan: P.AllowLan(), + Mode: T.Instance().Mode(), + LogLevel: log.Level(), + } +} + +func updateProxies(proxies map[string]C.Proxy) { + T.Instance().UpdateProxies(proxies) +} + +func updateRules(rules []C.Rule) { + T.Instance().UpdateRules(rules) +} + +func updateGeneral(general *config.General) { + allowLan := general.AllowLan + + P.SetAllowLan(allowLan) + if err := P.ReCreateHTTP(general.Port); err != nil { + log.Errorln("Start HTTP server error: %s", err.Error()) + } + + if err := P.ReCreateSocks(general.SocksPort); err != nil { + log.Errorln("Start SOCKS5 server error: %s", err.Error()) + } + + if err := P.ReCreateRedir(general.RedirPort); err != nil { + log.Errorln("Start Redir server error: %s", err.Error()) + } + + log.SetLevel(general.LogLevel) + T.Instance().SetMode(general.Mode) +} diff --git a/hub/hub.go b/hub/hub.go new file mode 100644 index 0000000000..4a3715e5f7 --- /dev/null +++ b/hub/hub.go @@ -0,0 +1,21 @@ +package hub + +import ( + "github.com/Dreamacro/clash/hub/executor" + "github.com/Dreamacro/clash/hub/route" +) + +// Parse call at the beginning of clash +func Parse() error { + cfg, err := executor.Parse() + if err != nil { + return err + } + + if cfg.General.ExternalController != "" { + go route.Start(cfg.General.ExternalController, cfg.General.Secret) + } + + executor.ApplyConfig(cfg) + return nil +} diff --git a/hub/proxies.go b/hub/proxies.go deleted file mode 100644 index 3a54ed8bf2..0000000000 --- a/hub/proxies.go +++ /dev/null @@ -1,217 +0,0 @@ -package hub - -import ( - "context" - "fmt" - "net/http" - "net/url" - "strconv" - "time" - - A "github.com/Dreamacro/clash/adapters/outbound" - C "github.com/Dreamacro/clash/constant" - - "github.com/go-chi/chi" - "github.com/go-chi/render" -) - -func proxyRouter() http.Handler { - r := chi.NewRouter() - r.Get("/", getProxies) - r.With(parseProxyName).Get("/{name}", getProxy) - r.With(parseProxyName).Get("/{name}/delay", getProxyDelay) - r.With(parseProxyName).Put("/{name}", updateProxy) - return r -} - -// When name is composed of a partial escape string, Golang does not unescape it -func parseProxyName(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - name := chi.URLParam(r, "name") - if newName, err := url.PathUnescape(name); err == nil { - name = newName - } - ctx := context.WithValue(r.Context(), contextKey("proxy name"), name) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -type SampleProxy struct { - Type string `json:"type"` -} - -type Selector struct { - Type string `json:"type"` - Now string `json:"now"` - All []string `json:"all"` -} - -type URLTest struct { - Type string `json:"type"` - Now string `json:"now"` -} - -type Fallback struct { - Type string `json:"type"` - Now string `json:"now"` -} - -func transformProxy(proxy C.Proxy) interface{} { - t := proxy.Type() - switch t { - case C.Selector: - selector := proxy.(*A.Selector) - return Selector{ - Type: t.String(), - Now: selector.Now(), - All: selector.All(), - } - case C.URLTest: - return URLTest{ - Type: t.String(), - Now: proxy.(*A.URLTest).Now(), - } - case C.Fallback: - return Fallback{ - Type: t.String(), - Now: proxy.(*A.Fallback).Now(), - } - default: - return SampleProxy{ - Type: proxy.Type().String(), - } - } -} - -type GetProxiesResponse struct { - Proxies map[string]interface{} `json:"proxies"` -} - -func getProxies(w http.ResponseWriter, r *http.Request) { - rawProxies := cfg.Proxies() - proxies := make(map[string]interface{}) - for name, proxy := range rawProxies { - proxies[name] = transformProxy(proxy) - } - render.JSON(w, r, GetProxiesResponse{Proxies: proxies}) -} - -func getProxy(w http.ResponseWriter, r *http.Request) { - name := r.Context().Value(contextKey("proxy name")).(string) - proxies := cfg.Proxies() - proxy, exist := proxies[name] - if !exist { - w.WriteHeader(http.StatusNotFound) - render.JSON(w, r, Error{ - Error: "Proxy not found", - }) - return - } - render.JSON(w, r, transformProxy(proxy)) -} - -type UpdateProxyRequest struct { - Name string `json:"name"` -} - -func updateProxy(w http.ResponseWriter, r *http.Request) { - req := UpdateProxyRequest{} - if err := render.DecodeJSON(r.Body, &req); err != nil { - w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, Error{ - Error: "Format error", - }) - return - } - - name := r.Context().Value(contextKey("proxy name")).(string) - proxies := cfg.Proxies() - proxy, exist := proxies[name] - if !exist { - w.WriteHeader(http.StatusNotFound) - render.JSON(w, r, Error{ - Error: "Proxy not found", - }) - return - } - - selector, ok := proxy.(*A.Selector) - if !ok { - w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, Error{ - Error: "Proxy can't update", - }) - return - } - - if err := selector.Set(req.Name); err != nil { - w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, Error{ - Error: fmt.Sprintf("Selector update error: %s", err.Error()), - }) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -type GetProxyDelayRequest struct { - URL string `json:"url"` - Timeout int16 `json:"timeout"` -} - -type GetProxyDelayResponse struct { - Delay int16 `json:"delay"` -} - -func getProxyDelay(w http.ResponseWriter, r *http.Request) { - query := r.URL.Query() - url := query.Get("url") - timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, Error{ - Error: "Format error", - }) - return - } - - name := r.Context().Value(contextKey("proxy name")).(string) - proxies := cfg.Proxies() - proxy, exist := proxies[name] - if !exist { - w.WriteHeader(http.StatusNotFound) - render.JSON(w, r, Error{ - Error: "Proxy not found", - }) - return - } - - sigCh := make(chan int16) - go func() { - t, err := A.DelayTest(proxy, url) - if err != nil { - sigCh <- 0 - } - sigCh <- t - }() - - select { - case <-time.After(time.Millisecond * time.Duration(timeout)): - w.WriteHeader(http.StatusRequestTimeout) - render.JSON(w, r, Error{ - Error: "Proxy delay test timeout", - }) - case t := <-sigCh: - if t == 0 { - w.WriteHeader(http.StatusServiceUnavailable) - render.JSON(w, r, Error{ - Error: "An error occurred in the delay test", - }) - } else { - render.JSON(w, r, GetProxyDelayResponse{ - Delay: t, - }) - } - } -} diff --git a/hub/route/configs.go b/hub/route/configs.go new file mode 100644 index 0000000000..66d6cb4339 --- /dev/null +++ b/hub/route/configs.go @@ -0,0 +1,69 @@ +package route + +import ( + "net/http" + + "github.com/Dreamacro/clash/hub/executor" + "github.com/Dreamacro/clash/log" + T "github.com/Dreamacro/clash/tunnel" + P "github.com/Dreamacro/clash/proxy" + + "github.com/go-chi/chi" + "github.com/go-chi/render" +) + +func configRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getConfigs) + return r +} + +type configSchema struct { + Port *int `json:"port"` + SocksPort *int `json:"socket-port"` + RedirPort *int `json:"redir-port"` + AllowLan *bool `json:"allow-lan"` + Mode *T.Mode `json:"mode"` + LogLevel *log.LogLevel `json:"log-level"` +} + +func getConfigs(w http.ResponseWriter, r *http.Request) { + general := executor.GetGeneral() + render.Respond(w, r, general) +} + +func pointerOrDefault (p *int, def int) int { + if p != nil { + return *p + } + + return def +} + +func updateConfigs(w http.ResponseWriter, r *http.Request) { + general := &configSchema{} + if err := render.DecodeJSON(r.Body, general); err != nil { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, ErrBadRequest) + return + } + + if general.AllowLan != nil { + P.SetAllowLan(*general.AllowLan) + } + + ports := P.GetPorts() + P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) + P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) + P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) + + if general.Mode != nil { + T.Instance().SetMode(*general.Mode) + } + + if general.LogLevel != nil { + log.SetLevel(*general.LogLevel) + } + + w.WriteHeader(http.StatusNoContent) +} diff --git a/hub/route/ctxkeys.go b/hub/route/ctxkeys.go new file mode 100644 index 0000000000..079329718c --- /dev/null +++ b/hub/route/ctxkeys.go @@ -0,0 +1,12 @@ +package route + +var ( + CtxKeyProxyName = contextKey("proxy name") + CtxKeyProxy = contextKey("proxy") +) + +type contextKey string + +func (c contextKey) String() string { + return "clash context key " + string(c) +} diff --git a/hub/route/errors.go b/hub/route/errors.go new file mode 100644 index 0000000000..b469e107f0 --- /dev/null +++ b/hub/route/errors.go @@ -0,0 +1,22 @@ +package route + +var ( + ErrUnauthorized = newError("Unauthorized") + ErrBadRequest = newError("Body invalid") + ErrForbidden = newError("Forbidden") + ErrNotFound = newError("Resource not found") + ErrRequestTimeout = newError("Timeout") +) + +// HTTPError is custom HTTP error for API +type HTTPError struct { + Message string `json:"message"` +} + +func (e *HTTPError) Error() string { + return e.Message +} + +func newError(msg string) *HTTPError { + return &HTTPError{Message: msg} +} diff --git a/hub/route/proxies.go b/hub/route/proxies.go new file mode 100644 index 0000000000..65beac1ec3 --- /dev/null +++ b/hub/route/proxies.go @@ -0,0 +1,137 @@ +package route + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + A "github.com/Dreamacro/clash/adapters/outbound" + C "github.com/Dreamacro/clash/constant" + T "github.com/Dreamacro/clash/tunnel" + + "github.com/go-chi/chi" + "github.com/go-chi/render" +) + +func proxyRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getProxies) + + r.Route("/{name}", func(r chi.Router) { + r.Use(parseProxyName, findProxyByName) + r.Get("/", getProxy) + r.Get("/delay", getProxyDelay) + r.Put("/", updateProxy) + }) + return r +} + +// When name is composed of a partial escape string, Golang does not unescape it +func parseProxyName(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + name := chi.URLParam(r, "name") + if newName, err := url.PathUnescape(name); err == nil { + name = newName + } + ctx := context.WithValue(r.Context(), CtxKeyProxyName, name) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func findProxyByName(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + name := r.Context().Value(CtxKeyProxyName).(string) + proxies := T.Instance().Proxies() + proxy, exist := proxies[name] + if !exist { + w.WriteHeader(http.StatusNotFound) + render.Respond(w, r, ErrNotFound) + return + } + + ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func getProxies(w http.ResponseWriter, r *http.Request) { + proxies := T.Instance().Proxies() + render.Respond(w, r, map[string]map[string]C.Proxy{ + "proxies": proxies, + }) +} + +func getProxy(w http.ResponseWriter, r *http.Request) { + proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) + render.Respond(w, r, proxy) +} + +type UpdateProxyRequest struct { + Name string `json:"name"` +} + +func updateProxy(w http.ResponseWriter, r *http.Request) { + req := UpdateProxyRequest{} + if err := render.DecodeJSON(r.Body, &req); err != nil { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, ErrBadRequest) + return + } + + proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) + + selector, ok := proxy.(*A.Selector) + if !ok { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, ErrBadRequest) + return + } + + if err := selector.Set(req.Name); err != nil { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error()))) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +func getProxyDelay(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + url := query.Get("url") + timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, ErrBadRequest) + return + } + + proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) + + sigCh := make(chan int16) + go func() { + t, err := A.DelayTest(proxy, url) + if err != nil { + sigCh <- 0 + } + sigCh <- t + }() + + select { + case <-time.After(time.Millisecond * time.Duration(timeout)): + w.WriteHeader(http.StatusRequestTimeout) + render.Respond(w, r, ErrRequestTimeout) + case t := <-sigCh: + if t == 0 { + w.WriteHeader(http.StatusServiceUnavailable) + render.Respond(w, r, newError("An error occurred in the delay test")) + } else { + render.Respond(w, r, map[string]int16{ + "delay": t, + }) + } + } +} diff --git a/hub/rules.go b/hub/route/rules.go similarity index 56% rename from hub/rules.go rename to hub/route/rules.go index c91120e8f1..02e19b9bdc 100644 --- a/hub/rules.go +++ b/hub/route/rules.go @@ -1,8 +1,10 @@ -package hub +package route import ( "net/http" + T "github.com/Dreamacro/clash/tunnel" + "github.com/go-chi/chi" "github.com/go-chi/render" ) @@ -10,7 +12,6 @@ import ( func ruleRouter() http.Handler { r := chi.NewRouter() r.Get("/", getRules) - r.Put("/", updateRules) return r } @@ -20,12 +21,8 @@ type Rule struct { Proxy string `json:"proxy"` } -type GetRulesResponse struct { - Rules []Rule `json:"rules"` -} - func getRules(w http.ResponseWriter, r *http.Request) { - rawRules := cfg.Rules() + rawRules := T.Instance().Rules() var rules []Rule for _, rule := range rawRules { @@ -37,19 +34,7 @@ func getRules(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusOK) - render.JSON(w, r, GetRulesResponse{ - Rules: rules, + render.Respond(w, r, map[string][]Rule{ + "rules": rules, }) } - -func updateRules(w http.ResponseWriter, r *http.Request) { - err := cfg.UpdateRules() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - render.JSON(w, r, Error{ - Error: err.Error(), - }) - return - } - w.WriteHeader(http.StatusNoContent) -} diff --git a/hub/server.go b/hub/route/server.go similarity index 62% rename from hub/server.go rename to hub/route/server.go index 304f991e84..72fe965567 100644 --- a/hub/server.go +++ b/hub/route/server.go @@ -1,4 +1,4 @@ -package hub +package route import ( "encoding/json" @@ -6,44 +6,32 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/config" - C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" T "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/cors" "github.com/go-chi/render" - log "github.com/sirupsen/logrus" ) -var secret = "" +var ( + serverSecret = "" + serverAddr = "" +) type Traffic struct { Up int64 `json:"up"` Down int64 `json:"down"` } -func newHub(signal chan struct{}) { - var addr string - ch := config.Instance().Subscribe() - signal <- struct{}{} - count := 0 - for { - elm := <-ch - event := elm.(*config.Event) - switch event.Type { - case "external-controller": - addr = event.Payload.(string) - count++ - case "secret": - secret = event.Payload.(string) - count++ - } - if count == 2 { - break - } +func Start(addr string, secret string) { + if serverAddr != "" { + return } + serverAddr = addr + serverSecret = secret + r := chi.NewRouter() cors := cors.New(cors.Options{ @@ -59,12 +47,12 @@ func newHub(signal chan struct{}) { r.With(jsonContentType).Get("/logs", getLogs) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) - r.Mount("/rules", ruleRouter()) + // r.Mount("/rules", ruleRouter()) - log.Infof("RESTful API listening at: %s", addr) + log.Infoln("RESTful API listening at: %s", addr) err := http.ListenAndServe(addr, r) if err != nil { - log.Errorf("External controller error: %s", err.Error()) + log.Errorln("External controller error: %s", err.Error()) } } @@ -81,18 +69,16 @@ func authentication(next http.Handler) http.Handler { header := r.Header.Get("Authorization") text := strings.SplitN(header, " ", 2) - if secret == "" { + if serverSecret == "" { next.ServeHTTP(w, r) return } hasUnvalidHeader := text[0] != "Bearer" - hasUnvalidSecret := len(text) == 2 && text[1] != secret + hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret if hasUnvalidHeader || hasUnvalidSecret { w.WriteHeader(http.StatusUnauthorized) - render.JSON(w, r, Error{ - Error: "Authentication failed", - }) + render.Respond(w, r, ErrUnauthorized) return } next.ServeHTTP(w, r) @@ -100,17 +86,11 @@ func authentication(next http.Handler) http.Handler { return http.HandlerFunc(fn) } -type contextKey string - -func (c contextKey) String() string { - return "clash context key " + string(c) -} - func traffic(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) tick := time.NewTicker(time.Second) - t := tunnel.Traffic() + t := T.Instance().Traffic() for range tick.C { up, down := t.Now() if err := json.NewEncoder(w).Encode(Traffic{ @@ -134,29 +114,18 @@ func getLogs(w http.ResponseWriter, r *http.Request) { levelText = "info" } - level, ok := C.LogLevelMapping[levelText] + level, ok := log.LogLevelMapping[levelText] if !ok { w.WriteHeader(http.StatusBadRequest) - render.JSON(w, r, Error{ - Error: "Level error", - }) + render.Respond(w, r, ErrBadRequest) return } - src := tunnel.Log() - sub, err := src.Subscribe() - defer src.UnSubscribe(sub) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - render.JSON(w, r, Error{ - Error: err.Error(), - }) - return - } + sub := log.Subscribe() render.Status(r, http.StatusOK) for elm := range sub { - log := elm.(T.Log) - if log.LogLevel > level { + log := elm.(*log.Event) + if log.LogLevel < level { continue } @@ -169,10 +138,3 @@ func getLogs(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() } } - -// Run initial hub -func Run() { - signal := make(chan struct{}) - go newHub(signal) - <-signal -} diff --git a/log/level.go b/log/level.go new file mode 100644 index 0000000000..4e28e71437 --- /dev/null +++ b/log/level.go @@ -0,0 +1,76 @@ +package log + +import ( + "encoding/json" + "errors" + + yaml "gopkg.in/yaml.v2" +) + +var ( + // LogLevelMapping is a mapping for LogLevel enum + LogLevelMapping = map[string]LogLevel{ + "error": ERROR, + "warning": WARNING, + "info": INFO, + "debug": DEBUG, + } +) + +const ( + DEBUG LogLevel = iota + INFO + WARNING + ERROR +) + +type LogLevel int + +// UnmarshalYAML unserialize Mode with yaml +func (l *LogLevel) UnmarshalYAML(data []byte) error { + var tp string + yaml.Unmarshal(data, &tp) + level, exist := LogLevelMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *l = level + return nil +} + +// MarshalYAML serialize Mode with yaml +func (l LogLevel) MarshalYAML() ([]byte, error) { + return yaml.Marshal(l.String()) +} + +// UnmarshalJSON unserialize Mode with json +func (l *LogLevel) UnmarshalJSON(data []byte) error { + var tp string + json.Unmarshal(data, tp) + level, exist := LogLevelMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *l = level + return nil +} + +// MarshalJSON serialize Mode with json +func (l LogLevel) MarshalJSON() ([]byte, error) { + return json.Marshal(l.String()) +} + +func (l LogLevel) String() string { + switch l { + case INFO: + return "info" + case WARNING: + return "warning" + case ERROR: + return "error" + case DEBUG: + return "debug" + default: + return "unknow" + } +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000000..c80606610d --- /dev/null +++ b/log/log.go @@ -0,0 +1,85 @@ +package log + +import ( + "fmt" + + "github.com/Dreamacro/clash/common/observable" + + log "github.com/sirupsen/logrus" +) + +var ( + logCh = make(chan interface{}) + source = observable.NewObservable(logCh) + level = INFO +) + +type Event struct { + LogLevel LogLevel + Payload string +} + +func (e *Event) Type() string { + return e.LogLevel.String() +} + +func Infoln(format string, v ...interface{}) { + event := newLog(INFO, format, v...) + logCh <- event + print(event) +} + +func Warnln(format string, v ...interface{}) { + event := newLog(WARNING, format, v...) + logCh <- event + print(event) +} + +func Errorln(format string, v ...interface{}) { + event := newLog(ERROR, format, v...) + logCh <- event + print(event) +} + +func Debugln(format string, v ...interface{}) { + event := newLog(DEBUG, format, v...) + logCh <- event + print(event) +} + +func Subscribe() observable.Subscription { + sub, _ := source.Subscribe() + return sub +} + +func Level() LogLevel { + return level +} + +func SetLevel(newLevel LogLevel) { + level = newLevel +} + +func print(data *Event) { + if data.LogLevel < level { + return + } + + switch data.LogLevel { + case INFO: + log.Infoln(data.Payload) + case WARNING: + log.Warnln(data.Payload) + case ERROR: + log.Errorln(data.Payload) + case DEBUG: + log.Debugln(data.Payload) + } +} + +func newLog(logLevel LogLevel, format string, v ...interface{}) *Event { + return &Event{ + LogLevel: logLevel, + Payload: fmt.Sprintf(format, v...), + } +} diff --git a/main.go b/main.go index b48106251e..be91250aca 100644 --- a/main.go +++ b/main.go @@ -10,8 +10,6 @@ import ( "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub" - "github.com/Dreamacro/clash/proxy" - "github.com/Dreamacro/clash/tunnel" log "github.com/sirupsen/logrus" ) @@ -26,10 +24,6 @@ func init() { } func main() { - tunnel.Instance().Run() - proxy.Instance().Run() - hub.Run() - if homedir != "" { if !filepath.IsAbs(homedir) { currentDir, _ := os.Getwd() @@ -38,9 +32,11 @@ func main() { C.SetHomeDir(homedir) } - config.Init() - err := config.Instance().Parse() - if err != nil { + if err := config.Init(C.Path.HomeDir()); err != nil { + log.Fatalf("Initial configuration directory error: %s", err.Error()) + } + + if err := hub.Parse(); err != nil { log.Fatalf("Parse config error: %s", err.Error()) } diff --git a/proxy/http/server.go b/proxy/http/server.go index 598300589c..db57b24991 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -6,31 +6,25 @@ import ( "net/http" "github.com/Dreamacro/clash/adapters/inbound" - C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" - - log "github.com/sirupsen/logrus" ) var ( tun = tunnel.Instance() ) -func NewHttpProxy(addr string) (*C.ProxySignal, error) { +func NewHttpProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { l, err := net.Listen("tcp", addr) if err != nil { - return nil, err + return nil, nil, err } done := make(chan struct{}) closed := make(chan struct{}) - signal := &C.ProxySignal{ - Done: done, - Closed: closed, - } go func() { - log.Infof("HTTP proxy listening at: %s", addr) + log.Infoln("HTTP proxy listening at: %s", addr) for { c, err := l.Accept() if err != nil { @@ -45,12 +39,11 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) { go func() { <-done - close(done) l.Close() closed <- struct{}{} }() - return signal, nil + return done, closed, nil } func handleConn(conn net.Conn) { diff --git a/proxy/listener.go b/proxy/listener.go index 06e658a327..5e635ba55e 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -1,116 +1,166 @@ package proxy import ( - "sync" + "fmt" + "net" + "strconv" - "github.com/Dreamacro/clash/config" - C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/socks" ) var ( - listener *Listener - once sync.Once + allowLan = false + + socksListener *listener + httpListener *listener + redirListener *listener ) -type Listener struct { - // signal for update - httpSignal *C.ProxySignal - socksSignal *C.ProxySignal - redirSignal *C.ProxySignal +type listener struct { + Address string + Done chan<- struct{} + Closed <-chan struct{} +} + +type Ports struct { + Port int `json:"port"` + SocksPort int `json:"socks-port"` + RedirPort int `json:"redir-port"` +} + +func AllowLan() bool { + return allowLan +} + +func SetAllowLan(al bool) { + allowLan = al } -func (l *Listener) updateHTTP(addr string) error { - if l.httpSignal != nil { - signal := l.httpSignal - signal.Done <- struct{}{} - <-signal.Closed - l.httpSignal = nil +func ReCreateHTTP(port int) error { + addr := genAddr(port, allowLan) + + if httpListener != nil { + if httpListener.Address == addr { + return nil + } + httpListener.Done <- struct{}{} + <-httpListener.Closed + httpListener = nil + } + + if portIsZero(addr) { + return nil } - signal, err := http.NewHttpProxy(addr) + done, closed, err := http.NewHttpProxy(addr) if err != nil { return err } - l.httpSignal = signal + httpListener = &listener{ + Address: addr, + Done: done, + Closed: closed, + } return nil } -func (l *Listener) updateSocks(addr string) error { - if l.socksSignal != nil { - signal := l.socksSignal - signal.Done <- struct{}{} - <-signal.Closed - l.socksSignal = nil +func ReCreateSocks(port int) error { + addr := genAddr(port, allowLan) + + if socksListener != nil { + if socksListener.Address == addr { + return nil + } + socksListener.Done <- struct{}{} + <-socksListener.Closed + socksListener = nil + } + + if portIsZero(addr) { + return nil } - signal, err := socks.NewSocksProxy(addr) + done, closed, err := socks.NewSocksProxy(addr) if err != nil { return err } - l.socksSignal = signal + socksListener = &listener{ + Address: addr, + Done: done, + Closed: closed, + } return nil } -func (l *Listener) updateRedir(addr string) error { - if l.redirSignal != nil { - signal := l.redirSignal - signal.Done <- struct{}{} - <-signal.Closed - l.redirSignal = nil +func ReCreateRedir(port int) error { + addr := genAddr(port, allowLan) + + if redirListener != nil { + if redirListener.Address == addr { + return nil + } + redirListener.Done <- struct{}{} + <-redirListener.Closed + redirListener = nil + } + + if portIsZero(addr) { + return nil } - signal, err := redir.NewRedirProxy(addr) + done, closed, err := redir.NewRedirProxy(addr) if err != nil { return err } - l.redirSignal = signal + redirListener = &listener{ + Address: addr, + Done: done, + Closed: closed, + } return nil } -func (l *Listener) process(signal chan<- struct{}) { - sub := config.Instance().Subscribe() - signal <- struct{}{} - reportCH := config.Instance().Report() - for elm := range sub { - event := elm.(*config.Event) - switch event.Type { - case "http-addr": - addr := event.Payload.(string) - err := l.updateHTTP(addr) - reportCH <- &config.Event{Type: "http-addr", Payload: err == nil} - case "socks-addr": - addr := event.Payload.(string) - err := l.updateSocks(addr) - reportCH <- &config.Event{Type: "socks-addr", Payload: err == nil} - case "redir-addr": - addr := event.Payload.(string) - err := l.updateRedir(addr) - reportCH <- &config.Event{Type: "redir-addr", Payload: err == nil} - } +// GetPorts return the ports of proxy servers +func GetPorts() *Ports { + ports := &Ports{} + + if httpListener != nil { + _, portStr, _ := net.SplitHostPort(httpListener.Address) + port, _ := strconv.Atoi(portStr) + ports.Port = port + } + + if socksListener != nil { + _, portStr, _ := net.SplitHostPort(socksListener.Address) + port, _ := strconv.Atoi(portStr) + ports.SocksPort = port } -} -// Run ensure config monitoring -func (l *Listener) Run() { - signal := make(chan struct{}) - go l.process(signal) - <-signal + if redirListener != nil { + _, portStr, _ := net.SplitHostPort(redirListener.Address) + port, _ := strconv.Atoi(portStr) + ports.RedirPort = port + } + + return ports } -func newListener() *Listener { - return &Listener{} +func portIsZero(addr string) bool { + _, port, err := net.SplitHostPort(addr) + if port == "0" || port == "" || err != nil { + return true + } + return false } -// Instance return singleton instance of Listener -func Instance() *Listener { - once.Do(func() { - listener = newListener() - }) - return listener +func genAddr(port int, allowLan bool) string { + if allowLan { + return fmt.Sprintf(":%d", port) + } + return fmt.Sprintf("127.0.0.1:%d", port) } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index f4b47f4b74..bb6d3c552c 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -4,7 +4,6 @@ import ( "net" "github.com/Dreamacro/clash/adapters/inbound" - C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" log "github.com/sirupsen/logrus" @@ -14,18 +13,14 @@ var ( tun = tunnel.Instance() ) -func NewRedirProxy(addr string) (*C.ProxySignal, error) { +func NewRedirProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { l, err := net.Listen("tcp", addr) if err != nil { - return nil, err + return nil, nil, err } done := make(chan struct{}) closed := make(chan struct{}) - signal := &C.ProxySignal{ - Done: done, - Closed: closed, - } go func() { log.Infof("Redir proxy listening at: %s", addr) @@ -43,12 +38,11 @@ func NewRedirProxy(addr string) (*C.ProxySignal, error) { go func() { <-done - close(done) l.Close() closed <- struct{}{} }() - return signal, nil + return done, closed, nil } func handleRedir(conn net.Conn) { diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 57645dc314..646050be1a 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -4,7 +4,6 @@ import ( "net" "github.com/Dreamacro/clash/adapters/inbound" - C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/go-shadowsocks2/socks" @@ -15,18 +14,14 @@ var ( tun = tunnel.Instance() ) -func NewSocksProxy(addr string) (*C.ProxySignal, error) { +func NewSocksProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { l, err := net.Listen("tcp", addr) if err != nil { - return nil, err + return nil, nil, err } done := make(chan struct{}) closed := make(chan struct{}) - signal := &C.ProxySignal{ - Done: done, - Closed: closed, - } go func() { log.Infof("SOCKS proxy listening at: %s", addr) @@ -44,12 +39,11 @@ func NewSocksProxy(addr string) (*C.ProxySignal, error) { go func() { <-done - close(done) l.Close() closed <- struct{}{} }() - return signal, nil + return done, closed, nil } func handleSocks(conn net.Conn) { diff --git a/tunnel/log.go b/tunnel/log.go deleted file mode 100644 index f703d62c41..0000000000 --- a/tunnel/log.go +++ /dev/null @@ -1,51 +0,0 @@ -package tunnel - -import ( - "fmt" - - C "github.com/Dreamacro/clash/constant" - - log "github.com/sirupsen/logrus" -) - -type Log struct { - LogLevel C.LogLevel - Payload string -} - -func (l *Log) Type() string { - return l.LogLevel.String() -} - -func print(data Log) { - switch data.LogLevel { - case C.INFO: - log.Infoln(data.Payload) - case C.WARNING: - log.Warnln(data.Payload) - case C.ERROR: - log.Errorln(data.Payload) - case C.DEBUG: - log.Debugln(data.Payload) - } -} - -func (t *Tunnel) subscribeLogs() { - sub, err := t.observable.Subscribe() - if err != nil { - log.Fatalf("Can't subscribe tunnel log: %s", err.Error()) - } - for elm := range sub { - data := elm.(Log) - if data.LogLevel <= t.logLevel { - print(data) - } - } -} - -func newLog(logLevel C.LogLevel, format string, v ...interface{}) Log { - return Log{ - LogLevel: logLevel, - Payload: fmt.Sprintf(format, v...), - } -} diff --git a/tunnel/mode.go b/tunnel/mode.go new file mode 100644 index 0000000000..b367ed3ada --- /dev/null +++ b/tunnel/mode.go @@ -0,0 +1,53 @@ +package tunnel + +import ( + "encoding/json" + "errors" +) + +type Mode int + +var ( + // ModeMapping is a mapping for Mode enum + ModeMapping = map[string]Mode{ + "Global": Global, + "Rule": Rule, + "Direct": Direct, + } +) + +const ( + Global Mode = iota + Rule + Direct +) + +// UnmarshalJSON unserialize Mode +func (m *Mode) UnmarshalJSON(data []byte) error { + var tp string + json.Unmarshal(data, tp) + mode, exist := ModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *m = mode + return nil +} + +// MarshalJSON serialize Mode +func (m Mode) MarshalJSON() ([]byte, error) { + return json.Marshal(m.String()) +} + +func (m Mode) String() string { + switch m { + case Global: + return "Global" + case Rule: + return "Rule" + case Direct: + return "Direct" + default: + return "Unknow" + } +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index fb43255408..c574adadd8 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -5,9 +5,8 @@ import ( "time" InboundAdapter "github.com/Dreamacro/clash/adapters/inbound" - "github.com/Dreamacro/clash/common/observable" - cfg "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" "gopkg.in/eapache/channels.v1" ) @@ -26,12 +25,7 @@ type Tunnel struct { traffic *C.Traffic // Outbound Rule - mode cfg.Mode - - // Log - logCh chan interface{} - observable *observable.Observable - logLevel C.LogLevel + mode Mode } // Add request to queue @@ -44,33 +38,38 @@ func (t *Tunnel) Traffic() *C.Traffic { return t.traffic } -// Log return clash log stream -func (t *Tunnel) Log() *observable.Observable { - return t.observable +// Rules return all rules +func (t *Tunnel) Rules() []C.Rule { + return t.rules } -func (t *Tunnel) configMonitor(signal chan<- struct{}) { - sub := cfg.Instance().Subscribe() - signal <- struct{}{} - for elm := range sub { - event := elm.(*cfg.Event) - switch event.Type { - case "proxies": - proxies := event.Payload.(map[string]C.Proxy) - t.configLock.Lock() - t.proxies = proxies - t.configLock.Unlock() - case "rules": - rules := event.Payload.([]C.Rule) - t.configLock.Lock() - t.rules = rules - t.configLock.Unlock() - case "mode": - t.mode = event.Payload.(cfg.Mode) - case "log-level": - t.logLevel = event.Payload.(C.LogLevel) - } - } +// UpdateRules handle update rules +func (t *Tunnel) UpdateRules(rules []C.Rule) { + t.configLock.Lock() + t.rules = rules + t.configLock.Unlock() +} + +// Proxies return all proxies +func (t *Tunnel) Proxies() map[string]C.Proxy { + return t.proxies +} + +// UpdateProxies handle update proxies +func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy) { + t.configLock.Lock() + t.proxies = proxies + t.configLock.Unlock() +} + +// Mode return current mode +func (t *Tunnel) Mode() Mode { + return t.mode +} + +// SetMode change the mode of tunnel +func (t *Tunnel) SetMode(mode Mode) { + t.mode = mode } func (t *Tunnel) process() { @@ -88,9 +87,9 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { var proxy C.Proxy switch t.mode { - case cfg.Direct: + case Direct: proxy = t.proxies["DIRECT"] - case cfg.Global: + case Global: proxy = t.proxies["GLOBAL"] // Rule default: @@ -98,7 +97,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } remoConn, err := proxy.Generator(metadata) if err != nil { - t.logCh <- newLog(C.WARNING, "Proxy connect error: %s", err.Error()) + log.Warnln("Proxy connect error: %s", err.Error()) return } defer remoConn.Close() @@ -121,34 +120,21 @@ func (t *Tunnel) match(metadata *C.Metadata) C.Proxy { if !ok { continue } - t.logCh <- newLog(C.INFO, "%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) + log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) return a } } - t.logCh <- newLog(C.INFO, "%v doesn't match any rule using DIRECT", metadata.String()) + log.Infoln("%v doesn't match any rule using DIRECT", metadata.String()) return t.proxies["DIRECT"] } -// Run initial task -func (t *Tunnel) Run() { - go t.process() - go t.subscribeLogs() - signal := make(chan struct{}) - go t.configMonitor(signal) - <-signal -} - func newTunnel() *Tunnel { - logCh := make(chan interface{}) return &Tunnel{ queue: channels.NewInfiniteChannel(), proxies: make(map[string]C.Proxy), - observable: observable.NewObservable(logCh), - logCh: logCh, configLock: &sync.RWMutex{}, traffic: C.NewTraffic(time.Second), - mode: cfg.Rule, - logLevel: C.INFO, + mode: Rule, } } @@ -156,6 +142,7 @@ func newTunnel() *Tunnel { func Instance() *Tunnel { once.Do(func() { tunnel = newTunnel() + go tunnel.process() }) return tunnel } From c7a349e1fe9c394a4b0a6a165d4a7d2eb72c8f3a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 21 Nov 2018 13:59:39 +0800 Subject: [PATCH 085/535] Improve: auto change payload to lowercase --- rules/domain.go | 4 +++- rules/domain_keyword.go | 2 +- rules/domain_suffix.go | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/rules/domain.go b/rules/domain.go index 181fc1d2a6..1c0fbdd1a5 100644 --- a/rules/domain.go +++ b/rules/domain.go @@ -1,6 +1,8 @@ package rules import ( + "strings" + C "github.com/Dreamacro/clash/constant" ) @@ -30,7 +32,7 @@ func (d *Domain) Payload() string { func NewDomain(domain string, adapter string) *Domain { return &Domain{ - domain: domain, + domain: strings.ToLower(domain), adapter: adapter, } } diff --git a/rules/domain_keyword.go b/rules/domain_keyword.go index f2b1dd1c75..0708b10189 100644 --- a/rules/domain_keyword.go +++ b/rules/domain_keyword.go @@ -33,7 +33,7 @@ func (dk *DomainKeyword) Payload() string { func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { return &DomainKeyword{ - keyword: keyword, + keyword: strings.ToLower(keyword), adapter: adapter, } } diff --git a/rules/domain_suffix.go b/rules/domain_suffix.go index c29ab06e82..710047738c 100644 --- a/rules/domain_suffix.go +++ b/rules/domain_suffix.go @@ -33,7 +33,7 @@ func (ds *DomainSuffix) Payload() string { func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { return &DomainSuffix{ - suffix: suffix, + suffix: strings.ToLower(suffix), adapter: adapter, } } From 05bf4d44ab679cf40dbf2d901da2403dc9ce7c8b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 21 Nov 2018 18:21:24 +0800 Subject: [PATCH 086/535] Change: replace `FINAL` with `MATCH` in a progressive way --- README.md | 5 +++-- config/config.go | 34 ++++++++++++++++++++++++---------- tunnel/tunnel.go | 2 +- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index efb29d43e5..d5e502034f 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,9 @@ Rule: - DOMAIN-SUFFIX,ad.com,REJECT - IP-CIDR,127.0.0.0/8,DIRECT - GEOIP,CN,DIRECT -# note: there is two "," -- FINAL,,Proxy +# FINAL would remove after prerelease +# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now +- MATCH,Proxy ``` ## Thanks diff --git a/config/config.go b/config/config.go index b62ba9d076..e3fdb848d9 100644 --- a/config/config.go +++ b/config/config.go @@ -275,25 +275,39 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { rulesConfig := cfg.Rule // parse rules - for _, line := range rulesConfig { - rule := strings.Split(line, ",") - if len(rule) < 3 { - continue + for idx, line := range rulesConfig { + rule := trimArr(strings.Split(line, ",")) + var ( + payload string + target string + ) + + switch len(rule) { + case 2: + target = rule[1] + case 3: + payload = rule[1] + target = rule[2] + default: + return nil, fmt.Errorf("Rules[%d] error: format invalid", idx) } + rule = trimArr(rule) switch rule[0] { case "DOMAIN": - rules = append(rules, R.NewDomain(rule[1], rule[2])) + rules = append(rules, R.NewDomain(payload, target)) case "DOMAIN-SUFFIX": - rules = append(rules, R.NewDomainSuffix(rule[1], rule[2])) + rules = append(rules, R.NewDomainSuffix(payload, target)) case "DOMAIN-KEYWORD": - rules = append(rules, R.NewDomainKeyword(rule[1], rule[2])) + rules = append(rules, R.NewDomainKeyword(payload, target)) case "GEOIP": - rules = append(rules, R.NewGEOIP(rule[1], rule[2])) + rules = append(rules, R.NewGEOIP(payload, target)) case "IP-CIDR", "IP-CIDR6": - rules = append(rules, R.NewIPCIDR(rule[1], rule[2])) + rules = append(rules, R.NewIPCIDR(payload, target)) + case "MATCH": + fallthrough case "FINAL": - rules = append(rules, R.NewFinal(rule[2])) + rules = append(rules, R.NewFinal(target)) } } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index c574adadd8..264e0e8c84 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -97,7 +97,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } remoConn, err := proxy.Generator(metadata) if err != nil { - log.Warnln("Proxy connect error: %s", err.Error()) + log.Warnln("Proxy[%s] connect [%s] error: %s", proxy.Name(), metadata.String(), err.Error()) return } defer remoConn.Close() From 970643b14443a3eab1a62c2a1016be6a4bb502cd Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Thu, 22 Nov 2018 11:54:01 +0800 Subject: [PATCH 087/535] Fix: goroutine leak while closing proxy (#43) * Fix: goroutine leak while closing proxy * Chore: improve proxy architecture * Fix: stack overflow --- proxy/http/server.go | 33 +++++++++++++++---------- proxy/listener.go | 58 ++++++++++++++++---------------------------- proxy/redir/tcp.go | 31 ++++++++++++++--------- proxy/socks/tcp.go | 31 ++++++++++++++--------- 4 files changed, 79 insertions(+), 74 deletions(-) diff --git a/proxy/http/server.go b/proxy/http/server.go index db57b24991..d95d91ad14 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -14,21 +14,25 @@ var ( tun = tunnel.Instance() ) -func NewHttpProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { +type httpListener struct { + net.Listener + address string + closed bool +} + +func NewHttpProxy(addr string) (*httpListener, error) { l, err := net.Listen("tcp", addr) if err != nil { - return nil, nil, err + return nil, err } - - done := make(chan struct{}) - closed := make(chan struct{}) + hl := &httpListener{l, addr, false} go func() { log.Infoln("HTTP proxy listening at: %s", addr) for { - c, err := l.Accept() + c, err := hl.Accept() if err != nil { - if _, open := <-done; !open { + if hl.closed { break } continue @@ -37,13 +41,16 @@ func NewHttpProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { } }() - go func() { - <-done - l.Close() - closed <- struct{}{} - }() + return hl, nil +} + +func (l *httpListener) Close() { + l.closed = true + l.Listener.Close() +} - return done, closed, nil +func (l *httpListener) Address() string { + return l.address } func handleConn(conn net.Conn) { diff --git a/proxy/listener.go b/proxy/listener.go index 5e635ba55e..82922aab81 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -13,15 +13,14 @@ import ( var ( allowLan = false - socksListener *listener - httpListener *listener - redirListener *listener + socksListener listener + httpListener listener + redirListener listener ) -type listener struct { - Address string - Done chan<- struct{} - Closed <-chan struct{} +type listener interface { + Close() + Address() string } type Ports struct { @@ -42,11 +41,10 @@ func ReCreateHTTP(port int) error { addr := genAddr(port, allowLan) if httpListener != nil { - if httpListener.Address == addr { + if httpListener.Address() == addr { return nil } - httpListener.Done <- struct{}{} - <-httpListener.Closed + httpListener.Close() httpListener = nil } @@ -54,16 +52,12 @@ func ReCreateHTTP(port int) error { return nil } - done, closed, err := http.NewHttpProxy(addr) + var err error + httpListener, err = http.NewHttpProxy(addr) if err != nil { return err } - httpListener = &listener{ - Address: addr, - Done: done, - Closed: closed, - } return nil } @@ -71,11 +65,10 @@ func ReCreateSocks(port int) error { addr := genAddr(port, allowLan) if socksListener != nil { - if socksListener.Address == addr { + if socksListener.Address() == addr { return nil } - socksListener.Done <- struct{}{} - <-socksListener.Closed + socksListener.Close() socksListener = nil } @@ -83,16 +76,12 @@ func ReCreateSocks(port int) error { return nil } - done, closed, err := socks.NewSocksProxy(addr) + var err error + socksListener, err = socks.NewSocksProxy(addr) if err != nil { return err } - socksListener = &listener{ - Address: addr, - Done: done, - Closed: closed, - } return nil } @@ -100,11 +89,10 @@ func ReCreateRedir(port int) error { addr := genAddr(port, allowLan) if redirListener != nil { - if redirListener.Address == addr { + if redirListener.Address() == addr { return nil } - redirListener.Done <- struct{}{} - <-redirListener.Closed + redirListener.Close() redirListener = nil } @@ -112,16 +100,12 @@ func ReCreateRedir(port int) error { return nil } - done, closed, err := redir.NewRedirProxy(addr) + var err error + redirListener, err = redir.NewRedirProxy(addr) if err != nil { return err } - redirListener = &listener{ - Address: addr, - Done: done, - Closed: closed, - } return nil } @@ -130,19 +114,19 @@ func GetPorts() *Ports { ports := &Ports{} if httpListener != nil { - _, portStr, _ := net.SplitHostPort(httpListener.Address) + _, portStr, _ := net.SplitHostPort(httpListener.Address()) port, _ := strconv.Atoi(portStr) ports.Port = port } if socksListener != nil { - _, portStr, _ := net.SplitHostPort(socksListener.Address) + _, portStr, _ := net.SplitHostPort(socksListener.Address()) port, _ := strconv.Atoi(portStr) ports.SocksPort = port } if redirListener != nil { - _, portStr, _ := net.SplitHostPort(redirListener.Address) + _, portStr, _ := net.SplitHostPort(redirListener.Address()) port, _ := strconv.Atoi(portStr) ports.RedirPort = port } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index bb6d3c552c..d54a17c49b 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -13,21 +13,25 @@ var ( tun = tunnel.Instance() ) -func NewRedirProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { +type redirListener struct { + net.Listener + address string + closed bool +} + +func NewRedirProxy(addr string) (*redirListener, error) { l, err := net.Listen("tcp", addr) if err != nil { - return nil, nil, err + return nil, err } - - done := make(chan struct{}) - closed := make(chan struct{}) + rl := &redirListener{l, addr, false} go func() { log.Infof("Redir proxy listening at: %s", addr) for { c, err := l.Accept() if err != nil { - if _, open := <-done; !open { + if rl.closed { break } continue @@ -36,13 +40,16 @@ func NewRedirProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { } }() - go func() { - <-done - l.Close() - closed <- struct{}{} - }() + return rl, nil +} + +func (l *redirListener) Close() { + l.closed = true + l.Listener.Close() +} - return done, closed, nil +func (l *redirListener) Address() string { + return l.address } func handleRedir(conn net.Conn) { diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 646050be1a..a4d4db4bda 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -14,21 +14,25 @@ var ( tun = tunnel.Instance() ) -func NewSocksProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { +type sockListener struct { + net.Listener + address string + closed bool +} + +func NewSocksProxy(addr string) (*sockListener, error) { l, err := net.Listen("tcp", addr) if err != nil { - return nil, nil, err + return nil, err } - done := make(chan struct{}) - closed := make(chan struct{}) - + sl := &sockListener{l, addr, false} go func() { log.Infof("SOCKS proxy listening at: %s", addr) for { c, err := l.Accept() if err != nil { - if _, open := <-done; !open { + if sl.closed { break } continue @@ -37,13 +41,16 @@ func NewSocksProxy(addr string) (chan<- struct{}, <-chan struct{}, error) { } }() - go func() { - <-done - l.Close() - closed <- struct{}{} - }() + return sl, nil +} + +func (l *sockListener) Close() { + l.closed = true + l.Listener.Close() +} - return done, closed, nil +func (l *sockListener) Address() string { + return l.address } func handleSocks(conn net.Conn) { From f6743d4d21a69b07f9310ba525d86b20bcc9a7fb Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sun, 25 Nov 2018 17:00:11 +0800 Subject: [PATCH 088/535] Fix: chrome crash when using SwitchyOmega by reject rule (#47) * Fix: chrome crash when using SwitchyOmega by reject rule --- adapters/outbound/reject.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index ae26022dc9..5a5357c06f 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -50,7 +50,7 @@ func NewReject() *Reject { type NopConn struct{} func (rw *NopConn) Read(b []byte) (int, error) { - return len(b), nil + return 0, io.EOF } func (rw *NopConn) Write(b []byte) (int, error) { From a64cea5011c78a7520cc07d318bd6b353ee9751d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 28 Nov 2018 10:38:30 +0800 Subject: [PATCH 089/535] Fix: patch config API --- hub/route/configs.go | 9 +++++---- log/level.go | 2 +- tunnel/mode.go | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/hub/route/configs.go b/hub/route/configs.go index 66d6cb4339..8954f93e4b 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -5,8 +5,8 @@ import ( "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" - T "github.com/Dreamacro/clash/tunnel" P "github.com/Dreamacro/clash/proxy" + T "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -15,6 +15,7 @@ import ( func configRouter() http.Handler { r := chi.NewRouter() r.Get("/", getConfigs) + r.Patch("/", patchConfigs) return r } @@ -32,7 +33,7 @@ func getConfigs(w http.ResponseWriter, r *http.Request) { render.Respond(w, r, general) } -func pointerOrDefault (p *int, def int) int { +func pointerOrDefault(p *int, def int) int { if p != nil { return *p } @@ -40,7 +41,7 @@ func pointerOrDefault (p *int, def int) int { return def } -func updateConfigs(w http.ResponseWriter, r *http.Request) { +func patchConfigs(w http.ResponseWriter, r *http.Request) { general := &configSchema{} if err := render.DecodeJSON(r.Body, general); err != nil { w.WriteHeader(http.StatusBadRequest) @@ -51,7 +52,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { if general.AllowLan != nil { P.SetAllowLan(*general.AllowLan) } - + ports := P.GetPorts() P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) diff --git a/log/level.go b/log/level.go index 4e28e71437..2bdbad4d97 100644 --- a/log/level.go +++ b/log/level.go @@ -46,7 +46,7 @@ func (l LogLevel) MarshalYAML() ([]byte, error) { // UnmarshalJSON unserialize Mode with json func (l *LogLevel) UnmarshalJSON(data []byte) error { var tp string - json.Unmarshal(data, tp) + json.Unmarshal(data, &tp) level, exist := LogLevelMapping[tp] if !exist { return errors.New("invalid mode") diff --git a/tunnel/mode.go b/tunnel/mode.go index b367ed3ada..e9eb531fff 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -25,7 +25,7 @@ const ( // UnmarshalJSON unserialize Mode func (m *Mode) UnmarshalJSON(data []byte) error { var tp string - json.Unmarshal(data, tp) + json.Unmarshal(data, &tp) mode, exist := ModeMapping[tp] if !exist { return errors.New("invalid mode") From dc24dd4d8985b255984a79e477e6bbc5fe338e55 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 28 Nov 2018 23:24:57 +0800 Subject: [PATCH 090/535] Fix: tls server name missing in vmess --- adapters/outbound/vmess.go | 3 ++- component/vmess/vmess.go | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 04f4b110f0..9894bf8034 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -75,7 +75,8 @@ func NewVmess(option VmessOption) (*Vmess, error) { AlterID: uint16(option.AlterID), Security: security, TLS: option.TLS, - Host: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + HostName: option.Server, + Port: strconv.Itoa(option.Port), NetWork: option.Network, WebSocketPath: option.WSPath, SkipCertVerify: option.SkipCertVerify, diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 3356d8bc31..8273ea8702 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -79,7 +79,8 @@ type Config struct { AlterID uint16 Security string TLS bool - Host string + HostName string + Port string NetWork string WebSocketPath string SkipCertVerify bool @@ -129,9 +130,12 @@ func NewClient(config Config) (*Client, error) { return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) } + host := net.JoinHostPort(config.HostName, config.Port) + var tlsConfig *tls.Config if config.TLS { tlsConfig = &tls.Config{ + ServerName: config.HostName, InsecureSkipVerify: config.SkipCertVerify, ClientSessionCache: config.SessionCacahe, } @@ -143,7 +147,7 @@ func NewClient(config Config) (*Client, error) { var wsConfig *websocketConfig if config.NetWork == "ws" { wsConfig = &websocketConfig{ - host: config.Host, + host: host, path: config.WebSocketPath, tls: config.TLS, tlsConfig: tlsConfig, @@ -155,7 +159,7 @@ func NewClient(config Config) (*Client, error) { uuid: &uid, security: security, tls: config.TLS, - host: config.Host, + host: host, wsConfig: wsConfig, tlsConfig: tlsConfig, }, nil From 9cfd26d44062c8d0811395cf944024bb680705b5 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 30 Nov 2018 17:42:40 +0800 Subject: [PATCH 091/535] Feat: add switch config file API --- hub/executor/executor.go | 6 ++++-- hub/hub.go | 2 +- hub/route/configs.go | 32 ++++++++++++++++++++++++++++++++ hub/route/server.go | 2 +- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 541c782d82..15b2d37a60 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -19,10 +19,12 @@ func ParseWithPath(path string) (*config.Config, error) { } // ApplyConfig dispatch configure to all parts -func ApplyConfig(cfg *config.Config) { +func ApplyConfig(cfg *config.Config, force bool) { + if force { + updateGeneral(cfg.General) + } updateProxies(cfg.Proxies) updateRules(cfg.Rules) - updateGeneral(cfg.General) } func GetGeneral() *config.General { diff --git a/hub/hub.go b/hub/hub.go index 4a3715e5f7..41e0c64a66 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -16,6 +16,6 @@ func Parse() error { go route.Start(cfg.General.ExternalController, cfg.General.Secret) } - executor.ApplyConfig(cfg) + executor.ApplyConfig(cfg, true) return nil } diff --git a/hub/route/configs.go b/hub/route/configs.go index 8954f93e4b..45e1a99ec3 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -2,6 +2,7 @@ package route import ( "net/http" + "path/filepath" "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" @@ -15,6 +16,7 @@ import ( func configRouter() http.Handler { r := chi.NewRouter() r.Get("/", getConfigs) + r.Put("/", updateConfigs) r.Patch("/", patchConfigs) return r } @@ -68,3 +70,33 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } + +type updateConfigRequest struct { + Path string `json:"path"` +} + +func updateConfigs(w http.ResponseWriter, r *http.Request) { + req := updateConfigRequest{} + if err := render.DecodeJSON(r.Body, &req); err != nil { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, ErrBadRequest) + return + } + + if !filepath.IsAbs(req.Path) { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, newError("path is not a absoluted path")) + return + } + + force := r.URL.Query().Get("force") == "true" + cfg, err := executor.ParseWithPath(req.Path) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + render.Respond(w, r, newError(err.Error())) + return + } + + executor.ApplyConfig(cfg, force) + w.WriteHeader(http.StatusNoContent) +} diff --git a/hub/route/server.go b/hub/route/server.go index 72fe965567..d7e5334b5c 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -47,7 +47,7 @@ func Start(addr string, secret string) { r.With(jsonContentType).Get("/logs", getLogs) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) - // r.Mount("/rules", ruleRouter()) + r.Mount("/rules", ruleRouter()) log.Infoln("RESTful API listening at: %s", addr) err := http.ListenAndServe(addr, r) From f5715c4f4217054a2770a21ab285a71d9e16ad1d Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 1 Dec 2018 09:32:02 +0800 Subject: [PATCH 092/535] Fix: chunk size limit in tls obfs (#54) * Fix: add chunkSize limit in TLSObfs * Chore: add length var for len(b) --- component/simple-obfs/tls.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/component/simple-obfs/tls.go b/component/simple-obfs/tls.go index e725944f1f..b763eefb44 100644 --- a/component/simple-obfs/tls.go +++ b/component/simple-obfs/tls.go @@ -14,6 +14,10 @@ func init() { rand.Seed(time.Now().Unix()) } +const ( + chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 +) + var bufPool = sync.Pool{New: func() interface{} { return make([]byte, 2048) }} // TLSObfs is shadowsocks tls simple-obfs implementation @@ -75,8 +79,23 @@ func (to *TLSObfs) Read(b []byte) (int, error) { // type + ver = 3 return to.read(b, 3) } - func (to *TLSObfs) Write(b []byte) (int, error) { + length := len(b) + for i := 0; i < length; i += chunkSize { + end := i + chunkSize + if end > length { + end = length + } + + n, err := to.write(b[i:end]) + if err != nil { + return n, err + } + } + return length, nil +} + +func (to *TLSObfs) write(b []byte) (int, error) { if to.firstRequest { helloMsg := makeClientHelloMsg(b, to.server) _, err := to.Conn.Write(helloMsg) From 6636db242bad3563406048807b4670f9fabf72a7 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 3 Dec 2018 23:27:00 +0800 Subject: [PATCH 093/535] Feature: add http/https [connect] proxy (#52) --- adapters/outbound/http.go | 148 ++++++++++++++++++++++++++++++++++++++ config/config.go | 7 ++ constant/adapters.go | 3 + 3 files changed, 158 insertions(+) create mode 100644 adapters/outbound/http.go diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go new file mode 100644 index 0000000000..8938aa8486 --- /dev/null +++ b/adapters/outbound/http.go @@ -0,0 +1,148 @@ +package adapters + +import ( + "bufio" + "bytes" + "crypto/tls" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "strconv" + + C "github.com/Dreamacro/clash/constant" +) + +// HTTPAdapter is a proxy adapter +type HTTPAdapter struct { + conn net.Conn +} + +// Close is used to close connection +func (ha *HTTPAdapter) Close() { + ha.conn.Close() +} + +func (ha *HTTPAdapter) Conn() net.Conn { + return ha.conn +} + +type Http struct { + addr string + name string + user string + pass string + tls bool + skipCertVerify bool + tlsConfig *tls.Config +} + +type HttpOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UserName string `proxy:"username,omitempty"` + Password string `proxy:"password,omitempty"` + TLS bool `proxy:"tls,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` +} + +func (h *Http) Name() string { + return h.name +} + +func (h *Http) Type() C.AdapterType { + return C.Http +} + +func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { + c, err := net.DialTimeout("tcp", h.addr, tcpTimeout) + if err == nil && h.tls { + cc := tls.Client(c, h.tlsConfig) + err = cc.Handshake() + c = cc + } + + if err != nil { + return nil, fmt.Errorf("%s connect error", h.addr) + } + tcpKeepAlive(c) + if err := h.shakeHand(metadata, c); err != nil { + return nil, err + } + + return &HTTPAdapter{conn: c}, nil +} + +func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { + var buf bytes.Buffer + var err error + + buf.WriteString("CONNECT ") + buf.WriteString(net.JoinHostPort(metadata.Host, metadata.Port)) + buf.WriteString(" HTTP/1.1\r\n") + buf.WriteString("Proxy-Connection: Keep-Alive\r\n") + + if h.user != "" && h.pass != "" { + auth := h.user + ":" + h.pass + buf.WriteString("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n") + } + // header ended + buf.WriteString("\r\n") + + _, err = rw.Write(buf.Bytes()) + if err != nil { + return err + } + + var req http.Request + resp, err := http.ReadResponse(bufio.NewReader(rw), &req) + if err != nil { + return err + } + + if resp.StatusCode == 200 { + return nil + } + + if resp.StatusCode == 407 { + return errors.New("HTTP need auth") + } + + if resp.StatusCode == 405 { + return errors.New("CONNECT method not allowed by proxy") + } + return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) +} + +func (h *Http) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": h.Type().String(), + }) +} + +func NewHttp(option HttpOption) *Http { + var tlsConfig *tls.Config + if option.TLS { + tlsConfig = &tls.Config{ + InsecureSkipVerify: option.SkipCertVerify, + ClientSessionCache: getClientSessionCache(), + MinVersion: tls.VersionTLS11, + MaxVersion: tls.VersionTLS12, + ServerName: option.Server, + } + } + + return &Http{ + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + name: option.Name, + user: option.UserName, + pass: option.Password, + tls: option.TLS, + skipCertVerify: option.SkipCertVerify, + tlsConfig: tlsConfig, + } +} diff --git a/config/config.go b/config/config.go index e3fdb848d9..d09b062e7f 100644 --- a/config/config.go +++ b/config/config.go @@ -173,6 +173,13 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { break } proxy = adapters.NewSocks5(*socksOption) + case "http": + httpOption := &adapters.HttpOption{} + err = decoder.Decode(mapping, httpOption) + if err != nil { + break + } + proxy = adapters.NewHttp(*httpOption) case "vmess": vmessOption := &adapters.VmessOption{} err = decoder.Decode(mapping, vmessOption) diff --git a/constant/adapters.go b/constant/adapters.go index fa0526d1b9..6746fb5a19 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -12,6 +12,7 @@ const ( Selector Shadowsocks Socks5 + Http URLTest Vmess ) @@ -50,6 +51,8 @@ func (at AdapterType) String() string { return "Shadowsocks" case Socks5: return "Socks5" + case Http: + return "Http" case URLTest: return "URLTest" case Vmess: From ca6e67a3845f6a73dcf0ccd9e2d7f49388414614 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 3 Dec 2018 23:41:40 +0800 Subject: [PATCH 094/535] Feature: add silent info level --- hub/executor/executor.go | 6 +++--- log/level.go | 4 ++++ proxy/redir/tcp.go | 5 ++--- proxy/socks/tcp.go | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 15b2d37a60..60fb56e4f1 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -48,6 +48,9 @@ func updateRules(rules []C.Rule) { } func updateGeneral(general *config.General) { + log.SetLevel(general.LogLevel) + T.Instance().SetMode(general.Mode) + allowLan := general.AllowLan P.SetAllowLan(allowLan) @@ -62,7 +65,4 @@ func updateGeneral(general *config.General) { if err := P.ReCreateRedir(general.RedirPort); err != nil { log.Errorln("Start Redir server error: %s", err.Error()) } - - log.SetLevel(general.LogLevel) - T.Instance().SetMode(general.Mode) } diff --git a/log/level.go b/log/level.go index 2bdbad4d97..a361f3c9fb 100644 --- a/log/level.go +++ b/log/level.go @@ -14,6 +14,7 @@ var ( "warning": WARNING, "info": INFO, "debug": DEBUG, + "silent": SILENT, } ) @@ -22,6 +23,7 @@ const ( INFO WARNING ERROR + SILENT ) type LogLevel int @@ -70,6 +72,8 @@ func (l LogLevel) String() string { return "error" case DEBUG: return "debug" + case SILENT: + return "silent" default: return "unknow" } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index d54a17c49b..ba55e458d6 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -4,9 +4,8 @@ import ( "net" "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" - - log "github.com/sirupsen/logrus" ) var ( @@ -27,7 +26,7 @@ func NewRedirProxy(addr string) (*redirListener, error) { rl := &redirListener{l, addr, false} go func() { - log.Infof("Redir proxy listening at: %s", addr) + log.Infoln("Redir proxy listening at: %s", addr) for { c, err := l.Accept() if err != nil { diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index a4d4db4bda..badb8b187a 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -4,10 +4,10 @@ import ( "net" "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/go-shadowsocks2/socks" - log "github.com/sirupsen/logrus" ) var ( @@ -28,7 +28,7 @@ func NewSocksProxy(addr string) (*sockListener, error) { sl := &sockListener{l, addr, false} go func() { - log.Infof("SOCKS proxy listening at: %s", addr) + log.Infoln("SOCKS proxy listening at: %s", addr) for { c, err := l.Accept() if err != nil { From da5db36ccf6335d594b1778127f1a47233b5ef10 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 5 Dec 2018 18:19:30 +0800 Subject: [PATCH 095/535] Fix: policy group unexpectedly closed --- config/config.go | 10 ---------- hub/executor/executor.go | 16 +++++++++++++++- hub/route/configs.go | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/config/config.go b/config/config.go index d09b062e7f..51ed4f2e8b 100644 --- a/config/config.go +++ b/config/config.go @@ -264,16 +264,6 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { } proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps) - - // close old goroutine - for _, proxy := range proxies { - switch raw := proxy.(type) { - case *adapters.URLTest: - raw.Close() - case *adapters.Fallback: - raw.Close() - } - } return proxies, nil } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 60fb56e4f1..87cf0032bd 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -1,6 +1,7 @@ package executor import ( + adapters "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" @@ -40,7 +41,20 @@ func GetGeneral() *config.General { } func updateProxies(proxies map[string]C.Proxy) { - T.Instance().UpdateProxies(proxies) + tunnel := T.Instance() + oldProxies := tunnel.Proxies() + + // close old goroutine + for _, proxy := range oldProxies { + switch raw := proxy.(type) { + case *adapters.URLTest: + raw.Close() + case *adapters.Fallback: + raw.Close() + } + } + + tunnel.UpdateProxies(proxies) } func updateRules(rules []C.Rule) { diff --git a/hub/route/configs.go b/hub/route/configs.go index 45e1a99ec3..bf4f89efae 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -72,7 +72,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { } type updateConfigRequest struct { - Path string `json:"path"` + Path string `json:"path"` } func updateConfigs(w http.ResponseWriter, r *http.Request) { From 03c249ecb11516f285271191b38a672e0fd9ffeb Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Wed, 5 Dec 2018 21:13:29 +0800 Subject: [PATCH 096/535] Feature: add custom DNS support (#56) --- adapters/inbound/socket.go | 7 +- adapters/inbound/util.go | 68 ++++----- adapters/outbound/direct.go | 7 +- common/cache/cache.go | 91 ++++++++++++ common/cache/cache_test.go | 70 ++++++++++ common/picker/picker.go | 22 +++ common/picker/picker_test.go | 44 ++++++ config/config.go | 149 ++++++++++++++++---- constant/metadata.go | 1 + dns/client.go | 263 +++++++++++++++++++++++++++++++++++ dns/server.go | 62 +++++++++ dns/util.go | 85 +++++++++++ go.mod | 11 +- go.sum | 28 ++-- hub/executor/executor.go | 19 +++ log/level.go | 29 ++-- log/log.go | 8 ++ proxy/http/server.go | 10 +- proxy/listener.go | 6 +- proxy/redir/tcp.go | 13 +- proxy/socks/tcp.go | 13 +- tunnel/mode.go | 18 ++- tunnel/tunnel.go | 39 ++++++ 23 files changed, 939 insertions(+), 124 deletions(-) create mode 100644 common/cache/cache.go create mode 100644 common/cache/cache_test.go create mode 100644 common/picker/picker.go create mode 100644 common/picker/picker_test.go create mode 100644 dns/client.go create mode 100644 dns/server.go create mode 100644 dns/util.go diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 8d701ad6e9..8c0ef49dc1 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -29,9 +29,12 @@ func (s *SocketAdapter) Conn() net.Conn { } // NewSocket is SocketAdapter generator -func NewSocket(target socks.Addr, conn net.Conn) *SocketAdapter { +func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType) *SocketAdapter { + metadata := parseSocksAddr(target) + metadata.Source = source + return &SocketAdapter{ conn: conn, - metadata: parseSocksAddr(target), + metadata: metadata, } } diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index 06499f0474..a1f5cd0855 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -10,32 +10,26 @@ import ( ) func parseSocksAddr(target socks.Addr) *C.Metadata { - var host, port string - var ip net.IP + metadata := &C.Metadata{ + NetWork: C.TCP, + AddrType: int(target[0]), + } switch target[0] { case socks.AtypDomainName: - host = string(target[2 : 2+target[1]]) - port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) - ipAddr, err := net.ResolveIPAddr("ip", host) - if err == nil { - ip = ipAddr.IP - } + metadata.Host = string(target[2 : 2+target[1]]) + metadata.Port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) case socks.AtypIPv4: - ip = net.IP(target[1 : 1+net.IPv4len]) - port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) + ip := net.IP(target[1 : 1+net.IPv4len]) + metadata.IP = &ip + metadata.Port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) case socks.AtypIPv6: - ip = net.IP(target[1 : 1+net.IPv6len]) - port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) + ip := net.IP(target[1 : 1+net.IPv6len]) + metadata.IP = &ip + metadata.Port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) } - return &C.Metadata{ - NetWork: C.TCP, - AddrType: int(target[0]), - Host: host, - IP: &ip, - Port: port, - } + return metadata } func parseHTTPAddr(request *http.Request) *C.Metadata { @@ -44,28 +38,26 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { if port == "" { port = "80" } - ipAddr, err := net.ResolveIPAddr("ip", host) - var resolveIP *net.IP - if err == nil { - resolveIP = &ipAddr.IP - } - var addType int - ip := net.ParseIP(host) - switch { - case ip == nil: - addType = socks.AtypDomainName - case ip.To4() == nil: - addType = socks.AtypIPv6 - default: - addType = socks.AtypIPv4 - } - - return &C.Metadata{ + metadata := &C.Metadata{ NetWork: C.TCP, - AddrType: addType, + Source: C.HTTP, + AddrType: C.AtypDomainName, Host: host, - IP: resolveIP, + IP: nil, Port: port, } + + ip := net.ParseIP(host) + if ip != nil { + switch { + case ip.To4() == nil: + metadata.AddrType = C.AtypIPv6 + default: + metadata.AddrType = C.AtypIPv4 + } + metadata.IP = &ip + } + + return metadata } diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 1a6a1144ca..052d3f3baa 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -33,7 +33,12 @@ func (d *Direct) Type() C.AdapterType { } func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - c, err := net.DialTimeout("tcp", net.JoinHostPort(metadata.String(), metadata.Port), tcpTimeout) + address := net.JoinHostPort(metadata.Host, metadata.Port) + if metadata.IP != nil { + address = net.JoinHostPort(metadata.IP.String(), metadata.Port) + } + + c, err := net.DialTimeout("tcp", address, tcpTimeout) if err != nil { return } diff --git a/common/cache/cache.go b/common/cache/cache.go new file mode 100644 index 0000000000..f33591eaff --- /dev/null +++ b/common/cache/cache.go @@ -0,0 +1,91 @@ +package cache + +import ( + "runtime" + "sync" + "time" +) + +// Cache store element with a expired time +type Cache struct { + *cache +} + +type cache struct { + mapping sync.Map + janitor *janitor +} + +type element struct { + Expired time.Time + Payload interface{} +} + +// Put element in Cache with its ttl +func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) { + c.mapping.Store(key, &element{ + Payload: payload, + Expired: time.Now().Add(ttl), + }) +} + +// Get element in Cache, and drop when it expired +func (c *cache) Get(key interface{}) interface{} { + item, exist := c.mapping.Load(key) + if !exist { + return nil + } + elm := item.(*element) + // expired + if time.Since(elm.Expired) > 0 { + c.mapping.Delete(key) + return nil + } + return elm.Payload +} + +func (c *cache) cleanup() { + c.mapping.Range(func(k, v interface{}) bool { + key := k.(string) + elm := v.(*element) + if time.Since(elm.Expired) > 0 { + c.mapping.Delete(key) + } + return true + }) +} + +type janitor struct { + interval time.Duration + stop chan struct{} +} + +func (j *janitor) process(c *cache) { + ticker := time.NewTicker(j.interval) + for { + select { + case <-ticker.C: + c.cleanup() + case <-j.stop: + ticker.Stop() + return + } + } +} + +func stopJanitor(c *Cache) { + c.janitor.stop <- struct{}{} +} + +// New return *Cache +func New(interval time.Duration) *Cache { + j := &janitor{ + interval: interval, + stop: make(chan struct{}), + } + c := &cache{janitor: j} + go j.process(c) + C := &Cache{c} + runtime.SetFinalizer(C, stopJanitor) + return C +} diff --git a/common/cache/cache_test.go b/common/cache/cache_test.go new file mode 100644 index 0000000000..101ca869e3 --- /dev/null +++ b/common/cache/cache_test.go @@ -0,0 +1,70 @@ +package cache + +import ( + "runtime" + "testing" + "time" +) + +func TestCache_Basic(t *testing.T) { + interval := 200 * time.Millisecond + ttl := 20 * time.Millisecond + c := New(interval) + c.Put("int", 1, ttl) + c.Put("string", "a", ttl) + + i := c.Get("int") + if i.(int) != 1 { + t.Error("should recv 1") + } + + s := c.Get("string") + if s.(string) != "a" { + t.Error("should recv 'a'") + } +} + +func TestCache_TTL(t *testing.T) { + interval := 200 * time.Millisecond + ttl := 20 * time.Millisecond + c := New(interval) + c.Put("int", 1, ttl) + + i := c.Get("int") + if i.(int) != 1 { + t.Error("should recv 1") + } + + time.Sleep(ttl * 2) + i = c.Get("int") + if i != nil { + t.Error("should recv nil") + } +} + +func TestCache_AutoCleanup(t *testing.T) { + interval := 10 * time.Millisecond + ttl := 15 * time.Millisecond + c := New(interval) + c.Put("int", 1, ttl) + + time.Sleep(ttl * 2) + i := c.Get("int") + if i != nil { + t.Error("should recv nil") + } +} + +func TestCache_AutoGC(t *testing.T) { + sign := make(chan struct{}) + go func() { + interval := 10 * time.Millisecond + ttl := 15 * time.Millisecond + c := New(interval) + c.Put("int", 1, ttl) + sign <- struct{}{} + }() + + <-sign + runtime.GC() +} diff --git a/common/picker/picker.go b/common/picker/picker.go new file mode 100644 index 0000000000..07e2076d15 --- /dev/null +++ b/common/picker/picker.go @@ -0,0 +1,22 @@ +package picker + +import "context" + +func SelectFast(ctx context.Context, in <-chan interface{}) <-chan interface{} { + out := make(chan interface{}) + go func() { + select { + case p, open := <-in: + if open { + out <- p + } + case <-ctx.Done(): + } + + close(out) + for range in { + } + }() + + return out +} diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go new file mode 100644 index 0000000000..f33627f743 --- /dev/null +++ b/common/picker/picker_test.go @@ -0,0 +1,44 @@ +package picker + +import ( + "context" + "testing" + "time" +) + +func sleepAndSend(delay int, in chan<- interface{}, input interface{}) { + time.Sleep(time.Millisecond * time.Duration(delay)) + in <- input +} + +func sleepAndClose(delay int, in chan interface{}) { + time.Sleep(time.Millisecond * time.Duration(delay)) + close(in) +} + +func TestPicker_Basic(t *testing.T) { + in := make(chan interface{}) + fast := SelectFast(context.Background(), in) + go sleepAndSend(20, in, 1) + go sleepAndSend(30, in, 2) + go sleepAndClose(40, in) + + number, exist := <-fast + if !exist || number != 1 { + t.Error("should recv 1", exist, number) + } +} + +func TestPicker_Timeout(t *testing.T) { + in := make(chan interface{}) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*5) + defer cancel() + fast := SelectFast(ctx, in) + go sleepAndSend(20, in, 1) + go sleepAndClose(30, in) + + _, exist := <-fast + if exist { + t.Error("should recv false") + } +} diff --git a/config/config.go b/config/config.go index 51ed4f2e8b..775f9497b0 100644 --- a/config/config.go +++ b/config/config.go @@ -3,12 +3,15 @@ package config import ( "fmt" "io/ioutil" + "net" + "net/url" "os" "strings" adapters "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" R "github.com/Dreamacro/clash/rules" T "github.com/Dreamacro/clash/tunnel" @@ -28,28 +31,49 @@ type General struct { Secret string `json:"secret,omitempty"` } -type rawConfig struct { - Port int `yaml:"port"` - SocksPort int `yaml:"socks-port"` - RedirPort int `yaml:"redir-port"` - AllowLan bool `yaml:"allow-lan"` - Mode string `yaml:"mode"` - LogLevel string `yaml:"log-level"` - ExternalController string `yaml:"external-controller"` - Secret string `yaml:"secret"` - - Proxy []map[string]interface{} `yaml:"Proxy"` - ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` - Rule []string `yaml:"Rule"` +// DNS config +type DNS struct { + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []dns.NameServer `yaml:"nameserver"` + Fallback []dns.NameServer `yaml:"fallback"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` } // Config is clash config manager type Config struct { General *General + DNS *DNS Rules []C.Rule Proxies map[string]C.Proxy } +type rawDNS struct { + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []string `yaml:"nameserver"` + Fallback []string `yaml:"fallback"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` +} + +type rawConfig struct { + Port int `yaml:"port"` + SocksPort int `yaml:"socks-port"` + RedirPort int `yaml:"redir-port"` + AllowLan bool `yaml:"allow-lan"` + Mode T.Mode `yaml:"mode"` + LogLevel log.LogLevel `yaml:"log-level"` + ExternalController string `yaml:"external-controller"` + Secret string `yaml:"secret"` + + DNS *rawDNS `yaml:"dns"` + Proxy []map[string]interface{} `yaml:"Proxy"` + ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` + Rule []string `yaml:"Rule"` +} + func readConfig(path string) (*rawConfig, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err @@ -66,8 +90,8 @@ func readConfig(path string) (*rawConfig, error) { // config with some default value rawConfig := &rawConfig{ AllowLan: false, - Mode: T.Rule.String(), - LogLevel: log.INFO.String(), + Mode: T.Rule, + LogLevel: log.INFO, Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, @@ -103,6 +127,12 @@ func Parse(path string) (*Config, error) { } config.Rules = rules + dnsCfg, err := parseDNS(rawCfg.DNS) + if err != nil { + return nil, err + } + config.DNS = dnsCfg + return config, nil } @@ -111,20 +141,10 @@ func parseGeneral(cfg *rawConfig) (*General, error) { socksPort := cfg.SocksPort redirPort := cfg.RedirPort allowLan := cfg.AllowLan - logLevelString := cfg.LogLevel - modeString := cfg.Mode externalController := cfg.ExternalController secret := cfg.Secret - - mode, exist := T.ModeMapping[modeString] - if !exist { - return nil, fmt.Errorf("General.mode value invalid") - } - - logLevel, exist := log.LogLevelMapping[logLevelString] - if !exist { - return nil, fmt.Errorf("General.log-level value invalid") - } + mode := cfg.Mode + logLevel := cfg.LogLevel general := &General{ Port: port, @@ -310,3 +330,78 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { return rules, nil } + +func hostWithDefaultPort(host string, defPort string) (string, error) { + if !strings.Contains(host, ":") { + host += ":" + } + + hostname, port, err := net.SplitHostPort(host) + if err != nil { + return "", err + } + + if port == "" { + port = defPort + } + + return net.JoinHostPort(hostname, port), nil +} + +func parseNameServer(servers []string) ([]dns.NameServer, error) { + nameservers := []dns.NameServer{} + log.Debugln("%#v", servers) + + for idx, server := range servers { + // parse without scheme .e.g 8.8.8.8:53 + if host, err := hostWithDefaultPort(server, "53"); err == nil { + nameservers = append( + nameservers, + dns.NameServer{Addr: host}, + ) + continue + } + + u, err := url.Parse(server) + if err != nil { + return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) + } + + if u.Scheme != "tls" { + return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) + } + + host, err := hostWithDefaultPort(u.Host, "853") + nameservers = append( + nameservers, + dns.NameServer{ + Net: "tcp-tls", + Addr: host, + }, + ) + } + + return nameservers, nil +} + +func parseDNS(cfg *rawDNS) (*DNS, error) { + if cfg.Enable && len(cfg.NameServer) == 0 { + return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") + } + + dnsCfg := &DNS{ + Enable: cfg.Enable, + Listen: cfg.Listen, + EnhancedMode: cfg.EnhancedMode, + } + + if nameserver, err := parseNameServer(cfg.NameServer); err == nil { + dnsCfg.NameServer = nameserver + } + + if fallback, err := parseNameServer(cfg.Fallback); err == nil { + dnsCfg.Fallback = fallback + } + + return dnsCfg, nil +} diff --git a/constant/metadata.go b/constant/metadata.go index dfc274e725..1cb19033e6 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -15,6 +15,7 @@ const ( HTTP SourceType = iota SOCKS + REDIR ) type NetWork int diff --git a/dns/client.go b/dns/client.go new file mode 100644 index 0000000000..162f16400c --- /dev/null +++ b/dns/client.go @@ -0,0 +1,263 @@ +package dns + +import ( + "context" + "crypto/tls" + "errors" + "net" + "strings" + "sync" + "time" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/common/picker" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + + D "github.com/miekg/dns" + geoip2 "github.com/oschwald/geoip2-golang" +) + +var ( + globalSessionCache = tls.NewLRUClientSessionCache(64) + + mmdb *geoip2.Reader + once sync.Once + resolver *Resolver +) + +type Resolver struct { + ipv6 bool + mapping bool + fallback []*nameserver + main []*nameserver + cache *cache.Cache +} + +type result struct { + Msg *D.Msg + Error error +} + +func isIPRequest(q D.Question) bool { + if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) { + return true + } + return false +} + +func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { + if len(m.Question) == 0 { + return nil, errors.New("should have one question at least") + } + + q := m.Question[0] + cache := r.cache.Get(q.String()) + if cache != nil { + return cache.(*D.Msg).Copy(), nil + } + defer func() { + if msg != nil { + putMsgToCache(r.cache, q.String(), msg) + if r.mapping { + ips, err := r.msgToIP(msg) + if err != nil { + log.Debugln("[DNS] msg to ip error: %s", err.Error()) + return + } + for _, ip := range ips { + putMsgToCache(r.cache, ip.String(), msg) + } + } + } + }() + + isIPReq := isIPRequest(q) + if isIPReq { + msg, err = r.resolveIP(m) + return + } + + msg, err = r.exchange(r.main, m) + return +} + +func (r *Resolver) exchange(servers []*nameserver, m *D.Msg) (msg *D.Msg, err error) { + in := make(chan interface{}) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + fast := picker.SelectFast(ctx, in) + + wg := sync.WaitGroup{} + wg.Add(len(servers)) + for _, server := range servers { + go func(s *nameserver) { + defer wg.Done() + msg, _, err := s.Client.Exchange(m, s.Address) + if err != nil || msg.Rcode != D.RcodeSuccess { + return + } + in <- &result{Msg: msg, Error: err} + }(server) + } + + // release in channel + go func() { + wg.Wait() + close(in) + }() + + elm, exist := <-fast + if !exist { + return nil, errors.New("All DNS requests failed") + } + + resp := elm.(*result) + msg, err = resp.Msg, resp.Error + return +} + +func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) { + msgCh := r.resolve(r.main, m) + if r.fallback == nil { + res := <-msgCh + msg, err = res.Msg, res.Error + return + } + fallbackMsg := r.resolve(r.fallback, m) + res := <-msgCh + if res.Error == nil { + if mmdb == nil { + return nil, errors.New("GeoIP can't use") + } + + ips, _ := r.msgToIP(res.Msg) + if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { + // release channel + go func() { <-fallbackMsg }() + msg = res.Msg + return + } + } + + res = <-fallbackMsg + msg, err = res.Msg, res.Error + return +} + +func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { + query := &D.Msg{} + dnsType := D.TypeA + if r.ipv6 { + dnsType = D.TypeAAAA + } + query.SetQuestion(D.Fqdn(host), dnsType) + + msg, err := r.Exchange(query) + if err != nil { + return nil, err + } + + var ips []net.IP + ips, err = r.msgToIP(msg) + if err != nil { + return nil, err + } + + ip = ips[0] + return +} + +func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) { + var ips []net.IP + + for _, answer := range msg.Answer { + if r.ipv6 { + ans, ok := answer.(*D.AAAA) + if !ok { + continue + } + ips = append(ips, ans.AAAA) + continue + } + + ans, ok := answer.(*D.A) + if !ok { + continue + } + ips = append(ips, ans.A) + } + + if len(ips) == 0 { + return nil, errors.New("Can't parse msg") + } + + return ips, nil +} + +func (r *Resolver) IPToHost(ip net.IP) (string, bool) { + cache := r.cache.Get(ip.String()) + if cache == nil { + return "", false + } + fqdn := cache.(*D.Msg).Question[0].Name + return strings.TrimRight(fqdn, "."), true +} + +func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result { + ch := make(chan *result) + go func() { + res, err := r.exchange(client, msg) + ch <- &result{Msg: res, Error: err} + }() + return ch +} + +type NameServer struct { + Net string + Addr string +} + +type nameserver struct { + Client *D.Client + Address string +} + +type Config struct { + Main, Fallback []NameServer + IPv6 bool + EnhancedMode EnhancedMode +} + +func transform(servers []NameServer) []*nameserver { + var ret []*nameserver + for _, s := range servers { + ret = append(ret, &nameserver{ + Client: &D.Client{ + Net: s.Net, + TLSConfig: &tls.Config{ + ClientSessionCache: globalSessionCache, + }, + }, + Address: s.Addr, + }) + } + return ret +} + +func New(config Config) *Resolver { + once.Do(func() { + mmdb, _ = geoip2.Open(C.Path.MMDB()) + }) + + r := &Resolver{ + main: transform(config.Main), + ipv6: config.IPv6, + cache: cache.New(time.Second * 60), + mapping: config.EnhancedMode == MAPPING, + } + if config.Fallback != nil { + r.fallback = transform(config.Fallback) + } + return r +} diff --git a/dns/server.go b/dns/server.go new file mode 100644 index 0000000000..2ece2e97ea --- /dev/null +++ b/dns/server.go @@ -0,0 +1,62 @@ +package dns + +import ( + "net" + + D "github.com/miekg/dns" +) + +var ( + address string + server = &Server{} +) + +type Server struct { + *D.Server + r *Resolver +} + +func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { + msg, err := s.r.Exchange(r) + + if err != nil { + D.HandleFailed(w, r) + return + } + msg.SetReply(r) + w.WriteMsg(msg) +} + +func ReCreateServer(addr string, resolver *Resolver) error { + if server.Server != nil { + server.Shutdown() + } + + if addr == address { + return nil + } + + _, port, err := net.SplitHostPort(addr) + if port == "0" || port == "" || err != nil { + return nil + } + + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return err + } + + p, err := net.ListenUDP("udp", udpAddr) + if err != nil { + return err + } + + address = addr + server = &Server{r: resolver} + server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} + + go func() { + server.ActivateAndServe() + }() + return nil +} diff --git a/dns/util.go b/dns/util.go new file mode 100644 index 0000000000..c64e49781b --- /dev/null +++ b/dns/util.go @@ -0,0 +1,85 @@ +package dns + +import ( + "encoding/json" + "errors" + "time" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/log" + yaml "gopkg.in/yaml.v2" + + D "github.com/miekg/dns" +) + +var ( + // EnhancedModeMapping is a mapping for EnhancedMode enum + EnhancedModeMapping = map[string]EnhancedMode{ + FAKEIP.String(): FAKEIP, + MAPPING.String(): MAPPING, + } +) + +const ( + FAKEIP EnhancedMode = iota + MAPPING +) + +type EnhancedMode int + +// UnmarshalYAML unserialize EnhancedMode with yaml +func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error { + var tp string + if err := unmarshal(&tp); err != nil { + return err + } + mode, exist := EnhancedModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *e = mode + return nil +} + +// MarshalYAML serialize EnhancedMode with yaml +func (e EnhancedMode) MarshalYAML() ([]byte, error) { + return yaml.Marshal(e.String()) +} + +// UnmarshalJSON unserialize EnhancedMode with json +func (e *EnhancedMode) UnmarshalJSON(data []byte) error { + var tp string + json.Unmarshal(data, &tp) + mode, exist := EnhancedModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *e = mode + return nil +} + +// MarshalJSON serialize EnhancedMode with json +func (e EnhancedMode) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e EnhancedMode) String() string { + switch e { + case FAKEIP: + return "fakeip" + case MAPPING: + return "redir-host" + default: + return "unknown" + } +} + +func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) { + if len(msg.Answer) == 0 { + log.Debugln("[DNS] answer length is zero: %#v", msg) + return + } + + ttl := time.Duration(msg.Answer[0].Header().Ttl) * time.Second + c.Put(key, msg, ttl) +} diff --git a/go.mod b/go.mod index 2e536bf593..595d53ef3b 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,20 @@ module github.com/Dreamacro/clash require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270 + github.com/Dreamacro/go-shadowsocks2 v0.1.2 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v3.3.3+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.1.0+incompatible github.com/gorilla/websocket v1.4.0 + github.com/miekg/dns v1.1.0 github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/maxminddb-golang v1.3.0 // indirect - github.com/sirupsen/logrus v1.1.0 - golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 + github.com/sirupsen/logrus v1.2.0 + golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 + golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect gopkg.in/eapache/channels.v1 v1.1.0 - gopkg.in/yaml.v2 v2.2.1 + gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 185a7fdfc0..4d0e310b41 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270 h1:ugkI+Yw5ArFnhF8KTbJxyWIyvxMCa8jWyUF+wIAulhM= -github.com/Dreamacro/go-shadowsocks2 v0.1.2-0.20181019110427-0a03f1a25270/go.mod h1:DlkXRxmh5K+99aTPQaVjsZ1fAZNFw42vXGcOjR3Otps= +github.com/Dreamacro/go-shadowsocks2 v0.1.2 h1:8KgWbwAw5PJF+i6F3tI2iW/Em9WDtAuDG4obot8bGLM= +github.com/Dreamacro/go-shadowsocks2 v0.1.2/go.mod h1:J5YbNUiKtaD7EJmQ4O9ruUTY9+IgrflPgm63K1nUE0I= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -16,22 +16,28 @@ github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedy github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= -github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/miekg/dns v1.1.0 h1:yv9O9RJbvVFkvW8PKYqp4x7HQkc5RWwmUY/L8MdUaIg= +github.com/miekg/dns v1.1.0/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= 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/sirupsen/logrus v1.1.0 h1:65VZabgUiV9ktjGM5nTq0+YurgTyX+YI2lSSfDjI+qU= -github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -39,5 +45,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 87cf0032bd..6db5c54df4 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -4,6 +4,7 @@ import ( adapters "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" P "github.com/Dreamacro/clash/proxy" T "github.com/Dreamacro/clash/tunnel" @@ -26,6 +27,8 @@ func ApplyConfig(cfg *config.Config, force bool) { } updateProxies(cfg.Proxies) updateRules(cfg.Rules) + updateGeneral(cfg.General) + updateDNS(cfg.DNS) } func GetGeneral() *config.General { @@ -40,6 +43,22 @@ func GetGeneral() *config.General { } } +func updateDNS(c *config.DNS) { + if c.Enable == false { + T.Instance().SetResolver(nil) + dns.ReCreateServer("", nil) + return + } + r := dns.New(dns.Config{ + Main: c.NameServer, + Fallback: c.Fallback, + IPv6: c.IPv6, + EnhancedMode: c.EnhancedMode, + }) + T.Instance().SetResolver(r) + dns.ReCreateServer(c.Listen, r) +} + func updateProxies(proxies map[string]C.Proxy) { tunnel := T.Instance() oldProxies := tunnel.Proxies() diff --git a/log/level.go b/log/level.go index a361f3c9fb..e95e36d2e1 100644 --- a/log/level.go +++ b/log/level.go @@ -3,18 +3,16 @@ package log import ( "encoding/json" "errors" - - yaml "gopkg.in/yaml.v2" ) var ( // LogLevelMapping is a mapping for LogLevel enum LogLevelMapping = map[string]LogLevel{ - "error": ERROR, - "warning": WARNING, - "info": INFO, - "debug": DEBUG, - "silent": SILENT, + ERROR.String(): ERROR, + WARNING.String(): WARNING, + INFO.String(): INFO, + DEBUG.String(): DEBUG, + SILENT.String(): SILENT, } ) @@ -28,10 +26,10 @@ const ( type LogLevel int -// UnmarshalYAML unserialize Mode with yaml -func (l *LogLevel) UnmarshalYAML(data []byte) error { +// UnmarshalYAML unserialize LogLevel with yaml +func (l *LogLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { var tp string - yaml.Unmarshal(data, &tp) + unmarshal(&tp) level, exist := LogLevelMapping[tp] if !exist { return errors.New("invalid mode") @@ -40,12 +38,7 @@ func (l *LogLevel) UnmarshalYAML(data []byte) error { return nil } -// MarshalYAML serialize Mode with yaml -func (l LogLevel) MarshalYAML() ([]byte, error) { - return yaml.Marshal(l.String()) -} - -// UnmarshalJSON unserialize Mode with json +// UnmarshalJSON unserialize LogLevel with json func (l *LogLevel) UnmarshalJSON(data []byte) error { var tp string json.Unmarshal(data, &tp) @@ -57,7 +50,7 @@ func (l *LogLevel) UnmarshalJSON(data []byte) error { return nil } -// MarshalJSON serialize Mode with json +// MarshalJSON serialize LogLevel with json func (l LogLevel) MarshalJSON() ([]byte, error) { return json.Marshal(l.String()) } @@ -75,6 +68,6 @@ func (l LogLevel) String() string { case SILENT: return "silent" default: - return "unknow" + return "unknown" } } diff --git a/log/log.go b/log/log.go index c80606610d..77a835cd32 100644 --- a/log/log.go +++ b/log/log.go @@ -14,6 +14,10 @@ var ( level = INFO ) +func init() { + log.SetLevel(log.DebugLevel) +} + type Event struct { LogLevel LogLevel Payload string @@ -47,6 +51,10 @@ func Debugln(format string, v ...interface{}) { print(event) } +func Fatalln(format string, v ...interface{}) { + log.Fatalf(format, v...) +} + func Subscribe() observable.Subscription { sub, _ := source.Subscribe() return sub diff --git a/proxy/http/server.go b/proxy/http/server.go index d95d91ad14..e66f549385 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -14,18 +14,18 @@ var ( tun = tunnel.Instance() ) -type httpListener struct { +type HttpListener struct { net.Listener address string closed bool } -func NewHttpProxy(addr string) (*httpListener, error) { +func NewHttpProxy(addr string) (*HttpListener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } - hl := &httpListener{l, addr, false} + hl := &HttpListener{l, addr, false} go func() { log.Infoln("HTTP proxy listening at: %s", addr) @@ -44,12 +44,12 @@ func NewHttpProxy(addr string) (*httpListener, error) { return hl, nil } -func (l *httpListener) Close() { +func (l *HttpListener) Close() { l.closed = true l.Listener.Close() } -func (l *httpListener) Address() string { +func (l *HttpListener) Address() string { return l.address } diff --git a/proxy/listener.go b/proxy/listener.go index 82922aab81..7fbcfd7793 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -13,9 +13,9 @@ import ( var ( allowLan = false - socksListener listener - httpListener listener - redirListener listener + socksListener *socks.SockListener + httpListener *http.HttpListener + redirListener *redir.RedirListener ) type listener interface { diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index ba55e458d6..ac88710a25 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -4,6 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/adapters/inbound" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" ) @@ -12,18 +13,18 @@ var ( tun = tunnel.Instance() ) -type redirListener struct { +type RedirListener struct { net.Listener address string closed bool } -func NewRedirProxy(addr string) (*redirListener, error) { +func NewRedirProxy(addr string) (*RedirListener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } - rl := &redirListener{l, addr, false} + rl := &RedirListener{l, addr, false} go func() { log.Infoln("Redir proxy listening at: %s", addr) @@ -42,12 +43,12 @@ func NewRedirProxy(addr string) (*redirListener, error) { return rl, nil } -func (l *redirListener) Close() { +func (l *RedirListener) Close() { l.closed = true l.Listener.Close() } -func (l *redirListener) Address() string { +func (l *RedirListener) Address() string { return l.address } @@ -58,5 +59,5 @@ func handleRedir(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocket(target, conn)) + tun.Add(adapters.NewSocket(target, conn, C.REDIR)) } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index badb8b187a..83220c091f 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -4,6 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/adapters/inbound" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" @@ -14,19 +15,19 @@ var ( tun = tunnel.Instance() ) -type sockListener struct { +type SockListener struct { net.Listener address string closed bool } -func NewSocksProxy(addr string) (*sockListener, error) { +func NewSocksProxy(addr string) (*SockListener, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } - sl := &sockListener{l, addr, false} + sl := &SockListener{l, addr, false} go func() { log.Infoln("SOCKS proxy listening at: %s", addr) for { @@ -44,12 +45,12 @@ func NewSocksProxy(addr string) (*sockListener, error) { return sl, nil } -func (l *sockListener) Close() { +func (l *SockListener) Close() { l.closed = true l.Listener.Close() } -func (l *sockListener) Address() string { +func (l *SockListener) Address() string { return l.address } @@ -60,5 +61,5 @@ func handleSocks(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocket(target, conn)) + tun.Add(adapters.NewSocket(target, conn, C.SOCKS)) } diff --git a/tunnel/mode.go b/tunnel/mode.go index e9eb531fff..69d0d6f8af 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -10,9 +10,9 @@ type Mode int var ( // ModeMapping is a mapping for Mode enum ModeMapping = map[string]Mode{ - "Global": Global, - "Rule": Rule, - "Direct": Direct, + Global.String(): Global, + Rule.String(): Rule, + Direct.String(): Direct, } ) @@ -34,6 +34,18 @@ func (m *Mode) UnmarshalJSON(data []byte) error { return nil } +// UnmarshalYAML unserialize Mode with yaml +func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error { + var tp string + unmarshal(&tp) + mode, exist := ModeMapping[tp] + if !exist { + return errors.New("invalid mode") + } + *m = mode + return nil +} + // MarshalJSON serialize Mode func (m Mode) MarshalJSON() ([]byte, error) { return json.Marshal(m.String()) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 264e0e8c84..930bd070eb 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -1,11 +1,13 @@ package tunnel import ( + "net" "sync" "time" InboundAdapter "github.com/Dreamacro/clash/adapters/inbound" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" "gopkg.in/eapache/channels.v1" @@ -23,6 +25,7 @@ type Tunnel struct { proxies map[string]C.Proxy configLock *sync.RWMutex traffic *C.Traffic + resolver *dns.Resolver // Outbound Rule mode Mode @@ -72,6 +75,11 @@ func (t *Tunnel) SetMode(mode Mode) { t.mode = mode } +// SetResolver change the resolver of tunnel +func (t *Tunnel) SetResolver(resolver *dns.Resolver) { + t.resolver = resolver +} + func (t *Tunnel) process() { queue := t.queue.Out() for { @@ -81,10 +89,41 @@ func (t *Tunnel) process() { } } +func (t *Tunnel) resolveIP(host string) (net.IP, error) { + if t.resolver == nil { + ipAddr, err := net.ResolveIPAddr("ip", host) + if err != nil { + return nil, err + } + + return ipAddr.IP, nil + } + + return t.resolver.ResolveIP(host) +} + func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer localConn.Close() metadata := localConn.Metadata() + if metadata.Source == C.REDIR && t.resolver != nil { + host, exist := t.resolver.IPToHost(*metadata.IP) + if exist { + metadata.Host = host + metadata.AddrType = C.AtypDomainName + } + } else if metadata.IP == nil && metadata.AddrType == C.AtypDomainName { + ip, err := t.resolveIP(metadata.Host) + if err != nil { + log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) + } else { + log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) + metadata.IP = &ip + } + } else { + log.Debugln("[DNS] unknown%#v", metadata) + } + var proxy C.Proxy switch t.mode { case Direct: From f192d591c7a76ed25982968489b0fd2c18a18372 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 5 Dec 2018 21:26:04 +0800 Subject: [PATCH 097/535] Chore: bump to 0.10.0 --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5e502034f..ea56287ca7 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ go get -u -v github.com/Dreamacro/clash Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases) -Requires Go >= 1.10. +Requires Go >= 1.11. ## Daemon @@ -90,7 +90,7 @@ allow-lan: false mode: Rule # set log level to stdout (default is info) -# info / warning / error / debug +# info / warning / error / debug / silent log-level: info # A RESTful API for clash @@ -99,6 +99,17 @@ external-controller: 127.0.0.1:9090 # Secret for RESTful API (Optional) # secret: "" +dns: + enable: true # set true to enable dns + ipv6: false # default is false + listen: 0.0.0.0:53 + enhanced-mode: redir-host + nameserver: + - 114.114.114.114 + - tls://dns.rubyfish.cn:853 # dns over tls + fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN + - 8.8.8.8 + Proxy: # shadowsocks @@ -122,11 +133,22 @@ Proxy: # socks5 - { name: "socks", type: socks5, server: server, port: 443 } +# socks5 with authentication +- { name: "socks", type: socks5, server: server, port: 443, username: "username", password: "password" } # with tls - { name: "socks", type: socks5, server: server, port: 443, tls: true } # with tls and skip-cert-verify - { name: "socks", type: socks5, server: server, port: 443, tls: true, skip-cert-verify: true } +# http +- { name: "http", type: http, server: server, port: 443 } +# http with authentication +- { name: "http", type: http, server: server, port: 443, username: "username", password: "password" } +# with tls (https) +- { name: "http", type: http, server: server, port: 443, tls: true } +# with tls (https) and skip-cert-verify +- { name: "http", type: http, server: server, port: 443, tls: true, skip-cert-verify: true } + Proxy Group: # url-test select which proxy will be used by benchmarking speed to a URL. - { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } From f93d6aa294ac2fdf3995c2851f6cc7c0649640ff Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 5 Dec 2018 21:52:31 +0800 Subject: [PATCH 098/535] Fix: crash when dns not set --- README.md | 18 +++++++++--------- config/config.go | 3 +++ dns/client.go | 2 ++ dns/util.go | 6 +++++- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ea56287ca7..60917f467b 100644 --- a/README.md +++ b/README.md @@ -100,15 +100,15 @@ external-controller: 127.0.0.1:9090 # secret: "" dns: - enable: true # set true to enable dns - ipv6: false # default is false - listen: 0.0.0.0:53 - enhanced-mode: redir-host - nameserver: - - 114.114.114.114 - - tls://dns.rubyfish.cn:853 # dns over tls - fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN - - 8.8.8.8 + # enable: true # set true to enable dns (default is false) + # ipv6: false # default is false + # listen: 0.0.0.0:53 + # enhanced-mode: redir-host + # nameserver: + # - 114.114.114.114 + # - tls://dns.rubyfish.cn:853 # dns over tls + # fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN + # - 8.8.8.8 Proxy: diff --git a/config/config.go b/config/config.go index 775f9497b0..f2ac2dbb82 100644 --- a/config/config.go +++ b/config/config.go @@ -95,6 +95,9 @@ func readConfig(path string) (*rawConfig, error) { Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, + DNS: &rawDNS{ + Enable: false, + }, } err = yaml.Unmarshal([]byte(data), &rawConfig) return rawConfig, err diff --git a/dns/client.go b/dns/client.go index 162f16400c..b443c5c0c1 100644 --- a/dns/client.go +++ b/dns/client.go @@ -250,6 +250,8 @@ func New(config Config) *Resolver { mmdb, _ = geoip2.Open(C.Path.MMDB()) }) + println(config.EnhancedMode) + r := &Resolver{ main: transform(config.Main), ipv6: config.IPv6, diff --git a/dns/util.go b/dns/util.go index c64e49781b..4102a584e5 100644 --- a/dns/util.go +++ b/dns/util.go @@ -15,13 +15,15 @@ import ( var ( // EnhancedModeMapping is a mapping for EnhancedMode enum EnhancedModeMapping = map[string]EnhancedMode{ + NORMAL.String(): NORMAL, FAKEIP.String(): FAKEIP, MAPPING.String(): MAPPING, } ) const ( - FAKEIP EnhancedMode = iota + NORMAL EnhancedMode = iota + FAKEIP MAPPING ) @@ -65,6 +67,8 @@ func (e EnhancedMode) MarshalJSON() ([]byte, error) { func (e EnhancedMode) String() string { switch e { + case NORMAL: + return "normal" case FAKEIP: return "fakeip" case MAPPING: From 2b93c9d4c90854641a40d2ccc7beb2c0da46b2d6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 6 Dec 2018 10:51:37 +0800 Subject: [PATCH 099/535] Fix: resolve ip crash --- dns/client.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/dns/client.go b/dns/client.go index b443c5c0c1..7559ea37d0 100644 --- a/dns/client.go +++ b/dns/client.go @@ -131,12 +131,14 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) { return nil, errors.New("GeoIP can't use") } - ips, _ := r.msgToIP(res.Msg) - if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { - // release channel - go func() { <-fallbackMsg }() - msg = res.Msg - return + ips, err := r.msgToIP(res.Msg) + if err == nil { + if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { + // release channel + go func() { <-fallbackMsg }() + msg = res.Msg + return msg, err + } } } From 6f1bc3d65b82322030c1e9cff86ef43174cb224e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 6 Dec 2018 10:54:45 +0800 Subject: [PATCH 100/535] Fix: add PATCH for CORS --- hub/route/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/route/server.go b/hub/route/server.go index d7e5334b5c..9ae1a3471b 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -36,7 +36,7 @@ func Start(addr string, secret string) { cors := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Content-Type", "Authorization"}, MaxAge: 300, }) From fcb1a7813ad0013411247f789ca59e0ab95399ce Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 6 Dec 2018 13:29:43 +0800 Subject: [PATCH 101/535] Fix: dns msg to ip --- dns/client.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/dns/client.go b/dns/client.go index 7559ea37d0..dad0ff2f0e 100644 --- a/dns/client.go +++ b/dns/client.go @@ -174,20 +174,12 @@ func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) { var ips []net.IP for _, answer := range msg.Answer { - if r.ipv6 { - ans, ok := answer.(*D.AAAA) - if !ok { - continue - } + switch ans := answer.(type) { + case *D.AAAA: ips = append(ips, ans.AAAA) - continue - } - - ans, ok := answer.(*D.A) - if !ok { - continue + case *D.A: + ips = append(ips, ans.A) } - ips = append(ips, ans.A) } if len(ips) == 0 { From fa9077969c917c5d1f753d5b1e831ea29451cdf3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 10 Dec 2018 11:00:52 +0800 Subject: [PATCH 102/535] Fix: dns crash & remove unused debug log --- config/config.go | 7 +++---- dns/client.go | 2 -- tunnel/tunnel.go | 2 -- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/config/config.go b/config/config.go index f2ac2dbb82..b9de6ed966 100644 --- a/config/config.go +++ b/config/config.go @@ -68,7 +68,7 @@ type rawConfig struct { ExternalController string `yaml:"external-controller"` Secret string `yaml:"secret"` - DNS *rawDNS `yaml:"dns"` + DNS rawDNS `yaml:"dns"` Proxy []map[string]interface{} `yaml:"Proxy"` ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` Rule []string `yaml:"Rule"` @@ -95,7 +95,7 @@ func readConfig(path string) (*rawConfig, error) { Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, - DNS: &rawDNS{ + DNS: rawDNS{ Enable: false, }, } @@ -353,7 +353,6 @@ func hostWithDefaultPort(host string, defPort string) (string, error) { func parseNameServer(servers []string) ([]dns.NameServer, error) { nameservers := []dns.NameServer{} - log.Debugln("%#v", servers) for idx, server := range servers { // parse without scheme .e.g 8.8.8.8:53 @@ -387,7 +386,7 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { return nameservers, nil } -func parseDNS(cfg *rawDNS) (*DNS, error) { +func parseDNS(cfg rawDNS) (*DNS, error) { if cfg.Enable && len(cfg.NameServer) == 0 { return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") } diff --git a/dns/client.go b/dns/client.go index dad0ff2f0e..f8c711b17a 100644 --- a/dns/client.go +++ b/dns/client.go @@ -244,8 +244,6 @@ func New(config Config) *Resolver { mmdb, _ = geoip2.Open(C.Path.MMDB()) }) - println(config.EnhancedMode) - r := &Resolver{ main: transform(config.Main), ipv6: config.IPv6, diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 930bd070eb..0b4e4ff0b3 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -120,8 +120,6 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) metadata.IP = &ip } - } else { - log.Debugln("[DNS] unknown%#v", metadata) } var proxy C.Proxy From 5e4b35e03ab0c658225c135a8510d0f7dd6437ef Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 10 Dec 2018 11:33:37 +0800 Subject: [PATCH 103/535] Chore: standardize API returns --- hub/route/configs.go | 22 +++++++++++----------- hub/route/proxies.go | 36 ++++++++++++++++++------------------ hub/route/rules.go | 3 +-- hub/route/server.go | 10 +++++----- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/hub/route/configs.go b/hub/route/configs.go index bf4f89efae..26d9ca4fce 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -32,7 +32,7 @@ type configSchema struct { func getConfigs(w http.ResponseWriter, r *http.Request) { general := executor.GetGeneral() - render.Respond(w, r, general) + render.JSON(w, r, general) } func pointerOrDefault(p *int, def int) int { @@ -46,8 +46,8 @@ func pointerOrDefault(p *int, def int) int { func patchConfigs(w http.ResponseWriter, r *http.Request) { general := &configSchema{} if err := render.DecodeJSON(r.Body, general); err != nil { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, ErrBadRequest) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) return } @@ -68,7 +68,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { log.SetLevel(*general.LogLevel) } - w.WriteHeader(http.StatusNoContent) + render.NoContent(w, r) } type updateConfigRequest struct { @@ -78,25 +78,25 @@ type updateConfigRequest struct { func updateConfigs(w http.ResponseWriter, r *http.Request) { req := updateConfigRequest{} if err := render.DecodeJSON(r.Body, &req); err != nil { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, ErrBadRequest) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) return } if !filepath.IsAbs(req.Path) { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, newError("path is not a absoluted path")) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError("path is not a absoluted path")) return } force := r.URL.Query().Get("force") == "true" cfg, err := executor.ParseWithPath(req.Path) if err != nil { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, newError(err.Error())) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) return } executor.ApplyConfig(cfg, force) - w.WriteHeader(http.StatusNoContent) + render.NoContent(w, r) } diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 65beac1ec3..404d1a3293 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -47,8 +47,8 @@ func findProxyByName(next http.Handler) http.Handler { proxies := T.Instance().Proxies() proxy, exist := proxies[name] if !exist { - w.WriteHeader(http.StatusNotFound) - render.Respond(w, r, ErrNotFound) + render.Status(r, http.StatusNotFound) + render.JSON(w, r, ErrNotFound) return } @@ -59,14 +59,14 @@ func findProxyByName(next http.Handler) http.Handler { func getProxies(w http.ResponseWriter, r *http.Request) { proxies := T.Instance().Proxies() - render.Respond(w, r, map[string]map[string]C.Proxy{ + render.JSON(w, r, map[string]map[string]C.Proxy{ "proxies": proxies, }) } func getProxy(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - render.Respond(w, r, proxy) + render.JSON(w, r, proxy) } type UpdateProxyRequest struct { @@ -76,8 +76,8 @@ type UpdateProxyRequest struct { func updateProxy(w http.ResponseWriter, r *http.Request) { req := UpdateProxyRequest{} if err := render.DecodeJSON(r.Body, &req); err != nil { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, ErrBadRequest) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) return } @@ -85,18 +85,18 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { selector, ok := proxy.(*A.Selector) if !ok { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, ErrBadRequest) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) return } if err := selector.Set(req.Name); err != nil { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error()))) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(fmt.Sprintf("Selector update error: %s", err.Error()))) return } - w.WriteHeader(http.StatusNoContent) + render.NoContent(w, r) } func getProxyDelay(w http.ResponseWriter, r *http.Request) { @@ -104,8 +104,8 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { url := query.Get("url") timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) if err != nil { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, ErrBadRequest) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) return } @@ -122,14 +122,14 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { select { case <-time.After(time.Millisecond * time.Duration(timeout)): - w.WriteHeader(http.StatusRequestTimeout) - render.Respond(w, r, ErrRequestTimeout) + render.Status(r, http.StatusRequestTimeout) + render.JSON(w, r, ErrRequestTimeout) case t := <-sigCh: if t == 0 { - w.WriteHeader(http.StatusServiceUnavailable) - render.Respond(w, r, newError("An error occurred in the delay test")) + render.Status(r, http.StatusServiceUnavailable) + render.JSON(w, r, newError("An error occurred in the delay test")) } else { - render.Respond(w, r, map[string]int16{ + render.JSON(w, r, map[string]int16{ "delay": t, }) } diff --git a/hub/route/rules.go b/hub/route/rules.go index 02e19b9bdc..b8cd3aeb16 100644 --- a/hub/route/rules.go +++ b/hub/route/rules.go @@ -33,8 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) { }) } - w.WriteHeader(http.StatusOK) - render.Respond(w, r, map[string][]Rule{ + render.JSON(w, r, map[string][]Rule{ "rules": rules, }) } diff --git a/hub/route/server.go b/hub/route/server.go index 9ae1a3471b..a3e7a79100 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -77,8 +77,8 @@ func authentication(next http.Handler) http.Handler { hasUnvalidHeader := text[0] != "Bearer" hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret if hasUnvalidHeader || hasUnvalidSecret { - w.WriteHeader(http.StatusUnauthorized) - render.Respond(w, r, ErrUnauthorized) + render.Status(r, http.StatusUnauthorized) + render.JSON(w, r, ErrUnauthorized) return } next.ServeHTTP(w, r) @@ -87,7 +87,7 @@ func authentication(next http.Handler) http.Handler { } func traffic(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) + render.Status(r, http.StatusOK) tick := time.NewTicker(time.Second) t := T.Instance().Traffic() @@ -116,8 +116,8 @@ func getLogs(w http.ResponseWriter, r *http.Request) { level, ok := log.LogLevelMapping[levelText] if !ok { - w.WriteHeader(http.StatusBadRequest) - render.Respond(w, r, ErrBadRequest) + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) return } From 34c86559740a1e7b54fb6bd87ae593e0cc30e416 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 10 Dec 2018 11:48:57 +0800 Subject: [PATCH 104/535] Fix: don't keepalive when connection is close (#65) fixed #60 --- tunnel/connection.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tunnel/connection.go b/tunnel/connection.go index 29061eb39c..b2271d5754 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -5,6 +5,7 @@ import ( "io" "net" "net/http" + "strings" "sync" "time" @@ -25,8 +26,13 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) conn := newTrafficTrack(proxy.Conn(), t.traffic) req := request.R host := req.Host + keepalive := true for { + if strings.ToLower(req.Header.Get("Connection")) == "close" { + keepalive = false + } + req.Header.Set("Connection", "close") req.RequestURI = "" adapters.RemoveHopByHopHeaders(req.Header) @@ -53,6 +59,10 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) break } + if !keepalive { + break + } + req, err = http.ReadRequest(bufio.NewReader(request.Conn())) if err != nil { break From 1607d3253fbe2216f5e2614d5128863b4eea5b2e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 11 Dec 2018 00:25:05 +0800 Subject: [PATCH 105/535] Feature: add websocket headers support in vmess --- README.md | 6 +-- adapters/outbound/vmess.go | 42 +++++++++++---------- common/structure/structure.go | 71 +++++++++++++++++++++++++++++++++++ component/vmess/vmess.go | 22 ++++++----- component/vmess/websocket.go | 11 +++++- 5 files changed, 118 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 60917f467b..509209aeac 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
-

A rule based tunnel in Go.

+

A rule-based tunnel in Go.

@@ -126,8 +126,8 @@ Proxy: - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } # with tls and skip-cert-verify - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true } -# with ws -- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path } +# with ws-path and ws-headers +- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } } # with ws + tls - { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 9894bf8034..dc3eb2d248 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -32,16 +32,17 @@ type Vmess struct { } type VmessOption struct { - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - UUID string `proxy:"uuid"` - AlterID int `proxy:"alterId"` - Cipher string `proxy:"cipher"` - TLS bool `proxy:"tls,omitempty"` - Network string `proxy:"network,omitempty"` - WSPath string `proxy:"ws-path,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + AlterID int `proxy:"alterId"` + Cipher string `proxy:"cipher"` + TLS bool `proxy:"tls,omitempty"` + Network string `proxy:"network,omitempty"` + WSPath string `proxy:"ws-path,omitempty"` + WSHeaders map[string]string `proxy:"ws-headers,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } func (v *Vmess) Name() string { @@ -71,16 +72,17 @@ func (v *Vmess) MarshalJSON() ([]byte, error) { func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: option.UUID, - AlterID: uint16(option.AlterID), - Security: security, - TLS: option.TLS, - HostName: option.Server, - Port: strconv.Itoa(option.Port), - NetWork: option.Network, - WebSocketPath: option.WSPath, - SkipCertVerify: option.SkipCertVerify, - SessionCacahe: getClientSessionCache(), + UUID: option.UUID, + AlterID: uint16(option.AlterID), + Security: security, + TLS: option.TLS, + HostName: option.Server, + Port: strconv.Itoa(option.Port), + NetWork: option.Network, + WebSocketPath: option.WSPath, + WebSocketHeaders: option.WSHeaders, + SkipCertVerify: option.SkipCertVerify, + SessionCacahe: getClientSessionCache(), }) if err != nil { return nil, err diff --git a/common/structure/structure.go b/common/structure/structure.go index ac824a0719..600f264dc4 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -1,5 +1,7 @@ package structure +// references: https://github.com/mitchellh/mapstructure + import ( "fmt" "reflect" @@ -70,6 +72,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error return d.decodeBool(name, data, val) case reflect.Slice: return d.decodeSlice(name, data, val) + case reflect.Map: + return d.decodeMap(name, data, val) default: return fmt.Errorf("type %s not support", val.Kind().String()) } @@ -158,3 +162,70 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) val.Set(valSlice) return nil } + +func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + valMap := val + + if valMap.IsNil() { + mapType := reflect.MapOf(valKeyType, valElemType) + valMap = reflect.MakeMap(mapType) + } + + dataVal := reflect.Indirect(reflect.ValueOf(data)) + if dataVal.Kind() != reflect.Map { + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } + + return d.decodeMapFromMap(name, dataVal, val, valMap) +} + +func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val reflect.Value, valMap reflect.Value) error { + valType := val.Type() + valKeyType := valType.Key() + valElemType := valType.Elem() + + errors := make([]string, 0) + + if dataVal.Len() == 0 { + if dataVal.IsNil() { + if !val.IsNil() { + val.Set(dataVal) + } + } else { + val.Set(valMap) + } + + return nil + } + + for _, k := range dataVal.MapKeys() { + fieldName := fmt.Sprintf("%s[%s]", name, k) + + currentKey := reflect.Indirect(reflect.New(valKeyType)) + if err := d.decode(fieldName, k.Interface(), currentKey); err != nil { + errors = append(errors, err.Error()) + continue + } + + v := dataVal.MapIndex(k).Interface() + currentVal := reflect.Indirect(reflect.New(valElemType)) + if err := d.decode(fieldName, v, currentVal); err != nil { + errors = append(errors, err.Error()) + continue + } + + valMap.SetMapIndex(currentKey, currentVal) + } + + val.Set(valMap) + + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, ",")) + } + + return nil +} diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 8273ea8702..604b45f688 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -75,16 +75,17 @@ type Client struct { // Config of vmess type Config struct { - UUID string - AlterID uint16 - Security string - TLS bool - HostName string - Port string - NetWork string - WebSocketPath string - SkipCertVerify bool - SessionCacahe tls.ClientSessionCache + UUID string + AlterID uint16 + Security string + TLS bool + HostName string + Port string + NetWork string + WebSocketPath string + WebSocketHeaders map[string]string + SkipCertVerify bool + SessionCacahe tls.ClientSessionCache } // New return a Conn with net.Conn and DstAddr @@ -149,6 +150,7 @@ func NewClient(config Config) (*Client, error) { wsConfig = &websocketConfig{ host: host, path: config.WebSocketPath, + headers: config.WebSocketHeaders, tls: config.TLS, tlsConfig: tlsConfig, } diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index 7bef5e559c..fc50b81361 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "net/http" "net/url" "strings" "time" @@ -21,6 +22,7 @@ type websocketConn struct { type websocketConfig struct { host string path string + headers map[string]string tls bool tlsConfig *tls.Config } @@ -127,7 +129,14 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) { Path: c.path, } - wsConn, resp, err := dialer.Dial(uri.String(), nil) + headers := http.Header{} + if c.headers != nil { + for k, v := range c.headers { + headers.Set(k, v) + } + } + + wsConn, resp, err := dialer.Dial(uri.String(), headers) if err != nil { var reason string if resp != nil { From afc4644dd1386eb2d17c7348eb6b377d84ad2a12 Mon Sep 17 00:00:00 2001 From: "yuri@FreeBSD" Date: Thu, 13 Dec 2018 19:30:43 -0800 Subject: [PATCH 106/535] Feature: FreeBSD compatibility patch (#63) --- proxy/redir/tcp_freebsd.go | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 proxy/redir/tcp_freebsd.go diff --git a/proxy/redir/tcp_freebsd.go b/proxy/redir/tcp_freebsd.go new file mode 100644 index 0000000000..f0ceb36c79 --- /dev/null +++ b/proxy/redir/tcp_freebsd.go @@ -0,0 +1,52 @@ +package redir + +import ( + "errors" + "net" + "syscall" + "unsafe" + + "github.com/Dreamacro/go-shadowsocks2/socks" +) + +const ( + SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv4.h + IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h +) + +func parserPacket(conn net.Conn) (socks.Addr, error) { + c, ok := conn.(*net.TCPConn) + if !ok { + return nil, errors.New("only work with TCP connection") + } + + rc, err := c.SyscallConn() + if err != nil { + return nil, err + } + + var addr socks.Addr + + rc.Control(func(fd uintptr) { + addr, err = getorigdst(fd) + }) + + return addr, err +} + +// Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c +func getorigdst(fd uintptr) (socks.Addr, error) { + raw := syscall.RawSockaddrInet4{} + siz := unsafe.Sizeof(raw) + _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); + if err != 0 { + return nil, err + } + + addr := make([]byte, 1+net.IPv4len+2) + addr[0] = socks.AtypIPv4 + copy(addr[1:1+net.IPv4len], raw.Addr[:]) + port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian + addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] + return addr, nil +} From a6bbc67afb928d42fcf641ac436e997083fb5b37 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 20 Dec 2018 01:29:13 +0800 Subject: [PATCH 107/535] Feature: add custom ui support in API --- config/config.go | 17 +++++++++++++++-- hub/hub.go | 4 ++++ hub/route/server.go | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index b9de6ed966..f7e69c5ce7 100644 --- a/config/config.go +++ b/config/config.go @@ -6,6 +6,7 @@ import ( "net" "net/url" "os" + "path/filepath" "strings" adapters "github.com/Dreamacro/clash/adapters/outbound" @@ -27,8 +28,9 @@ type General struct { AllowLan bool `json:"allow-lan"` Mode T.Mode `json:"mode"` LogLevel log.LogLevel `json:"log-level"` - ExternalController string `json:"external-controller,omitempty"` - Secret string `json:"secret,omitempty"` + ExternalController string + ExternalUI string + Secret string } // DNS config @@ -66,6 +68,7 @@ type rawConfig struct { Mode T.Mode `yaml:"mode"` LogLevel log.LogLevel `yaml:"log-level"` ExternalController string `yaml:"external-controller"` + ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` DNS rawDNS `yaml:"dns"` @@ -145,10 +148,19 @@ func parseGeneral(cfg *rawConfig) (*General, error) { redirPort := cfg.RedirPort allowLan := cfg.AllowLan externalController := cfg.ExternalController + externalUI := cfg.ExternalUI secret := cfg.Secret mode := cfg.Mode logLevel := cfg.LogLevel + if !filepath.IsAbs(externalUI) { + externalUI = filepath.Join(C.Path.HomeDir(), externalUI) + } + + if _, err := os.Stat(externalUI); os.IsNotExist(err) { + return nil, fmt.Errorf("external-ui: %s not exist", externalUI) + } + general := &General{ Port: port, SocksPort: socksPort, @@ -157,6 +169,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) { Mode: mode, LogLevel: logLevel, ExternalController: externalController, + ExternalUI: externalUI, Secret: secret, } return general, nil diff --git a/hub/hub.go b/hub/hub.go index 41e0c64a66..d15fa364c7 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -12,6 +12,10 @@ func Parse() error { return err } + if cfg.General.ExternalUI != "" { + route.SetUIPath(cfg.General.ExternalUI) + } + if cfg.General.ExternalController != "" { go route.Start(cfg.General.ExternalController, cfg.General.Secret) } diff --git a/hub/route/server.go b/hub/route/server.go index a3e7a79100..0ee92124c4 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -17,6 +17,8 @@ import ( var ( serverSecret = "" serverAddr = "" + + uiPath = "" ) type Traffic struct { @@ -24,6 +26,10 @@ type Traffic struct { Down int64 `json:"down"` } +func SetUIPath(path string) { + uiPath = path +} + func Start(addr string, secret string) { if serverAddr != "" { return @@ -49,6 +55,14 @@ func Start(addr string, secret string) { r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) + if uiPath != "" { + fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath))) + r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP) + r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { + fs.ServeHTTP(w, r) + }) + } + log.Infoln("RESTful API listening at: %s", addr) err := http.ListenAndServe(addr, r) if err != nil { From a46041b81c465353cb045398de586d38daee679a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 20 Dec 2018 22:23:31 +0800 Subject: [PATCH 108/535] Fix: force param make no sense --- hub/executor/executor.go | 1 - 1 file changed, 1 deletion(-) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 6db5c54df4..fb6372e381 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -27,7 +27,6 @@ func ApplyConfig(cfg *config.Config, force bool) { } updateProxies(cfg.Proxies) updateRules(cfg.Rules) - updateGeneral(cfg.General) updateDNS(cfg.DNS) } From 49635eab6cbc5c667ea1d015d4f3c06851f53a01 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 20 Dec 2018 22:34:38 +0800 Subject: [PATCH 109/535] Chore: update `external-ui` explanation --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 509209aeac..31c2bb203a 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,10 @@ log-level: info # A RESTful API for clash external-controller: 127.0.0.1:9090 +# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui` +# input is a relative path to the configuration directory or an absolute path +# external-ui: folder + # Secret for RESTful API (Optional) # secret: "" From ef6260282f6130569f8b3e2ac55f92766e7927e3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 21 Dec 2018 10:55:21 +0800 Subject: [PATCH 110/535] Fix: parse external-ui --- config/config.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index f7e69c5ce7..8e48f8c75f 100644 --- a/config/config.go +++ b/config/config.go @@ -153,12 +153,14 @@ func parseGeneral(cfg *rawConfig) (*General, error) { mode := cfg.Mode logLevel := cfg.LogLevel - if !filepath.IsAbs(externalUI) { - externalUI = filepath.Join(C.Path.HomeDir(), externalUI) - } + if externalUI != "" { + if !filepath.IsAbs(externalUI) { + externalUI = filepath.Join(C.Path.HomeDir(), externalUI) + } - if _, err := os.Stat(externalUI); os.IsNotExist(err) { - return nil, fmt.Errorf("external-ui: %s not exist", externalUI) + if _, err := os.Stat(externalUI); os.IsNotExist(err) { + return nil, fmt.Errorf("external-ui: %s not exist", externalUI) + } } general := &General{ From 551ab68c1ee7bdd8fd85d75c93b3068753d9003a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E8=BE=B0=E6=96=87?= Date: Fri, 21 Dec 2018 17:48:29 +0800 Subject: [PATCH 111/535] Fix: allow access to external-ui without authentication (#75) --- hub/route/server.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/hub/route/server.go b/hub/route/server.go index 0ee92124c4..ee626cb732 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -47,19 +47,23 @@ func Start(addr string, secret string) { MaxAge: 300, }) - r.Use(cors.Handler, authentication) - - r.With(jsonContentType).Get("/traffic", traffic) - r.With(jsonContentType).Get("/logs", getLogs) - r.Mount("/configs", configRouter()) - r.Mount("/proxies", proxyRouter()) - r.Mount("/rules", ruleRouter()) + r.Group(func(r chi.Router) { + r.Use(cors.Handler, authentication) + + r.With(jsonContentType).Get("/traffic", traffic) + r.With(jsonContentType).Get("/logs", getLogs) + r.Mount("/configs", configRouter()) + r.Mount("/proxies", proxyRouter()) + r.Mount("/rules", ruleRouter()) + }) if uiPath != "" { - fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath))) - r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP) - r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { - fs.ServeHTTP(w, r) + r.Group(func(r chi.Router) { + fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath))) + r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP) + r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { + fs.ServeHTTP(w, r) + }) }) } From a7cfc81885c2639d294b274400b7f9421e37a456 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 21 Dec 2018 22:51:37 +0800 Subject: [PATCH 112/535] Fix: ignore some general configuration --- config/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index 8e48f8c75f..ea249dce9d 100644 --- a/config/config.go +++ b/config/config.go @@ -28,9 +28,9 @@ type General struct { AllowLan bool `json:"allow-lan"` Mode T.Mode `json:"mode"` LogLevel log.LogLevel `json:"log-level"` - ExternalController string - ExternalUI string - Secret string + ExternalController string `json:"-"` + ExternalUI string `json:"-"` + Secret string `json:"-"` } // DNS config From cb118d43719ccde61db1eb3dd64e99fefcb02548 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 22 Dec 2018 23:56:42 +0800 Subject: [PATCH 113/535] Chore: improve outbound architecture --- adapters/outbound/base.go | 26 ++++++++++++++++++ adapters/outbound/direct.go | 45 ++++++++------------------------ adapters/outbound/fallback.go | 22 +++++++--------- adapters/outbound/http.go | 40 +++++----------------------- adapters/outbound/reject.go | 40 +++++++--------------------- adapters/outbound/selector.go | 18 +++++-------- adapters/outbound/shadowsocks.go | 35 ++++++------------------- adapters/outbound/socks5.go | 40 +++++----------------------- adapters/outbound/urltest.go | 18 +++++-------- adapters/outbound/util.go | 2 +- adapters/outbound/vmess.go | 40 +++++----------------------- constant/adapters.go | 7 +---- tunnel/connection.go | 11 ++++---- 13 files changed, 105 insertions(+), 239 deletions(-) create mode 100644 adapters/outbound/base.go diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go new file mode 100644 index 0000000000..9701d0e1c2 --- /dev/null +++ b/adapters/outbound/base.go @@ -0,0 +1,26 @@ +package adapters + +import ( + "encoding/json" + + C "github.com/Dreamacro/clash/constant" +) + +type Base struct { + name string + tp C.AdapterType +} + +func (b *Base) Name() string { + return b.name +} + +func (b *Base) Type() C.AdapterType { + return b.tp +} + +func (b *Base) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": b.Type().String(), + }) +} diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 052d3f3baa..b46bf500b0 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -1,38 +1,16 @@ package adapters import ( - "encoding/json" "net" C "github.com/Dreamacro/clash/constant" ) -// DirectAdapter is a directly connected adapter -type DirectAdapter struct { - conn net.Conn +type Direct struct { + *Base } -// Close is used to close connection -func (d *DirectAdapter) Close() { - d.conn.Close() -} - -// Conn is used to http request -func (d *DirectAdapter) Conn() net.Conn { - return d.conn -} - -type Direct struct{} - -func (d *Direct) Name() string { - return "DIRECT" -} - -func (d *Direct) Type() C.AdapterType { - return C.Direct -} - -func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (d *Direct) Generator(metadata *C.Metadata) (net.Conn, error) { address := net.JoinHostPort(metadata.Host, metadata.Port) if metadata.IP != nil { address = net.JoinHostPort(metadata.IP.String(), metadata.Port) @@ -40,18 +18,17 @@ func (d *Direct) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err er c, err := net.DialTimeout("tcp", address, tcpTimeout) if err != nil { - return + return nil, err } tcpKeepAlive(c) - return &DirectAdapter{conn: c}, nil -} - -func (d *Direct) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": d.Type().String(), - }) + return c, nil } func NewDirect() *Direct { - return &Direct{} + return &Direct{ + Base: &Base{ + name: "DIRECT", + tp: C.Direct, + }, + } } diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 0acaabbb7c..ea456fd37c 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -3,6 +3,7 @@ package adapters import ( "encoding/json" "errors" + "net" "sync" "time" @@ -15,7 +16,7 @@ type proxy struct { } type Fallback struct { - name string + *Base proxies []*proxy rawURL string interval time.Duration @@ -29,14 +30,6 @@ type FallbackOption struct { Interval int `proxy:"interval"` } -func (f *Fallback) Name() string { - return f.name -} - -func (f *Fallback) Type() C.AdapterType { - return C.Fallback -} - func (f *Fallback) Now() string { _, proxy := f.findNextValidProxy(0) if proxy != nil { @@ -45,7 +38,7 @@ func (f *Fallback) Now() string { return f.proxies[0].RawProxy.Name() } -func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) { idx := 0 var proxy *proxy for { @@ -53,13 +46,13 @@ func (f *Fallback) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err if proxy == nil { break } - adapter, err = proxy.RawProxy.Generator(metadata) + adapter, err := proxy.RawProxy.Generator(metadata) if err != nil { proxy.Valid = false idx++ continue } - return + return adapter, err } return f.proxies[0].RawProxy.Generator(metadata) } @@ -138,7 +131,10 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { } Fallback := &Fallback{ - name: option.Name, + Base: &Base{ + name: option.Name, + tp: C.Fallback, + }, proxies: warpperProxies, rawURL: option.URL, interval: interval, diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 8938aa8486..2892d1021f 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -5,7 +5,6 @@ import ( "bytes" "crypto/tls" "encoding/base64" - "encoding/json" "errors" "fmt" "io" @@ -16,23 +15,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) -// HTTPAdapter is a proxy adapter -type HTTPAdapter struct { - conn net.Conn -} - -// Close is used to close connection -func (ha *HTTPAdapter) Close() { - ha.conn.Close() -} - -func (ha *HTTPAdapter) Conn() net.Conn { - return ha.conn -} - type Http struct { + *Base addr string - name string user string pass string tls bool @@ -50,15 +35,7 @@ type HttpOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (h *Http) Name() string { - return h.name -} - -func (h *Http) Type() C.AdapterType { - return C.Http -} - -func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (h *Http) Generator(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", h.addr, tcpTimeout) if err == nil && h.tls { cc := tls.Client(c, h.tlsConfig) @@ -74,7 +51,7 @@ func (h *Http) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err erro return nil, err } - return &HTTPAdapter{conn: c}, nil + return c, nil } func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { @@ -118,12 +95,6 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) } -func (h *Http) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": h.Type().String(), - }) -} - func NewHttp(option HttpOption) *Http { var tlsConfig *tls.Config if option.TLS { @@ -137,8 +108,11 @@ func NewHttp(option HttpOption) *Http { } return &Http{ + Base: &Base{ + name: option.Name, + tp: C.Http, + }, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - name: option.Name, user: option.UserName, pass: option.Password, tls: option.TLS, diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index 5a5357c06f..9f49ae8588 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -1,7 +1,6 @@ package adapters import ( - "encoding/json" "io" "net" "time" @@ -9,42 +8,21 @@ import ( C "github.com/Dreamacro/clash/constant" ) -// RejectAdapter is a reject connected adapter -type RejectAdapter struct { - conn net.Conn -} - -// Close is used to close connection -func (r *RejectAdapter) Close() {} - -// Conn is used to http request -func (r *RejectAdapter) Conn() net.Conn { - return r.conn -} - type Reject struct { + *Base } -func (r *Reject) Name() string { - return "REJECT" -} - -func (r *Reject) Type() C.AdapterType { - return C.Reject -} - -func (r *Reject) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { - return &RejectAdapter{conn: &NopConn{}}, nil -} - -func (r *Reject) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": r.Type().String(), - }) +func (r *Reject) Generator(metadata *C.Metadata) (net.Conn, error) { + return &NopConn{}, nil } func NewReject() *Reject { - return &Reject{} + return &Reject{ + Base: &Base{ + name: "REJECT", + tp: C.Reject, + }, + } } type NopConn struct{} diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index e4dbd6d92a..28f39c4077 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -3,13 +3,14 @@ package adapters import ( "encoding/json" "errors" + "net" "sort" C "github.com/Dreamacro/clash/constant" ) type Selector struct { - name string + *Base selected C.Proxy proxies map[string]C.Proxy } @@ -19,15 +20,7 @@ type SelectorOption struct { Proxies []string `proxy:"proxies"` } -func (s *Selector) Name() string { - return s.name -} - -func (s *Selector) Type() C.AdapterType { - return C.Selector -} - -func (s *Selector) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (s *Selector) Generator(metadata *C.Metadata) (net.Conn, error) { return s.selected.Generator(metadata) } @@ -68,7 +61,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) { } s := &Selector{ - name: name, + Base: &Base{ + name: name, + tp: C.Selector, + }, proxies: mapping, selected: proxies[0], } diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 3135608420..feb8f098a3 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -7,30 +7,16 @@ import ( "net" "strconv" - "github.com/Dreamacro/clash/component/simple-obfs" + obfs "github.com/Dreamacro/clash/component/simple-obfs" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/go-shadowsocks2/core" "github.com/Dreamacro/go-shadowsocks2/socks" ) -// ShadowsocksAdapter is a shadowsocks adapter -type ShadowsocksAdapter struct { - conn net.Conn -} - -// Close is used to close connection -func (ss *ShadowsocksAdapter) Close() { - ss.conn.Close() -} - -func (ss *ShadowsocksAdapter) Conn() net.Conn { - return ss.conn -} - type ShadowSocks struct { + *Base server string - name string obfs string obfsHost string cipher core.Cipher @@ -46,15 +32,7 @@ type ShadowSocksOption struct { ObfsHost string `proxy:"obfs-host,omitempty"` } -func (ss *ShadowSocks) Name() string { - return ss.name -} - -func (ss *ShadowSocks) Type() C.AdapterType { - return C.Shadowsocks -} - -func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (ss *ShadowSocks) Generator(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", ss.server) @@ -69,7 +47,7 @@ func (ss *ShadowSocks) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, } c = ss.cipher.StreamConn(c) _, err = c.Write(serializesSocksAddr(metadata)) - return &ShadowsocksAdapter{conn: c}, err + return c, err } func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { @@ -94,8 +72,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } return &ShadowSocks{ + Base: &Base{ + name: option.Name, + tp: C.Shadowsocks, + }, server: server, - name: option.Name, cipher: ciph, obfs: obfs, obfsHost: obfsHost, diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index dc958235d7..2b4613d63e 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -3,7 +3,6 @@ package adapters import ( "bytes" "crypto/tls" - "encoding/json" "errors" "fmt" "io" @@ -15,23 +14,9 @@ import ( "github.com/Dreamacro/go-shadowsocks2/socks" ) -// Socks5Adapter is a shadowsocks adapter -type Socks5Adapter struct { - conn net.Conn -} - -// Close is used to close connection -func (ss *Socks5Adapter) Close() { - ss.conn.Close() -} - -func (ss *Socks5Adapter) Conn() net.Conn { - return ss.conn -} - type Socks5 struct { + *Base addr string - name string user string pass string tls bool @@ -49,15 +34,7 @@ type Socks5Option struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (ss *Socks5) Name() string { - return ss.name -} - -func (ss *Socks5) Type() C.AdapterType { - return C.Socks5 -} - -func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (ss *Socks5) Generator(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) if err == nil && ss.tls { @@ -73,13 +50,7 @@ func (ss *Socks5) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err e if err := ss.shakeHand(metadata, c); err != nil { return nil, err } - return &Socks5Adapter{conn: c}, nil -} - -func (ss *Socks5) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]string{ - "type": ss.Type().String(), - }) + return c, nil } func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { @@ -154,8 +125,11 @@ func NewSocks5(option Socks5Option) *Socks5 { } return &Socks5{ + Base: &Base{ + name: option.Name, + tp: C.Socks5, + }, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - name: option.Name, user: option.UserName, pass: option.Password, tls: option.TLS, diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 1466c2771c..b8b144aaff 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -3,6 +3,7 @@ package adapters import ( "encoding/json" "errors" + "net" "sort" "sync" "sync/atomic" @@ -12,7 +13,7 @@ import ( ) type URLTest struct { - name string + *Base proxies []C.Proxy rawURL string fast C.Proxy @@ -28,19 +29,11 @@ type URLTestOption struct { Interval int `proxy:"interval"` } -func (u *URLTest) Name() string { - return u.name -} - -func (u *URLTest) Type() C.AdapterType { - return C.URLTest -} - func (u *URLTest) Now() string { return u.fast.Name() } -func (u *URLTest) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (u *URLTest) Generator(metadata *C.Metadata) (net.Conn, error) { a, err := u.fast.Generator(metadata) if err != nil { go u.speedTest() @@ -128,7 +121,10 @@ func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { interval := time.Duration(option.Interval) * time.Second urlTest := &URLTest{ - name: option.Name, + Base: &Base{ + name: option.Name, + tp: C.URLTest, + }, proxies: proxies[:], rawURL: option.URL, fast: proxies[0], diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index f38b55e662..6dedc9b72a 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -36,7 +36,7 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) { defer instance.Close() transport := &http.Transport{ Dial: func(string, string) (net.Conn, error) { - return instance.Conn(), nil + return instance, nil }, // from http.DefaultTransport MaxIdleConns: 100, diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index dc3eb2d248..67820acc68 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -1,7 +1,6 @@ package adapters import ( - "encoding/json" "fmt" "net" "strconv" @@ -11,22 +10,8 @@ import ( C "github.com/Dreamacro/clash/constant" ) -// VmessAdapter is a vmess adapter -type VmessAdapter struct { - conn net.Conn -} - -// Close is used to close connection -func (v *VmessAdapter) Close() { - v.conn.Close() -} - -func (v *VmessAdapter) Conn() net.Conn { - return v.conn -} - type Vmess struct { - name string + *Base server string client *vmess.Client } @@ -45,28 +30,14 @@ type VmessOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (v *Vmess) Name() string { - return v.name -} - -func (v *Vmess) Type() C.AdapterType { - return C.Vmess -} - -func (v *Vmess) Generator(metadata *C.Metadata) (adapter C.ProxyAdapter, err error) { +func (v *Vmess) Generator(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", v.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", v.server) } tcpKeepAlive(c) c, err = v.client.New(c, parseVmessAddr(metadata)) - return &VmessAdapter{conn: c}, err -} - -func (v *Vmess) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ - "type": v.Type().String(), - }) + return c, err } func NewVmess(option VmessOption) (*Vmess, error) { @@ -89,7 +60,10 @@ func NewVmess(option VmessOption) (*Vmess, error) { } return &Vmess{ - name: option.Name, + Base: &Base{ + name: option.Name, + tp: C.Vmess, + }, server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), client: client, }, nil diff --git a/constant/adapters.go b/constant/adapters.go index 6746fb5a19..293b50edf5 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -17,11 +17,6 @@ const ( Vmess ) -type ProxyAdapter interface { - Conn() net.Conn - Close() -} - type ServerAdapter interface { Metadata() *Metadata Close() @@ -30,7 +25,7 @@ type ServerAdapter interface { type Proxy interface { Name() string Type() AdapterType - Generator(metadata *Metadata) (ProxyAdapter, error) + Generator(metadata *Metadata) (net.Conn, error) MarshalJSON() ([]byte, error) } diff --git a/tunnel/connection.go b/tunnel/connection.go index b2271d5754..8b9832d7c5 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -9,8 +9,7 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/adapters/inbound" - C "github.com/Dreamacro/clash/constant" + adapters "github.com/Dreamacro/clash/adapters/inbound" ) const ( @@ -22,8 +21,8 @@ const ( var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} -func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) { - conn := newTrafficTrack(proxy.Conn(), t.traffic) +func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { + conn := newTrafficTrack(outbound, t.traffic) req := request.R host := req.Host keepalive := true @@ -76,8 +75,8 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, proxy C.ProxyAdapter) } } -func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, proxy C.ProxyAdapter) { - conn := newTrafficTrack(proxy.Conn(), t.traffic) +func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, outbound net.Conn) { + conn := newTrafficTrack(outbound, t.traffic) relay(request.Conn(), conn) } From 532ec88964e3af180557f8f4cb29992075a366db Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 23 Dec 2018 00:42:08 +0800 Subject: [PATCH 114/535] Chore: make a consistent code style --- component/vmess/websocket.go | 2 +- proxy/redir/tcp_freebsd.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index fc50b81361..02e95d55ef 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -118,7 +118,7 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) { dialer.TLSClientConfig = c.tlsConfig } - host, port, err := net.SplitHostPort(c.host) + host, port, _ := net.SplitHostPort(c.host) if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { host = c.host } diff --git a/proxy/redir/tcp_freebsd.go b/proxy/redir/tcp_freebsd.go index f0ceb36c79..1f90121b55 100644 --- a/proxy/redir/tcp_freebsd.go +++ b/proxy/redir/tcp_freebsd.go @@ -38,7 +38,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { func getorigdst(fd uintptr) (socks.Addr, error) { raw := syscall.RawSockaddrInet4{} siz := unsafe.Sizeof(raw) - _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); + _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0) if err != 0 { return nil, err } From 3f6c707aa9bd362dd3b6ab5ffe782720a1fb4eff Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 23 Dec 2018 20:25:49 +0800 Subject: [PATCH 115/535] Fix: patch config field --- hub/route/configs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/route/configs.go b/hub/route/configs.go index 26d9ca4fce..3895c955e6 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -23,7 +23,7 @@ func configRouter() http.Handler { type configSchema struct { Port *int `json:"port"` - SocksPort *int `json:"socket-port"` + SocksPort *int `json:"socks-port"` RedirPort *int `json:"redir-port"` AllowLan *bool `json:"allow-lan"` Mode *T.Mode `json:"mode"` From 7b5e1f759cf91694379e1c23e619ecd750abf442 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 29 Dec 2018 14:11:54 +0800 Subject: [PATCH 116/535] Fix: authentication with stream api --- hub/route/server.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/hub/route/server.go b/hub/route/server.go index ee626cb732..d530684a19 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -42,16 +42,20 @@ func Start(addr string, secret string) { cors := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}, + AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, AllowedHeaders: []string{"Content-Type", "Authorization"}, MaxAge: 300, }) + root := chi.NewRouter().With(jsonContentType) + root.Get("/traffic", traffic) + root.Get("/logs", getLogs) + + r.Get("/", hello) r.Group(func(r chi.Router) { r.Use(cors.Handler, authentication) - r.With(jsonContentType).Get("/traffic", traffic) - r.With(jsonContentType).Get("/logs", getLogs) + r.Mount("/", root) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) @@ -104,6 +108,10 @@ func authentication(next http.Handler) http.Handler { return http.HandlerFunc(fn) } +func hello(w http.ResponseWriter, r *http.Request) { + render.JSON(w, r, render.M{"hello": "clash"}) +} + func traffic(w http.ResponseWriter, r *http.Request) { render.Status(r, http.StatusOK) From 4e91118a0512a352a93b99b5bfd5b37668001dc6 Mon Sep 17 00:00:00 2001 From: Noah Shang Date: Mon, 31 Dec 2018 20:57:21 +0800 Subject: [PATCH 117/535] Feature: add freebsd release (#80) add freebsd support --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 622afd3242..30224a1133 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME=clash BINDIR=bin GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' -all: linux macos win64 +all: linux macos freebsd win64 linux: GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ @@ -10,13 +10,17 @@ linux: macos: GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ +freebsd: + GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + win64: GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe -releases: linux macos win64 +releases: linux macos freebsd win64 chmod +x $(BINDIR)/$(NAME)-* gzip $(BINDIR)/$(NAME)-linux gzip $(BINDIR)/$(NAME)-macos + gzip $(BINDIR)/$(NAME)-freebsd zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe clean: From 7768c5b933e080b787e7a83ba900c63a9f764538 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Thu, 3 Jan 2019 10:49:09 +0800 Subject: [PATCH 118/535] Chore: add more platform release (#83) --- .gitignore | 1 + Makefile | 86 ++++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 75 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 5ba719067c..9a9bbe1904 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.dll *.so *.dylib +bin/* # Test binary, build with `go test -c` *.test diff --git a/Makefile b/Makefile index 30224a1133..2ea64f96c6 100644 --- a/Makefile +++ b/Makefile @@ -2,26 +2,88 @@ NAME=clash BINDIR=bin GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' -all: linux macos freebsd win64 +PLATFORM_LIST = \ + darwin-amd64 \ + linux-386 \ + linux-amd64 \ + linux-armv5 \ + linux-armv6 \ + linux-armv7 \ + linux-armv8 \ + linux-mips-softfloat \ + linux-mips-hardfloat \ + linux-mipsle \ + linux-mips64 \ + linux-mips64le \ + freebsd-386 \ + freebsd-amd64 -linux: - GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ +WINDOWS_ARCH_LIST = \ + windows-386 \ + windows-amd64 + +all: linux-amd64 darwin-amd64 windows-amd64 # Most used -macos: +darwin-amd64: GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ -freebsd: +linux-386: + GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-amd64: + GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-armv5: + GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-armv6: + GOARCH=arm GOOS=linux GOARM=6 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-armv7: + GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-armv8: + GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-mips-softfloat: + GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-mips-hardfloat: + GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-mipsle: + GOARCH=mipsle GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-mips64: + GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-mips64le: + GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +freebsd-386: + GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +freebsd-amd64: GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ -win64: +windows-386: GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe -releases: linux macos freebsd win64 - chmod +x $(BINDIR)/$(NAME)-* - gzip $(BINDIR)/$(NAME)-linux - gzip $(BINDIR)/$(NAME)-macos - gzip $(BINDIR)/$(NAME)-freebsd - zip -m -j $(BINDIR)/$(NAME)-win64.zip $(BINDIR)/$(NAME)-win64.exe +windows-amd64: + GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) +zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) + +$(gz_releases): %.gz : % + chmod +x $(BINDIR)/$(NAME)-$(basename $@) + gzip -f $(BINDIR)/$(NAME)-$(basename $@) + +$(zip_releases): %.zip : % + zip -m -j $(BINDIR)/$(NAME)-$(basename $@).zip $(BINDIR)/$(NAME)-$(basename $@).exe + +all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) +releases: $(gz_releases) $(zip_releases) clean: rm $(BINDIR)/* From 15a77fa71b8d9d285176ae0609d60df3963aa0de Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sun, 6 Jan 2019 14:31:42 +0800 Subject: [PATCH 119/535] Fix: print log when start dns server failed (#87) --- hub/executor/executor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index fb6372e381..116b0dd870 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -55,7 +55,9 @@ func updateDNS(c *config.DNS) { EnhancedMode: c.EnhancedMode, }) T.Instance().SetResolver(r) - dns.ReCreateServer(c.Listen, r) + if err := dns.ReCreateServer(c.Listen, r); err != nil { + log.Errorln("Start DNS server error: %s", err.Error()) + } } func updateProxies(proxies map[string]C.Proxy) { From 83fac44010dcae4183ab5c4f58d10ae5ebdc5d17 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 7 Jan 2019 10:47:25 +0800 Subject: [PATCH 120/535] Fix: nghttpx return 400 error (#84) --- adapters/outbound/http.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 2892d1021f..4ac7479a87 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -58,9 +58,9 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { var buf bytes.Buffer var err error - buf.WriteString("CONNECT ") - buf.WriteString(net.JoinHostPort(metadata.Host, metadata.Port)) - buf.WriteString(" HTTP/1.1\r\n") + addr := net.JoinHostPort(metadata.Host, metadata.Port) + buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") + buf.WriteString("Host: " + metadata.Host + "\r\n") buf.WriteString("Proxy-Connection: Keep-Alive\r\n") if h.user != "" && h.pass != "" { From bd6c6a9ad1a79a677553cde7c8a956d1ce5b2c11 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 14 Jan 2019 10:35:11 +0800 Subject: [PATCH 121/535] Chore: print origin rule when format error (#92) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index ea249dce9d..95f2772114 100644 --- a/config/config.go +++ b/config/config.go @@ -324,7 +324,7 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { payload = rule[1] target = rule[2] default: - return nil, fmt.Errorf("Rules[%d] error: format invalid", idx) + return nil, fmt.Errorf("Rules[%d] [- %s] error: format invalid", idx, line) } rule = trimArr(rule) From 36b5d1f18f28f2d01022ead8b90d04cc6b31cf79 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 25 Jan 2019 15:38:14 +0800 Subject: [PATCH 122/535] Fix: DNS server returns the correct TTL --- common/cache/cache.go | 15 +++++++++++++++ dns/client.go | 11 +++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/common/cache/cache.go b/common/cache/cache.go index f33591eaff..bb16adcab2 100644 --- a/common/cache/cache.go +++ b/common/cache/cache.go @@ -44,6 +44,21 @@ func (c *cache) Get(key interface{}) interface{} { return elm.Payload } +// GetWithExpire element in Cache with Expire Time +func (c *cache) GetWithExpire(key interface{}) (item interface{}, expired time.Time) { + item, exist := c.mapping.Load(key) + if !exist { + return + } + elm := item.(*element) + // expired + if time.Since(elm.Expired) > 0 { + c.mapping.Delete(key) + return + } + return elm.Payload, elm.Expired +} + func (c *cache) cleanup() { c.mapping.Range(func(k, v interface{}) bool { key := k.(string) diff --git a/dns/client.go b/dns/client.go index f8c711b17a..a0d3a38335 100644 --- a/dns/client.go +++ b/dns/client.go @@ -52,9 +52,16 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { } q := m.Question[0] - cache := r.cache.Get(q.String()) + cache, expireTime := r.cache.GetWithExpire(q.String()) if cache != nil { - return cache.(*D.Msg).Copy(), nil + msg = cache.(*D.Msg).Copy() + if len(msg.Answer) > 0 { + ttl := uint32(expireTime.Sub(time.Now()).Seconds()) + for _, answer := range msg.Answer { + answer.Header().Ttl = ttl + } + } + return } defer func() { if msg != nil { From 558169890881230040f8c586a232dfe9abdc4614 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Tue, 29 Jan 2019 23:46:18 +0800 Subject: [PATCH 123/535] Chore: improve programming style (#109) --- tunnel/tunnel.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 0b4e4ff0b3..4c024aba69 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -153,12 +153,10 @@ func (t *Tunnel) match(metadata *C.Metadata) C.Proxy { for _, rule := range t.rules { if rule.IsMatch(metadata) { - a, ok := t.proxies[rule.Adapter()] - if !ok { - continue + if a, ok := t.proxies[rule.Adapter()]; ok { + log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) + return a } - log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) - return a } } log.Infoln("%v doesn't match any rule using DIRECT", metadata.String()) From e30a628702617f3fa134cc24665672b768e2bc92 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 30 Jan 2019 20:22:52 +0800 Subject: [PATCH 124/535] Fix: shadow variable --- common/cache/cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/cache/cache.go b/common/cache/cache.go index bb16adcab2..6b252c203e 100644 --- a/common/cache/cache.go +++ b/common/cache/cache.go @@ -45,7 +45,7 @@ func (c *cache) Get(key interface{}) interface{} { } // GetWithExpire element in Cache with Expire Time -func (c *cache) GetWithExpire(key interface{}) (item interface{}, expired time.Time) { +func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) { item, exist := c.mapping.Load(key) if !exist { return From bfe51e46b0e3081b72cb12a13d102a2bc0aa8033 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 2 Feb 2019 20:47:38 +0800 Subject: [PATCH 125/535] Improve: lazy resolve ip --- constant/metadata.go | 16 +++++++++++---- tunnel/tunnel.go | 48 +++++++++++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/constant/metadata.go b/constant/metadata.go index 1cb19033e6..f47a8c8335 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -39,9 +39,17 @@ type Metadata struct { Port string } -func (addr *Metadata) String() string { - if addr.Host == "" { - return addr.IP.String() +func (m *Metadata) String() string { + if m.Host == "" { + return m.IP.String() } - return addr.Host + return m.Host +} + +func (m *Metadata) Valid() bool { + return m.Host != "" || m.IP != nil +} + +func (m *Metadata) NeedLoopUpHost() bool { + return m.Source == REDIR } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 4c024aba69..30ce12bacd 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -1,6 +1,7 @@ package tunnel import ( + "fmt" "net" "sync" "time" @@ -10,7 +11,7 @@ import ( "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" - "gopkg.in/eapache/channels.v1" + channels "gopkg.in/eapache/channels.v1" ) var ( @@ -80,6 +81,10 @@ func (t *Tunnel) SetResolver(resolver *dns.Resolver) { t.resolver = resolver } +func (t *Tunnel) hasResolver() bool { + return t.resolver != nil +} + func (t *Tunnel) process() { queue := t.queue.Out() for { @@ -106,20 +111,12 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer localConn.Close() metadata := localConn.Metadata() - if metadata.Source == C.REDIR && t.resolver != nil { + if metadata.NeedLoopUpHost() && t.hasResolver() { host, exist := t.resolver.IPToHost(*metadata.IP) if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName } - } else if metadata.IP == nil && metadata.AddrType == C.AtypDomainName { - ip, err := t.resolveIP(metadata.Host) - if err != nil { - log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) - } else { - log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) - metadata.IP = &ip - } } var proxy C.Proxy @@ -130,8 +127,18 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { proxy = t.proxies["GLOBAL"] // Rule default: - proxy = t.match(metadata) + var err error + proxy, err = t.match(metadata) + if err != nil { + return + } } + + if !metadata.Valid() { + log.Warnln("[Metadata] not valid: %#v", metadata) + return + } + remoConn, err := proxy.Generator(metadata) if err != nil { log.Warnln("Proxy[%s] connect [%s] error: %s", proxy.Name(), metadata.String(), err.Error()) @@ -147,20 +154,33 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } } -func (t *Tunnel) match(metadata *C.Metadata) C.Proxy { +func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { + return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.IP == nil +} + +func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { t.configLock.RLock() defer t.configLock.RUnlock() for _, rule := range t.rules { + if t.shouldResolveIP(rule, metadata) { + ip, err := t.resolveIP(metadata.Host) + if err != nil { + return nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error()) + } + log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) + metadata.IP = &ip + } + if rule.IsMatch(metadata) { if a, ok := t.proxies[rule.Adapter()]; ok { log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) - return a + return a, nil } } } log.Infoln("%v doesn't match any rule using DIRECT", metadata.String()) - return t.proxies["DIRECT"] + return t.proxies["DIRECT"], nil } func newTunnel() *Tunnel { From 42d33fe6298c5ba31ad1cb7a18808949b2411b03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E8=BE=B0=E6=96=87?= Date: Sat, 2 Feb 2019 21:03:13 +0800 Subject: [PATCH 126/535] Feature: SOURCE-IP-CIDR rule type (#96) --- README.md | 1 + adapters/inbound/http.go | 4 +++- adapters/inbound/https.go | 4 +++- adapters/inbound/socket.go | 1 + adapters/inbound/util.go | 7 +++++++ config/config.go | 4 +++- constant/metadata.go | 1 + constant/rule.go | 3 +++ rules/ipcidr.go | 23 ++++++++++++++--------- tunnel/tunnel.go | 6 +++--- 10 files changed, 39 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 31c2bb203a..ec256ba1af 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,7 @@ Rule: - DOMAIN,google.com,Proxy - DOMAIN-SUFFIX,ad.com,REJECT - IP-CIDR,127.0.0.0/8,DIRECT +- SOURCE-IP-CIDR,192.168.1.201/32,DIRECT - GEOIP,CN,DIRECT # FINAL would remove after prerelease # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now diff --git a/adapters/inbound/http.go b/adapters/inbound/http.go index 01aa14b8e2..8aa21e7c25 100644 --- a/adapters/inbound/http.go +++ b/adapters/inbound/http.go @@ -32,8 +32,10 @@ func (h *HTTPAdapter) Conn() net.Conn { // NewHTTP is HTTPAdapter generator func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { + metadata := parseHTTPAddr(request) + metadata.SourceIP = parseSourceIP(conn) return &HTTPAdapter{ - metadata: parseHTTPAddr(request), + metadata: metadata, R: request, conn: conn, } diff --git a/adapters/inbound/https.go b/adapters/inbound/https.go index 6e36180b7c..e951268659 100644 --- a/adapters/inbound/https.go +++ b/adapters/inbound/https.go @@ -7,8 +7,10 @@ import ( // NewHTTPS is HTTPAdapter generator func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { + metadata := parseHTTPAddr(request) + metadata.SourceIP = parseSourceIP(conn) return &SocketAdapter{ - metadata: parseHTTPAddr(request), + metadata: metadata, conn: conn, } } diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 8c0ef49dc1..66f13f65e3 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -32,6 +32,7 @@ func (s *SocketAdapter) Conn() net.Conn { func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType) *SocketAdapter { metadata := parseSocksAddr(target) metadata.Source = source + metadata.SourceIP = parseSourceIP(conn) return &SocketAdapter{ conn: conn, diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index a1f5cd0855..b059920aae 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -61,3 +61,10 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { return metadata } + +func parseSourceIP(conn net.Conn) *net.IP { + if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok { + return &addr.IP + } + return nil +} diff --git a/config/config.go b/config/config.go index 95f2772114..979da3ef1e 100644 --- a/config/config.go +++ b/config/config.go @@ -338,7 +338,9 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { case "GEOIP": rules = append(rules, R.NewGEOIP(payload, target)) case "IP-CIDR", "IP-CIDR6": - rules = append(rules, R.NewIPCIDR(payload, target)) + rules = append(rules, R.NewIPCIDR(payload, target, false)) + case "SOURCE-IP-CIDR": + rules = append(rules, R.NewIPCIDR(payload, target, true)) case "MATCH": fallthrough case "FINAL": diff --git a/constant/metadata.go b/constant/metadata.go index f47a8c8335..4a361dfbff 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -33,6 +33,7 @@ type SourceType int type Metadata struct { NetWork NetWork Source SourceType + SourceIP *net.IP AddrType int Host string IP *net.IP diff --git a/constant/rule.go b/constant/rule.go index 1a8f5f97c1..ba287501d7 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -7,6 +7,7 @@ const ( DomainKeyword GEOIP IPCIDR + SourceIPCIDR FINAL ) @@ -24,6 +25,8 @@ func (rt RuleType) String() string { return "GEOIP" case IPCIDR: return "IPCIDR" + case SourceIPCIDR: + return "SourceIPCIDR" case FINAL: return "FINAL" default: diff --git a/rules/ipcidr.go b/rules/ipcidr.go index fe1a9d9cd6..597ac3e19a 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -7,20 +7,24 @@ import ( ) type IPCIDR struct { - ipnet *net.IPNet - adapter string + ipnet *net.IPNet + adapter string + isSourceIP bool } func (i *IPCIDR) RuleType() C.RuleType { + if i.isSourceIP { + return C.SourceIPCIDR + } return C.IPCIDR } func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { - if metadata.IP == nil { - return false + ip := metadata.IP + if i.isSourceIP { + ip = metadata.SourceIP } - - return i.ipnet.Contains(*metadata.IP) + return i.ipnet.Contains(*ip) } func (i *IPCIDR) Adapter() string { @@ -31,12 +35,13 @@ func (i *IPCIDR) Payload() string { return i.ipnet.String() } -func NewIPCIDR(s string, adapter string) *IPCIDR { +func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR { _, ipnet, err := net.ParseCIDR(s) if err != nil { } return &IPCIDR{ - ipnet: ipnet, - adapter: adapter, + ipnet: ipnet, + adapter: adapter, + isSourceIP: isSourceIP, } } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 30ce12bacd..969208183c 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -141,7 +141,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { remoConn, err := proxy.Generator(metadata) if err != nil { - log.Warnln("Proxy[%s] connect [%s] error: %s", proxy.Name(), metadata.String(), err.Error()) + log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) return } defer remoConn.Close() @@ -174,12 +174,12 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { if rule.IsMatch(metadata) { if a, ok := t.proxies[rule.Adapter()]; ok { - log.Infoln("%v match %s using %s", metadata.String(), rule.RuleType().String(), rule.Adapter()) + log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter()) return a, nil } } } - log.Infoln("%v doesn't match any rule using DIRECT", metadata.String()) + log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SourceIP.String(), metadata.String()) return t.proxies["DIRECT"], nil } From b594cbc68d36abe46092735fbc978a31d829327a Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 2 Feb 2019 21:11:27 +0800 Subject: [PATCH 127/535] Fix: parse ip string when use socks proxy (#100) --- dns/client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dns/client.go b/dns/client.go index a0d3a38335..29b4207cf6 100644 --- a/dns/client.go +++ b/dns/client.go @@ -155,6 +155,11 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) { } func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { + ip = net.ParseIP(host) + if ip != nil { + return ip, nil + } + query := &D.Msg{} dnsType := D.TypeA if r.ipv6 { From 1016355ef6a134d253586f39d871f094c1d8b5e9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 2 Feb 2019 21:37:36 +0800 Subject: [PATCH 128/535] Chore: log dns server address when success --- hub/executor/executor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 116b0dd870..390d430473 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -57,7 +57,9 @@ func updateDNS(c *config.DNS) { T.Instance().SetResolver(r) if err := dns.ReCreateServer(c.Listen, r); err != nil { log.Errorln("Start DNS server error: %s", err.Error()) + return } + log.Infoln("DNS server listening at: %s", c.Listen) } func updateProxies(proxies map[string]C.Proxy) { From 754df5ba9bb447e858bbdcc28c344876b2519bf7 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 2 Feb 2019 21:53:30 +0800 Subject: [PATCH 129/535] Chore: update dependencies --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 595d53ef3b..cb4146dee8 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,18 @@ module github.com/Dreamacro/clash require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.2 + github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 github.com/eapache/queue v1.1.0 // indirect - github.com/go-chi/chi v3.3.3+incompatible + github.com/go-chi/chi v4.0.1+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 - github.com/gofrs/uuid v3.1.0+incompatible + github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.0 - github.com/miekg/dns v1.1.0 + github.com/miekg/dns v1.1.4 github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/maxminddb-golang v1.3.0 // indirect - github.com/sirupsen/logrus v1.2.0 - golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 + github.com/sirupsen/logrus v1.3.0 + golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect gopkg.in/eapache/channels.v1 v1.1.0 diff --git a/go.sum b/go.sum index 4d0e310b41..2a2f1273e8 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,39 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.2 h1:8KgWbwAw5PJF+i6F3tI2iW/Em9WDtAuDG4obot8bGLM= -github.com/Dreamacro/go-shadowsocks2 v0.1.2/go.mod h1:J5YbNUiKtaD7EJmQ4O9ruUTY9+IgrflPgm63K1nUE0I= +github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 h1:1axYxE0ZLJy40+ulq46XQt7MaJDJr4iGer1NQz7jmKw= +github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8= -github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v4.0.1+incompatible h1:RSRC5qmFPtO90t7pTL0DBMNpZFsb/sHF3RXVlDgFisA= +github.com/go-chi/chi v4.0.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= -github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= -github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.0 h1:yv9O9RJbvVFkvW8PKYqp4x7HQkc5RWwmUY/L8MdUaIg= -github.com/miekg/dns v1.1.0/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0= +github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= 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/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= -golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc= +golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= From 53b5ef199f9e5810b63ea9fff7efbf2b912d0b9d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 4 Feb 2019 09:39:17 +0800 Subject: [PATCH 130/535] Fix: parse proxies shadow variable --- config/config.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index 979da3ef1e..8d9baa4b68 100644 --- a/config/config.go +++ b/config/config.go @@ -251,6 +251,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) } var group C.Proxy + var ps []C.Proxy var err error switch groupType { case "url-test": @@ -260,7 +261,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { break } - ps, err := getProxies(proxies, urlTestOption.Proxies) + ps, err = getProxies(proxies, urlTestOption.Proxies) if err != nil { return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } @@ -272,7 +273,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { break } - ps, err := getProxies(proxies, selectorOption.Proxies) + ps, err = getProxies(proxies, selectorOption.Proxies) if err != nil { return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } @@ -284,7 +285,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { break } - ps, err := getProxies(proxies, fallbackOption.Proxies) + ps, err = getProxies(proxies, fallbackOption.Proxies) if err != nil { return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } From 2383cca2ce30caa47f3fc62580c5b9bdf65dafc8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 11 Feb 2019 15:25:10 +0800 Subject: [PATCH 131/535] Feature: add v2ray-plugin --- adapters/outbound/shadowsocks.go | 112 ++++++++++++++---- common/structure/structure.go | 8 ++ component/v2ray-plugin/mux.go | 173 ++++++++++++++++++++++++++++ component/v2ray-plugin/websocket.go | 37 ++++++ component/vmess/vmess.go | 18 +-- component/vmess/websocket.go | 28 ++--- 6 files changed, 332 insertions(+), 44 deletions(-) create mode 100644 component/v2ray-plugin/mux.go create mode 100644 component/v2ray-plugin/websocket.go diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index feb8f098a3..abddbb375b 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -2,12 +2,15 @@ package adapters import ( "bytes" + "crypto/tls" "encoding/json" "fmt" "net" "strconv" + "github.com/Dreamacro/clash/common/structure" obfs "github.com/Dreamacro/clash/component/simple-obfs" + v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/go-shadowsocks2/core" @@ -16,34 +19,60 @@ import ( type ShadowSocks struct { *Base - server string - obfs string - obfsHost string - cipher core.Cipher + server string + cipher core.Cipher + + // obfs + obfsMode string + obfsOption *simpleObfsOption + wsOption *v2rayObfs.WebsocketOption } type ShadowSocksOption struct { - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - Password string `proxy:"password"` - Cipher string `proxy:"cipher"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Password string `proxy:"password"` + Cipher string `proxy:"cipher"` + Plugin string `proxy:"plugin,omitempty"` + PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"` + + // deprecated when bump to 1.0 Obfs string `proxy:"obfs,omitempty"` ObfsHost string `proxy:"obfs-host,omitempty"` } +type simpleObfsOption struct { + Mode string `obfs:"mode"` + Host string `obfs:"host,omitempty"` +} + +type v2rayObfsOption struct { + Mode string `obfs:"mode"` + Host string `obfs:"host,omitempty"` + Path string `obfs:"path,omitempty"` + TLS bool `obfs:"tls,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` +} + func (ss *ShadowSocks) Generator(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) if err != nil { - return nil, fmt.Errorf("%s connect error", ss.server) + return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) } tcpKeepAlive(c) - switch ss.obfs { + switch ss.obfsMode { case "tls": - c = obfs.NewTLSObfs(c, ss.obfsHost) + c = obfs.NewTLSObfs(c, ss.obfsOption.Host) case "http": _, port, _ := net.SplitHostPort(ss.server) - c = obfs.NewHTTPObfs(c, ss.obfsHost, port) + c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) + case "websocket": + var err error + c, err = v2rayObfs.NewWebsocketObfs(c, ss.wsOption) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) + } } c = ss.cipher.StreamConn(c) _, err = c.Write(serializesSocksAddr(metadata)) @@ -65,10 +94,49 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } - obfs := option.Obfs - obfsHost := "bing.com" - if option.ObfsHost != "" { - obfsHost = option.ObfsHost + var wsOption *v2rayObfs.WebsocketOption + var obfsOption *simpleObfsOption + obfsMode := "" + + // forward compatibility before 1.0 + if option.Obfs != "" { + obfsMode = option.Obfs + obfsOption = &simpleObfsOption{ + Host: "bing.com", + } + if option.ObfsHost != "" { + obfsOption.Host = option.ObfsHost + } + } + + decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) + if option.Plugin == "obfs" { + opts := simpleObfsOption{Host: "bing.com"} + if err := decoder.Decode(option.PluginOpts, &opts); err != nil { + return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error()) + } + obfsMode = opts.Mode + obfsOption = &opts + } else if option.Plugin == "v2ray-plugin" { + opts := v2rayObfsOption{Host: "bing.com"} + if err := decoder.Decode(option.PluginOpts, &opts); err != nil { + return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error()) + } + obfsMode = opts.Mode + var tlsConfig *tls.Config + if opts.TLS { + tlsConfig = &tls.Config{ + ServerName: opts.Host, + InsecureSkipVerify: opts.SkipCertVerify, + ClientSessionCache: getClientSessionCache(), + } + } + + wsOption = &v2rayObfs.WebsocketOption{ + Host: opts.Host, + Path: opts.Path, + TLSConfig: tlsConfig, + } } return &ShadowSocks{ @@ -76,10 +144,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { name: option.Name, tp: C.Shadowsocks, }, - server: server, - cipher: ciph, - obfs: obfs, - obfsHost: obfsHost, + server: server, + cipher: ciph, + + obfsMode: obfsMode, + wsOption: wsOption, + obfsOption: obfsOption, }, nil } diff --git a/common/structure/structure.go b/common/structure/structure.go index 600f264dc4..9d56b703eb 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -74,6 +74,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error return d.decodeSlice(name, data, val) case reflect.Map: return d.decodeMap(name, data, val) + case reflect.Interface: + return d.setInterface(name, data, val) default: return fmt.Errorf("type %s not support", val.Kind().String()) } @@ -229,3 +231,9 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle return nil } + +func (d *Decoder) setInterface(name string, data interface{}, val reflect.Value) (err error) { + dataVal := reflect.ValueOf(data) + val.Set(dataVal) + return nil +} diff --git a/component/v2ray-plugin/mux.go b/component/v2ray-plugin/mux.go new file mode 100644 index 0000000000..7191331b17 --- /dev/null +++ b/component/v2ray-plugin/mux.go @@ -0,0 +1,173 @@ +package obfs + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net" +) + +type SessionStatus = byte + +const ( + SessionStatusNew SessionStatus = 0x01 + SessionStatusKeep SessionStatus = 0x02 + SessionStatusEnd SessionStatus = 0x03 + SessionStatusKeepAlive SessionStatus = 0x04 +) + +const ( + OptionNone = byte(0x00) + OptionData = byte(0x01) + OptionError = byte(0x02) +) + +type MuxOption struct { + ID [2]byte + Port uint16 + Host string + Type string +} + +// Mux is an mux-compatible client for v2ray-plugin, not a complete implementation +type Mux struct { + net.Conn + buf bytes.Buffer + id [2]byte + length [2]byte + status [2]byte + otb []byte + remain int +} + +func (m *Mux) Read(b []byte) (int, error) { + if m.remain != 0 { + length := m.remain + if len(b) < m.remain { + length = len(b) + } + + n, err := m.Conn.Read(b[:length]) + if err != nil { + return 0, err + } + m.remain = m.remain - n + return n, nil + } + + for { + _, err := io.ReadFull(m.Conn, m.length[:]) + if err != nil { + return 0, err + } + length := binary.BigEndian.Uint16(m.length[:]) + if length > 512 { + return 0, errors.New("invalid metalen") + } + + _, err = io.ReadFull(m.Conn, m.id[:]) + if err != nil { + return 0, err + } + + _, err = m.Conn.Read(m.status[:]) + if err != nil { + return 0, err + } + + opcode := m.status[0] + if opcode == SessionStatusKeepAlive { + continue + } + + opts := m.status[1] + + if opts != OptionData { + continue + } + + _, err = io.ReadFull(m.Conn, m.length[:]) + if err != nil { + return 0, err + } + dataLen := int(binary.BigEndian.Uint16(m.length[:])) + m.remain = dataLen + if dataLen > len(b) { + dataLen = len(b) + } + + n, err := m.Conn.Read(b[:dataLen]) + m.remain -= n + return n, err + } +} + +func (m *Mux) Write(b []byte) (int, error) { + if m.otb != nil { + // create a sub connection + if _, err := m.Conn.Write(m.otb); err != nil { + return 0, err + } + m.otb = nil + } + m.buf.Reset() + binary.Write(&m.buf, binary.BigEndian, uint16(4)) + m.buf.Write(m.id[:]) + m.buf.WriteByte(SessionStatusKeep) + m.buf.WriteByte(OptionData) + binary.Write(&m.buf, binary.BigEndian, uint16(len(b))) + m.buf.Write(b) + + return m.Conn.Write(m.buf.Bytes()) +} + +func (m *Mux) Close() error { + _, err := m.Conn.Write([]byte{0x0, 0x4, m.id[0], m.id[1], SessionStatusEnd, OptionNone}) + if err != nil { + return err + } + return m.Conn.Close() +} + +func NewMux(conn net.Conn, option MuxOption) *Mux { + buf := &bytes.Buffer{} + + // fill empty length + buf.Write([]byte{0x0, 0x0}) + buf.Write(option.ID[:]) + buf.WriteByte(SessionStatusNew) + buf.WriteByte(OptionNone) + + // tcp + netType := byte(0x1) + if option.Type == "udp" { + netType = byte(0x2) + } + buf.WriteByte(netType) + + // port + binary.Write(buf, binary.BigEndian, option.Port) + + // address + ip := net.ParseIP(option.Host) + if ip == nil { + buf.WriteByte(0x2) + buf.WriteString(option.Host) + } else if ipv4 := ip.To4(); ipv4 != nil { + buf.WriteByte(0x1) + buf.Write(ipv4) + } else { + buf.WriteByte(0x3) + buf.Write(ip.To16()) + } + + metadata := buf.Bytes() + binary.BigEndian.PutUint16(metadata[:2], uint16(len(metadata)-2)) + + return &Mux{ + Conn: conn, + id: option.ID, + otb: metadata, + } +} diff --git a/component/v2ray-plugin/websocket.go b/component/v2ray-plugin/websocket.go new file mode 100644 index 0000000000..03d6f854b5 --- /dev/null +++ b/component/v2ray-plugin/websocket.go @@ -0,0 +1,37 @@ +package obfs + +import ( + "crypto/tls" + "net" + + "github.com/Dreamacro/clash/component/vmess" +) + +// WebsocketOption is options of websocket obfs +type WebsocketOption struct { + Host string + Path string + TLSConfig *tls.Config +} + +// NewWebsocketObfs return a HTTPObfs +func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) { + config := &vmess.WebsocketConfig{ + Host: option.Host, + Path: option.Path, + TLS: option.TLSConfig != nil, + TLSConfig: option.TLSConfig, + } + + var err error + conn, err = vmess.NewWebsocketConn(conn, config) + if err != nil { + return nil, err + } + conn = NewMux(conn, MuxOption{ + ID: [2]byte{0, 0}, + Host: "127.0.0.1", + Port: 0, + }) + return conn, nil +} diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 604b45f688..e0a7a24fc9 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -69,7 +69,7 @@ type Client struct { security Security tls bool host string - wsConfig *websocketConfig + wsConfig *WebsocketConfig tlsConfig *tls.Config } @@ -93,7 +93,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { var err error r := rand.Intn(len(c.user)) if c.wsConfig != nil { - conn, err = newWebsocketConn(conn, c.wsConfig) + conn, err = NewWebsocketConn(conn, c.wsConfig) if err != nil { return nil, err } @@ -145,14 +145,14 @@ func NewClient(config Config) (*Client, error) { } } - var wsConfig *websocketConfig + var wsConfig *WebsocketConfig if config.NetWork == "ws" { - wsConfig = &websocketConfig{ - host: host, - path: config.WebSocketPath, - headers: config.WebSocketHeaders, - tls: config.TLS, - tlsConfig: tlsConfig, + wsConfig = &WebsocketConfig{ + Host: host, + Path: config.WebSocketPath, + Headers: config.WebSocketHeaders, + TLS: config.TLS, + TLSConfig: tlsConfig, } } diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index 02e95d55ef..bd06c39d9c 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -19,12 +19,12 @@ type websocketConn struct { remoteAddr net.Addr } -type websocketConfig struct { - host string - path string - headers map[string]string - tls bool - tlsConfig *tls.Config +type WebsocketConfig struct { + Host string + Path string + Headers map[string]string + TLS bool + TLSConfig *tls.Config } // Read implements net.Conn.Read() @@ -102,7 +102,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { return wsc.conn.SetWriteDeadline(t) } -func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) { +func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { dialer := &websocket.Dialer{ NetDial: func(network, addr string) (net.Conn, error) { return conn, nil @@ -113,25 +113,25 @@ func newWebsocketConn(conn net.Conn, c *websocketConfig) (net.Conn, error) { } scheme := "ws" - if c.tls { + if c.TLS { scheme = "wss" - dialer.TLSClientConfig = c.tlsConfig + dialer.TLSClientConfig = c.TLSConfig } - host, port, _ := net.SplitHostPort(c.host) + host, port, _ := net.SplitHostPort(c.Host) if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { - host = c.host + host = c.Host } uri := url.URL{ Scheme: scheme, Host: host, - Path: c.path, + Path: c.Path, } headers := http.Header{} - if c.headers != nil { - for k, v := range c.headers { + if c.Headers != nil { + for k, v := range c.Headers { headers.Set(k, v) } } From 1339487ce48ccc30e6fe43409844f754ddad6af0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 11 Feb 2019 15:44:42 +0800 Subject: [PATCH 132/535] Fix: tun2socks not lookup IP --- constant/metadata.go | 4 ---- dns/client.go | 4 ++++ tunnel/tunnel.go | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/constant/metadata.go b/constant/metadata.go index 4a361dfbff..4bb02885d1 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -50,7 +50,3 @@ func (m *Metadata) String() string { func (m *Metadata) Valid() bool { return m.Host != "" || m.IP != nil } - -func (m *Metadata) NeedLoopUpHost() bool { - return m.Source == REDIR -} diff --git a/dns/client.go b/dns/client.go index 29b4207cf6..cfae7b9f84 100644 --- a/dns/client.go +++ b/dns/client.go @@ -219,6 +219,10 @@ func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result { return ch } +func (r *Resolver) IsMapping() bool { + return r.mapping +} + type NameServer struct { Net string Addr string diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 969208183c..2663ca3171 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -107,11 +107,15 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) { return t.resolver.ResolveIP(host) } +func (t *Tunnel) needLookupIP() bool { + return t.hasResolver() && t.resolver.IsMapping() +} + func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer localConn.Close() metadata := localConn.Metadata() - if metadata.NeedLoopUpHost() && t.hasResolver() { + if t.needLookupIP() { host, exist := t.resolver.IPToHost(*metadata.IP) if exist { metadata.Host = host From 8da19e81a4f6768c221a125d9a901c0994f36b20 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 11 Feb 2019 15:55:17 +0800 Subject: [PATCH 133/535] Update: README.md --- README.md | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ec256ba1af..ca460d56d8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@

Clash -
- Clash -
+
Clash

A rule-based tunnel in Go.

@@ -13,7 +11,7 @@ alt="Travis-CI">
- + @@ -58,8 +56,6 @@ If you have Docker installed, you can run clash directly using `docker-compose`. ## Config -**NOTE: after v0.8.0, clash using yaml as configuration file** - The default configuration directory is `$HOME/.config/clash` The name of the configuration file is `config.yml` @@ -103,7 +99,7 @@ external-controller: 127.0.0.1:9090 # Secret for RESTful API (Optional) # secret: "" -dns: +# dns: # enable: true # set true to enable dns (default is false) # ipv6: false # default is false # listen: 0.0.0.0:53 @@ -121,7 +117,32 @@ Proxy: # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 # In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305 - { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" } -- { name: "ss2", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", obfs: tls, obfs-host: bing.com } + +# old obfs configuration remove after prerelease +- name: "ss2" + type: ss + server: server + port: 443 + cipher: AEAD_CHACHA20_POLY1305 + password: "password" + plugin: obfs + plugin-opts: + mode: tls # or http + # host: bing.com + +- name: "ss3" + type: ss + server: server + port: 443 + cipher: AEAD_CHACHA20_POLY1305 + password: "password" + plugin: v2ray-plugin + plugin-opts: + mode: websocket # no QUIC now + # tls: true # wss + # skip-cert-verify: true + # host: bing.com + # path: "/" # vmess # cipher support auto/aes-128-gcm/chacha20-poly1305/none @@ -155,10 +176,10 @@ Proxy: Proxy Group: # url-test select which proxy will be used by benchmarking speed to a URL. -- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } +- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. -- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: http://www.gstatic.com/generate_204, interval: 300 } +- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } # select is used for selecting proxy or proxy group # you can use RESTful API to switch proxy, is recommended for use in GUI. From 26a87f9d34d05bc16d7f1c2026d8957768d67c78 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 11 Feb 2019 17:20:42 +0800 Subject: [PATCH 134/535] Fix: `redir-host` mode crash --- tunnel/tunnel.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 2663ca3171..77e810581f 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -107,15 +107,15 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) { return t.resolver.ResolveIP(host) } -func (t *Tunnel) needLookupIP() bool { - return t.hasResolver() && t.resolver.IsMapping() +func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { + return t.hasResolver() && t.resolver.IsMapping() && metadata.Host == "" && metadata.IP != nil } func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer localConn.Close() metadata := localConn.Metadata() - if t.needLookupIP() { + if t.needLookupIP(metadata) { host, exist := t.resolver.IPToHost(*metadata.IP) if exist { metadata.Host = host From 5920b0575219f06bf250cde7374e1bde6344e5f0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 12 Feb 2019 12:29:33 +0800 Subject: [PATCH 135/535] Fix: crash when ip is nil --- rules/ipcidr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/ipcidr.go b/rules/ipcidr.go index 597ac3e19a..c2a02ef86b 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -24,7 +24,7 @@ func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { if i.isSourceIP { ip = metadata.SourceIP } - return i.ipnet.Contains(*ip) + return ip != nil && i.ipnet.Contains(*ip) } func (i *IPCIDR) Adapter() string { From 7a0717830c4912b85834a27b9e5827a7bd596030 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 13 Feb 2019 23:45:43 +0800 Subject: [PATCH 136/535] Fix: api invalid returning --- hub/route/proxies.go | 4 ++-- hub/route/rules.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 404d1a3293..3da30a2444 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -59,7 +59,7 @@ func findProxyByName(next http.Handler) http.Handler { func getProxies(w http.ResponseWriter, r *http.Request) { proxies := T.Instance().Proxies() - render.JSON(w, r, map[string]map[string]C.Proxy{ + render.JSON(w, r, render.M{ "proxies": proxies, }) } @@ -129,7 +129,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { render.Status(r, http.StatusServiceUnavailable) render.JSON(w, r, newError("An error occurred in the delay test")) } else { - render.JSON(w, r, map[string]int16{ + render.JSON(w, r, render.M{ "delay": t, }) } diff --git a/hub/route/rules.go b/hub/route/rules.go index b8cd3aeb16..6a223ab5a4 100644 --- a/hub/route/rules.go +++ b/hub/route/rules.go @@ -24,7 +24,7 @@ type Rule struct { func getRules(w http.ResponseWriter, r *http.Request) { rawRules := T.Instance().Rules() - var rules []Rule + rules := []Rule{} for _, rule := range rawRules { rules = append(rules, Rule{ Type: rule.RuleType().String(), @@ -33,7 +33,7 @@ func getRules(w http.ResponseWriter, r *http.Request) { }) } - render.JSON(w, r, map[string][]Rule{ + render.JSON(w, r, render.M{ "rules": rules, }) } From 8636a4f58994dadb2a9a1a6811db02dc5346631b Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Thu, 14 Feb 2019 11:37:47 +0800 Subject: [PATCH 137/535] Fix: return 502 in http outbound (#116) --- adapters/outbound/http.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 4ac7479a87..485688361e 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -58,9 +58,9 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { var buf bytes.Buffer var err error - addr := net.JoinHostPort(metadata.Host, metadata.Port) + addr := net.JoinHostPort(metadata.String(), metadata.Port) buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") - buf.WriteString("Host: " + metadata.Host + "\r\n") + buf.WriteString("Host: " + metadata.String() + "\r\n") buf.WriteString("Proxy-Connection: Keep-Alive\r\n") if h.user != "" && h.pass != "" { From c295c5e412b46bb0d735390e76ab84e560c9e245 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 15 Feb 2019 14:25:20 +0800 Subject: [PATCH 138/535] Feature: add load-balance group --- adapters/outbound/loadbalance.go | 94 +++++++++++++++++++ common/murmur3/murmur.go | 50 +++++++++++ common/murmur3/murmur32.go | 149 +++++++++++++++++++++++++++++++ config/config.go | 21 ++++- constant/adapters.go | 3 + go.mod | 2 +- 6 files changed, 314 insertions(+), 5 deletions(-) create mode 100644 adapters/outbound/loadbalance.go create mode 100644 common/murmur3/murmur.go create mode 100644 common/murmur3/murmur32.go diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go new file mode 100644 index 0000000000..3edeae5de0 --- /dev/null +++ b/adapters/outbound/loadbalance.go @@ -0,0 +1,94 @@ +package adapters + +import ( + "encoding/json" + "errors" + "net" + + "github.com/Dreamacro/clash/common/murmur3" + C "github.com/Dreamacro/clash/constant" + + "golang.org/x/net/publicsuffix" +) + +type LoadBalance struct { + *Base + proxies []C.Proxy + maxRetry int +} + +func getKey(metadata *C.Metadata) string { + if metadata.Host != "" { + // ip host + if ip := net.ParseIP(metadata.Host); ip != nil { + return metadata.Host + } + + if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil { + return etld + } + } + + if metadata.IP == nil { + return "" + } + + return metadata.IP.String() +} + +func jumpHash(key uint64, buckets int32) int32 { + var b, j int64 + + for j < int64(buckets) { + b = j + key = key*2862933555777941757 + 1 + j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1))) + } + + return int32(b) +} + +func (lb *LoadBalance) Generator(metadata *C.Metadata) (net.Conn, error) { + key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) + buckets := int32(len(lb.proxies)) + for i := 0; i < lb.maxRetry; i++ { + idx := jumpHash(key, buckets) + if proxy, err := lb.proxies[idx].Generator(metadata); err == nil { + return proxy, nil + } + key++ + } + + return lb.proxies[0].Generator(metadata) +} + +func (lb *LoadBalance) MarshalJSON() ([]byte, error) { + var all []string + for _, proxy := range lb.proxies { + all = append(all, proxy.Name()) + } + return json.Marshal(map[string]interface{}{ + "type": lb.Type().String(), + "all": all, + }) +} + +type LoadBalanceOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` +} + +func NewLoadBalance(name string, proxies []C.Proxy) (*LoadBalance, error) { + if len(proxies) == 0 { + return nil, errors.New("Provide at least one proxy") + } + + return &LoadBalance{ + Base: &Base{ + name: name, + tp: C.LoadBalance, + }, + proxies: proxies, + maxRetry: 3, + }, nil +} diff --git a/common/murmur3/murmur.go b/common/murmur3/murmur.go new file mode 100644 index 0000000000..f4470290b3 --- /dev/null +++ b/common/murmur3/murmur.go @@ -0,0 +1,50 @@ +package murmur3 + +type bmixer interface { + bmix(p []byte) (tail []byte) + Size() (n int) + reset() +} + +type digest struct { + clen int // Digested input cumulative length. + tail []byte // 0 to Size()-1 bytes view of `buf'. + buf [16]byte // Expected (but not required) to be Size() large. + seed uint32 // Seed for initializing the hash. + bmixer +} + +func (d *digest) BlockSize() int { return 1 } + +func (d *digest) Write(p []byte) (n int, err error) { + n = len(p) + d.clen += n + + if len(d.tail) > 0 { + // Stick back pending bytes. + nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1]. + if nfree < len(p) { + // One full block can be formed. + block := append(d.tail, p[:nfree]...) + p = p[nfree:] + _ = d.bmix(block) // No tail. + } else { + // Tail's buf is large enough to prevent reallocs. + p = append(d.tail, p...) + } + } + + d.tail = d.bmix(p) + + // Keep own copy of the 0 to Size()-1 pending bytes. + nn := copy(d.buf[:], d.tail) + d.tail = d.buf[:nn] + + return n, nil +} + +func (d *digest) Reset() { + d.clen = 0 + d.tail = nil + d.bmixer.reset() +} diff --git a/common/murmur3/murmur32.go b/common/murmur3/murmur32.go new file mode 100644 index 0000000000..a4e4801e87 --- /dev/null +++ b/common/murmur3/murmur32.go @@ -0,0 +1,149 @@ +package murmur3 + +// https://github.com/spaolacci/murmur3/blob/master/murmur32.go + +import ( + "hash" + "unsafe" +) + +// Make sure interfaces are correctly implemented. +var ( + _ hash.Hash32 = new(digest32) + _ bmixer = new(digest32) +) + +const ( + c1_32 uint32 = 0xcc9e2d51 + c2_32 uint32 = 0x1b873593 +) + +// digest32 represents a partial evaluation of a 32 bites hash. +type digest32 struct { + digest + h1 uint32 // Unfinalized running hash. +} + +// New32 returns new 32-bit hasher +func New32() hash.Hash32 { return New32WithSeed(0) } + +// New32WithSeed returns new 32-bit hasher set with explicit seed value +func New32WithSeed(seed uint32) hash.Hash32 { + d := new(digest32) + d.seed = seed + d.bmixer = d + d.Reset() + return d +} + +func (d *digest32) Size() int { return 4 } + +func (d *digest32) reset() { d.h1 = d.seed } + +func (d *digest32) Sum(b []byte) []byte { + h := d.Sum32() + return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h)) +} + +// Digest as many blocks as possible. +func (d *digest32) bmix(p []byte) (tail []byte) { + h1 := d.h1 + + nblocks := len(p) / 4 + for i := 0; i < nblocks; i++ { + k1 := *(*uint32)(unsafe.Pointer(&p[i*4])) + + k1 *= c1_32 + k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 *= c2_32 + + h1 ^= k1 + h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) + h1 = h1*4 + h1 + 0xe6546b64 + } + d.h1 = h1 + return p[nblocks*d.Size():] +} + +func (d *digest32) Sum32() (h1 uint32) { + + h1 = d.h1 + + var k1 uint32 + switch len(d.tail) & 3 { + case 3: + k1 ^= uint32(d.tail[2]) << 16 + fallthrough + case 2: + k1 ^= uint32(d.tail[1]) << 8 + fallthrough + case 1: + k1 ^= uint32(d.tail[0]) + k1 *= c1_32 + k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 *= c2_32 + h1 ^= k1 + } + + h1 ^= uint32(d.clen) + + h1 ^= h1 >> 16 + h1 *= 0x85ebca6b + h1 ^= h1 >> 13 + h1 *= 0xc2b2ae35 + h1 ^= h1 >> 16 + + return h1 +} + +func Sum32(data []byte) uint32 { return Sum32WithSeed(data, 0) } + +func Sum32WithSeed(data []byte, seed uint32) uint32 { + h1 := seed + + nblocks := len(data) / 4 + var p uintptr + if len(data) > 0 { + p = uintptr(unsafe.Pointer(&data[0])) + } + p1 := p + uintptr(4*nblocks) + for ; p < p1; p += 4 { + k1 := *(*uint32)(unsafe.Pointer(p)) + + k1 *= c1_32 + k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 *= c2_32 + + h1 ^= k1 + h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) + h1 = h1*4 + h1 + 0xe6546b64 + } + + tail := data[nblocks*4:] + + var k1 uint32 + switch len(tail) & 3 { + case 3: + k1 ^= uint32(tail[2]) << 16 + fallthrough + case 2: + k1 ^= uint32(tail[1]) << 8 + fallthrough + case 1: + k1 ^= uint32(tail[0]) + k1 *= c1_32 + k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 *= c2_32 + h1 ^= k1 + } + + h1 ^= uint32(len(data)) + + h1 ^= h1 >> 16 + h1 *= 0x85ebca6b + h1 ^= h1 >> 13 + h1 *= 0xc2b2ae35 + h1 ^= h1 >> 16 + + return h1 +} diff --git a/config/config.go b/config/config.go index 8d9baa4b68..7f725406ff 100644 --- a/config/config.go +++ b/config/config.go @@ -195,7 +195,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { } var proxy C.Proxy - var err error + err := fmt.Errorf("can't parse") switch proxyType { case "ss": ssOption := &adapters.ShadowSocksOption{} @@ -251,8 +251,9 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) } var group C.Proxy - var ps []C.Proxy - var err error + ps := []C.Proxy{} + + err := fmt.Errorf("can't parse") switch groupType { case "url-test": urlTestOption := &adapters.URLTestOption{} @@ -290,6 +291,18 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } group, err = adapters.NewFallback(*fallbackOption, ps) + case "load-balance": + loadBalanceOption := &adapters.LoadBalanceOption{} + err = decoder.Decode(mapping, loadBalanceOption) + if err != nil { + break + } + + ps, err = getProxies(proxies, loadBalanceOption.Proxies) + if err != nil { + return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) + } + group, err = adapters.NewLoadBalance(loadBalanceOption.Name, ps) } if err != nil { return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) @@ -297,7 +310,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { proxies[groupName] = group } - var ps []C.Proxy + ps := []C.Proxy{} for _, v := range proxies { ps = append(ps, v) } diff --git a/constant/adapters.go b/constant/adapters.go index 293b50edf5..082743cf92 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -15,6 +15,7 @@ const ( Http URLTest Vmess + LoadBalance ) type ServerAdapter interface { @@ -52,6 +53,8 @@ func (at AdapterType) String() string { return "URLTest" case Vmess: return "Vmess" + case LoadBalance: + return "LoadBalance" default: return "Unknow" } diff --git a/go.mod b/go.mod index cb4146dee8..31c879ec2c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/sirupsen/logrus v1.3.0 golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 - golang.org/x/net v0.0.0-20181108082009-03003ca0c849 // indirect + golang.org/x/net v0.0.0-20181108082009-03003ca0c849 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.2 From 287ad5bc531f6fe222a1469278e3d7fcb0c27b56 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 15 Feb 2019 21:55:15 +0800 Subject: [PATCH 139/535] Fix: vmess handshake block (#117) --- component/vmess/conn.go | 18 ++++++------------ component/vmess/vmess.go | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/component/vmess/conn.go b/component/vmess/conn.go index 8efe7ffcc3..15b1e05cb7 100644 --- a/component/vmess/conn.go +++ b/component/vmess/conn.go @@ -35,20 +35,10 @@ type Conn struct { respV byte security byte - sent bool received bool } func (vc *Conn) Write(b []byte) (int, error) { - if vc.sent { - return vc.writer.Write(b) - } - - if err := vc.sendRequest(); err != nil { - return 0, err - } - - vc.sent = true return vc.writer.Write(b) } @@ -153,7 +143,7 @@ func hashTimestamp(t time.Time) []byte { } // newConn return a Conn instance -func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn { +func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) (*Conn, error) { randBytes := make([]byte, 33) rand.Read(randBytes) reqBodyIV := make([]byte, 16) @@ -196,7 +186,7 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn { reader = newAEADReader(conn, aead, respBodyIV[:]) } - return &Conn{ + c := &Conn{ Conn: conn, id: id, dst: dst, @@ -209,4 +199,8 @@ func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security) *Conn { writer: writer, security: security, } + if err := c.sendRequest(); err != nil { + return nil, err + } + return c, nil } diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index e0a7a24fc9..71b30c1563 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -100,7 +100,7 @@ func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { } else if c.tls { conn = tls.Client(conn, c.tlsConfig) } - return newConn(conn, c.user[r], dst, c.security), nil + return newConn(conn, c.user[r], dst, c.security) } // NewClient return Client instance From e7997a035bfcbc943c0b71de2b0367a8183e8676 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 15 Feb 2019 22:01:11 +0800 Subject: [PATCH 140/535] Chore: update README.md --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ca460d56d8..889e0e6903 100644 --- a/README.md +++ b/README.md @@ -181,21 +181,24 @@ Proxy Group: # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. - { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } +# load-balance: The request of the same eTLD will be dial on the same proxy. +- { name: "load-balance", type: load-balance, proxies: ["ss1", "ss2", "vmess1"] } + # select is used for selecting proxy or proxy group # you can use RESTful API to switch proxy, is recommended for use in GUI. - { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] } Rule: -- DOMAIN-SUFFIX,google.com,Proxy -- DOMAIN-KEYWORD,google,Proxy -- DOMAIN,google.com,Proxy +- DOMAIN-SUFFIX,google.com,auto +- DOMAIN-KEYWORD,google,auto +- DOMAIN,google.com,auto - DOMAIN-SUFFIX,ad.com,REJECT - IP-CIDR,127.0.0.0/8,DIRECT - SOURCE-IP-CIDR,192.168.1.201/32,DIRECT - GEOIP,CN,DIRECT # FINAL would remove after prerelease # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now -- MATCH,Proxy +- MATCH,auto ``` ## Thanks From 575720e0cc7c674aef356346837dd832b2659f78 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 15 Feb 2019 23:43:28 +0800 Subject: [PATCH 141/535] Fix: windows 386 build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2ea64f96c6..a29a8e3e4d 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ freebsd-amd64: GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ windows-386: - GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe windows-amd64: GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe From 5c8bb24121f90377c1af91021c7d54bcd3719a0a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 18 Feb 2019 20:14:18 +0800 Subject: [PATCH 142/535] Fix: crash when directly request proxy server --- proxy/http/server.go | 4 ++-- tunnel/tunnel.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/proxy/http/server.go b/proxy/http/server.go index e66f549385..7c20e20fca 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -5,7 +5,7 @@ import ( "net" "net/http" - "github.com/Dreamacro/clash/adapters/inbound" + adapters "github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" ) @@ -56,7 +56,7 @@ func (l *HttpListener) Address() string { func handleConn(conn net.Conn) { br := bufio.NewReader(conn) request, err := http.ReadRequest(br) - if err != nil { + if err != nil || !request.URL.IsAbs() { conn.Close() return } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 77e810581f..d98ec40a40 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -115,6 +115,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { defer localConn.Close() metadata := localConn.Metadata() + if !metadata.Valid() { + log.Warnln("[Metadata] not valid: %#v", metadata) + return + } + if t.needLookupIP(metadata) { host, exist := t.resolver.IPToHost(*metadata.IP) if exist { @@ -138,11 +143,6 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } } - if !metadata.Valid() { - log.Warnln("[Metadata] not valid: %#v", metadata) - return - } - remoConn, err := proxy.Generator(metadata) if err != nil { log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) From c0bd82d62b21dba2a1bfeed2e10b498e34ce7f3e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 18 Feb 2019 21:53:57 +0800 Subject: [PATCH 143/535] Chore: rename `final` --- config/config.go | 2 +- constant/rule.go | 6 +++--- rules/final.go | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/config.go b/config/config.go index 7f725406ff..c9b348c1e9 100644 --- a/config/config.go +++ b/config/config.go @@ -358,7 +358,7 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { case "MATCH": fallthrough case "FINAL": - rules = append(rules, R.NewFinal(target)) + rules = append(rules, R.NewMatch(target)) } } diff --git a/constant/rule.go b/constant/rule.go index ba287501d7..e142efc061 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -8,7 +8,7 @@ const ( GEOIP IPCIDR SourceIPCIDR - FINAL + MATCH ) type RuleType int @@ -27,8 +27,8 @@ func (rt RuleType) String() string { return "IPCIDR" case SourceIPCIDR: return "SourceIPCIDR" - case FINAL: - return "FINAL" + case MATCH: + return "MATCH" default: return "Unknow" } diff --git a/rules/final.go b/rules/final.go index 1cc3b88851..88df0680d2 100644 --- a/rules/final.go +++ b/rules/final.go @@ -4,28 +4,28 @@ import ( C "github.com/Dreamacro/clash/constant" ) -type Final struct { +type Match struct { adapter string } -func (f *Final) RuleType() C.RuleType { - return C.FINAL +func (f *Match) RuleType() C.RuleType { + return C.MATCH } -func (f *Final) IsMatch(metadata *C.Metadata) bool { +func (f *Match) IsMatch(metadata *C.Metadata) bool { return true } -func (f *Final) Adapter() string { +func (f *Match) Adapter() string { return f.adapter } -func (f *Final) Payload() string { +func (f *Match) Payload() string { return "" } -func NewFinal(adapter string) *Final { - return &Final{ +func NewMatch(adapter string) *Match { + return &Match{ adapter: adapter, } } From 04927229ffbb1d118b812a96791762e55fbf3434 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 21 Feb 2019 16:16:49 +0800 Subject: [PATCH 144/535] Fix: disconnect normal proxy request --- proxy/http/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/http/server.go b/proxy/http/server.go index 7c20e20fca..1ed913323e 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -56,7 +56,7 @@ func (l *HttpListener) Address() string { func handleConn(conn net.Conn) { br := bufio.NewReader(conn) request, err := http.ReadRequest(br) - if err != nil || !request.URL.IsAbs() { + if err != nil || request.URL.Host == "" { conn.Close() return } From ca5399a16ed7f54ea3abc281e88c026253c89472 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 23 Feb 2019 20:31:59 +0800 Subject: [PATCH 145/535] Fix: dns cache behavior --- dns/client.go | 23 ++++++++--------------- dns/util.go | 28 ++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/dns/client.go b/dns/client.go index cfae7b9f84..234f14f570 100644 --- a/dns/client.go +++ b/dns/client.go @@ -12,7 +12,6 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/picker" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" geoip2 "github.com/oschwald/geoip2-golang" @@ -55,23 +54,17 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { cache, expireTime := r.cache.GetWithExpire(q.String()) if cache != nil { msg = cache.(*D.Msg).Copy() - if len(msg.Answer) > 0 { - ttl := uint32(expireTime.Sub(time.Now()).Seconds()) - for _, answer := range msg.Answer { - answer.Header().Ttl = ttl - } - } + setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) return } defer func() { - if msg != nil { - putMsgToCache(r.cache, q.String(), msg) - if r.mapping { - ips, err := r.msgToIP(msg) - if err != nil { - log.Debugln("[DNS] msg to ip error: %s", err.Error()) - return - } + if msg == nil { + return + } + + putMsgToCache(r.cache, q.String(), msg) + if r.mapping { + if ips, err := r.msgToIP(msg); err == nil { for _, ip := range ips { putMsgToCache(r.cache, ip.String(), msg) } diff --git a/dns/util.go b/dns/util.go index 4102a584e5..2eee70c479 100644 --- a/dns/util.go +++ b/dns/util.go @@ -79,11 +79,31 @@ func (e EnhancedMode) String() string { } func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) { - if len(msg.Answer) == 0 { - log.Debugln("[DNS] answer length is zero: %#v", msg) + var ttl time.Duration + if len(msg.Answer) != 0 { + ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second + } else if len(msg.Ns) != 0 { + ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second + } else if len(msg.Extra) != 0 { + ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second + } else { + log.Debugln("[DNS] response msg error: %#v", msg) return } - ttl := time.Duration(msg.Answer[0].Header().Ttl) * time.Second - c.Put(key, msg, ttl) + c.Put(key, msg.Copy(), ttl) +} + +func setMsgTTL(msg *D.Msg, ttl uint32) { + for _, answer := range msg.Answer { + answer.Header().Ttl = ttl + } + + for _, ns := range msg.Ns { + ns.Header().Ttl = ttl + } + + for _, extra := range msg.Extra { + extra.Header().Ttl = ttl + } } From 815e80f72007d06b888022599b7490a15c659472 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 24 Feb 2019 01:26:51 +0800 Subject: [PATCH 146/535] Fix: dns use Extra records --- dns/client.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/dns/client.go b/dns/client.go index 234f14f570..634722fca4 100644 --- a/dns/client.go +++ b/dns/client.go @@ -64,10 +64,9 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { putMsgToCache(r.cache, q.String(), msg) if r.mapping { - if ips, err := r.msgToIP(msg); err == nil { - for _, ip := range ips { - putMsgToCache(r.cache, ip.String(), msg) - } + ips := r.msgToIP(msg) + for _, ip := range ips { + putMsgToCache(r.cache, ip.String(), msg) } } }() @@ -131,8 +130,7 @@ func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) { return nil, errors.New("GeoIP can't use") } - ips, err := r.msgToIP(res.Msg) - if err == nil { + if ips := r.msgToIP(res.Msg); len(ips) != 0 { if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { // release channel go func() { <-fallbackMsg }() @@ -165,18 +163,17 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { return nil, err } - var ips []net.IP - ips, err = r.msgToIP(msg) - if err != nil { - return nil, err + ips := r.msgToIP(msg) + if len(ips) == 0 { + return nil, errors.New("can't found ip") } ip = ips[0] return } -func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) { - var ips []net.IP +func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { + ips := []net.IP{} for _, answer := range msg.Answer { switch ans := answer.(type) { @@ -187,11 +184,16 @@ func (r *Resolver) msgToIP(msg *D.Msg) ([]net.IP, error) { } } - if len(ips) == 0 { - return nil, errors.New("Can't parse msg") + for _, extra := range msg.Extra { + switch record := extra.(type) { + case *D.AAAA: + ips = append(ips, record.AAAA) + case *D.A: + ips = append(ips, record.A) + } } - return ips, nil + return ips } func (r *Resolver) IPToHost(ip net.IP) (string, bool) { From d75f9ff78354b4bcf2eb8a552b0408a0431bb0eb Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 27 Feb 2019 01:02:43 +0800 Subject: [PATCH 147/535] Migration: go 1.12 --- .travis.yml | 2 +- README.md | 2 +- adapters/outbound/http.go | 2 -- adapters/outbound/socks5.go | 2 -- constant/path.go | 13 +++---------- main.go | 3 +++ 6 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6fa211d4b4..32d4e3db03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go sudo: false go: - - "1.11" + - '1.12' install: - "go mod download" env: diff --git a/README.md b/README.md index 889e0e6903..3ec9cef8ff 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ go get -u -v github.com/Dreamacro/clash Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases) -Requires Go >= 1.11. +Requires Go >= 1.12. ## Daemon diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 485688361e..cf1fb7484f 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -101,8 +101,6 @@ func NewHttp(option HttpOption) *Http { tlsConfig = &tls.Config{ InsecureSkipVerify: option.SkipCertVerify, ClientSessionCache: getClientSessionCache(), - MinVersion: tls.VersionTLS11, - MaxVersion: tls.VersionTLS12, ServerName: option.Server, } } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 2b4613d63e..97e883e4f2 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -118,8 +118,6 @@ func NewSocks5(option Socks5Option) *Socks5 { tlsConfig = &tls.Config{ InsecureSkipVerify: option.SkipCertVerify, ClientSessionCache: getClientSessionCache(), - MinVersion: tls.VersionTLS11, - MaxVersion: tls.VersionTLS12, ServerName: option.Server, } } diff --git a/constant/path.go b/constant/path.go index ad7b847ded..5d08e36cd4 100644 --- a/constant/path.go +++ b/constant/path.go @@ -2,7 +2,6 @@ package constant import ( "os" - "os/user" P "path" ) @@ -16,17 +15,11 @@ type path struct { } func init() { - currentUser, err := user.Current() - var homedir string + homedir, err := os.UserHomeDir() if err != nil { - dir := os.Getenv("HOME") - if dir == "" { - dir, _ = os.Getwd() - } - homedir = dir - } else { - homedir = currentUser.HomeDir + homedir, _ = os.Getwd() } + homedir = P.Join(homedir, ".config", Name) Path = &path{homedir: homedir} } diff --git a/main.go b/main.go index be91250aca..7733010194 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,9 @@ func init() { } func main() { + // enable tls 1.3 and remove when go 1.13 + os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1") + if homedir != "" { if !filepath.IsAbs(homedir) { currentDir, _ := os.Getwd() From 0011c7acfec7f7a8db9e90c150674f5d0bddbcb3 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Fri, 1 Mar 2019 00:52:30 +0800 Subject: [PATCH 148/535] Improve: support tcp dns server & return an error when parsing nameserver (#127) --- README.md | 2 +- config/config.go | 39 +++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3ec9cef8ff..f670adb220 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ external-controller: 127.0.0.1:9090 # - 114.114.114.114 # - tls://dns.rubyfish.cn:853 # dns over tls # fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN - # - 8.8.8.8 + # - tcp://1.1.1.1 Proxy: diff --git a/config/config.go b/config/config.go index c9b348c1e9..40fa889f08 100644 --- a/config/config.go +++ b/config/config.go @@ -387,33 +387,40 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { for idx, server := range servers { // parse without scheme .e.g 8.8.8.8:53 - if host, err := hostWithDefaultPort(server, "53"); err == nil { - nameservers = append( - nameservers, - dns.NameServer{Addr: host}, - ) - continue + if !strings.Contains(server, "://") { + server = "udp://" + server } - u, err := url.Parse(server) if err != nil { return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } - if u.Scheme != "tls" { + var host, dnsNetType string + switch u.Scheme { + case "udp": + host, err = hostWithDefaultPort(u.Host, "53") + dnsNetType = "" // UDP + case "tcp": + host, err = hostWithDefaultPort(u.Host, "53") + dnsNetType = "tcp" // TCP + case "tls": + host, err = hostWithDefaultPort(u.Host, "853") + dnsNetType = "tcp-tls" // DNS over TLS + default: return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) } + if err != nil { + return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) + } - host, err := hostWithDefaultPort(u.Host, "853") nameservers = append( nameservers, dns.NameServer{ - Net: "tcp-tls", + Net: dnsNetType, Addr: host, }, ) } - return nameservers, nil } @@ -427,13 +434,13 @@ func parseDNS(cfg rawDNS) (*DNS, error) { Listen: cfg.Listen, EnhancedMode: cfg.EnhancedMode, } - - if nameserver, err := parseNameServer(cfg.NameServer); err == nil { - dnsCfg.NameServer = nameserver + var err error + if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil { + return nil, err } - if fallback, err := parseNameServer(cfg.Fallback); err == nil { - dnsCfg.Fallback = fallback + if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback); err != nil { + return nil, err } return dnsCfg, nil From 23bb01a4df2f1296856d60228710ba315d478c9f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 3 Mar 2019 11:51:15 +0800 Subject: [PATCH 149/535] Fix: http request keepAlive with right http header --- tunnel/connection.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 8b9832d7c5..25c3656e42 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -25,12 +25,9 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { conn := newTrafficTrack(outbound, t.traffic) req := request.R host := req.Host - keepalive := true for { - if strings.ToLower(req.Header.Get("Connection")) == "close" { - keepalive = false - } + keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" req.Header.Set("Connection", "close") req.RequestURI = "" @@ -58,7 +55,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } - if !keepalive { + if !keepAlive { break } From 7683271fe6bd8dea6b6d449d52aff255079817d8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 3 Mar 2019 11:59:07 +0800 Subject: [PATCH 150/535] Style: rename `Generator` with `Dial` --- adapters/outbound/direct.go | 2 +- adapters/outbound/fallback.go | 6 +++--- adapters/outbound/http.go | 2 +- adapters/outbound/loadbalance.go | 6 +++--- adapters/outbound/reject.go | 2 +- adapters/outbound/selector.go | 4 ++-- adapters/outbound/shadowsocks.go | 2 +- adapters/outbound/socks5.go | 2 +- adapters/outbound/urltest.go | 4 ++-- adapters/outbound/util.go | 2 +- adapters/outbound/vmess.go | 2 +- constant/adapters.go | 2 +- tunnel/tunnel.go | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index b46bf500b0..245c28b331 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -10,7 +10,7 @@ type Direct struct { *Base } -func (d *Direct) Generator(metadata *C.Metadata) (net.Conn, error) { +func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) { address := net.JoinHostPort(metadata.Host, metadata.Port) if metadata.IP != nil { address = net.JoinHostPort(metadata.IP.String(), metadata.Port) diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index ea456fd37c..0007301084 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -38,7 +38,7 @@ func (f *Fallback) Now() string { return f.proxies[0].RawProxy.Name() } -func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) { +func (f *Fallback) Dial(metadata *C.Metadata) (net.Conn, error) { idx := 0 var proxy *proxy for { @@ -46,7 +46,7 @@ func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) { if proxy == nil { break } - adapter, err := proxy.RawProxy.Generator(metadata) + adapter, err := proxy.RawProxy.Dial(metadata) if err != nil { proxy.Valid = false idx++ @@ -54,7 +54,7 @@ func (f *Fallback) Generator(metadata *C.Metadata) (net.Conn, error) { } return adapter, err } - return f.proxies[0].RawProxy.Generator(metadata) + return f.proxies[0].RawProxy.Dial(metadata) } func (f *Fallback) MarshalJSON() ([]byte, error) { diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index cf1fb7484f..79d8b31e65 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -35,7 +35,7 @@ type HttpOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (h *Http) Generator(metadata *C.Metadata) (net.Conn, error) { +func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", h.addr, tcpTimeout) if err == nil && h.tls { cc := tls.Client(c, h.tlsConfig) diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index 3edeae5de0..0ba08731e4 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -48,18 +48,18 @@ func jumpHash(key uint64, buckets int32) int32 { return int32(b) } -func (lb *LoadBalance) Generator(metadata *C.Metadata) (net.Conn, error) { +func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) { key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) buckets := int32(len(lb.proxies)) for i := 0; i < lb.maxRetry; i++ { idx := jumpHash(key, buckets) - if proxy, err := lb.proxies[idx].Generator(metadata); err == nil { + if proxy, err := lb.proxies[idx].Dial(metadata); err == nil { return proxy, nil } key++ } - return lb.proxies[0].Generator(metadata) + return lb.proxies[0].Dial(metadata) } func (lb *LoadBalance) MarshalJSON() ([]byte, error) { diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index 9f49ae8588..f78d4f4556 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -12,7 +12,7 @@ type Reject struct { *Base } -func (r *Reject) Generator(metadata *C.Metadata) (net.Conn, error) { +func (r *Reject) Dial(metadata *C.Metadata) (net.Conn, error) { return &NopConn{}, nil } diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index 28f39c4077..a0a126a59c 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -20,8 +20,8 @@ type SelectorOption struct { Proxies []string `proxy:"proxies"` } -func (s *Selector) Generator(metadata *C.Metadata) (net.Conn, error) { - return s.selected.Generator(metadata) +func (s *Selector) Dial(metadata *C.Metadata) (net.Conn, error) { + return s.selected.Dial(metadata) } func (s *Selector) MarshalJSON() ([]byte, error) { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index abddbb375b..b4d57c0cdd 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -55,7 +55,7 @@ type v2rayObfsOption struct { SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` } -func (ss *ShadowSocks) Generator(metadata *C.Metadata) (net.Conn, error) { +func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 97e883e4f2..2adb999a18 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -34,7 +34,7 @@ type Socks5Option struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (ss *Socks5) Generator(metadata *C.Metadata) (net.Conn, error) { +func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) if err == nil && ss.tls { diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index b8b144aaff..5f5cfb4e68 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -33,8 +33,8 @@ func (u *URLTest) Now() string { return u.fast.Name() } -func (u *URLTest) Generator(metadata *C.Metadata) (net.Conn, error) { - a, err := u.fast.Generator(metadata) +func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) { + a, err := u.fast.Dial(metadata) if err != nil { go u.speedTest() } diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 6dedc9b72a..5c07e95557 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -29,7 +29,7 @@ func DelayTest(proxy C.Proxy, url string) (t int16, err error) { } start := time.Now() - instance, err := proxy.Generator(&addr) + instance, err := proxy.Dial(&addr) if err != nil { return } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 67820acc68..6c137432e4 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -30,7 +30,7 @@ type VmessOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (v *Vmess) Generator(metadata *C.Metadata) (net.Conn, error) { +func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) { c, err := net.DialTimeout("tcp", v.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", v.server) diff --git a/constant/adapters.go b/constant/adapters.go index 082743cf92..e3ce76e40c 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -26,7 +26,7 @@ type ServerAdapter interface { type Proxy interface { Name() string Type() AdapterType - Generator(metadata *Metadata) (net.Conn, error) + Dial(metadata *Metadata) (net.Conn, error) MarshalJSON() ([]byte, error) } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index d98ec40a40..6fec24eb5e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -143,7 +143,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } } - remoConn, err := proxy.Generator(metadata) + remoConn, err := proxy.Dial(metadata) if err != nil { log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) return From 7f0c7d78029026a56c45bafc398e3a46cb2dbad9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 3 Mar 2019 17:23:59 +0800 Subject: [PATCH 151/535] Fix: should not return extra ip in msgToIP --- dns/client.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/dns/client.go b/dns/client.go index 634722fca4..1d68dc701c 100644 --- a/dns/client.go +++ b/dns/client.go @@ -184,15 +184,6 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { } } - for _, extra := range msg.Extra { - switch record := extra.(type) { - case *D.AAAA: - ips = append(ips, record.AAAA) - case *D.A: - ips = append(ips, record.A) - } - } - return ips } From 8c608f5d7a5b701320c96d7d5c193c121bfad026 Mon Sep 17 00:00:00 2001 From: Rico Date: Fri, 15 Mar 2019 12:43:46 +1100 Subject: [PATCH 152/535] Feature: add custom headers support in v2ray-plugin (#137) --- adapters/outbound/shadowsocks.go | 13 +++++++------ component/v2ray-plugin/websocket.go | 2 ++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index b4d57c0cdd..62045997bc 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -48,11 +48,12 @@ type simpleObfsOption struct { } type v2rayObfsOption struct { - Mode string `obfs:"mode"` - Host string `obfs:"host,omitempty"` - Path string `obfs:"path,omitempty"` - TLS bool `obfs:"tls,omitempty"` - SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` + Mode string `obfs:"mode"` + Host string `obfs:"host,omitempty"` + Path string `obfs:"path,omitempty"` + TLS bool `obfs:"tls,omitempty"` + Headers map[string]string `obfs:"headers,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` } func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) { @@ -131,10 +132,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { ClientSessionCache: getClientSessionCache(), } } - wsOption = &v2rayObfs.WebsocketOption{ Host: opts.Host, Path: opts.Path, + Headers: opts.Headers, TLSConfig: tlsConfig, } } diff --git a/component/v2ray-plugin/websocket.go b/component/v2ray-plugin/websocket.go index 03d6f854b5..1e53cb9b61 100644 --- a/component/v2ray-plugin/websocket.go +++ b/component/v2ray-plugin/websocket.go @@ -11,6 +11,7 @@ import ( type WebsocketOption struct { Host string Path string + Headers map[string]string TLSConfig *tls.Config } @@ -20,6 +21,7 @@ func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) Host: option.Host, Path: option.Path, TLS: option.TLSConfig != nil, + Headers: option.Headers, TLSConfig: option.TLSConfig, } From acf55a7f641cb6f522546397dcc15f4fba9359f1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 16 Mar 2019 00:43:16 +0800 Subject: [PATCH 153/535] Improve: `Dial` would reset proxy alive status --- adapters/outbound/base.go | 57 ++++++++++++++++++++++++++++++ adapters/outbound/fallback.go | 60 ++++++++------------------------ adapters/outbound/loadbalance.go | 8 ++--- adapters/outbound/urltest.go | 8 +++-- adapters/outbound/util.go | 49 -------------------------- config/config.go | 15 ++++---- constant/adapters.go | 9 ++++- hub/executor/executor.go | 10 ++---- hub/route/proxies.go | 9 +++-- 9 files changed, 103 insertions(+), 122 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 9701d0e1c2..34bade770b 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -2,6 +2,9 @@ package adapters import ( "encoding/json" + "net" + "net/http" + "time" C "github.com/Dreamacro/clash/constant" ) @@ -19,8 +22,62 @@ func (b *Base) Type() C.AdapterType { return b.tp } +func (b *Base) Destroy() {} + func (b *Base) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]string{ "type": b.Type().String(), }) } + +type Proxy struct { + C.ProxyAdapter + alive bool +} + +func (p *Proxy) Alive() bool { + return p.alive +} + +func (p *Proxy) Dial(metadata *C.Metadata) (net.Conn, error) { + conn, err := p.ProxyAdapter.Dial(metadata) + p.alive = err == nil + return conn, err +} + +// URLTest get the delay for the specified URL +func (p *Proxy) URLTest(url string) (t int16, err error) { + addr, err := urlToMetadata(url) + if err != nil { + return + } + + start := time.Now() + instance, err := p.ProxyAdapter.Dial(&addr) + if err != nil { + return + } + defer instance.Close() + transport := &http.Transport{ + Dial: func(string, string) (net.Conn, error) { + return instance, nil + }, + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + client := http.Client{Transport: transport} + resp, err := client.Get(url) + if err != nil { + return + } + resp.Body.Close() + t = int16(time.Since(start) / time.Millisecond) + return +} + +func NewProxy(adapter C.ProxyAdapter) *Proxy { + return &Proxy{adapter, true} +} diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 0007301084..78f573ae23 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -10,14 +10,9 @@ import ( C "github.com/Dreamacro/clash/constant" ) -type proxy struct { - RawProxy C.Proxy - Valid bool -} - type Fallback struct { *Base - proxies []*proxy + proxies []C.Proxy rawURL string interval time.Duration done chan struct{} @@ -31,36 +26,19 @@ type FallbackOption struct { } func (f *Fallback) Now() string { - _, proxy := f.findNextValidProxy(0) - if proxy != nil { - return proxy.RawProxy.Name() - } - return f.proxies[0].RawProxy.Name() + proxy := f.findAliveProxy() + return proxy.Name() } func (f *Fallback) Dial(metadata *C.Metadata) (net.Conn, error) { - idx := 0 - var proxy *proxy - for { - idx, proxy = f.findNextValidProxy(idx) - if proxy == nil { - break - } - adapter, err := proxy.RawProxy.Dial(metadata) - if err != nil { - proxy.Valid = false - idx++ - continue - } - return adapter, err - } - return f.proxies[0].RawProxy.Dial(metadata) + proxy := f.findAliveProxy() + return proxy.Dial(metadata) } func (f *Fallback) MarshalJSON() ([]byte, error) { var all []string for _, proxy := range f.proxies { - all = append(all, proxy.RawProxy.Name()) + all = append(all, proxy.Name()) } return json.Marshal(map[string]interface{}{ "type": f.Type().String(), @@ -69,7 +47,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) { }) } -func (f *Fallback) Close() { +func (f *Fallback) Destroy() { f.done <- struct{}{} } @@ -87,13 +65,13 @@ Loop: } } -func (f *Fallback) findNextValidProxy(start int) (int, *proxy) { - for i := start; i < len(f.proxies); i++ { - if f.proxies[i].Valid { - return i, f.proxies[i] +func (f *Fallback) findAliveProxy() C.Proxy { + for _, proxy := range f.proxies { + if proxy.Alive() { + return proxy } } - return -1, nil + return f.proxies[0] } func (f *Fallback) validTest() { @@ -101,9 +79,8 @@ func (f *Fallback) validTest() { wg.Add(len(f.proxies)) for _, p := range f.proxies { - go func(p *proxy) { - _, err := DelayTest(p.RawProxy, f.rawURL) - p.Valid = err == nil + go func(p C.Proxy) { + p.URLTest(f.rawURL) wg.Done() }(p) } @@ -122,20 +99,13 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { } interval := time.Duration(option.Interval) * time.Second - warpperProxies := make([]*proxy, len(proxies)) - for idx := range proxies { - warpperProxies[idx] = &proxy{ - RawProxy: proxies[idx], - Valid: true, - } - } Fallback := &Fallback{ Base: &Base{ name: option.Name, tp: C.Fallback, }, - proxies: warpperProxies, + proxies: proxies, rawURL: option.URL, interval: interval, done: make(chan struct{}), diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index 0ba08731e4..9c183ccf68 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -51,12 +51,12 @@ func jumpHash(key uint64, buckets int32) int32 { func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) { key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) buckets := int32(len(lb.proxies)) - for i := 0; i < lb.maxRetry; i++ { + for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { idx := jumpHash(key, buckets) - if proxy, err := lb.proxies[idx].Dial(metadata); err == nil { - return proxy, nil + proxy := lb.proxies[idx] + if proxy.Alive() { + return proxy.Dial(metadata) } - key++ } return lb.proxies[0].Dial(metadata) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 5f5cfb4e68..6cce88a36c 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "encoding/json" "errors" "net" @@ -9,6 +10,7 @@ import ( "sync/atomic" "time" + "github.com/Dreamacro/clash/common/picker" C "github.com/Dreamacro/clash/constant" ) @@ -54,7 +56,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) { }) } -func (u *URLTest) Close() { +func (u *URLTest) Destroy() { u.done <- struct{}{} } @@ -81,12 +83,12 @@ func (u *URLTest) speedTest() { wg := sync.WaitGroup{} wg.Add(len(u.proxies)) c := make(chan interface{}) - fast := selectFast(c) + fast := picker.SelectFast(context.Background(), c) timer := time.NewTimer(u.interval) for _, p := range u.proxies { go func(p C.Proxy) { - _, err := DelayTest(p, u.rawURL) + _, err := p.URLTest(u.rawURL) if err == nil { c <- p } diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 5c07e95557..363bcc3e34 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "fmt" "net" - "net/http" "net/url" "sync" "time" @@ -21,39 +20,6 @@ var ( once sync.Once ) -// DelayTest get the delay for the specified URL -func DelayTest(proxy C.Proxy, url string) (t int16, err error) { - addr, err := urlToMetadata(url) - if err != nil { - return - } - - start := time.Now() - instance, err := proxy.Dial(&addr) - if err != nil { - return - } - defer instance.Close() - transport := &http.Transport{ - Dial: func(string, string) (net.Conn, error) { - return instance, nil - }, - // from http.DefaultTransport - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - client := http.Client{Transport: transport} - resp, err := client.Get(url) - if err != nil { - return - } - resp.Body.Close() - t = int16(time.Since(start) / time.Millisecond) - return -} - func urlToMetadata(rawURL string) (addr C.Metadata, err error) { u, err := url.Parse(rawURL) if err != nil { @@ -81,21 +47,6 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) { return } -func selectFast(in chan interface{}) chan interface{} { - out := make(chan interface{}) - go func() { - p, open := <-in - if open { - out <- p - } - close(out) - for range in { - } - }() - - return out -} - func tcpKeepAlive(c net.Conn) { if tcp, ok := c.(*net.TCPConn); ok { tcp.SetKeepAlive(true) diff --git a/config/config.go b/config/config.go index 40fa889f08..1c94895219 100644 --- a/config/config.go +++ b/config/config.go @@ -184,8 +184,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) - proxies["DIRECT"] = adapters.NewDirect() - proxies["REJECT"] = adapters.NewReject() + proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect()) + proxies["REJECT"] = adapters.NewProxy(adapters.NewReject()) // parse proxy for idx, mapping := range proxiesConfig { @@ -194,7 +194,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return nil, fmt.Errorf("Proxy %d missing type", idx) } - var proxy C.Proxy + var proxy C.ProxyAdapter err := fmt.Errorf("can't parse") switch proxyType { case "ss": @@ -236,7 +236,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { if _, exist := proxies[proxy.Name()]; exist { return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) } - proxies[proxy.Name()] = proxy + proxies[proxy.Name()] = adapters.NewProxy(proxy) } // parse proxy group @@ -250,7 +250,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { if _, exist := proxies[groupName]; exist { return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) } - var group C.Proxy + var group C.ProxyAdapter ps := []C.Proxy{} err := fmt.Errorf("can't parse") @@ -307,7 +307,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { if err != nil { return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) } - proxies[groupName] = group + proxies[groupName] = adapters.NewProxy(group) } ps := []C.Proxy{} @@ -315,7 +315,8 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { ps = append(ps, v) } - proxies["GLOBAL"], _ = adapters.NewSelector("GLOBAL", ps) + global, _ := adapters.NewSelector("GLOBAL", ps) + proxies["GLOBAL"] = adapters.NewProxy(global) return proxies, nil } diff --git a/constant/adapters.go b/constant/adapters.go index e3ce76e40c..0fee6aead5 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -23,13 +23,20 @@ type ServerAdapter interface { Close() } -type Proxy interface { +type ProxyAdapter interface { Name() string Type() AdapterType Dial(metadata *Metadata) (net.Conn, error) + Destroy() MarshalJSON() ([]byte, error) } +type Proxy interface { + ProxyAdapter + Alive() bool + URLTest(url string) (int16, error) +} + // AdapterType is enum of adapter type type AdapterType int diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 390d430473..936bf4c680 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -1,7 +1,6 @@ package executor import ( - adapters "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -66,14 +65,9 @@ func updateProxies(proxies map[string]C.Proxy) { tunnel := T.Instance() oldProxies := tunnel.Proxies() - // close old goroutine + // close proxy group goroutine for _, proxy := range oldProxies { - switch raw := proxy.(type) { - case *adapters.URLTest: - raw.Close() - case *adapters.Fallback: - raw.Close() - } + proxy.Destroy() } tunnel.UpdateProxies(proxies) diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 3da30a2444..77255b7a71 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -81,12 +81,11 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { return } - proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - - selector, ok := proxy.(*A.Selector) + proxy := r.Context().Value(CtxKeyProxy).(*A.Proxy) + selector, ok := proxy.ProxyAdapter.(*A.Selector) if !ok { render.Status(r, http.StatusBadRequest) - render.JSON(w, r, ErrBadRequest) + render.JSON(w, r, newError("Must be a Selector")) return } @@ -113,7 +112,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { sigCh := make(chan int16) go func() { - t, err := A.DelayTest(proxy, url) + t, err := proxy.URLTest(url) if err != nil { sigCh <- 0 } From 63446da5fa1aacdbcc19b56bbf65cace6e03ffa7 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Sun, 17 Mar 2019 14:08:15 +0800 Subject: [PATCH 154/535] Fix: expand UDPSize to avoid resolving error (#139) --- dns/client.go | 1 + dns/server.go | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/dns/client.go b/dns/client.go index 1d68dc701c..1dcaae82ec 100644 --- a/dns/client.go +++ b/dns/client.go @@ -234,6 +234,7 @@ func transform(servers []NameServer) []*nameserver { TLSConfig: &tls.Config{ ClientSessionCache: globalSessionCache, }, + UDPSize: 4096, }, Address: s.Addr, }) diff --git a/dns/server.go b/dns/server.go index 2ece2e97ea..9db92d78b1 100644 --- a/dns/server.go +++ b/dns/server.go @@ -1,8 +1,10 @@ package dns import ( + "fmt" "net" + "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" ) @@ -20,6 +22,11 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { msg, err := s.r.Exchange(r) if err != nil { + if len(r.Question) > 0 { + q := r.Question[0] + qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String()) + log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err) + } D.HandleFailed(w, r) return } From 7a9d986ff38c356ef78c720b3cb5a597bd5ca3c3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 17 Mar 2019 14:52:39 +0800 Subject: [PATCH 155/535] Feature: add delay history and improve url-test behavior --- adapters/outbound/base.go | 56 ++++++++++++++++++++++++++-- adapters/outbound/urltest.go | 19 +++++++++- common/queue/queue.go | 71 ++++++++++++++++++++++++++++++++++++ constant/adapters.go | 10 ++++- hub/route/proxies.go | 2 +- 5 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 common/queue/queue.go diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 34bade770b..f234ced6d0 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/Dreamacro/clash/common/queue" C "github.com/Dreamacro/clash/constant" ) @@ -32,7 +33,8 @@ func (b *Base) MarshalJSON() ([]byte, error) { type Proxy struct { C.ProxyAdapter - alive bool + history *queue.Queue + alive bool } func (p *Proxy) Alive() bool { @@ -45,8 +47,54 @@ func (p *Proxy) Dial(metadata *C.Metadata) (net.Conn, error) { return conn, err } +func (p *Proxy) DelayHistory() []C.DelayHistory { + queue := p.history.Copy() + histories := []C.DelayHistory{} + for _, item := range queue { + histories = append(histories, item.(C.DelayHistory)) + } + return histories +} + +func (p *Proxy) LastDelay() (delay uint16) { + head := p.history.First() + if head == nil { + delay-- + return + } + history := head.(C.DelayHistory) + if history.Delay == 0 { + delay-- + return + } + return history.Delay +} + +func (p *Proxy) MarshalJSON() ([]byte, error) { + inner, err := p.ProxyAdapter.MarshalJSON() + if err != nil { + return inner, err + } + + mapping := map[string]interface{}{} + json.Unmarshal(inner, &mapping) + mapping["history"] = p.DelayHistory() + return json.Marshal(mapping) +} + // URLTest get the delay for the specified URL -func (p *Proxy) URLTest(url string) (t int16, err error) { +func (p *Proxy) URLTest(url string) (t uint16, err error) { + defer func() { + record := C.DelayHistory{Time: time.Now()} + if err == nil { + record.Delay = t + } + p.history.Put(record) + if p.history.Len() > 10 { + p.history.Pop() + } + }() + addr, err := urlToMetadata(url) if err != nil { return @@ -74,10 +122,10 @@ func (p *Proxy) URLTest(url string) (t int16, err error) { return } resp.Body.Close() - t = int16(time.Since(start) / time.Millisecond) + t = uint16(time.Since(start) / time.Millisecond) return } func NewProxy(adapter C.ProxyAdapter) *Proxy { - return &Proxy{adapter, true} + return &Proxy{adapter, queue.New(10), true} } diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 6cce88a36c..84fdccbe6a 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -38,7 +38,7 @@ func (u *URLTest) Now() string { func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) { a, err := u.fast.Dial(metadata) if err != nil { - go u.speedTest() + u.fallback() } return a, err } @@ -74,6 +74,23 @@ Loop: } } +func (u *URLTest) fallback() { + fast := u.proxies[0] + min := fast.LastDelay() + for _, proxy := range u.proxies[1:] { + if !proxy.Alive() { + continue + } + + delay := proxy.LastDelay() + if delay < min { + fast = proxy + min = delay + } + } + u.fast = fast +} + func (u *URLTest) speedTest() { if atomic.AddInt32(&u.once, 1) != 1 { return diff --git a/common/queue/queue.go b/common/queue/queue.go new file mode 100644 index 0000000000..7cb51b8513 --- /dev/null +++ b/common/queue/queue.go @@ -0,0 +1,71 @@ +package queue + +import ( + "sync" +) + +// Queue is a simple concurrent safe queue +type Queue struct { + items []interface{} + lock sync.RWMutex +} + +// Put add the item to the queue. +func (q *Queue) Put(items ...interface{}) { + if len(items) == 0 { + return + } + + q.lock.Lock() + q.items = append(q.items, items...) + q.lock.Unlock() +} + +// Pop returns the head of items. +func (q *Queue) Pop() interface{} { + if len(q.items) == 0 { + return nil + } + + q.lock.Lock() + head := q.items[0] + q.items = q.items[1:] + q.lock.Unlock() + return head +} + +// First returns the head of items without deleting. +func (q *Queue) First() interface{} { + if len(q.items) == 0 { + return nil + } + + q.lock.RLock() + head := q.items[0] + q.lock.RUnlock() + return head +} + +// Copy get the copy of queue. +func (q *Queue) Copy() []interface{} { + items := []interface{}{} + q.lock.RLock() + items = append(items, q.items...) + q.lock.RUnlock() + return items +} + +// Len returns the number of items in this queue. +func (q *Queue) Len() int64 { + q.lock.Lock() + defer q.lock.Unlock() + + return int64(len(q.items)) +} + +// New is a constructor for a new concurrent safe queue. +func New(hint int64) *Queue { + return &Queue{ + items: make([]interface{}, 0, hint), + } +} diff --git a/constant/adapters.go b/constant/adapters.go index 0fee6aead5..be755554a5 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -2,6 +2,7 @@ package constant import ( "net" + "time" ) // Adapter Type @@ -31,10 +32,17 @@ type ProxyAdapter interface { MarshalJSON() ([]byte, error) } +type DelayHistory struct { + Time time.Time `json:"time"` + Delay uint16 `json:"delay"` +} + type Proxy interface { ProxyAdapter Alive() bool - URLTest(url string) (int16, error) + DelayHistory() []DelayHistory + LastDelay() uint16 + URLTest(url string) (uint16, error) } // AdapterType is enum of adapter type diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 77255b7a71..7122191707 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -110,7 +110,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - sigCh := make(chan int16) + sigCh := make(chan uint16) go func() { t, err := proxy.URLTest(url) if err != nil { From f99da37168ac2662d4294e391fe9d8db4a5e55fc Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 23 Mar 2019 16:29:27 +0800 Subject: [PATCH 156/535] Fix: fallback & url-test lose efficacy --- adapters/outbound/base.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index f234ced6d0..7941c2f7b0 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -56,16 +56,20 @@ func (p *Proxy) DelayHistory() []C.DelayHistory { return histories } +// LastDelay return last history record. if proxy is not alive, return the max value of int16. func (p *Proxy) LastDelay() (delay uint16) { + var max uint16 = 0xffff + if !p.alive { + return max + } + head := p.history.First() if head == nil { - delay-- - return + return max } history := head.(C.DelayHistory) if history.Delay == 0 { - delay-- - return + return max } return history.Delay } @@ -101,7 +105,7 @@ func (p *Proxy) URLTest(url string) (t uint16, err error) { } start := time.Now() - instance, err := p.ProxyAdapter.Dial(&addr) + instance, err := p.Dial(&addr) if err != nil { return } From bb267e4a1f7bd37d3b79de385dceaa75f71070f0 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Sat, 23 Mar 2019 19:24:26 +0800 Subject: [PATCH 157/535] Feature: add version command (#148) --- Makefile | 10 +++++++--- constant/version.go | 6 ++++++ main.go | 9 +++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 constant/version.go diff --git a/Makefile b/Makefile index a29a8e3e4d..8c5919ffbf 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ NAME=clash BINDIR=bin -GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s' +VERSION=$(shell git describe --tags --long --dirty || echo "unkown version") +BUILDTIME=$(shell date -u) +GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ + -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ + -w -s' PLATFORM_LIST = \ darwin-amd64 \ @@ -77,10 +81,10 @@ zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) $(gz_releases): %.gz : % chmod +x $(BINDIR)/$(NAME)-$(basename $@) - gzip -f $(BINDIR)/$(NAME)-$(basename $@) + tar -czf $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-$(basename $@) $(zip_releases): %.zip : % - zip -m -j $(BINDIR)/$(NAME)-$(basename $@).zip $(BINDIR)/$(NAME)-$(basename $@).exe + zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST) diff --git a/constant/version.go b/constant/version.go new file mode 100644 index 0000000000..17a26f8c7e --- /dev/null +++ b/constant/version.go @@ -0,0 +1,6 @@ +package constant + +var ( + Version = "unknown version" + BuildTime = "unknown time" +) diff --git a/main.go b/main.go index 7733010194..2e9c4b25e4 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,11 @@ package main import ( "flag" + "fmt" "os" "os/signal" "path/filepath" + "runtime" "syscall" "github.com/Dreamacro/clash/config" @@ -15,15 +17,22 @@ import ( ) var ( + version bool homedir string ) func init() { flag.StringVar(&homedir, "d", "", "set configuration directory") + flag.BoolVar(&version, "v", false, "show current version of clash") flag.Parse() } func main() { + if version { + fmt.Printf("Clash %s %s %s %s\n", C.Version, runtime.GOOS, runtime.GOARCH, C.BuildTime) + return + } + // enable tls 1.3 and remove when go 1.13 os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1") From 14600a8170a8af630aa5a6d08c41c725a44816e9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 23 Mar 2019 19:41:41 +0800 Subject: [PATCH 158/535] Fix: dns hot reload no effect --- dns/server.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dns/server.go b/dns/server.go index 9db92d78b1..93311aebf8 100644 --- a/dns/server.go +++ b/dns/server.go @@ -34,15 +34,20 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { w.WriteMsg(msg) } -func ReCreateServer(addr string, resolver *Resolver) error { - if server.Server != nil { - server.Shutdown() - } +func (s *Server) setReslover(r *Resolver) { + s.r = r +} +func ReCreateServer(addr string, resolver *Resolver) error { if addr == address { + server.setReslover(resolver) return nil } + if server.Server != nil { + server.Shutdown() + } + _, port, err := net.SplitHostPort(addr) if port == "0" || port == "" || err != nil { return nil From 791d72e05b2c4fd9c094c022a3b0ab0667c9bbdf Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 25 Mar 2019 20:42:20 +0800 Subject: [PATCH 159/535] Fix: crash when key value is nil --- common/structure/structure.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/structure/structure.go b/common/structure/structure.go index 9d56b703eb..8e595a17fd 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -47,11 +47,11 @@ func (d *Decoder) Decode(src map[string]interface{}, dst interface{}) error { } value, ok := src[key] - if !ok { + if !ok || value == nil { if omitempty { continue } - return fmt.Errorf("key %s missing", key) + return fmt.Errorf("key '%s' missing", key) } err := d.decode(key, value, v.Field(idx)) @@ -114,7 +114,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) val.SetString(strconv.FormatInt(dataVal.Int(), 10)) default: err = fmt.Errorf( - "'%s' expected type'%s', got unconvertible type '%s'", + "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type(), ) } @@ -131,7 +131,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) ( val.SetBool(dataVal.Int() != 0) default: err = fmt.Errorf( - "'%s' expected type'%s', got unconvertible type '%s'", + "'%s' expected type '%s', got unconvertible type '%s'", name, val.Type(), dataVal.Type(), ) } From d1f68865587bed9d1793644d378129bc9d9e53e4 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Tue, 26 Mar 2019 23:48:03 +0800 Subject: [PATCH 160/535] Style: use atomic CompareAndSwap (#151) --- adapters/outbound/urltest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 84fdccbe6a..ecca07b71f 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -92,7 +92,7 @@ func (u *URLTest) fallback() { } func (u *URLTest) speedTest() { - if atomic.AddInt32(&u.once, 1) != 1 { + if atomic.CompareAndSwapInt32(&u.once, 0, 1) { return } defer atomic.StoreInt32(&u.once, 0) From d3b280a7e5d83be92ecb5e07b586dd974994373e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 28 Mar 2019 18:20:19 +0800 Subject: [PATCH 161/535] Fix: reuse Current.HomeDir until go 1.13 release --- constant/path.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/constant/path.go b/constant/path.go index 5d08e36cd4..32c67aadc8 100644 --- a/constant/path.go +++ b/constant/path.go @@ -2,6 +2,7 @@ package constant import ( "os" + "os/user" P "path" ) @@ -15,9 +16,16 @@ type path struct { } func init() { - homedir, err := os.UserHomeDir() + currentUser, err := user.Current() + var homedir string if err != nil { - homedir, _ = os.Getwd() + dir := os.Getenv("HOME") + if dir == "" { + dir, _ = os.Getwd() + } + homedir = dir + } else { + homedir = currentUser.HomeDir } homedir = P.Join(homedir, ".config", Name) From 18f885a92ab62524d5ff96852b7897fc7cbdbe3a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 28 Mar 2019 19:00:41 +0800 Subject: [PATCH 162/535] Feature: add interval url test for load-balance --- adapters/outbound/base.go | 5 ++- adapters/outbound/loadbalance.go | 58 ++++++++++++++++++++++++++++---- config/config.go | 2 +- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 7941c2f7b0..c73a15424e 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -43,7 +43,9 @@ func (p *Proxy) Alive() bool { func (p *Proxy) Dial(metadata *C.Metadata) (net.Conn, error) { conn, err := p.ProxyAdapter.Dial(metadata) - p.alive = err == nil + if err != nil { + p.alive = false + } return conn, err } @@ -89,6 +91,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { // URLTest get the delay for the specified URL func (p *Proxy) URLTest(url string) (t uint16, err error) { defer func() { + p.alive = err == nil record := C.DelayHistory{Time: time.Now()} if err == nil { record.Delay = t diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index 9c183ccf68..df2145b274 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "net" + "sync" + "time" "github.com/Dreamacro/clash/common/murmur3" C "github.com/Dreamacro/clash/constant" @@ -15,6 +17,9 @@ type LoadBalance struct { *Base proxies []C.Proxy maxRetry int + rawURL string + interval time.Duration + done chan struct{} } func getKey(metadata *C.Metadata) string { @@ -62,6 +67,38 @@ func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) { return lb.proxies[0].Dial(metadata) } +func (lb *LoadBalance) Destroy() { + lb.done <- struct{}{} +} + +func (lb *LoadBalance) validTest() { + wg := sync.WaitGroup{} + wg.Add(len(lb.proxies)) + + for _, p := range lb.proxies { + go func(p C.Proxy) { + p.URLTest(lb.rawURL) + wg.Done() + }(p) + } + + wg.Wait() +} + +func (lb *LoadBalance) loop() { + tick := time.NewTicker(lb.interval) + go lb.validTest() +Loop: + for { + select { + case <-tick.C: + go lb.validTest() + case <-lb.done: + break Loop + } + } +} + func (lb *LoadBalance) MarshalJSON() ([]byte, error) { var all []string for _, proxy := range lb.proxies { @@ -74,21 +111,30 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { } type LoadBalanceOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` + URL string `proxy:"url"` + Interval int `proxy:"interval"` } -func NewLoadBalance(name string, proxies []C.Proxy) (*LoadBalance, error) { +func NewLoadBalance(option LoadBalanceOption, proxies []C.Proxy) (*LoadBalance, error) { if len(proxies) == 0 { return nil, errors.New("Provide at least one proxy") } - return &LoadBalance{ + interval := time.Duration(option.Interval) * time.Second + + lb := &LoadBalance{ Base: &Base{ - name: name, + name: option.Name, tp: C.LoadBalance, }, proxies: proxies, maxRetry: 3, - }, nil + rawURL: option.URL, + interval: interval, + done: make(chan struct{}), + } + go lb.loop() + return lb, nil } diff --git a/config/config.go b/config/config.go index 1c94895219..cc769903e0 100644 --- a/config/config.go +++ b/config/config.go @@ -302,7 +302,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { if err != nil { return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } - group, err = adapters.NewLoadBalance(loadBalanceOption.Name, ps) + group, err = adapters.NewLoadBalance(*loadBalanceOption, ps) } if err != nil { return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) From 531f487629b1ca54b3990fe12f8a6ec4b8416d1e Mon Sep 17 00:00:00 2001 From: Comzyh Date: Fri, 29 Mar 2019 10:27:26 +0800 Subject: [PATCH 163/535] Fix: incorrect mutex in speedTest (#153) --- adapters/outbound/urltest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index ecca07b71f..6ea6dd4071 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -92,7 +92,7 @@ func (u *URLTest) fallback() { } func (u *URLTest) speedTest() { - if atomic.CompareAndSwapInt32(&u.once, 0, 1) { + if !atomic.CompareAndSwapInt32(&u.once, 0, 1) { return } defer atomic.StoreInt32(&u.once, 0) From 2036f8cb7a885dd82616c55f54032ba67a0d2c91 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 30 Mar 2019 14:11:59 +0800 Subject: [PATCH 164/535] Fix: IP-CIDR invalid payload crash --- config/config.go | 27 +++++++++++++++++++-------- rules/ipcidr.go | 1 + 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/config/config.go b/config/config.go index cc769903e0..190478a178 100644 --- a/config/config.go +++ b/config/config.go @@ -339,28 +339,39 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { payload = rule[1] target = rule[2] default: - return nil, fmt.Errorf("Rules[%d] [- %s] error: format invalid", idx, line) + return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line) } rule = trimArr(rule) + var parsed C.Rule switch rule[0] { case "DOMAIN": - rules = append(rules, R.NewDomain(payload, target)) + parsed = R.NewDomain(payload, target) case "DOMAIN-SUFFIX": - rules = append(rules, R.NewDomainSuffix(payload, target)) + parsed = R.NewDomainSuffix(payload, target) case "DOMAIN-KEYWORD": - rules = append(rules, R.NewDomainKeyword(payload, target)) + parsed = R.NewDomainKeyword(payload, target) case "GEOIP": - rules = append(rules, R.NewGEOIP(payload, target)) + parsed = R.NewGEOIP(payload, target) case "IP-CIDR", "IP-CIDR6": - rules = append(rules, R.NewIPCIDR(payload, target, false)) + if rule := R.NewIPCIDR(payload, target, false); rule != nil { + parsed = rule + } case "SOURCE-IP-CIDR": - rules = append(rules, R.NewIPCIDR(payload, target, true)) + if rule := R.NewIPCIDR(payload, target, true); rule != nil { + parsed = rule + } case "MATCH": fallthrough case "FINAL": - rules = append(rules, R.NewMatch(target)) + parsed = R.NewMatch(target) } + + if parsed == nil { + return nil, fmt.Errorf("Rules[%d] [%s] error: payload invalid", idx, line) + } + + rules = append(rules, parsed) } return rules, nil diff --git a/rules/ipcidr.go b/rules/ipcidr.go index c2a02ef86b..87214554d7 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -38,6 +38,7 @@ func (i *IPCIDR) Payload() string { func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR { _, ipnet, err := net.ParseCIDR(s) if err != nil { + return nil } return &IPCIDR{ ipnet: ipnet, From 744728cb842080063d3ac9e744d11ce2c50d34e7 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 30 Mar 2019 14:20:04 +0800 Subject: [PATCH 165/535] Chore: update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f670adb220..84c606edd9 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,12 @@ Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/r Requires Go >= 1.12. +Checkout Clash version: + +```sh +clash -v +``` + ## Daemon Unfortunately, there is no native elegant way to implement golang's daemon. @@ -143,6 +149,8 @@ Proxy: # skip-cert-verify: true # host: bing.com # path: "/" + # headers: + # custom: value # vmess # cipher support auto/aes-128-gcm/chacha20-poly1305/none @@ -182,7 +190,7 @@ Proxy Group: - { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } # load-balance: The request of the same eTLD will be dial on the same proxy. -- { name: "load-balance", type: load-balance, proxies: ["ss1", "ss2", "vmess1"] } +- { name: "load-balance", type: load-balance, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } # select is used for selecting proxy or proxy group # you can use RESTful API to switch proxy, is recommended for use in GUI. From 593a63c2283b3aff4afad2bfa36578f5446ce657 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 30 Mar 2019 15:34:24 +0800 Subject: [PATCH 166/535] Fix: make releases script --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8c5919ffbf..b155571161 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME=clash BINDIR=bin -VERSION=$(shell git describe --tags --long --dirty || echo "unkown version") +VERSION=$(shell git describe --tags || echo "unkown version") BUILDTIME=$(shell date -u) GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ @@ -81,7 +81,7 @@ zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) $(gz_releases): %.gz : % chmod +x $(BINDIR)/$(NAME)-$(basename $@) - tar -czf $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-$(basename $@) + gzip -f -S -$(VERSION).gz $(BINDIR)/$(NAME)-$(basename $@) $(zip_releases): %.zip : % zip -m -j $(BINDIR)/$(NAME)-$(basename $@)-$(VERSION).zip $(BINDIR)/$(NAME)-$(basename $@).exe From 7770e184303e39f556db963a141636450fe9865b Mon Sep 17 00:00:00 2001 From: Birkhoff Lee Date: Mon, 15 Apr 2019 19:05:01 +0800 Subject: [PATCH 167/535] Chore: add GitBook link to README.md (#155) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 84c606edd9..6ec51b62aa 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,9 @@ Rule: - MATCH,auto ``` +## Documentations +https://clash.gitbook.io/ + ## Thanks [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) From 49f8902961b35a32c0fab005778ee0cbb5ea020e Mon Sep 17 00:00:00 2001 From: Jang Rush Date: Mon, 22 Apr 2019 09:57:08 +0800 Subject: [PATCH 168/535] Fix: typo in initial config file (#166) --- config/initial.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initial.go b/config/initial.go index fe13c66da3..8f198b7af2 100644 --- a/config/initial.go +++ b/config/initial.go @@ -65,7 +65,7 @@ func Init(dir string) error { // initial config.yml if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { - log.Info("Can't find config, create a empty file") + log.Info("Can't find config, create an empty file") os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) } From c92cda6980848a47c39a06e0648fdcab947bb657 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 23 Apr 2019 23:29:36 +0800 Subject: [PATCH 169/535] Feature: socks5 udp associate --- README.md | 4 +- adapters/inbound/http.go | 14 +--- adapters/inbound/https.go | 2 +- adapters/inbound/socket.go | 17 ++--- adapters/inbound/util.go | 1 - adapters/outbound/base.go | 10 +++ adapters/outbound/direct.go | 11 ++++ adapters/outbound/fallback.go | 10 +++ adapters/outbound/loadbalance.go | 18 +++++ adapters/outbound/selector.go | 8 +++ adapters/outbound/shadowsocks.go | 37 +++++++++++ adapters/outbound/socks5.go | 2 + adapters/outbound/urltest.go | 8 +++ common/pool/pool.go | 15 +++++ component/simple-obfs/http.go | 10 +-- component/simple-obfs/tls.go | 13 ++-- component/vmess/aead.go | 14 ++-- component/vmess/chunk.go | 15 ++--- constant/adapters.go | 4 +- go.mod | 2 +- go.sum | 4 +- proxy/redir/tcp.go | 2 +- proxy/socks/tcp.go | 110 ++++++++++++++++++++++++++++++- tunnel/connection.go | 70 +++++++++++++++----- tunnel/tunnel.go | 23 +++++-- 25 files changed, 339 insertions(+), 85 deletions(-) create mode 100644 common/pool/pool.go diff --git a/README.md b/README.md index 6ec51b62aa..7e834d34ed 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Proxy: # The types of cipher are consistent with go-shadowsocks2 # support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 # In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305 -- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password" } +- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", udp: true } # old obfs configuration remove after prerelease - name: "ss2" @@ -226,5 +226,5 @@ https://clash.gitbook.io/ - [x] Complementing the necessary rule operators - [x] Redir proxy -- [ ] UDP support +- [ ] UDP support (vmess, outbound socks5) - [ ] Connection manager diff --git a/adapters/inbound/http.go b/adapters/inbound/http.go index 8aa21e7c25..e97bcc3a15 100644 --- a/adapters/inbound/http.go +++ b/adapters/inbound/http.go @@ -10,26 +10,16 @@ import ( // HTTPAdapter is a adapter for HTTP connection type HTTPAdapter struct { + net.Conn metadata *C.Metadata - conn net.Conn R *http.Request } -// Close HTTP connection -func (h *HTTPAdapter) Close() { - h.conn.Close() -} - // Metadata return destination metadata func (h *HTTPAdapter) Metadata() *C.Metadata { return h.metadata } -// Conn return raw net.Conn of HTTP -func (h *HTTPAdapter) Conn() net.Conn { - return h.conn -} - // NewHTTP is HTTPAdapter generator func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { metadata := parseHTTPAddr(request) @@ -37,7 +27,7 @@ func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { return &HTTPAdapter{ metadata: metadata, R: request, - conn: conn, + Conn: conn, } } diff --git a/adapters/inbound/https.go b/adapters/inbound/https.go index e951268659..5207a5daf1 100644 --- a/adapters/inbound/https.go +++ b/adapters/inbound/https.go @@ -11,6 +11,6 @@ func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { metadata.SourceIP = parseSourceIP(conn) return &SocketAdapter{ metadata: metadata, - conn: conn, + Conn: conn, } } diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 66f13f65e3..0d1be44bba 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -9,33 +9,24 @@ import ( // SocketAdapter is a adapter for socks and redir connection type SocketAdapter struct { - conn net.Conn + net.Conn metadata *C.Metadata } -// Close socks and redir connection -func (s *SocketAdapter) Close() { - s.conn.Close() -} - // Metadata return destination metadata func (s *SocketAdapter) Metadata() *C.Metadata { return s.metadata } -// Conn return raw net.Conn -func (s *SocketAdapter) Conn() net.Conn { - return s.conn -} - // NewSocket is SocketAdapter generator -func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType) *SocketAdapter { +func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter { metadata := parseSocksAddr(target) + metadata.NetWork = netType metadata.Source = source metadata.SourceIP = parseSourceIP(conn) return &SocketAdapter{ - conn: conn, + Conn: conn, metadata: metadata, } } diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index b059920aae..c29c06c851 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -11,7 +11,6 @@ import ( func parseSocksAddr(target socks.Addr) *C.Metadata { metadata := &C.Metadata{ - NetWork: C.TCP, AddrType: int(target[0]), } diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index c73a15424e..5887712119 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -2,6 +2,7 @@ package adapters import ( "encoding/json" + "errors" "net" "net/http" "time" @@ -13,6 +14,7 @@ import ( type Base struct { name string tp C.AdapterType + udp bool } func (b *Base) Name() string { @@ -23,6 +25,14 @@ func (b *Base) Type() C.AdapterType { return b.tp } +func (b *Base) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + return nil, nil, errors.New("no support") +} + +func (b *Base) SupportUDP() bool { + return b.udp +} + func (b *Base) Destroy() {} func (b *Base) MarshalJSON() ([]byte, error) { diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 245c28b331..1bd0a6f660 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -24,11 +24,22 @@ func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) { return c, nil } +func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + pc, err := net.ListenPacket("udp", "") + if err != nil { + return nil, nil, err + } + + addr, _ := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + return pc, addr, nil +} + func NewDirect() *Direct { return &Direct{ Base: &Base{ name: "DIRECT", tp: C.Direct, + udp: true, }, } } diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 78f573ae23..913383a448 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -35,6 +35,16 @@ func (f *Fallback) Dial(metadata *C.Metadata) (net.Conn, error) { return proxy.Dial(metadata) } +func (f *Fallback) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + proxy := f.findAliveProxy() + return proxy.DialUDP(metadata) +} + +func (f *Fallback) SupportUDP() bool { + proxy := f.findAliveProxy() + return proxy.SupportUDP() +} + func (f *Fallback) MarshalJSON() ([]byte, error) { var all []string for _, proxy := range f.proxies { diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index df2145b274..761a184f82 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -67,6 +67,24 @@ func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) { return lb.proxies[0].Dial(metadata) } +func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) + buckets := int32(len(lb.proxies)) + for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { + idx := jumpHash(key, buckets) + proxy := lb.proxies[idx] + if proxy.Alive() { + return proxy.DialUDP(metadata) + } + } + + return lb.proxies[0].DialUDP(metadata) +} + +func (lb *LoadBalance) SupportUDP() bool { + return true +} + func (lb *LoadBalance) Destroy() { lb.done <- struct{}{} } diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index a0a126a59c..39b4ac0d9b 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -24,6 +24,14 @@ func (s *Selector) Dial(metadata *C.Metadata) (net.Conn, error) { return s.selected.Dial(metadata) } +func (s *Selector) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + return s.selected.DialUDP(metadata) +} + +func (s *Selector) SupportUDP() bool { + return s.selected.SupportUDP() +} + func (s *Selector) MarshalJSON() ([]byte, error) { var all []string for k := range s.proxies { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 62045997bc..f477d9d363 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -8,6 +8,7 @@ import ( "net" "strconv" + "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/structure" obfs "github.com/Dreamacro/clash/component/simple-obfs" v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin" @@ -34,6 +35,7 @@ type ShadowSocksOption struct { Port int `proxy:"port"` Password string `proxy:"password"` Cipher string `proxy:"cipher"` + UDP bool `proxy:"udp,omitempty"` Plugin string `proxy:"plugin,omitempty"` PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"` @@ -80,6 +82,19 @@ func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) { return c, err } +func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + pc, err := net.ListenPacket("udp", "") + if err != nil { + return nil, nil, err + } + + addr, _ := net.ResolveUDPAddr("udp", ss.server) + remoteAddr, _ := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + + pc = ss.cipher.PacketConn(pc) + return &ssUDPConn{PacketConn: pc, rAddr: remoteAddr}, addr, nil +} + func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]string{ "type": ss.Type().String(), @@ -144,6 +159,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { Base: &Base{ name: option.Name, tp: C.Shadowsocks, + udp: option.UDP, }, server: server, cipher: ciph, @@ -173,3 +189,24 @@ func serializesSocksAddr(metadata *C.Metadata) []byte { } return bytes.Join(buf, nil) } + +type ssUDPConn struct { + net.PacketConn + rAddr net.Addr +} + +func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { + buf := pool.BufPool.Get().([]byte) + defer pool.BufPool.Put(buf[:cap(buf)]) + rAddr := socks.ParseAddr(uc.rAddr.String()) + copy(buf[len(rAddr):], b) + copy(buf, rAddr) + return uc.PacketConn.WriteTo(buf[:len(rAddr)+len(b)], addr) +} + +func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, a, e := uc.PacketConn.ReadFrom(b) + addr := socks.SplitAddr(b[:n]) + copy(b, b[len(addr):]) + return n - len(addr), a, e +} diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 2adb999a18..bd8680d5f3 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -31,6 +31,7 @@ type Socks5Option struct { UserName string `proxy:"username,omitempty"` Password string `proxy:"password,omitempty"` TLS bool `proxy:"tls,omitempty"` + UDP bool `proxy:"udp,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } @@ -126,6 +127,7 @@ func NewSocks5(option Socks5Option) *Socks5 { Base: &Base{ name: option.Name, tp: C.Socks5, + udp: option.UDP, }, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), user: option.UserName, diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 6ea6dd4071..cd20c62fd7 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -43,6 +43,14 @@ func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) { return a, err } +func (u *URLTest) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + return u.fast.DialUDP(metadata) +} + +func (u *URLTest) SupportUDP() bool { + return u.fast.SupportUDP() +} + func (u *URLTest) MarshalJSON() ([]byte, error) { var all []string for _, proxy := range u.proxies { diff --git a/common/pool/pool.go b/common/pool/pool.go new file mode 100644 index 0000000000..aa651dc791 --- /dev/null +++ b/common/pool/pool.go @@ -0,0 +1,15 @@ +package pool + +import ( + "sync" +) + +const ( + // io.Copy default buffer size is 32 KiB + // but the maximum packet size of vmess/shadowsocks is about 16 KiB + // so define a buffer of 20 KiB to reduce the memory of each TCP relay + bufferSize = 20 * 1024 +) + +// BufPool provide buffer for relay +var BufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} diff --git a/component/simple-obfs/http.go b/component/simple-obfs/http.go index 4c7c60dd64..cd62cf7661 100644 --- a/component/simple-obfs/http.go +++ b/component/simple-obfs/http.go @@ -8,6 +8,8 @@ import ( "math/rand" "net" "net/http" + + "github.com/Dreamacro/clash/common/pool" ) // HTTPObfs is shadowsocks http simple-obfs implementation @@ -32,15 +34,15 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) { } if ho.firstResponse { - buf := bufPool.Get().([]byte) + buf := pool.BufPool.Get().([]byte) n, err := ho.Conn.Read(buf) if err != nil { - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) return 0, err } idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) if idx == -1 { - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) return 0, io.EOF } ho.firstResponse = false @@ -50,7 +52,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) { ho.buf = buf[:idx+4+length] ho.offset = idx + 4 + n } else { - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) } return n, nil } diff --git a/component/simple-obfs/tls.go b/component/simple-obfs/tls.go index b763eefb44..37db79a1e6 100644 --- a/component/simple-obfs/tls.go +++ b/component/simple-obfs/tls.go @@ -6,8 +6,9 @@ import ( "io" "math/rand" "net" - "sync" "time" + + "github.com/Dreamacro/clash/common/pool" ) func init() { @@ -18,8 +19,6 @@ const ( chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 ) -var bufPool = sync.Pool{New: func() interface{} { return make([]byte, 2048) }} - // TLSObfs is shadowsocks tls simple-obfs implementation type TLSObfs struct { net.Conn @@ -30,12 +29,12 @@ type TLSObfs struct { } func (to *TLSObfs) read(b []byte, discardN int) (int, error) { - buf := bufPool.Get().([]byte) + buf := pool.BufPool.Get().([]byte) _, err := io.ReadFull(to.Conn, buf[:discardN]) if err != nil { return 0, err } - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) sizeBuf := make([]byte, 2) _, err = io.ReadFull(to.Conn, sizeBuf) @@ -103,7 +102,7 @@ func (to *TLSObfs) write(b []byte) (int, error) { return len(b), err } - size := bufPool.Get().([]byte) + size := pool.BufPool.Get().([]byte) binary.BigEndian.PutUint16(size[:2], uint16(len(b))) buf := &bytes.Buffer{} @@ -111,7 +110,7 @@ func (to *TLSObfs) write(b []byte) (int, error) { buf.Write(size[:2]) buf.Write(b) _, err := to.Conn.Write(buf.Bytes()) - bufPool.Put(size[:cap(size)]) + pool.BufPool.Put(size[:cap(size)]) return len(b), err } diff --git a/component/vmess/aead.go b/component/vmess/aead.go index 342f373e0b..5c8da0deb0 100644 --- a/component/vmess/aead.go +++ b/component/vmess/aead.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "errors" "io" + + "github.com/Dreamacro/clash/common/pool" ) type aeadWriter struct { @@ -20,8 +22,8 @@ func newAEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) *aeadWriter { } func (w *aeadWriter) Write(b []byte) (n int, err error) { - buf := bufPool.Get().([]byte) - defer bufPool.Put(buf[:cap(buf)]) + buf := pool.BufPool.Get().([]byte) + defer pool.BufPool.Put(buf[:cap(buf)]) length := len(b) for { if length == 0 { @@ -71,7 +73,7 @@ func (r *aeadReader) Read(b []byte) (int, error) { n := copy(b, r.buf[r.offset:]) r.offset += n if r.offset == len(r.buf) { - bufPool.Put(r.buf[:cap(r.buf)]) + pool.BufPool.Put(r.buf[:cap(r.buf)]) r.buf = nil } return n, nil @@ -87,10 +89,10 @@ func (r *aeadReader) Read(b []byte) (int, error) { return 0, errors.New("Buffer is larger than standard") } - buf := bufPool.Get().([]byte) + buf := pool.BufPool.Get().([]byte) _, err = io.ReadFull(r.Reader, buf[:size]) if err != nil { - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) return 0, err } @@ -105,7 +107,7 @@ func (r *aeadReader) Read(b []byte) (int, error) { realLen := size - r.Overhead() n := copy(b, buf[:realLen]) if len(b) >= realLen { - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) return n, nil } diff --git a/component/vmess/chunk.go b/component/vmess/chunk.go index b396fdd6e6..7f30d0f0fd 100644 --- a/component/vmess/chunk.go +++ b/component/vmess/chunk.go @@ -4,7 +4,8 @@ import ( "encoding/binary" "errors" "io" - "sync" + + "github.com/Dreamacro/clash/common/pool" ) const ( @@ -13,8 +14,6 @@ const ( maxSize = 17 * 1024 // 2 + chunkSize + aead.Overhead() ) -var bufPool = sync.Pool{New: func() interface{} { return make([]byte, maxSize) }} - type chunkReader struct { io.Reader buf []byte @@ -35,7 +34,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) { n := copy(b, cr.buf[cr.offset:]) cr.offset += n if cr.offset == len(cr.buf) { - bufPool.Put(cr.buf[:cap(cr.buf)]) + pool.BufPool.Put(cr.buf[:cap(cr.buf)]) cr.buf = nil } return n, nil @@ -60,10 +59,10 @@ func (cr *chunkReader) Read(b []byte) (int, error) { return size, nil } - buf := bufPool.Get().([]byte) + buf := pool.BufPool.Get().([]byte) _, err = io.ReadFull(cr.Reader, buf[:size]) if err != nil { - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) return 0, err } n := copy(b, cr.buf[:]) @@ -77,8 +76,8 @@ type chunkWriter struct { } func (cw *chunkWriter) Write(b []byte) (n int, err error) { - buf := bufPool.Get().([]byte) - defer bufPool.Put(buf[:cap(buf)]) + buf := pool.BufPool.Get().([]byte) + defer pool.BufPool.Put(buf[:cap(buf)]) length := len(b) for { if length == 0 { diff --git a/constant/adapters.go b/constant/adapters.go index be755554a5..e05cd58ed8 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -20,14 +20,16 @@ const ( ) type ServerAdapter interface { + net.Conn Metadata() *Metadata - Close() } type ProxyAdapter interface { Name() string Type() AdapterType Dial(metadata *Metadata) (net.Conn, error) + DialUDP(metadata *Metadata) (net.PacketConn, net.Addr, error) + SupportUDP() bool Destroy() MarshalJSON() ([]byte, error) } diff --git a/go.mod b/go.mod index 31c879ec2c..43fa2bb031 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/Dreamacro/clash require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 + github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.0.1+incompatible github.com/go-chi/cors v1.0.0 diff --git a/go.sum b/go.sum index 2a2f1273e8..ac71fa0c50 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112 h1:1axYxE0ZLJy40+ulq46XQt7MaJDJr4iGer1NQz7jmKw= -github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190202135136-da4602d8f112/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo= +github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f h1:nlImrmI6I2AVjJ2AvE3w3f7fi8rhLQAhZO1Gs31+/nE= +github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index ac88710a25..1c0fee6579 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -59,5 +59,5 @@ func handleRedir(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocket(target, conn, C.REDIR)) + tun.Add(adapters.NewSocket(target, conn, C.REDIR, C.TCP)) } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 83220c091f..3d884ff58d 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -1,9 +1,11 @@ package socks import ( + "io" "net" + "strconv" - "github.com/Dreamacro/clash/adapters/inbound" + adapters "github.com/Dreamacro/clash/adapters/inbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" @@ -15,6 +17,41 @@ var ( tun = tunnel.Instance() ) +// Error represents a SOCKS error +type Error byte + +func (err Error) Error() string { + return "SOCKS error: " + strconv.Itoa(int(err)) +} + +// SOCKS request commands as defined in RFC 1928 section 4. +const ( + CmdConnect = 1 + CmdBind = 2 + CmdUDPAssociate = 3 +) + +// SOCKS address types as defined in RFC 1928 section 5. +const ( + AtypIPv4 = 1 + AtypDomainName = 3 + AtypIPv6 = 4 +) + +const MaxAddrLen = 1 + 1 + 255 + 2 + +// SOCKS errors as defined in RFC 1928 section 6. +const ( + ErrGeneralFailure = Error(1) + ErrConnectionNotAllowed = Error(2) + ErrNetworkUnreachable = Error(3) + ErrHostUnreachable = Error(4) + ErrConnectionRefused = Error(5) + ErrTTLExpired = Error(6) + ErrCommandNotSupported = Error(7) + ErrAddressNotSupported = Error(8) +) + type SockListener struct { net.Listener address string @@ -55,11 +92,78 @@ func (l *SockListener) Address() string { } func handleSocks(conn net.Conn) { - target, err := socks.Handshake(conn) + target, command, err := handshake(conn) if err != nil { conn.Close() return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocket(target, conn, C.SOCKS)) + if command == CmdUDPAssociate { + tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP)) + return + } + tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) +} + +// handshake fast-tracks SOCKS initialization to get target address to connect. +func handshake(rw io.ReadWriter) (addr socks.Addr, command int, err error) { + // Read RFC 1928 for request and reply structure and sizes. + buf := make([]byte, MaxAddrLen) + // read VER, NMETHODS, METHODS + if _, err = io.ReadFull(rw, buf[:2]); err != nil { + return + } + nmethods := buf[1] + if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil { + return + } + // write VER METHOD + if _, err = rw.Write([]byte{5, 0}); err != nil { + return + } + // read VER CMD RSV ATYP DST.ADDR DST.PORT + if _, err = io.ReadFull(rw, buf[:3]); err != nil { + return + } + if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate { + err = ErrCommandNotSupported + return + } + + command = int(buf[1]) + addr, err = readAddr(rw, buf) + if err != nil { + return + } + // write VER REP RSV ATYP BND.ADDR BND.PORT + _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) + return +} + +func readAddr(r io.Reader, b []byte) (socks.Addr, error) { + if len(b) < MaxAddrLen { + return nil, io.ErrShortBuffer + } + _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type + if err != nil { + return nil, err + } + + switch b[0] { + case AtypDomainName: + _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length + if err != nil { + return nil, err + } + _, err = io.ReadFull(r, b[2:2+b[1]+2]) + return b[:1+1+b[1]+2], err + case AtypIPv4: + _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) + return b[:1+net.IPv4len+2], err + case AtypIPv6: + _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) + return b[:1+net.IPv6len+2], err + } + + return nil, ErrAddressNotSupported } diff --git a/tunnel/connection.go b/tunnel/connection.go index 25c3656e42..a8928a624e 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -6,21 +6,12 @@ import ( "net" "net/http" "strings" - "sync" "time" adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/common/pool" ) -const ( - // io.Copy default buffer size is 32 KiB - // but the maximum packet size of vmess/shadowsocks is about 16 KiB - // so define a buffer of 20 KiB to reduce the memory of each TCP relay - bufferSize = 20 * 1024 -) - -var bufPool = sync.Pool{New: func() interface{} { return make([]byte, bufferSize) }} - func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { conn := newTrafficTrack(outbound, t.traffic) req := request.R @@ -50,7 +41,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } else { resp.Close = true } - err = resp.Write(request.Conn()) + err = resp.Write(request) if err != nil || resp.Close { break } @@ -59,7 +50,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } - req, err = http.ReadRequest(bufio.NewReader(request.Conn())) + req, err = http.ReadRequest(bufio.NewReader(request)) if err != nil { break } @@ -72,9 +63,52 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } } -func (t *Tunnel) handleSOCKS(request *adapters.SocketAdapter, outbound net.Conn) { +func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { conn := newTrafficTrack(outbound, t.traffic) - relay(request.Conn(), conn) + relay(request, conn) +} + +func (t *Tunnel) handleUDPOverTCP(conn net.Conn, pc net.PacketConn, addr net.Addr) error { + ch := make(chan error, 1) + + go func() { + buf := pool.BufPool.Get().([]byte) + defer pool.BufPool.Put(buf) + for { + n, err := conn.Read(buf) + if err != nil { + ch <- err + return + } + pc.SetReadDeadline(time.Now().Add(120 * time.Second)) + if _, err = pc.WriteTo(buf[:n], addr); err != nil { + ch <- err + return + } + t.traffic.Up() <- int64(n) + ch <- nil + } + }() + + buf := pool.BufPool.Get().([]byte) + defer pool.BufPool.Put(buf) + + for { + pc.SetReadDeadline(time.Now().Add(120 * time.Second)) + n, _, err := pc.ReadFrom(buf) + if err != nil { + break + } + + if _, err := conn.Write(buf[:n]); err != nil { + break + } + + t.traffic.Down() <- int64(n) + } + + <-ch + return nil } // relay copies between left and right bidirectionally. @@ -82,16 +116,16 @@ func relay(leftConn, rightConn net.Conn) { ch := make(chan error) go func() { - buf := bufPool.Get().([]byte) + buf := pool.BufPool.Get().([]byte) _, err := io.CopyBuffer(leftConn, rightConn, buf) - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) leftConn.SetReadDeadline(time.Now()) ch <- err }() - buf := bufPool.Get().([]byte) + buf := pool.BufPool.Get().([]byte) io.CopyBuffer(rightConn, leftConn, buf) - bufPool.Put(buf[:cap(buf)]) + pool.BufPool.Put(buf[:cap(buf)]) rightConn.SetReadDeadline(time.Now()) <-ch } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 6fec24eb5e..775f2f1c01 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -19,7 +19,7 @@ var ( once sync.Once ) -// Tunnel handle proxy socket and HTTP/SOCKS socket +// Tunnel handle relay inbound proxy and outbound proxy type Tunnel struct { queue *channels.InfiniteChannel rules []C.Rule @@ -143,6 +143,12 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } } + if metadata.NetWork == C.UDP { + pc, addr, _ := proxy.DialUDP(metadata) + t.handleUDPOverTCP(localConn, pc, addr) + return + } + remoConn, err := proxy.Dial(metadata) if err != nil { log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) @@ -154,7 +160,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { case *InboundAdapter.HTTPAdapter: t.handleHTTP(adapter, remoConn) case *InboundAdapter.SocketAdapter: - t.handleSOCKS(adapter, remoConn) + t.handleSocket(adapter, remoConn) } } @@ -177,10 +183,17 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { } if rule.IsMatch(metadata) { - if a, ok := t.proxies[rule.Adapter()]; ok { - log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter()) - return a, nil + adapter, ok := t.proxies[rule.Adapter()] + if !ok { + continue } + + if metadata.NetWork == C.UDP && !adapter.SupportUDP() { + continue + } + + log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter()) + return adapter, nil } } log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SourceIP.String(), metadata.String()) From 90e3dccacdca2e356c5393a0b723c5591d128c68 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 24 Apr 2019 10:29:29 +0800 Subject: [PATCH 170/535] Fix: add missing error check --- adapters/outbound/direct.go | 5 ++++- adapters/outbound/shadowsocks.go | 11 +++++++++-- tunnel/tunnel.go | 7 ++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 1bd0a6f660..6cf18c09e8 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -30,7 +30,10 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) return nil, nil, err } - addr, _ := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + if err != nil { + return nil, nil, err + } return pc, addr, nil } diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index f477d9d363..b51f235441 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -88,8 +88,15 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, return nil, nil, err } - addr, _ := net.ResolveUDPAddr("udp", ss.server) - remoteAddr, _ := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + addr, err := net.ResolveUDPAddr("udp", ss.server) + if err != nil { + return nil, nil, err + } + + remoteAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + if err != nil { + return nil, nil, err + } pc = ss.cipher.PacketConn(pc) return &ssUDPConn{PacketConn: pc, rAddr: remoteAddr}, addr, nil diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 775f2f1c01..7af3daf34c 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -144,7 +144,12 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } if metadata.NetWork == C.UDP { - pc, addr, _ := proxy.DialUDP(metadata) + pc, addr, err := proxy.DialUDP(metadata) + defer pc.Close() + if err != nil { + log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) + } + t.handleUDPOverTCP(localConn, pc, addr) return } From cec220677455a0851b2d16be6f298664b2556c85 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 24 Apr 2019 12:02:52 +0800 Subject: [PATCH 171/535] Feature: add experimental config for resolving ip fail behavior --- README.md | 4 +++ config/config.go | 27 +++++++++++++------ hub/executor/executor.go | 5 ++++ proxy/socks/udp.go | 1 - tunnel/tunnel.go | 58 +++++++++++++++++++++++++--------------- 5 files changed, 65 insertions(+), 30 deletions(-) delete mode 100644 proxy/socks/udp.go diff --git a/README.md b/README.md index 7e834d34ed..3b753a6d03 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,10 @@ external-controller: 127.0.0.1:9090 # Secret for RESTful API (Optional) # secret: "" +# experimental feature +experimental: + ignore-resolve-fail: true # ignore dns reslove fail, default value is true + # dns: # enable: true # set true to enable dns (default is false) # ipv6: false # default is false diff --git a/config/config.go b/config/config.go index 190478a178..ec82f788c5 100644 --- a/config/config.go +++ b/config/config.go @@ -43,12 +43,18 @@ type DNS struct { EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` } +// Experimental config +type Experimental struct { + IgnoreResolveFail bool `yaml:"ignore-resolve-fail"` +} + // Config is clash config manager type Config struct { - General *General - DNS *DNS - Rules []C.Rule - Proxies map[string]C.Proxy + General *General + DNS *DNS + Experimental *Experimental + Rules []C.Rule + Proxies map[string]C.Proxy } type rawDNS struct { @@ -71,10 +77,11 @@ type rawConfig struct { ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` - DNS rawDNS `yaml:"dns"` - Proxy []map[string]interface{} `yaml:"Proxy"` - ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` - Rule []string `yaml:"Rule"` + DNS rawDNS `yaml:"dns"` + Experimental Experimental `yaml:"experimental"` + Proxy []map[string]interface{} `yaml:"Proxy"` + ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` + Rule []string `yaml:"Rule"` } func readConfig(path string) (*rawConfig, error) { @@ -98,6 +105,9 @@ func readConfig(path string) (*rawConfig, error) { Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, + Experimental: Experimental{ + IgnoreResolveFail: true, + }, DNS: rawDNS{ Enable: false, }, @@ -114,6 +124,7 @@ func Parse(path string) (*Config, error) { if err != nil { return nil, err } + config.Experimental = &rawCfg.Experimental general, err := parseGeneral(rawCfg) if err != nil { diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 936bf4c680..6bb370c9d4 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -27,6 +27,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateProxies(cfg.Proxies) updateRules(cfg.Rules) updateDNS(cfg.DNS) + updateExperimental(cfg.Experimental) } func GetGeneral() *config.General { @@ -41,6 +42,10 @@ func GetGeneral() *config.General { } } +func updateExperimental(c *config.Experimental) { + T.Instance().UpdateExperimental(c.IgnoreResolveFail) +} + func updateDNS(c *config.DNS) { if c.Enable == false { T.Instance().SetResolver(nil) diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go deleted file mode 100644 index a266580e17..0000000000 --- a/proxy/socks/udp.go +++ /dev/null @@ -1 +0,0 @@ -package socks diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 7af3daf34c..ec5e2b2cba 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -21,12 +21,15 @@ var ( // Tunnel handle relay inbound proxy and outbound proxy type Tunnel struct { - queue *channels.InfiniteChannel - rules []C.Rule - proxies map[string]C.Proxy - configLock *sync.RWMutex - traffic *C.Traffic - resolver *dns.Resolver + queue *channels.InfiniteChannel + rules []C.Rule + proxies map[string]C.Proxy + configMux *sync.RWMutex + traffic *C.Traffic + resolver *dns.Resolver + + // experimental features + ignoreResolveFail bool // Outbound Rule mode Mode @@ -49,9 +52,9 @@ func (t *Tunnel) Rules() []C.Rule { // UpdateRules handle update rules func (t *Tunnel) UpdateRules(rules []C.Rule) { - t.configLock.Lock() + t.configMux.Lock() t.rules = rules - t.configLock.Unlock() + t.configMux.Unlock() } // Proxies return all proxies @@ -61,9 +64,16 @@ func (t *Tunnel) Proxies() map[string]C.Proxy { // UpdateProxies handle update proxies func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy) { - t.configLock.Lock() + t.configMux.Lock() t.proxies = proxies - t.configLock.Unlock() + t.configMux.Unlock() +} + +// UpdateExperimental handle update experimental config +func (t *Tunnel) UpdateExperimental(ignoreResolveFail bool) { + t.configMux.Lock() + t.ignoreResolveFail = ignoreResolveFail + t.configMux.Unlock() } // Mode return current mode @@ -174,17 +184,23 @@ func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { } func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { - t.configLock.RLock() - defer t.configLock.RUnlock() + t.configMux.RLock() + defer t.configMux.RUnlock() + var resolved bool for _, rule := range t.rules { - if t.shouldResolveIP(rule, metadata) { + if !resolved && t.shouldResolveIP(rule, metadata) { ip, err := t.resolveIP(metadata.Host) if err != nil { - return nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error()) + if !t.ignoreResolveFail { + return nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error()) + } + log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) + } else { + log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) + metadata.IP = &ip } - log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) - metadata.IP = &ip + resolved = true } if rule.IsMatch(metadata) { @@ -207,11 +223,11 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { func newTunnel() *Tunnel { return &Tunnel{ - queue: channels.NewInfiniteChannel(), - proxies: make(map[string]C.Proxy), - configLock: &sync.RWMutex{}, - traffic: C.NewTraffic(time.Second), - mode: Rule, + queue: channels.NewInfiniteChannel(), + proxies: make(map[string]C.Proxy), + configMux: &sync.RWMutex{}, + traffic: C.NewTraffic(time.Second), + mode: Rule, } } From 936ea3aa5506fe70bafb3571312872c2b049052c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 25 Apr 2019 13:48:47 +0800 Subject: [PATCH 172/535] Feature: support outbound socks5 udp --- README.md | 2 +- adapters/inbound/socket.go | 4 +- adapters/inbound/util.go | 10 +- adapters/outbound/shadowsocks.go | 27 +--- adapters/outbound/socks5.go | 81 ++++------ adapters/outbound/util.go | 36 +++++ component/socks5/socks5.go | 246 +++++++++++++++++++++++++++++++ proxy/redir/tcp_darwin.go | 6 +- proxy/redir/tcp_freebsd.go | 10 +- proxy/redir/tcp_linux.go | 10 +- proxy/redir/tcp_windows.go | 4 +- proxy/socks/tcp.go | 107 +------------- 12 files changed, 337 insertions(+), 206 deletions(-) create mode 100644 component/socks5/socks5.go diff --git a/README.md b/README.md index 3b753a6d03..6197d73cb0 100644 --- a/README.md +++ b/README.md @@ -230,5 +230,5 @@ https://clash.gitbook.io/ - [x] Complementing the necessary rule operators - [x] Redir proxy -- [ ] UDP support (vmess, outbound socks5) +- [ ] UDP support (vmess) - [ ] Connection manager diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 0d1be44bba..fae80e0148 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -3,8 +3,8 @@ package adapters import ( "net" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/go-shadowsocks2/socks" ) // SocketAdapter is a adapter for socks and redir connection @@ -19,7 +19,7 @@ func (s *SocketAdapter) Metadata() *C.Metadata { } // NewSocket is SocketAdapter generator -func NewSocket(target socks.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter { +func NewSocket(target socks5.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter { metadata := parseSocksAddr(target) metadata.NetWork = netType metadata.Source = source diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index c29c06c851..d5bca74d59 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -5,24 +5,24 @@ import ( "net/http" "strconv" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/go-shadowsocks2/socks" ) -func parseSocksAddr(target socks.Addr) *C.Metadata { +func parseSocksAddr(target socks5.Addr) *C.Metadata { metadata := &C.Metadata{ AddrType: int(target[0]), } switch target[0] { - case socks.AtypDomainName: + case socks5.AtypDomainName: metadata.Host = string(target[2 : 2+target[1]]) metadata.Port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) - case socks.AtypIPv4: + case socks5.AtypIPv4: ip := net.IP(target[1 : 1+net.IPv4len]) metadata.IP = &ip metadata.Port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) - case socks.AtypIPv6: + case socks5.AtypIPv6: ip := net.IP(target[1 : 1+net.IPv6len]) metadata.IP = &ip metadata.Port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index b51f235441..8c8e92bfb2 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -1,7 +1,6 @@ package adapters import ( - "bytes" "crypto/tls" "encoding/json" "fmt" @@ -11,11 +10,11 @@ import ( "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/structure" obfs "github.com/Dreamacro/clash/component/simple-obfs" + "github.com/Dreamacro/clash/component/socks5" v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/go-shadowsocks2/core" - "github.com/Dreamacro/go-shadowsocks2/socks" ) type ShadowSocks struct { @@ -177,26 +176,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { }, nil } -func serializesSocksAddr(metadata *C.Metadata) []byte { - var buf [][]byte - aType := uint8(metadata.AddrType) - p, _ := strconv.Atoi(metadata.Port) - port := []byte{uint8(p >> 8), uint8(p & 0xff)} - switch metadata.AddrType { - case socks.AtypDomainName: - len := uint8(len(metadata.Host)) - host := []byte(metadata.Host) - buf = [][]byte{{aType, len}, host, port} - case socks.AtypIPv4: - host := metadata.IP.To4() - buf = [][]byte{{aType}, host, port} - case socks.AtypIPv6: - host := metadata.IP.To16() - buf = [][]byte{{aType}, host, port} - } - return bytes.Join(buf, nil) -} - type ssUDPConn struct { net.PacketConn rAddr net.Addr @@ -205,7 +184,7 @@ type ssUDPConn struct { func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { buf := pool.BufPool.Get().([]byte) defer pool.BufPool.Put(buf[:cap(buf)]) - rAddr := socks.ParseAddr(uc.rAddr.String()) + rAddr := socks5.ParseAddr(uc.rAddr.String()) copy(buf[len(rAddr):], b) copy(buf, rAddr) return uc.PacketConn.WriteTo(buf[:len(rAddr)+len(b)], addr) @@ -213,7 +192,7 @@ func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { n, a, e := uc.PacketConn.ReadFrom(b) - addr := socks.SplitAddr(b[:n]) + addr := socks5.SplitAddr(b[:n]) copy(b, b[len(addr):]) return n - len(addr), a, e } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index bd8680d5f3..7f52b58eb1 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -1,17 +1,13 @@ package adapters import ( - "bytes" "crypto/tls" - "errors" "fmt" - "io" "net" "strconv" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - - "github.com/Dreamacro/go-shadowsocks2/socks" ) type Socks5 struct { @@ -48,69 +44,44 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { return nil, fmt.Errorf("%s connect error", ss.addr) } tcpKeepAlive(c) - if err := ss.shakeHand(metadata, c); err != nil { + var user *socks5.User + if ss.user != "" { + user = &socks5.User{ + Username: ss.user, + Password: ss.pass, + } + } + if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { return nil, err } return c, nil } -func (ss *Socks5) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { - buf := make([]byte, socks.MaxAddrLen) - var err error - - // VER, NMETHODS, METHODS - if len(ss.user) > 0 { - _, err = rw.Write([]byte{5, 1, 2}) - } else { - _, err = rw.Write([]byte{5, 1, 0}) - } - if err != nil { - return err - } +func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) - // VER, METHOD - if _, err := io.ReadFull(rw, buf[:2]); err != nil { - return err + if err == nil && ss.tls { + cc := tls.Client(c, ss.tlsConfig) + err = cc.Handshake() + c = cc } - if buf[0] != 5 { - return errors.New("SOCKS version error") + if err != nil { + return nil, nil, fmt.Errorf("%s connect error", ss.addr) } - - if buf[1] == 2 { - // password protocol version - authMsg := &bytes.Buffer{} - authMsg.WriteByte(1) - authMsg.WriteByte(uint8(len(ss.user))) - authMsg.WriteString(ss.user) - authMsg.WriteByte(uint8(len(ss.pass))) - authMsg.WriteString(ss.pass) - - if _, err := rw.Write(authMsg.Bytes()); err != nil { - return err - } - - if _, err := io.ReadFull(rw, buf[:2]); err != nil { - return err - } - - if buf[1] != 0 { - return errors.New("rejected username/password") + tcpKeepAlive(c) + var user *socks5.User + if ss.user != "" { + user = &socks5.User{ + Username: ss.user, + Password: ss.pass, } - } else if buf[1] != 0 { - return errors.New("SOCKS need auth") } - // VER, CMD, RSV, ADDR - if _, err := rw.Write(bytes.Join([][]byte{{5, 1, 0}, serializesSocksAddr(metadata)}, []byte(""))); err != nil { - return err + if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user); err != nil { + return nil, nil, err } - - if _, err := io.ReadFull(rw, buf[:10]); err != nil { - return err - } - - return nil + return &fakeUDPConn{Conn: c}, c.LocalAddr(), nil } func NewSocks5(option Socks5Option) *Socks5 { diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 363bcc3e34..94d19d4ba7 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -1,13 +1,16 @@ package adapters import ( + "bytes" "crypto/tls" "fmt" "net" "net/url" + "strconv" "sync" "time" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" ) @@ -60,3 +63,36 @@ func getClientSessionCache() tls.ClientSessionCache { }) return globalClientSessionCache } + +func serializesSocksAddr(metadata *C.Metadata) []byte { + var buf [][]byte + aType := uint8(metadata.AddrType) + p, _ := strconv.Atoi(metadata.Port) + port := []byte{uint8(p >> 8), uint8(p & 0xff)} + switch metadata.AddrType { + case socks5.AtypDomainName: + len := uint8(len(metadata.Host)) + host := []byte(metadata.Host) + buf = [][]byte{{aType, len}, host, port} + case socks5.AtypIPv4: + host := metadata.IP.To4() + buf = [][]byte{{aType}, host, port} + case socks5.AtypIPv6: + host := metadata.IP.To16() + buf = [][]byte{{aType}, host, port} + } + return bytes.Join(buf, nil) +} + +type fakeUDPConn struct { + net.Conn +} + +func (fuc *fakeUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return fuc.Conn.Write(b) +} + +func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, err := fuc.Conn.Read(b) + return n, fuc.RemoteAddr(), err +} diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go new file mode 100644 index 0000000000..0d909e6342 --- /dev/null +++ b/component/socks5/socks5.go @@ -0,0 +1,246 @@ +package socks5 + +import ( + "bytes" + "errors" + "io" + "net" + "strconv" +) + +// Error represents a SOCKS error +type Error byte + +func (err Error) Error() string { + return "SOCKS error: " + strconv.Itoa(int(err)) +} + +// Command is request commands as defined in RFC 1928 section 4. +type Command = uint8 + +// SOCKS request commands as defined in RFC 1928 section 4. +const ( + CmdConnect Command = 1 + CmdBind Command = 2 + CmdUDPAssociate Command = 3 +) + +// SOCKS address types as defined in RFC 1928 section 5. +const ( + AtypIPv4 = 1 + AtypDomainName = 3 + AtypIPv6 = 4 +) + +// MaxAddrLen is the maximum size of SOCKS address in bytes. +const MaxAddrLen = 1 + 1 + 255 + 2 + +// Addr represents a SOCKS address as defined in RFC 1928 section 5. +type Addr = []byte + +// SOCKS errors as defined in RFC 1928 section 6. +const ( + ErrGeneralFailure = Error(1) + ErrConnectionNotAllowed = Error(2) + ErrNetworkUnreachable = Error(3) + ErrHostUnreachable = Error(4) + ErrConnectionRefused = Error(5) + ErrTTLExpired = Error(6) + ErrCommandNotSupported = Error(7) + ErrAddressNotSupported = Error(8) +) + +type User struct { + Username string + Password string +} + +// ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side. +func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) { + // Read RFC 1928 for request and reply structure and sizes. + buf := make([]byte, MaxAddrLen) + // read VER, NMETHODS, METHODS + if _, err = io.ReadFull(rw, buf[:2]); err != nil { + return + } + nmethods := buf[1] + if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil { + return + } + // write VER METHOD + if _, err = rw.Write([]byte{5, 0}); err != nil { + return + } + // read VER CMD RSV ATYP DST.ADDR DST.PORT + if _, err = io.ReadFull(rw, buf[:3]); err != nil { + return + } + + if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate { + err = ErrCommandNotSupported + return + } + + command = buf[1] + addr, err = readAddr(rw, buf) + if err != nil { + return + } + // write VER REP RSV ATYP BND.ADDR BND.PORT + _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) + return +} + +// ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side. +func ClientHandshake(rw io.ReadWriter, addr Addr, cammand Command, user *User) error { + buf := make([]byte, MaxAddrLen) + var err error + + // VER, NMETHODS, METHODS + if user != nil { + _, err = rw.Write([]byte{5, 1, 2}) + } else { + _, err = rw.Write([]byte{5, 1, 0}) + } + if err != nil { + return err + } + + // VER, METHOD + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return err + } + + if buf[0] != 5 { + return errors.New("SOCKS version error") + } + + if buf[1] == 2 { + // password protocol version + authMsg := &bytes.Buffer{} + authMsg.WriteByte(1) + authMsg.WriteByte(uint8(len(user.Username))) + authMsg.WriteString(user.Username) + authMsg.WriteByte(uint8(len(user.Password))) + authMsg.WriteString(user.Password) + + if _, err := rw.Write(authMsg.Bytes()); err != nil { + return err + } + + if _, err := io.ReadFull(rw, buf[:2]); err != nil { + return err + } + + if buf[1] != 0 { + return errors.New("rejected username/password") + } + } else if buf[1] != 0 { + return errors.New("SOCKS need auth") + } + + // VER, CMD, RSV, ADDR + if _, err := rw.Write(bytes.Join([][]byte{{5, cammand, 0}, addr}, []byte(""))); err != nil { + return err + } + + if _, err := io.ReadFull(rw, buf[:10]); err != nil { + return err + } + + return nil +} + +func readAddr(r io.Reader, b []byte) (Addr, error) { + if len(b) < MaxAddrLen { + return nil, io.ErrShortBuffer + } + _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type + if err != nil { + return nil, err + } + + switch b[0] { + case AtypDomainName: + _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length + if err != nil { + return nil, err + } + _, err = io.ReadFull(r, b[2:2+b[1]+2]) + return b[:1+1+b[1]+2], err + case AtypIPv4: + _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) + return b[:1+net.IPv4len+2], err + case AtypIPv6: + _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) + return b[:1+net.IPv6len+2], err + } + + return nil, ErrAddressNotSupported +} + +// SplitAddr slices a SOCKS address from beginning of b. Returns nil if failed. +func SplitAddr(b []byte) Addr { + addrLen := 1 + if len(b) < addrLen { + return nil + } + + switch b[0] { + case AtypDomainName: + if len(b) < 2 { + return nil + } + addrLen = 1 + 1 + int(b[1]) + 2 + case AtypIPv4: + addrLen = 1 + net.IPv4len + 2 + case AtypIPv6: + addrLen = 1 + net.IPv6len + 2 + default: + return nil + + } + + if len(b) < addrLen { + return nil + } + + return b[:addrLen] +} + +// ParseAddr parses the address in string s. Returns nil if failed. +func ParseAddr(s string) Addr { + var addr Addr + host, port, err := net.SplitHostPort(s) + if err != nil { + return nil + } + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + addr = make([]byte, 1+net.IPv4len+2) + addr[0] = AtypIPv4 + copy(addr[1:], ip4) + } else { + addr = make([]byte, 1+net.IPv6len+2) + addr[0] = AtypIPv6 + copy(addr[1:], ip) + } + } else { + if len(host) > 255 { + return nil + } + addr = make([]byte, 1+1+len(host)+2) + addr[0] = AtypDomainName + addr[1] = byte(len(host)) + copy(addr[2:], host) + } + + portnum, err := strconv.ParseUint(port, 10, 16) + if err != nil { + return nil + } + + addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum) + + return addr +} diff --git a/proxy/redir/tcp_darwin.go b/proxy/redir/tcp_darwin.go index f032a03d1e..4e9ade1e5d 100644 --- a/proxy/redir/tcp_darwin.go +++ b/proxy/redir/tcp_darwin.go @@ -5,10 +5,10 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) -func parserPacket(c net.Conn) (socks.Addr, error) { +func parserPacket(c net.Conn) (socks5.Addr, error) { const ( PfInout = 0 PfIn = 1 @@ -51,7 +51,7 @@ func parserPacket(c net.Conn) (socks.Addr, error) { } addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 + addr[0] = socks5.AtypIPv4 copy(addr[1:1+net.IPv4len], nl.rdaddr[:4]) copy(addr[1+net.IPv4len:], nl.rdxport[:2]) return addr, nil diff --git a/proxy/redir/tcp_freebsd.go b/proxy/redir/tcp_freebsd.go index 1f90121b55..f9cdf5a6a9 100644 --- a/proxy/redir/tcp_freebsd.go +++ b/proxy/redir/tcp_freebsd.go @@ -6,7 +6,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) const ( @@ -14,7 +14,7 @@ const ( IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h ) -func parserPacket(conn net.Conn) (socks.Addr, error) { +func parserPacket(conn net.Conn) (socks5.Addr, error) { c, ok := conn.(*net.TCPConn) if !ok { return nil, errors.New("only work with TCP connection") @@ -25,7 +25,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { return nil, err } - var addr socks.Addr + var addr socks5.Addr rc.Control(func(fd uintptr) { addr, err = getorigdst(fd) @@ -35,7 +35,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { } // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c -func getorigdst(fd uintptr) (socks.Addr, error) { +func getorigdst(fd uintptr) (socks5.Addr, error) { raw := syscall.RawSockaddrInet4{} siz := unsafe.Sizeof(raw) _, _, err := syscall.Syscall6(syscall.SYS_GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0) @@ -44,7 +44,7 @@ func getorigdst(fd uintptr) (socks.Addr, error) { } addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 + addr[0] = socks5.AtypIPv4 copy(addr[1:1+net.IPv4len], raw.Addr[:]) port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] diff --git a/proxy/redir/tcp_linux.go b/proxy/redir/tcp_linux.go index 1cd5a3ee54..ac3a455c3a 100644 --- a/proxy/redir/tcp_linux.go +++ b/proxy/redir/tcp_linux.go @@ -6,7 +6,7 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) const ( @@ -14,7 +14,7 @@ const ( IP6T_SO_ORIGINAL_DST = 80 // from linux/include/uapi/linux/netfilter_ipv6/ip6_tables.h ) -func parserPacket(conn net.Conn) (socks.Addr, error) { +func parserPacket(conn net.Conn) (socks5.Addr, error) { c, ok := conn.(*net.TCPConn) if !ok { return nil, errors.New("only work with TCP connection") @@ -25,7 +25,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { return nil, err } - var addr socks.Addr + var addr socks5.Addr rc.Control(func(fd uintptr) { addr, err = getorigdst(fd) @@ -35,7 +35,7 @@ func parserPacket(conn net.Conn) (socks.Addr, error) { } // Call getorigdst() from linux/net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c -func getorigdst(fd uintptr) (socks.Addr, error) { +func getorigdst(fd uintptr) (socks5.Addr, error) { raw := syscall.RawSockaddrInet4{} siz := unsafe.Sizeof(raw) if err := socketcall(GETSOCKOPT, fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&raw)), uintptr(unsafe.Pointer(&siz)), 0); err != nil { @@ -43,7 +43,7 @@ func getorigdst(fd uintptr) (socks.Addr, error) { } addr := make([]byte, 1+net.IPv4len+2) - addr[0] = socks.AtypIPv4 + addr[0] = socks5.AtypIPv4 copy(addr[1:1+net.IPv4len], raw.Addr[:]) port := (*[2]byte)(unsafe.Pointer(&raw.Port)) // big-endian addr[1+net.IPv4len], addr[1+net.IPv4len+1] = port[0], port[1] diff --git a/proxy/redir/tcp_windows.go b/proxy/redir/tcp_windows.go index cce61c079b..58b3e91205 100644 --- a/proxy/redir/tcp_windows.go +++ b/proxy/redir/tcp_windows.go @@ -4,9 +4,9 @@ import ( "errors" "net" - "github.com/Dreamacro/go-shadowsocks2/socks" + "github.com/Dreamacro/clash/component/socks5" ) -func parserPacket(conn net.Conn) (socks.Addr, error) { +func parserPacket(conn net.Conn) (socks5.Addr, error) { return nil, errors.New("Windows not support yet") } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 3d884ff58d..08789cbe9c 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -1,57 +1,19 @@ package socks import ( - "io" "net" - "strconv" adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" - - "github.com/Dreamacro/go-shadowsocks2/socks" ) var ( tun = tunnel.Instance() ) -// Error represents a SOCKS error -type Error byte - -func (err Error) Error() string { - return "SOCKS error: " + strconv.Itoa(int(err)) -} - -// SOCKS request commands as defined in RFC 1928 section 4. -const ( - CmdConnect = 1 - CmdBind = 2 - CmdUDPAssociate = 3 -) - -// SOCKS address types as defined in RFC 1928 section 5. -const ( - AtypIPv4 = 1 - AtypDomainName = 3 - AtypIPv6 = 4 -) - -const MaxAddrLen = 1 + 1 + 255 + 2 - -// SOCKS errors as defined in RFC 1928 section 6. -const ( - ErrGeneralFailure = Error(1) - ErrConnectionNotAllowed = Error(2) - ErrNetworkUnreachable = Error(3) - ErrHostUnreachable = Error(4) - ErrConnectionRefused = Error(5) - ErrTTLExpired = Error(6) - ErrCommandNotSupported = Error(7) - ErrAddressNotSupported = Error(8) -) - type SockListener struct { net.Listener address string @@ -92,78 +54,15 @@ func (l *SockListener) Address() string { } func handleSocks(conn net.Conn) { - target, command, err := handshake(conn) + target, command, err := socks5.ServerHandshake(conn) if err != nil { conn.Close() return } conn.(*net.TCPConn).SetKeepAlive(true) - if command == CmdUDPAssociate { + if command == socks5.CmdUDPAssociate { tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP)) return } tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) } - -// handshake fast-tracks SOCKS initialization to get target address to connect. -func handshake(rw io.ReadWriter) (addr socks.Addr, command int, err error) { - // Read RFC 1928 for request and reply structure and sizes. - buf := make([]byte, MaxAddrLen) - // read VER, NMETHODS, METHODS - if _, err = io.ReadFull(rw, buf[:2]); err != nil { - return - } - nmethods := buf[1] - if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil { - return - } - // write VER METHOD - if _, err = rw.Write([]byte{5, 0}); err != nil { - return - } - // read VER CMD RSV ATYP DST.ADDR DST.PORT - if _, err = io.ReadFull(rw, buf[:3]); err != nil { - return - } - if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate { - err = ErrCommandNotSupported - return - } - - command = int(buf[1]) - addr, err = readAddr(rw, buf) - if err != nil { - return - } - // write VER REP RSV ATYP BND.ADDR BND.PORT - _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) - return -} - -func readAddr(r io.Reader, b []byte) (socks.Addr, error) { - if len(b) < MaxAddrLen { - return nil, io.ErrShortBuffer - } - _, err := io.ReadFull(r, b[:1]) // read 1st byte for address type - if err != nil { - return nil, err - } - - switch b[0] { - case AtypDomainName: - _, err = io.ReadFull(r, b[1:2]) // read 2nd byte for domain length - if err != nil { - return nil, err - } - _, err = io.ReadFull(r, b[2:2+b[1]+2]) - return b[:1+1+b[1]+2], err - case AtypIPv4: - _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) - return b[:1+net.IPv4len+2], err - case AtypIPv6: - _, err = io.ReadFull(r, b[1:1+net.IPv6len+2]) - return b[:1+net.IPv6len+2], err - } - - return nil, ErrAddressNotSupported -} From 762f2275124d4988907f774c1080a24d444d3ad7 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 25 Apr 2019 16:32:15 +0800 Subject: [PATCH 173/535] Feature: support vmess udp --- README.md | 2 +- adapters/outbound/vmess.go | 13 +++++++++++++ component/vmess/conn.go | 6 +++++- component/vmess/vmess.go | 1 + 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6197d73cb0..48ed9dc0e4 100644 --- a/README.md +++ b/README.md @@ -230,5 +230,5 @@ https://clash.gitbook.io/ - [x] Complementing the necessary rule operators - [x] Redir proxy -- [ ] UDP support (vmess) +- [x] UDP support - [ ] Connection manager diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 6c137432e4..823469f761 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -24,6 +24,7 @@ type VmessOption struct { AlterID int `proxy:"alterId"` Cipher string `proxy:"cipher"` TLS bool `proxy:"tls,omitempty"` + UDP bool `proxy:"udp,omitempty"` Network string `proxy:"network,omitempty"` WSPath string `proxy:"ws-path,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"` @@ -40,6 +41,16 @@ func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) { return c, err } +func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { + c, err := net.DialTimeout("tcp", v.server, tcpTimeout) + if err != nil { + return nil, nil, fmt.Errorf("%s connect error", v.server) + } + tcpKeepAlive(c) + c, err = v.client.New(c, parseVmessAddr(metadata)) + return &fakeUDPConn{Conn: c}, c.LocalAddr(), err +} + func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ @@ -63,6 +74,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { Base: &Base{ name: option.Name, tp: C.Vmess, + udp: option.UDP, }, server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), client: client, @@ -90,6 +102,7 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { port, _ := strconv.Atoi(metadata.Port) return &vmess.DstAddr{ + UDP: metadata.NetWork == C.UDP, AddrType: addrType, Addr: addr, Port: uint(port), diff --git a/component/vmess/conn.go b/component/vmess/conn.go index 15b1e05cb7..44ff57eeaa 100644 --- a/component/vmess/conn.go +++ b/component/vmess/conn.go @@ -77,7 +77,11 @@ func (vc *Conn) sendRequest() error { // P Sec Reserve Cmd buf.WriteByte(byte(p<<4) | byte(vc.security)) buf.WriteByte(0) - buf.WriteByte(CommandTCP) + if vc.dst.UDP { + buf.WriteByte(CommandUDP) + } else { + buf.WriteByte(CommandTCP) + } // Port AddrType Addr binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port)) diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 71b30c1563..23a7b508da 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -57,6 +57,7 @@ const ( // DstAddr store destination address type DstAddr struct { + UDP bool AddrType byte Addr []byte Port uint From f352f4479e2e947b17f500b07af59b6571c472aa Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 3 May 2019 00:05:14 +0800 Subject: [PATCH 174/535] Feature: support fakeip --- README.md | 2 +- component/fakeip/pool.go | 50 +++++++++++++++++++++++++++++++++++ component/fakeip/pool_test.go | 44 ++++++++++++++++++++++++++++++ config/config.go | 19 ++++++++++++- dns/client.go | 10 +++++++ dns/server.go | 50 +++++++++++++++++++++++++++++++++++ dns/util.go | 2 +- hub/executor/executor.go | 1 + tunnel/tunnel.go | 6 ++++- 9 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 component/fakeip/pool.go create mode 100644 component/fakeip/pool_test.go diff --git a/README.md b/README.md index 48ed9dc0e4..485c43149b 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ experimental: # enable: true # set true to enable dns (default is false) # ipv6: false # default is false # listen: 0.0.0.0:53 - # enhanced-mode: redir-host + # enhanced-mode: redir-host # or fake-ip # nameserver: # - 114.114.114.114 # - tls://dns.rubyfish.cn:853 # dns over tls diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go new file mode 100644 index 0000000000..76c421e3e2 --- /dev/null +++ b/component/fakeip/pool.go @@ -0,0 +1,50 @@ +package fakeip + +import ( + "errors" + "net" +) + +// Pool is a implementation about fake ip generator without storage +type Pool struct { + max uint32 + min uint32 + offset uint32 +} + +// Get return a new fake ip +func (p *Pool) Get() net.IP { + ip := uintToIP(p.min + p.offset) + p.offset = (p.offset + 1) % (p.max - p.min) + return ip +} + +func ipToUint(ip net.IP) uint32 { + v := uint32(ip[0]) << 24 + v += uint32(ip[1]) << 16 + v += uint32(ip[2]) << 8 + v += uint32(ip[3]) + return v +} + +func uintToIP(v uint32) net.IP { + return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) +} + +// New return Pool instance +func New(ipnet *net.IPNet) (*Pool, error) { + min := ipToUint(ipnet.IP) + 1 + + ones, bits := ipnet.Mask.Size() + total := 1< Date: Mon, 6 May 2019 21:00:29 +0800 Subject: [PATCH 175/535] Chore: fix socks reader overflow & update dependencies --- component/socks5/socks5.go | 5 +++-- go.mod | 12 ++++++------ go.sum | 32 +++++++++++++++++--------------- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index 0d909e6342..3dbe222925 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -166,8 +166,9 @@ func readAddr(r io.Reader, b []byte) (Addr, error) { if err != nil { return nil, err } - _, err = io.ReadFull(r, b[2:2+b[1]+2]) - return b[:1+1+b[1]+2], err + domainLength := uint16(b[1]) + _, err = io.ReadFull(r, b[2:2+domainLength+2]) + return b[:1+1+domainLength+2], err case AtypIPv4: _, err = io.ReadFull(r, b[1:1+net.IPv4len+2]) return b[:1+net.IPv4len+2], err diff --git a/go.mod b/go.mod index 43fa2bb031..a4cc9e5f02 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,19 @@ module github.com/Dreamacro/clash require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f + github.com/Dreamacro/go-shadowsocks2 v0.1.3 github.com/eapache/queue v1.1.0 // indirect - github.com/go-chi/chi v4.0.1+incompatible + github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.0 - github.com/miekg/dns v1.1.4 + github.com/miekg/dns v1.1.9 github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/maxminddb-golang v1.3.0 // indirect - github.com/sirupsen/logrus v1.3.0 - golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 - golang.org/x/net v0.0.0-20181108082009-03003ca0c849 + github.com/sirupsen/logrus v1.4.1 + golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index ac71fa0c50..c9aef37013 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f h1:nlImrmI6I2AVjJ2AvE3w3f7fi8rhLQAhZO1Gs31+/nE= -github.com/Dreamacro/go-shadowsocks2 v0.1.3-0.20190406142755-9128a199439f/go.mod h1:giIuN+TuUudTxHc1jjTOyyQYiJ3VXp1pWOHdJbSCAPo= +github.com/Dreamacro/go-shadowsocks2 v0.1.3 h1:1ffY/q4e3o+MnztYgIq1iZiX1BWoWQ6D3AIO1kkb8bc= +github.com/Dreamacro/go-shadowsocks2 v0.1.3/go.mod h1:0x17IhQ+mlY6q/ffKRpzaE7u4aHMxxnitTRSrV5G6TU= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/go-chi/chi v4.0.1+incompatible h1:RSRC5qmFPtO90t7pTL0DBMNpZFsb/sHF3RXVlDgFisA= -github.com/go-chi/chi v4.0.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= @@ -18,29 +18,31 @@ github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0= -github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.9 h1:OIdC9wT96RzuZMf2PfKRhFgsStHUUBZLM/lo1LqiM9E= +github.com/miekg/dns v1.1.9/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= 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/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= -github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc= -golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849 h1:FSqE2GGG7wzsYUsWiQ8MZrvEd1EOyU3NCF0AW3Wtltg= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= -golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/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-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= From 225c530d13833690a00a0241aea296c676312024 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 9 May 2019 21:00:29 +0800 Subject: [PATCH 176/535] Feature: add DST-PORT and SRC-PORT --- adapters/inbound/http.go | 5 +++- adapters/inbound/https.go | 5 +++- adapters/inbound/socket.go | 9 ++++-- adapters/inbound/util.go | 29 +++++++++++--------- adapters/outbound/direct.go | 8 +++--- adapters/outbound/http.go | 2 +- adapters/outbound/loadbalance.go | 4 +-- adapters/outbound/shadowsocks.go | 2 +- adapters/outbound/util.go | 10 +++---- adapters/outbound/vmess.go | 6 ++-- config/config.go | 12 ++++++++ constant/metadata.go | 17 ++++++------ constant/rule.go | 12 ++++++-- rules/geoip.go | 4 +-- rules/ipcidr.go | 6 ++-- rules/port.go | 47 ++++++++++++++++++++++++++++++++ tunnel/tunnel.go | 18 ++++++------ 17 files changed, 137 insertions(+), 59 deletions(-) create mode 100644 rules/port.go diff --git a/adapters/inbound/http.go b/adapters/inbound/http.go index e97bcc3a15..f60544f8c7 100644 --- a/adapters/inbound/http.go +++ b/adapters/inbound/http.go @@ -23,7 +23,10 @@ func (h *HTTPAdapter) Metadata() *C.Metadata { // NewHTTP is HTTPAdapter generator func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { metadata := parseHTTPAddr(request) - metadata.SourceIP = parseSourceIP(conn) + if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { + metadata.SrcIP = ip + metadata.SrcPort = port + } return &HTTPAdapter{ metadata: metadata, R: request, diff --git a/adapters/inbound/https.go b/adapters/inbound/https.go index 5207a5daf1..a880af74a3 100644 --- a/adapters/inbound/https.go +++ b/adapters/inbound/https.go @@ -8,7 +8,10 @@ import ( // NewHTTPS is HTTPAdapter generator func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { metadata := parseHTTPAddr(request) - metadata.SourceIP = parseSourceIP(conn) + if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { + metadata.SrcIP = ip + metadata.SrcPort = port + } return &SocketAdapter{ metadata: metadata, Conn: conn, diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index fae80e0148..017f742439 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -19,11 +19,14 @@ func (s *SocketAdapter) Metadata() *C.Metadata { } // NewSocket is SocketAdapter generator -func NewSocket(target socks5.Addr, conn net.Conn, source C.SourceType, netType C.NetWork) *SocketAdapter { +func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, netType C.NetWork) *SocketAdapter { metadata := parseSocksAddr(target) metadata.NetWork = netType - metadata.Source = source - metadata.SourceIP = parseSourceIP(conn) + metadata.Type = source + if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { + metadata.SrcIP = ip + metadata.SrcPort = port + } return &SocketAdapter{ Conn: conn, diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index d5bca74d59..9d977cf835 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -17,15 +17,15 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata { switch target[0] { case socks5.AtypDomainName: metadata.Host = string(target[2 : 2+target[1]]) - metadata.Port = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) + metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) case socks5.AtypIPv4: ip := net.IP(target[1 : 1+net.IPv4len]) - metadata.IP = &ip - metadata.Port = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) + metadata.DstIP = &ip + metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) case socks5.AtypIPv6: ip := net.IP(target[1 : 1+net.IPv6len]) - metadata.IP = &ip - metadata.Port = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) + metadata.DstIP = &ip + metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) } return metadata @@ -40,11 +40,11 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { metadata := &C.Metadata{ NetWork: C.TCP, - Source: C.HTTP, + Type: C.HTTP, AddrType: C.AtypDomainName, Host: host, - IP: nil, - Port: port, + DstIP: nil, + DstPort: port, } ip := net.ParseIP(host) @@ -55,15 +55,18 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { default: metadata.AddrType = C.AtypIPv4 } - metadata.IP = &ip + metadata.DstIP = &ip } return metadata } -func parseSourceIP(conn net.Conn) *net.IP { - if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok { - return &addr.IP +func parseAddr(addr string) (*net.IP, string, error) { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, "", err } - return nil + + ip := net.ParseIP(host) + return &ip, port, nil } diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 6cf18c09e8..563f7074cf 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -11,9 +11,9 @@ type Direct struct { } func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) { - address := net.JoinHostPort(metadata.Host, metadata.Port) - if metadata.IP != nil { - address = net.JoinHostPort(metadata.IP.String(), metadata.Port) + address := net.JoinHostPort(metadata.Host, metadata.DstPort) + if metadata.DstIP != nil { + address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) } c, err := net.DialTimeout("tcp", address, tcpTimeout) @@ -30,7 +30,7 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) return nil, nil, err } - addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, nil, err } diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 79d8b31e65..58f335dc8d 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -58,7 +58,7 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { var buf bytes.Buffer var err error - addr := net.JoinHostPort(metadata.String(), metadata.Port) + addr := net.JoinHostPort(metadata.String(), metadata.DstPort) buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") buf.WriteString("Host: " + metadata.String() + "\r\n") buf.WriteString("Proxy-Connection: Keep-Alive\r\n") diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index 761a184f82..e870271347 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -34,11 +34,11 @@ func getKey(metadata *C.Metadata) string { } } - if metadata.IP == nil { + if metadata.DstIP == nil { return "" } - return metadata.IP.String() + return metadata.DstIP.String() } func jumpHash(key uint64, buckets int32) int32 { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 8c8e92bfb2..e9e157f903 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -92,7 +92,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, return nil, nil, err } - remoteAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.Port)) + remoteAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, nil, err } diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 94d19d4ba7..d7c134b311 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -44,8 +44,8 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) { addr = C.Metadata{ AddrType: C.AtypDomainName, Host: u.Hostname(), - IP: nil, - Port: port, + DstIP: nil, + DstPort: port, } return } @@ -67,7 +67,7 @@ func getClientSessionCache() tls.ClientSessionCache { func serializesSocksAddr(metadata *C.Metadata) []byte { var buf [][]byte aType := uint8(metadata.AddrType) - p, _ := strconv.Atoi(metadata.Port) + p, _ := strconv.Atoi(metadata.DstPort) port := []byte{uint8(p >> 8), uint8(p & 0xff)} switch metadata.AddrType { case socks5.AtypDomainName: @@ -75,10 +75,10 @@ func serializesSocksAddr(metadata *C.Metadata) []byte { host := []byte(metadata.Host) buf = [][]byte{{aType, len}, host, port} case socks5.AtypIPv4: - host := metadata.IP.To4() + host := metadata.DstIP.To4() buf = [][]byte{{aType}, host, port} case socks5.AtypIPv6: - host := metadata.IP.To16() + host := metadata.DstIP.To16() buf = [][]byte{{aType}, host, port} } return bytes.Join(buf, nil) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 823469f761..e1113bd187 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -88,11 +88,11 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { case C.AtypIPv4: addrType = byte(vmess.AtypIPv4) addr = make([]byte, net.IPv4len) - copy(addr[:], metadata.IP.To4()) + copy(addr[:], metadata.DstIP.To4()) case C.AtypIPv6: addrType = byte(vmess.AtypIPv6) addr = make([]byte, net.IPv6len) - copy(addr[:], metadata.IP.To16()) + copy(addr[:], metadata.DstIP.To16()) case C.AtypDomainName: addrType = byte(vmess.AtypDomainName) addr = make([]byte, len(metadata.Host)+1) @@ -100,7 +100,7 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { copy(addr[1:], []byte(metadata.Host)) } - port, _ := strconv.Atoi(metadata.Port) + port, _ := strconv.Atoi(metadata.DstPort) return &vmess.DstAddr{ UDP: metadata.NetWork == C.UDP, AddrType: addrType, diff --git a/config/config.go b/config/config.go index 570ec9fc88..ddc0548a85 100644 --- a/config/config.go +++ b/config/config.go @@ -372,12 +372,24 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { if rule := R.NewIPCIDR(payload, target, false); rule != nil { parsed = rule } + // deprecated when bump to 1.0 case "SOURCE-IP-CIDR": + fallthrough + case "SRC-IP-CIDR": if rule := R.NewIPCIDR(payload, target, true); rule != nil { parsed = rule } + case "SRC-PORT": + if rule := R.NewPort(payload, target, true); rule != nil { + parsed = rule + } + case "DST-PORT": + if rule := R.NewPort(payload, target, false); rule != nil { + parsed = rule + } case "MATCH": fallthrough + // deprecated when bump to 1.0 case "FINAL": parsed = R.NewMatch(target) } diff --git a/constant/metadata.go b/constant/metadata.go index 4bb02885d1..f95bea586f 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -13,7 +13,7 @@ const ( TCP NetWork = iota UDP - HTTP SourceType = iota + HTTP Type = iota SOCKS REDIR ) @@ -27,26 +27,27 @@ func (n *NetWork) String() string { return "udp" } -type SourceType int +type Type int // Metadata is used to store connection address type Metadata struct { NetWork NetWork - Source SourceType - SourceIP *net.IP + Type Type + SrcIP *net.IP + DstIP *net.IP + SrcPort string + DstPort string AddrType int Host string - IP *net.IP - Port string } func (m *Metadata) String() string { if m.Host == "" { - return m.IP.String() + return m.DstIP.String() } return m.Host } func (m *Metadata) Valid() bool { - return m.Host != "" || m.IP != nil + return m.Host != "" || m.DstIP != nil } diff --git a/constant/rule.go b/constant/rule.go index e142efc061..fa8a12c798 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -7,7 +7,9 @@ const ( DomainKeyword GEOIP IPCIDR - SourceIPCIDR + SrcIPCIDR + SrcPort + DstPort MATCH ) @@ -25,8 +27,12 @@ func (rt RuleType) String() string { return "GEOIP" case IPCIDR: return "IPCIDR" - case SourceIPCIDR: - return "SourceIPCIDR" + case SrcIPCIDR: + return "SrcIPCIDR" + case SrcPort: + return "SrcPort" + case DstPort: + return "DstPort" case MATCH: return "MATCH" default: diff --git a/rules/geoip.go b/rules/geoip.go index 5fb9a1f7f8..07cc2eff9c 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -24,10 +24,10 @@ func (g *GEOIP) RuleType() C.RuleType { } func (g *GEOIP) IsMatch(metadata *C.Metadata) bool { - if metadata.IP == nil { + if metadata.DstIP == nil { return false } - record, _ := mmdb.Country(*metadata.IP) + record, _ := mmdb.Country(*metadata.DstIP) return record.Country.IsoCode == g.country } diff --git a/rules/ipcidr.go b/rules/ipcidr.go index 87214554d7..e81603008e 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -14,15 +14,15 @@ type IPCIDR struct { func (i *IPCIDR) RuleType() C.RuleType { if i.isSourceIP { - return C.SourceIPCIDR + return C.SrcIPCIDR } return C.IPCIDR } func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { - ip := metadata.IP + ip := metadata.DstIP if i.isSourceIP { - ip = metadata.SourceIP + ip = metadata.SrcIP } return ip != nil && i.ipnet.Contains(*ip) } diff --git a/rules/port.go b/rules/port.go new file mode 100644 index 0000000000..ba9bad57c2 --- /dev/null +++ b/rules/port.go @@ -0,0 +1,47 @@ +package rules + +import ( + "strconv" + + C "github.com/Dreamacro/clash/constant" +) + +type Port struct { + adapter string + port string + isSource bool +} + +func (p *Port) RuleType() C.RuleType { + if p.isSource { + return C.SrcPort + } + return C.DstPort +} + +func (p *Port) IsMatch(metadata *C.Metadata) bool { + if p.isSource { + return metadata.SrcPort == p.port + } + return metadata.DstPort == p.port +} + +func (p *Port) Adapter() string { + return p.adapter +} + +func (p *Port) Payload() string { + return p.port +} + +func NewPort(port string, adapter string, isSource bool) *Port { + _, err := strconv.Atoi(port) + if err != nil { + return nil + } + return &Port{ + adapter: adapter, + port: port, + isSource: isSource, + } +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index c5c0a3a26a..1a95eefa48 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -118,7 +118,7 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) { } func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { - return t.hasResolver() && (t.resolver.IsMapping() || t.resolver.IsFakeIP()) && metadata.Host == "" && metadata.IP != nil + return t.hasResolver() && (t.resolver.IsMapping() || t.resolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil } func (t *Tunnel) handleConn(localConn C.ServerAdapter) { @@ -132,12 +132,12 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { // preprocess enhanced-mode metadata if t.needLookupIP(metadata) { - host, exist := t.resolver.IPToHost(*metadata.IP) + host, exist := t.resolver.IPToHost(*metadata.DstIP) if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName if t.resolver.IsFakeIP() { - metadata.IP = nil + metadata.DstIP = nil } } } @@ -161,7 +161,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { pc, addr, err := proxy.DialUDP(metadata) defer pc.Close() if err != nil { - log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) + log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) } t.handleUDPOverTCP(localConn, pc, addr) @@ -170,7 +170,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { remoConn, err := proxy.Dial(metadata) if err != nil { - log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SourceIP.String(), metadata.String(), err.Error()) + log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) return } defer remoConn.Close() @@ -184,7 +184,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { - return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.IP == nil + return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.DstIP == nil } func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { @@ -202,7 +202,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) } else { log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) - metadata.IP = &ip + metadata.DstIP = &ip } resolved = true } @@ -217,11 +217,11 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { continue } - log.Infoln("%s --> %v match %s using %s", metadata.SourceIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter()) + log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter()) return adapter, nil } } - log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SourceIP.String(), metadata.String()) + log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) return t.proxies["DIRECT"], nil } From 243d8a284432505ce8e77a1b8f9e5bf2c1b7282f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 9 May 2019 21:05:47 +0800 Subject: [PATCH 177/535] Chore: update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 485c43149b..c9818647df 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ experimental: # ipv6: false # default is false # listen: 0.0.0.0:53 # enhanced-mode: redir-host # or fake-ip + # # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it # nameserver: # - 114.114.114.114 # - tls://dns.rubyfish.cn:853 # dns over tls @@ -206,8 +207,11 @@ Rule: - DOMAIN,google.com,auto - DOMAIN-SUFFIX,ad.com,REJECT - IP-CIDR,127.0.0.0/8,DIRECT -- SOURCE-IP-CIDR,192.168.1.201/32,DIRECT +# rename SOURCE-IP-CIDR and would remove after prerelease +- SRC-IP-CIDR,192.168.1.201/32,DIRECT - GEOIP,CN,DIRECT +- DST-PORT,80,DIRECT +- SRC-PORT,7777,DIRECT # FINAL would remove after prerelease # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now - MATCH,auto From 0d4a999707da2ec0c989aa3fe2b8faff5950340c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 12 May 2019 10:48:07 +0800 Subject: [PATCH 178/535] Chore: adjust fake-ip ttl --- dns/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dns/server.go b/dns/server.go index ad955a2ace..7e41035542 100644 --- a/dns/server.go +++ b/dns/server.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "net" - "time" "github.com/Dreamacro/clash/log" "github.com/miekg/dns" @@ -58,10 +57,10 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) { q := r.Question[0] - cache, expireTime := s.r.cache.GetWithExpire("fakeip:" + q.String()) + cache, _ := s.r.cache.GetWithExpire("fakeip:" + q.String()) if cache != nil { msg = cache.(*D.Msg).Copy() - setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) + setMsgTTL(msg, 1) return } @@ -73,6 +72,7 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) { putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg) putMsgToCache(s.r.cache, ip.String(), msg) + setMsgTTL(msg, 1) }() rr := &D.A{} From 71a08ad8e2594d498360ff6d13a9990ad8025167 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 14 May 2019 21:35:34 +0800 Subject: [PATCH 179/535] Chore: clean up code --- dns/client.go | 5 ++--- dns/server.go | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dns/client.go b/dns/client.go index 33dce1c90d..862d935d05 100644 --- a/dns/client.go +++ b/dns/client.go @@ -99,7 +99,7 @@ func (r *Resolver) exchange(servers []*nameserver, m *D.Msg) (msg *D.Msg, err er if err != nil || msg.Rcode != D.RcodeSuccess { return } - in <- &result{Msg: msg, Error: err} + in <- msg }(server) } @@ -114,8 +114,7 @@ func (r *Resolver) exchange(servers []*nameserver, m *D.Msg) (msg *D.Msg, err er return nil, errors.New("All DNS requests failed") } - resp := elm.(*result) - msg, err = resp.Msg, resp.Error + msg = elm.(*D.Msg) return } diff --git a/dns/server.go b/dns/server.go index 7e41035542..59af5289a9 100644 --- a/dns/server.go +++ b/dns/server.go @@ -57,10 +57,9 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) { q := r.Question[0] - cache, _ := s.r.cache.GetWithExpire("fakeip:" + q.String()) + cache := s.r.cache.Get("fakeip:" + q.String()) if cache != nil { msg = cache.(*D.Msg).Copy() - setMsgTTL(msg, 1) return } @@ -72,6 +71,8 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) { putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg) putMsgToCache(s.r.cache, ip.String(), msg) + + // putMsgToCache depend on msg ttl to set cache expired time, then set msg ref ttl to 1 setMsgTTL(msg, 1) }() From 0eccbb023c7bc7f426e1e5fd25632cca18e1a999 Mon Sep 17 00:00:00 2001 From: ezksd Date: Wed, 15 May 2019 14:40:14 +0800 Subject: [PATCH 180/535] Feature: make the selector proxies order as same as the order in the config file (#180) * make the proxies order the same as the order in config file * formatting & rename variable --- adapters/outbound/selector.go | 22 ++++++++++------------ config/config.go | 8 ++++++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index 39b4ac0d9b..a14a7ce3a5 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -4,15 +4,15 @@ import ( "encoding/json" "errors" "net" - "sort" C "github.com/Dreamacro/clash/constant" ) type Selector struct { *Base - selected C.Proxy - proxies map[string]C.Proxy + selected C.Proxy + proxies map[string]C.Proxy + proxyList []string } type SelectorOption struct { @@ -33,15 +33,10 @@ func (s *Selector) SupportUDP() bool { } func (s *Selector) MarshalJSON() ([]byte, error) { - var all []string - for k := range s.proxies { - all = append(all, k) - } - sort.Strings(all) return json.Marshal(map[string]interface{}{ "type": s.Type().String(), "now": s.Now(), - "all": all, + "all": s.proxyList, }) } @@ -64,8 +59,10 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) { } mapping := make(map[string]C.Proxy) - for _, proxy := range proxies { + proxyList := make([]string, len(proxies)) + for idx, proxy := range proxies { mapping[proxy.Name()] = proxy + proxyList[idx] = proxy.Name() } s := &Selector{ @@ -73,8 +70,9 @@ func NewSelector(name string, proxies []C.Proxy) (*Selector, error) { name: name, tp: C.Selector, }, - proxies: mapping, - selected: proxies[0], + proxies: mapping, + selected: proxies[0], + proxyList: proxyList, } return s, nil } diff --git a/config/config.go b/config/config.go index ddc0548a85..9ecf6c3938 100644 --- a/config/config.go +++ b/config/config.go @@ -194,6 +194,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) { func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { proxies := make(map[string]C.Proxy) + proxyList := []string{} proxiesConfig := cfg.Proxy groupsConfig := cfg.ProxyGroup @@ -201,6 +202,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect()) proxies["REJECT"] = adapters.NewProxy(adapters.NewReject()) + proxyList = append(proxyList, "DIRECT", "REJECT") // parse proxy for idx, mapping := range proxiesConfig { @@ -252,6 +254,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) } proxies[proxy.Name()] = adapters.NewProxy(proxy) + proxyList = append(proxyList, proxy.Name()) } // parse proxy group @@ -323,11 +326,12 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) } proxies[groupName] = adapters.NewProxy(group) + proxyList = append(proxyList, groupName) } ps := []C.Proxy{} - for _, v := range proxies { - ps = append(ps, v) + for _, v := range proxyList { + ps = append(ps, proxies[v]) } global, _ := adapters.NewSelector("GLOBAL", ps) From b5897544852dfb730e51c58896f76d5f4d016ce3 Mon Sep 17 00:00:00 2001 From: recall704 Date: Thu, 16 May 2019 14:19:37 +0800 Subject: [PATCH 181/535] Chore: fix typo (#182) --- Makefile | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b155571161..6899d9015f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ NAME=clash BINDIR=bin -VERSION=$(shell git describe --tags || echo "unkown version") +VERSION=$(shell git describe --tags || echo "unknown version") BUILDTIME=$(shell date -u) GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ diff --git a/README.md b/README.md index c9818647df..f0cbf5e811 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ external-controller: 127.0.0.1:9090 # experimental feature experimental: - ignore-resolve-fail: true # ignore dns reslove fail, default value is true + ignore-resolve-fail: true # ignore dns resolve fail, default value is true # dns: # enable: true # set true to enable dns (default is false) From e837470a6a6b4e8473e52c10381ceccd7b76a808 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 16 May 2019 18:40:20 +0800 Subject: [PATCH 182/535] Fix: udp crash in tunnel --- tunnel/tunnel.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 1a95eefa48..faeaf46ddf 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -159,10 +159,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { if metadata.NetWork == C.UDP { pc, addr, err := proxy.DialUDP(metadata) - defer pc.Close() if err != nil { log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) + return } + defer pc.Close() t.handleUDPOverTCP(localConn, pc, addr) return From a4b8e286db78d47ee8738790137693d34b039a9f Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sat, 18 May 2019 17:44:12 +0800 Subject: [PATCH 183/535] Fix: incorrect fake ip dns ttl (#187) --- dns/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/server.go b/dns/server.go index 59af5289a9..26b69073ab 100644 --- a/dns/server.go +++ b/dns/server.go @@ -60,6 +60,7 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) { cache := s.r.cache.Get("fakeip:" + q.String()) if cache != nil { msg = cache.(*D.Msg).Copy() + setMsgTTL(msg, 1) return } @@ -72,7 +73,6 @@ func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) { putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg) putMsgToCache(s.r.cache, ip.String(), msg) - // putMsgToCache depend on msg ttl to set cache expired time, then set msg ref ttl to 1 setMsgTTL(msg, 1) }() From 89168e6c969c466874c519287bd72f9e570a073d Mon Sep 17 00:00:00 2001 From: Fndroid Date: Sat, 18 May 2019 17:52:42 +0800 Subject: [PATCH 184/535] Fix: DNS server not recreate correctly (#186) --- dns/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dns/server.go b/dns/server.go index 26b69073ab..21dcac0935 100644 --- a/dns/server.go +++ b/dns/server.go @@ -97,6 +97,7 @@ func ReCreateServer(addr string, resolver *Resolver) error { if server.Server != nil { server.Shutdown() + address = "" } _, port, err := net.SplitHostPort(addr) From ad13ad8dbab82745e86fc3da89fcec3f71b7d2dd Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 23 May 2019 23:27:29 +0800 Subject: [PATCH 185/535] Fix: add mutex for fake ip pool --- component/fakeip/pool.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 76c421e3e2..32d5d574e4 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -3,6 +3,7 @@ package fakeip import ( "errors" "net" + "sync" ) // Pool is a implementation about fake ip generator without storage @@ -10,10 +11,13 @@ type Pool struct { max uint32 min uint32 offset uint32 + mux *sync.Mutex } // Get return a new fake ip func (p *Pool) Get() net.IP { + p.mux.Lock() + defer p.mux.Unlock() ip := uintToIP(p.min + p.offset) p.offset = (p.offset + 1) % (p.max - p.min) return ip @@ -46,5 +50,6 @@ func New(ipnet *net.IPNet) (*Pool, error) { return &Pool{ min: min, max: max, + mux: &sync.Mutex{}, }, nil } From 016e7bd0b45a9ce2507262b870edd62abb985fb4 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 26 May 2019 13:55:13 +0800 Subject: [PATCH 186/535] Style: fix go vet warning --- common/murmur3/murmur32.go | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/common/murmur3/murmur32.go b/common/murmur3/murmur32.go index a4e4801e87..9861eecd88 100644 --- a/common/murmur3/murmur32.go +++ b/common/murmur3/murmur32.go @@ -4,6 +4,7 @@ package murmur3 import ( "hash" + "math/bits" "unsafe" ) @@ -54,11 +55,11 @@ func (d *digest32) bmix(p []byte) (tail []byte) { k1 := *(*uint32)(unsafe.Pointer(&p[i*4])) k1 *= c1_32 - k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 = bits.RotateLeft32(k1, 15) k1 *= c2_32 h1 ^= k1 - h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) + h1 = bits.RotateLeft32(h1, 13) h1 = h1*4 + h1 + 0xe6546b64 } d.h1 = h1 @@ -80,7 +81,7 @@ func (d *digest32) Sum32() (h1 uint32) { case 1: k1 ^= uint32(d.tail[0]) k1 *= c1_32 - k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 = bits.RotateLeft32(k1, 15) k1 *= c2_32 h1 ^= k1 } @@ -102,20 +103,15 @@ func Sum32WithSeed(data []byte, seed uint32) uint32 { h1 := seed nblocks := len(data) / 4 - var p uintptr - if len(data) > 0 { - p = uintptr(unsafe.Pointer(&data[0])) - } - p1 := p + uintptr(4*nblocks) - for ; p < p1; p += 4 { - k1 := *(*uint32)(unsafe.Pointer(p)) + for i := 0; i < nblocks; i++ { + k1 := *(*uint32)(unsafe.Pointer(&data[i*4])) k1 *= c1_32 - k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 = bits.RotateLeft32(k1, 15) k1 *= c2_32 h1 ^= k1 - h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) + h1 = bits.RotateLeft32(h1, 13) h1 = h1*4 + h1 + 0xe6546b64 } @@ -132,7 +128,7 @@ func Sum32WithSeed(data []byte, seed uint32) uint32 { case 1: k1 ^= uint32(tail[0]) k1 *= c1_32 - k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) + k1 = bits.RotateLeft32(k1, 15) k1 *= c2_32 h1 ^= k1 } From cba548114f0e52e57231542f4b431641747332c3 Mon Sep 17 00:00:00 2001 From: Jonathan Gao Date: Thu, 13 Jun 2019 20:18:07 +0800 Subject: [PATCH 187/535] Fix: use original sequence for url-test group (#201) --- adapters/outbound/urltest.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index cd20c62fd7..9a219cc095 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "net" - "sort" "sync" "sync/atomic" "time" @@ -56,7 +55,6 @@ func (u *URLTest) MarshalJSON() ([]byte, error) { for _, proxy := range u.proxies { all = append(all, proxy.Name()) } - sort.Strings(all) return json.Marshal(map[string]interface{}{ "type": u.Type().String(), "now": u.Now(), From 6adafde9a0bfcfe49aedefb4b46974544e14e344 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 18 Jun 2019 20:37:53 +0800 Subject: [PATCH 188/535] Fix: strict ss obfs check --- adapters/outbound/shadowsocks.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index e9e157f903..7d42272305 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -137,6 +137,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { if err := decoder.Decode(option.PluginOpts, &opts); err != nil { return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error()) } + + if opts.Mode != "tls" && opts.Mode != "http" { + return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode) + } obfsMode = opts.Mode obfsOption = &opts } else if option.Plugin == "v2ray-plugin" { @@ -144,7 +148,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { if err := decoder.Decode(option.PluginOpts, &opts); err != nil { return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error()) } + + if opts.Mode != "websocket" { + return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode) + } obfsMode = opts.Mode + var tlsConfig *tls.Config if opts.TLS { tlsConfig = &tls.Config{ From 407de7388cbb76296b26a364b450a810fb443854 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 18 Jun 2019 20:55:26 +0800 Subject: [PATCH 189/535] Standardized: use recommend extension & forward compatibility before 1.0 --- README.md | 2 +- config/config.go | 17 ++++++++++++++++- config/initial.go | 2 +- constant/path.go | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f0cbf5e811..d345681842 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ If you have Docker installed, you can run clash directly using `docker-compose`. The default configuration directory is `$HOME/.config/clash` -The name of the configuration file is `config.yml` +The name of the configuration file is `config.yaml` If you want to use another directory, you can use `-d` to control the configuration directory diff --git a/config/config.go b/config/config.go index 9ecf6c3938..e89e934d04 100644 --- a/config/config.go +++ b/config/config.go @@ -87,11 +87,26 @@ type rawConfig struct { Rule []string `yaml:"Rule"` } +// forward compatibility before 1.0 +func readRawConfig(path string) ([]byte, error) { + data, err := ioutil.ReadFile(path) + if err == nil && len(data) != 0 { + return data, nil + } + + if filepath.Ext(path) != ".yaml" { + return nil, err + } + + path = path[:len(path)-5] + ".yml" + return ioutil.ReadFile(path) +} + func readConfig(path string) (*rawConfig, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err } - data, err := ioutil.ReadFile(path) + data, err := readRawConfig(path) if err != nil { return nil, err } diff --git a/config/initial.go b/config/initial.go index 8f198b7af2..cf61d1bc72 100644 --- a/config/initial.go +++ b/config/initial.go @@ -63,7 +63,7 @@ func Init(dir string) error { } } - // initial config.yml + // initial config.yaml if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { log.Info("Can't find config, create an empty file") os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) diff --git a/constant/path.go b/constant/path.go index 32c67aadc8..bfe158a1cd 100644 --- a/constant/path.go +++ b/constant/path.go @@ -42,7 +42,7 @@ func (p *path) HomeDir() string { } func (p *path) Config() string { - return P.Join(p.homedir, "config.yml") + return P.Join(p.homedir, "config.yaml") } func (p *path) MMDB() string { From ba5eefb6dd984860b95f413c71b7c49f0c3bbd43 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 19 Jun 2019 22:12:25 +0800 Subject: [PATCH 190/535] Chore: clean up Dockfile --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8f72c2e4ca..c36a4ffc48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,4 @@ RUN apk add --no-cache ca-certificates COPY --from=builder /Country.mmdb /root/.config/clash/ COPY --from=builder /clash / -EXPOSE 7890 7891 - ENTRYPOINT ["/clash"] From bcf5b21208a4b6a7f77d4b308d2cf45db815bb95 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Thu, 20 Jun 2019 11:03:50 +0800 Subject: [PATCH 191/535] Fix: check target is valid in rules (#210) --- config/config.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index e89e934d04..8b4e0bc952 100644 --- a/config/config.go +++ b/config/config.go @@ -157,7 +157,7 @@ func Parse(path string) (*Config, error) { } config.Proxies = proxies - rules, err := parseRules(rawCfg) + rules, err := parseRules(rawCfg, proxies) if err != nil { return nil, err } @@ -354,7 +354,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return proxies, nil } -func parseRules(cfg *rawConfig) ([]C.Rule, error) { +func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { rules := []C.Rule{} rulesConfig := cfg.Rule @@ -376,6 +376,10 @@ func parseRules(cfg *rawConfig) ([]C.Rule, error) { return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line) } + if _, ok := proxies[target]; !ok { + return nil, fmt.Errorf("Rules[%d] [%s] error: proxy [%s] not found", idx, line, target) + } + rule = trimArr(rule) var parsed C.Rule switch rule[0] { From aa3516ca2497c90db0f4a50926a9212a8dcbdf6f Mon Sep 17 00:00:00 2001 From: Windendless Date: Thu, 20 Jun 2019 15:50:01 +0800 Subject: [PATCH 192/535] Chore: use 'dns' for ALPN in tcp-tls nameserver (#209) --- dns/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dns/client.go b/dns/client.go index 862d935d05..4af716bd22 100644 --- a/dns/client.go +++ b/dns/client.go @@ -240,6 +240,8 @@ func transform(servers []NameServer) []*nameserver { Net: s.Net, TLSConfig: &tls.Config{ ClientSessionCache: globalSessionCache, + // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 + NextProtos: []string{"dns"}, }, UDPSize: 4096, }, From 2417cfda1219af7d80d39662007fc1d45e5cfd14 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 26 Jun 2019 20:30:57 +0800 Subject: [PATCH 193/535] Chore: set log output to stdout --- log/log.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/log/log.go b/log/log.go index 77a835cd32..251929de1d 100644 --- a/log/log.go +++ b/log/log.go @@ -2,6 +2,7 @@ package log import ( "fmt" + "os" "github.com/Dreamacro/clash/common/observable" @@ -15,6 +16,7 @@ var ( ) func init() { + log.SetOutput(os.Stdout) log.SetLevel(log.DebugLevel) } From 1c792b46c9fdb110e742a83a846ef1f859b557fd Mon Sep 17 00:00:00 2001 From: bobo liu Date: Thu, 27 Jun 2019 17:04:25 +0800 Subject: [PATCH 194/535] Feature: local socks5/http(s) auth (#216) --- README.md | 5 +++ component/auth/auth.go | 46 ++++++++++++++++++++++++++ component/socks5/socks5.go | 68 ++++++++++++++++++++++++++++++++++++-- config/config.go | 30 +++++++++++++---- hub/executor/executor.go | 25 ++++++++++---- proxy/auth/auth.go | 17 ++++++++++ proxy/http/server.go | 40 ++++++++++++++++++++-- proxy/socks/tcp.go | 3 +- 8 files changed, 215 insertions(+), 19 deletions(-) create mode 100644 component/auth/auth.go create mode 100644 proxy/auth/auth.go diff --git a/README.md b/README.md index d345681842..6ee6a3f942 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,11 @@ external-controller: 127.0.0.1:9090 experimental: ignore-resolve-fail: true # ignore dns resolve fail, default value is true +# authentication of local SOCKS5/HTTP(S) server +# authentication: +# - "user1:pass1" +# - "user2:pass2" + # dns: # enable: true # set true to enable dns (default is false) # ipv6: false # default is false diff --git a/component/auth/auth.go b/component/auth/auth.go new file mode 100644 index 0000000000..98414d8bda --- /dev/null +++ b/component/auth/auth.go @@ -0,0 +1,46 @@ +package auth + +import ( + "sync" +) + +type Authenticator interface { + Verify(user string, pass string) bool + Users() []string +} + +type AuthUser struct { + User string + Pass string +} + +type inMemoryAuthenticator struct { + storage *sync.Map + usernames []string +} + +func (au *inMemoryAuthenticator) Verify(user string, pass string) bool { + realPass, ok := au.storage.Load(user) + return ok && realPass == pass +} + +func (au *inMemoryAuthenticator) Users() []string { return au.usernames } + +func NewAuthenticator(users []AuthUser) Authenticator { + if len(users) == 0 { + return nil + } + + au := &inMemoryAuthenticator{storage: &sync.Map{}} + for _, user := range users { + au.storage.Store(user.User, user.Pass) + } + usernames := make([]string, 0, len(users)) + au.storage.Range(func(key, value interface{}) bool { + usernames = append(usernames, key.(string)) + return true + }) + au.usernames = usernames + + return au +} diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index 3dbe222925..e945fe2cdd 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -6,6 +6,8 @@ import ( "io" "net" "strconv" + + "github.com/Dreamacro/clash/component/auth" ) // Error represents a SOCKS error @@ -35,6 +37,9 @@ const ( // MaxAddrLen is the maximum size of SOCKS address in bytes. const MaxAddrLen = 1 + 1 + 255 + 2 +// MaxAuthLen is the maximum size of user/password field in SOCKS5 Auth +const MaxAuthLen = 255 + // Addr represents a SOCKS address as defined in RFC 1928 section 5. type Addr = []byte @@ -50,13 +55,16 @@ const ( ErrAddressNotSupported = Error(8) ) +// Auth errors used to return a specific "Auth failed" error +var ErrAuth = errors.New("auth failed") + type User struct { Username string Password string } // ServerHandshake fast-tracks SOCKS initialization to get target address to connect on server side. -func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) { +func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, command Command, err error) { // Read RFC 1928 for request and reply structure and sizes. buf := make([]byte, MaxAddrLen) // read VER, NMETHODS, METHODS @@ -67,10 +75,64 @@ func ServerHandshake(rw io.ReadWriter) (addr Addr, command Command, err error) { if _, err = io.ReadFull(rw, buf[:nmethods]); err != nil { return } + // write VER METHOD - if _, err = rw.Write([]byte{5, 0}); err != nil { - return + if authenticator != nil { + if _, err = rw.Write([]byte{5, 2}); err != nil { + return + } + + // Get header + header := make([]byte, 2) + if _, err = io.ReadFull(rw, header); err != nil { + return + } + + authBuf := make([]byte, MaxAuthLen) + // Get username + userLen := int(header[1]) + if userLen <= 0 { + rw.Write([]byte{1, 1}) + err = ErrAuth + return + } + if _, err = io.ReadFull(rw, authBuf[:userLen]); err != nil { + return + } + user := string(authBuf[:userLen]) + + // Get password + if _, err = rw.Read(header[:1]); err != nil { + return + } + passLen := int(header[0]) + if passLen <= 0 { + rw.Write([]byte{1, 1}) + err = ErrAuth + return + } + if _, err = io.ReadFull(rw, authBuf[:passLen]); err != nil { + return + } + pass := string(authBuf[:passLen]) + + // Verify + if ok := authenticator.Verify(string(user), string(pass)); !ok { + rw.Write([]byte{1, 1}) + err = ErrAuth + return + } + + // Response auth state + if _, err = rw.Write([]byte{1, 0}); err != nil { + return + } + } else { + if _, err = rw.Write([]byte{5, 0}); err != nil { + return + } } + // read VER CMD RSV ATYP DST.ADDR DST.PORT if _, err = io.ReadFull(rw, buf[:3]); err != nil { return diff --git a/config/config.go b/config/config.go index 8b4e0bc952..6107bde5ed 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ import ( adapters "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/common/structure" + "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/fakeip" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -26,6 +27,7 @@ type General struct { Port int `json:"port"` SocksPort int `json:"socks-port"` RedirPort int `json:"redir-port"` + Authentication []string `json:"authentication"` AllowLan bool `json:"allow-lan"` Mode T.Mode `json:"mode"` LogLevel log.LogLevel `json:"log-level"` @@ -56,6 +58,7 @@ type Config struct { DNS *DNS Experimental *Experimental Rules []C.Rule + Users []auth.AuthUser Proxies map[string]C.Proxy } @@ -73,6 +76,7 @@ type rawConfig struct { Port int `yaml:"port"` SocksPort int `yaml:"socks-port"` RedirPort int `yaml:"redir-port"` + Authentication []string `yaml:"authentication"` AllowLan bool `yaml:"allow-lan"` Mode T.Mode `yaml:"mode"` LogLevel log.LogLevel `yaml:"log-level"` @@ -117,12 +121,13 @@ func readConfig(path string) (*rawConfig, error) { // config with some default value rawConfig := &rawConfig{ - AllowLan: false, - Mode: T.Rule, - LogLevel: log.INFO, - Rule: []string{}, - Proxy: []map[string]interface{}{}, - ProxyGroup: []map[string]interface{}{}, + AllowLan: false, + Mode: T.Rule, + Authentication: []string{}, + LogLevel: log.INFO, + Rule: []string{}, + Proxy: []map[string]interface{}{}, + ProxyGroup: []map[string]interface{}{}, Experimental: Experimental{ IgnoreResolveFail: true, }, @@ -169,6 +174,8 @@ func Parse(path string) (*Config, error) { } config.DNS = dnsCfg + config.Users = parseAuthentication(rawCfg.Authentication) + return config, nil } @@ -520,3 +527,14 @@ func parseDNS(cfg rawDNS) (*DNS, error) { return dnsCfg, nil } + +func parseAuthentication(rawRecords []string) []auth.AuthUser { + users := make([]auth.AuthUser, 0) + for _, line := range rawRecords { + userData := strings.SplitN(line, ":", 2) + if len(userData) == 2 { + users = append(users, auth.AuthUser{User: userData[0], Pass: userData[1]}) + } + } + return users +} diff --git a/hub/executor/executor.go b/hub/executor/executor.go index ee401edd01..0686cd0cf4 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -1,11 +1,13 @@ package executor import ( + "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" P "github.com/Dreamacro/clash/proxy" + authStore "github.com/Dreamacro/clash/proxy/auth" T "github.com/Dreamacro/clash/tunnel" ) @@ -21,6 +23,7 @@ func ParseWithPath(path string) (*config.Config, error) { // ApplyConfig dispatch configure to all parts func ApplyConfig(cfg *config.Config, force bool) { + updateUsers(cfg.Users) if force { updateGeneral(cfg.General) } @@ -33,12 +36,13 @@ func ApplyConfig(cfg *config.Config, force bool) { func GetGeneral() *config.General { ports := P.GetPorts() return &config.General{ - Port: ports.Port, - SocksPort: ports.SocksPort, - RedirPort: ports.RedirPort, - AllowLan: P.AllowLan(), - Mode: T.Instance().Mode(), - LogLevel: log.Level(), + Port: ports.Port, + SocksPort: ports.SocksPort, + RedirPort: ports.RedirPort, + Authentication: authStore.Authenticator().Users(), + AllowLan: P.AllowLan(), + Mode: T.Instance().Mode(), + LogLevel: log.Level(), } } @@ -90,6 +94,7 @@ func updateGeneral(general *config.General) { allowLan := general.AllowLan P.SetAllowLan(allowLan) + if err := P.ReCreateHTTP(general.Port); err != nil { log.Errorln("Start HTTP server error: %s", err.Error()) } @@ -102,3 +107,11 @@ func updateGeneral(general *config.General) { log.Errorln("Start Redir server error: %s", err.Error()) } } + +func updateUsers(users []auth.AuthUser) { + authenticator := auth.NewAuthenticator(users) + authStore.SetAuthenticator(authenticator) + if authenticator != nil { + log.Infoln("Authentication of local server updated") + } +} diff --git a/proxy/auth/auth.go b/proxy/auth/auth.go new file mode 100644 index 0000000000..2c29e186ec --- /dev/null +++ b/proxy/auth/auth.go @@ -0,0 +1,17 @@ +package auth + +import ( + "github.com/Dreamacro/clash/component/auth" +) + +var ( + authenticator auth.Authenticator +) + +func Authenticator() auth.Authenticator { + return authenticator +} + +func SetAuthenticator(au auth.Authenticator) { + authenticator = au +} diff --git a/proxy/http/server.go b/proxy/http/server.go index 1ed913323e..51941ef6e0 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -2,11 +2,17 @@ package http import ( "bufio" + "encoding/base64" "net" "net/http" + "strings" + "time" adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/log" + authStore "github.com/Dreamacro/clash/proxy/auth" "github.com/Dreamacro/clash/tunnel" ) @@ -18,6 +24,7 @@ type HttpListener struct { net.Listener address string closed bool + cache *cache.Cache } func NewHttpProxy(addr string) (*HttpListener, error) { @@ -25,10 +32,11 @@ func NewHttpProxy(addr string) (*HttpListener, error) { if err != nil { return nil, err } - hl := &HttpListener{l, addr, false} + hl := &HttpListener{l, addr, false, cache.New(30 * time.Second)} go func() { log.Infoln("HTTP proxy listening at: %s", addr) + for { c, err := hl.Accept() if err != nil { @@ -37,7 +45,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) { } continue } - go handleConn(c) + go handleConn(c, hl.cache) } }() @@ -53,7 +61,19 @@ func (l *HttpListener) Address() string { return l.address } -func handleConn(conn net.Conn) { +func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) { + if result := cache.Get(loginStr); result != nil { + ret = result.(bool) + } + loginData, err := base64.StdEncoding.DecodeString(loginStr) + login := strings.Split(string(loginData), ":") + ret = err == nil && len(login) == 2 && authenticator.Verify(login[0], login[1]) + + cache.Put(loginStr, ret, time.Minute) + return +} + +func handleConn(conn net.Conn, cache *cache.Cache) { br := bufio.NewReader(conn) request, err := http.ReadRequest(br) if err != nil || request.URL.Host == "" { @@ -61,6 +81,20 @@ func handleConn(conn net.Conn) { return } + authenticator := authStore.Authenticator() + if authenticator != nil { + if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 { + _, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")) + conn.Close() + return + } else if !canActivate(authStrings[1], authenticator, cache) { + conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) + log.Infoln("Auth failed from %s", conn.RemoteAddr().String()) + conn.Close() + return + } + } + if request.Method == http.MethodConnect { _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) if err != nil { diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 08789cbe9c..47bfa0373d 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -7,6 +7,7 @@ import ( "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" + authStore "github.com/Dreamacro/clash/proxy/auth" "github.com/Dreamacro/clash/tunnel" ) @@ -54,7 +55,7 @@ func (l *SockListener) Address() string { } func handleSocks(conn net.Conn) { - target, command, err := socks5.ServerHandshake(conn) + target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) if err != nil { conn.Close() return From 53528f82753b389eadd99b60131cb7e3c84c4f92 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 27 Jun 2019 20:45:12 +0800 Subject: [PATCH 195/535] Fix: crash when authenticator is nil --- hub/executor/executor.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 0686cd0cf4..a0bd1c8663 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -35,15 +35,22 @@ func ApplyConfig(cfg *config.Config, force bool) { func GetGeneral() *config.General { ports := P.GetPorts() - return &config.General{ + authenticator := []string{} + if auth := authStore.Authenticator(); auth != nil { + authenticator = auth.Users() + } + + general := &config.General{ Port: ports.Port, SocksPort: ports.SocksPort, RedirPort: ports.RedirPort, - Authentication: authStore.Authenticator().Users(), + Authentication: authenticator, AllowLan: P.AllowLan(), Mode: T.Instance().Mode(), LogLevel: log.Level(), } + + return general } func updateExperimental(c *config.Experimental) { From 662038e40e8b461cd0cc744dc5297f6273d9efd4 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 27 Jun 2019 22:56:24 +0800 Subject: [PATCH 196/535] Fix: log correctly path --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 6107bde5ed..9253dd5f64 100644 --- a/config/config.go +++ b/config/config.go @@ -116,7 +116,7 @@ func readConfig(path string) (*rawConfig, error) { } if len(data) == 0 { - return nil, fmt.Errorf("Configuration file %s is empty", C.Path.Config()) + return nil, fmt.Errorf("Configuration file %s is empty", path) } // config with some default value From bc3fc0c840e608616d8e2afda3b36d486f1b5521 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 28 Jun 2019 12:29:08 +0800 Subject: [PATCH 197/535] Feature: support DoH --- README.md | 1 + config/config.go | 5 + dns/client.go | 264 ++----------------------------------------- dns/doh.go | 75 ++++++++++++ dns/resolver.go | 289 +++++++++++++++++++++++++++++++++++++++++++++++ dns/util.go | 32 ++++++ go.mod | 2 +- go.sum | 4 +- 8 files changed, 412 insertions(+), 260 deletions(-) create mode 100644 dns/doh.go create mode 100644 dns/resolver.go diff --git a/README.md b/README.md index 6ee6a3f942..a031ecea54 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ experimental: # nameserver: # - 114.114.114.114 # - tls://dns.rubyfish.cn:853 # dns over tls + # - https://1.1.1.1/dns-query # dns over https # fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN # - tcp://1.1.1.1 diff --git a/config/config.go b/config/config.go index 9253dd5f64..598ac83412 100644 --- a/config/config.go +++ b/config/config.go @@ -475,9 +475,14 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { case "tls": host, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "tcp-tls" // DNS over TLS + case "https": + clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} + host = clearURL.String() + dnsNetType = "https" // DNS over HTTPS default: return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) } + if err != nil { return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } diff --git a/dns/client.go b/dns/client.go index 4af716bd22..881fcded4e 100644 --- a/dns/client.go +++ b/dns/client.go @@ -2,270 +2,20 @@ package dns import ( "context" - "crypto/tls" - "errors" - "net" - "strings" - "sync" - "time" - - "github.com/Dreamacro/clash/common/cache" - "github.com/Dreamacro/clash/common/picker" - "github.com/Dreamacro/clash/component/fakeip" - C "github.com/Dreamacro/clash/constant" D "github.com/miekg/dns" - geoip2 "github.com/oschwald/geoip2-golang" -) - -var ( - globalSessionCache = tls.NewLRUClientSessionCache(64) - - mmdb *geoip2.Reader - once sync.Once - resolver *Resolver ) -type Resolver struct { - ipv6 bool - mapping bool - fakeip bool - pool *fakeip.Pool - fallback []*nameserver - main []*nameserver - cache *cache.Cache -} - -type result struct { - Msg *D.Msg - Error error -} - -func isIPRequest(q D.Question) bool { - if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) { - return true - } - return false -} - -func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { - if len(m.Question) == 0 { - return nil, errors.New("should have one question at least") - } - - q := m.Question[0] - cache, expireTime := r.cache.GetWithExpire(q.String()) - if cache != nil { - msg = cache.(*D.Msg).Copy() - setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) - return - } - defer func() { - if msg == nil { - return - } - - putMsgToCache(r.cache, q.String(), msg) - if r.mapping { - ips := r.msgToIP(msg) - for _, ip := range ips { - putMsgToCache(r.cache, ip.String(), msg) - } - } - }() - - isIPReq := isIPRequest(q) - if isIPReq { - msg, err = r.resolveIP(m) - return - } - - msg, err = r.exchange(r.main, m) - return -} - -func (r *Resolver) exchange(servers []*nameserver, m *D.Msg) (msg *D.Msg, err error) { - in := make(chan interface{}) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - fast := picker.SelectFast(ctx, in) - - wg := sync.WaitGroup{} - wg.Add(len(servers)) - for _, server := range servers { - go func(s *nameserver) { - defer wg.Done() - msg, _, err := s.Client.Exchange(m, s.Address) - if err != nil || msg.Rcode != D.RcodeSuccess { - return - } - in <- msg - }(server) - } - - // release in channel - go func() { - wg.Wait() - close(in) - }() - - elm, exist := <-fast - if !exist { - return nil, errors.New("All DNS requests failed") - } - - msg = elm.(*D.Msg) - return -} - -func (r *Resolver) resolveIP(m *D.Msg) (msg *D.Msg, err error) { - msgCh := r.resolve(r.main, m) - if r.fallback == nil { - res := <-msgCh - msg, err = res.Msg, res.Error - return - } - fallbackMsg := r.resolve(r.fallback, m) - res := <-msgCh - if res.Error == nil { - if mmdb == nil { - return nil, errors.New("GeoIP can't use") - } - - if ips := r.msgToIP(res.Msg); len(ips) != 0 { - if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { - // release channel - go func() { <-fallbackMsg }() - msg = res.Msg - return msg, err - } - } - } - - res = <-fallbackMsg - msg, err = res.Msg, res.Error - return -} - -func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { - ip = net.ParseIP(host) - if ip != nil { - return ip, nil - } - - query := &D.Msg{} - dnsType := D.TypeA - if r.ipv6 { - dnsType = D.TypeAAAA - } - query.SetQuestion(D.Fqdn(host), dnsType) - - msg, err := r.Exchange(query) - if err != nil { - return nil, err - } - - ips := r.msgToIP(msg) - if len(ips) == 0 { - return nil, errors.New("can't found ip") - } - - ip = ips[0] - return -} - -func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { - ips := []net.IP{} - - for _, answer := range msg.Answer { - switch ans := answer.(type) { - case *D.AAAA: - ips = append(ips, ans.AAAA) - case *D.A: - ips = append(ips, ans.A) - } - } - - return ips -} - -func (r *Resolver) IPToHost(ip net.IP) (string, bool) { - cache := r.cache.Get(ip.String()) - if cache == nil { - return "", false - } - fqdn := cache.(*D.Msg).Question[0].Name - return strings.TrimRight(fqdn, "."), true -} - -func (r *Resolver) resolve(client []*nameserver, msg *D.Msg) <-chan *result { - ch := make(chan *result) - go func() { - res, err := r.exchange(client, msg) - ch <- &result{Msg: res, Error: err} - }() - return ch -} - -func (r *Resolver) IsMapping() bool { - return r.mapping -} - -func (r *Resolver) IsFakeIP() bool { - return r.fakeip -} - -type NameServer struct { - Net string - Addr string -} - -type nameserver struct { - Client *D.Client +type client struct { + *D.Client Address string } -type Config struct { - Main, Fallback []NameServer - IPv6 bool - EnhancedMode EnhancedMode - Pool *fakeip.Pool -} - -func transform(servers []NameServer) []*nameserver { - var ret []*nameserver - for _, s := range servers { - ret = append(ret, &nameserver{ - Client: &D.Client{ - Net: s.Net, - TLSConfig: &tls.Config{ - ClientSessionCache: globalSessionCache, - // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 - NextProtos: []string{"dns"}, - }, - UDPSize: 4096, - }, - Address: s.Addr, - }) - } - return ret +func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { + return c.ExchangeContext(context.Background(), m) } -func New(config Config) *Resolver { - once.Do(func() { - mmdb, _ = geoip2.Open(C.Path.MMDB()) - }) - - r := &Resolver{ - main: transform(config.Main), - ipv6: config.IPv6, - cache: cache.New(time.Second * 60), - mapping: config.EnhancedMode == MAPPING, - fakeip: config.EnhancedMode == FAKEIP, - pool: config.Pool, - } - if config.Fallback != nil { - r.fallback = transform(config.Fallback) - } - return r +func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address) + return } diff --git a/dns/doh.go b/dns/doh.go new file mode 100644 index 0000000000..51a69df538 --- /dev/null +++ b/dns/doh.go @@ -0,0 +1,75 @@ +package dns + +import ( + "bytes" + "context" + "crypto/tls" + "io/ioutil" + "net/http" + + D "github.com/miekg/dns" +) + +const ( + // dotMimeType is the DoH mimetype that should be used. + dotMimeType = "application/dns-message" + + // dotPath is the URL path that should be used. + dotPath = "/dns-query" +) + +var dohTransport = &http.Transport{ + TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, +} + +type dohClient struct { + url string +} + +func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { + return dc.ExchangeContext(context.Background(), m) +} + +func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + req, err := dc.newRequest(m) + if err != nil { + return nil, err + } + + req = req.WithContext(ctx) + return dc.doRequest(req) +} + +// newRequest returns a new DoH request given a dns.Msg. +func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) { + buf, err := m.Pack() + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, dc.url+"?bla=foo:443", bytes.NewReader(buf)) + if err != nil { + return req, err + } + + req.Header.Set("content-type", dotMimeType) + req.Header.Set("accept", dotMimeType) + return req, nil +} + +func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { + client := &http.Client{Transport: dohTransport} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + buf, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + msg = &D.Msg{} + err = msg.Unpack(buf) + return msg, err +} diff --git a/dns/resolver.go b/dns/resolver.go new file mode 100644 index 0000000000..bb0710d285 --- /dev/null +++ b/dns/resolver.go @@ -0,0 +1,289 @@ +package dns + +import ( + "context" + "crypto/tls" + "errors" + "net" + "strings" + "sync" + "time" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/common/picker" + "github.com/Dreamacro/clash/component/fakeip" + C "github.com/Dreamacro/clash/constant" + + D "github.com/miekg/dns" + geoip2 "github.com/oschwald/geoip2-golang" +) + +var ( + globalSessionCache = tls.NewLRUClientSessionCache(64) + + mmdb *geoip2.Reader + once sync.Once +) + +type resolver interface { + Exchange(m *D.Msg) (msg *D.Msg, err error) + ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) +} + +type result struct { + Msg *D.Msg + Error error +} + +type Resolver struct { + ipv6 bool + mapping bool + fakeip bool + pool *fakeip.Pool + fallback []resolver + main []resolver + cache *cache.Cache +} + +// ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA +func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { + ch := make(chan net.IP) + go func() { + ip, err := r.resolveIP(host, D.TypeA) + if err != nil { + close(ch) + return + } + ch <- ip + }() + + ip, err = r.resolveIP(host, D.TypeAAAA) + if err == nil { + go func() { + <-ch + }() + return + } + + ip, closed := <-ch + if closed { + return nil, errors.New("can't found ip") + } + + return ip, nil +} + +// ResolveIPv4 request with TypeA +func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) { + ip = net.ParseIP(host) + if ip != nil { + return ip, nil + } + + query := &D.Msg{} + query.SetQuestion(D.Fqdn(host), D.TypeA) + + msg, err := r.Exchange(query) + if err != nil { + return nil, err + } + + ips := r.msgToIP(msg) + if len(ips) == 0 { + return nil, errors.New("can't found ip") + } + + ip = ips[0] + return +} + +// Exchange a batch of dns request, and it use cache +func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { + if len(m.Question) == 0 { + return nil, errors.New("should have one question at least") + } + + q := m.Question[0] + cache, expireTime := r.cache.GetWithExpire(q.String()) + if cache != nil { + msg = cache.(*D.Msg).Copy() + setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) + return + } + defer func() { + if msg == nil { + return + } + + putMsgToCache(r.cache, q.String(), msg) + if r.mapping { + ips := r.msgToIP(msg) + for _, ip := range ips { + putMsgToCache(r.cache, ip.String(), msg) + } + } + }() + + isIPReq := isIPRequest(q) + if isIPReq { + msg, err = r.fallbackExchange(m) + return + } + + msg, err = r.batchExchange(r.main, m) + return +} + +// IPToHost return fake-ip or redir-host mapping host +func (r *Resolver) IPToHost(ip net.IP) (string, bool) { + cache := r.cache.Get(ip.String()) + if cache == nil { + return "", false + } + fqdn := cache.(*D.Msg).Question[0].Name + return strings.TrimRight(fqdn, "."), true +} + +func (r *Resolver) IsMapping() bool { + return r.mapping +} + +func (r *Resolver) IsFakeIP() bool { + return r.fakeip +} + +func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) { + in := make(chan interface{}) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + fast := picker.SelectFast(ctx, in) + + wg := sync.WaitGroup{} + wg.Add(len(clients)) + for _, r := range clients { + go func(r resolver) { + defer wg.Done() + msg, err := r.ExchangeContext(ctx, m) + if err != nil || msg.Rcode != D.RcodeSuccess { + return + } + in <- msg + }(r) + } + + // release in channel + go func() { + wg.Wait() + close(in) + }() + + elm, exist := <-fast + if !exist { + return nil, errors.New("All DNS requests failed") + } + + msg = elm.(*D.Msg) + return +} + +func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { + msgCh := r.asyncExchange(r.main, m) + if r.fallback == nil { + res := <-msgCh + msg, err = res.Msg, res.Error + return + } + fallbackMsg := r.asyncExchange(r.fallback, m) + res := <-msgCh + if res.Error == nil { + if mmdb == nil { + return nil, errors.New("GeoIP can't use") + } + + if ips := r.msgToIP(res.Msg); len(ips) != 0 { + if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { + // release channel + go func() { <-fallbackMsg }() + msg = res.Msg + return msg, err + } + } + } + + res = <-fallbackMsg + msg, err = res.Msg, res.Error + return +} + +func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) { + query := &D.Msg{} + query.SetQuestion(D.Fqdn(host), dnsType) + + msg, err := r.Exchange(query) + if err != nil { + return nil, err + } + + ips := r.msgToIP(msg) + if len(ips) == 0 { + return nil, errors.New("can't found ip") + } + + ip = ips[0] + return +} + +func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { + ips := []net.IP{} + + for _, answer := range msg.Answer { + switch ans := answer.(type) { + case *D.AAAA: + ips = append(ips, ans.AAAA) + case *D.A: + ips = append(ips, ans.A) + } + } + + return ips +} + +func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result { + ch := make(chan *result) + go func() { + res, err := r.batchExchange(client, msg) + ch <- &result{Msg: res, Error: err} + }() + return ch +} + +type NameServer struct { + Net string + Addr string +} + +type Config struct { + Main, Fallback []NameServer + IPv6 bool + EnhancedMode EnhancedMode + Pool *fakeip.Pool +} + +func New(config Config) *Resolver { + once.Do(func() { + mmdb, _ = geoip2.Open(C.Path.MMDB()) + }) + + r := &Resolver{ + main: transform(config.Main), + ipv6: config.IPv6, + cache: cache.New(time.Second * 60), + mapping: config.EnhancedMode == MAPPING, + fakeip: config.EnhancedMode == FAKEIP, + pool: config.Pool, + } + if len(config.Fallback) != 0 { + r.fallback = transform(config.Fallback) + } + return r +} diff --git a/dns/util.go b/dns/util.go index c14cb47a7d..e29ea1b003 100644 --- a/dns/util.go +++ b/dns/util.go @@ -1,6 +1,7 @@ package dns import ( + "crypto/tls" "encoding/json" "errors" "time" @@ -107,3 +108,34 @@ func setMsgTTL(msg *D.Msg, ttl uint32) { extra.Header().Ttl = ttl } } + +func isIPRequest(q D.Question) bool { + if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) { + return true + } + return false +} + +func transform(servers []NameServer) []resolver { + ret := []resolver{} + for _, s := range servers { + if s.Net == "https" { + ret = append(ret, &dohClient{url: s.Addr}) + continue + } + + ret = append(ret, &client{ + Client: &D.Client{ + Net: s.Net, + TLSConfig: &tls.Config{ + ClientSessionCache: globalSessionCache, + // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 + NextProtos: []string{"dns"}, + }, + UDPSize: 4096, + }, + Address: s.Addr, + }) + } + return ret +} diff --git a/go.mod b/go.mod index a4cc9e5f02..6ffd572eb3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.0 - github.com/miekg/dns v1.1.9 + github.com/miekg/dns v1.1.14 github.com/oschwald/geoip2-golang v1.2.1 github.com/oschwald/maxminddb-golang v1.3.0 // indirect github.com/sirupsen/logrus v1.4.1 diff --git a/go.sum b/go.sum index c9aef37013..f4ca045a93 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.9 h1:OIdC9wT96RzuZMf2PfKRhFgsStHUUBZLM/lo1LqiM9E= -github.com/miekg/dns v1.1.9/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA= +github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= From 57fdd223f1079af84582c6152ba108d366a6a161 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 29 Jun 2019 00:58:59 +0800 Subject: [PATCH 198/535] Feature: custom dns ipv4/ipv6 dual stack --- adapters/outbound/direct.go | 4 ++-- adapters/outbound/http.go | 2 +- adapters/outbound/shadowsocks.go | 4 ++-- adapters/outbound/socks5.go | 4 ++-- adapters/outbound/util.go | 28 ++++++++++++++++++++++++++++ adapters/outbound/vmess.go | 4 ++-- config/config.go | 1 + dns/iputil.go | 32 ++++++++++++++++++++++++++++++++ dns/resolver.go | 18 ++++++++++++++---- hub/executor/executor.go | 9 ++++++--- tunnel/tunnel.go | 27 ++++----------------------- 11 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 dns/iputil.go diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 563f7074cf..491c170efc 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -16,7 +16,7 @@ func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) { address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) } - c, err := net.DialTimeout("tcp", address, tcpTimeout) + c, err := dialTimeout("tcp", address, tcpTimeout) if err != nil { return nil, err } @@ -30,7 +30,7 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) return nil, nil, err } - addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) + addr, err := resolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, nil, err } diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 58f335dc8d..a617de5b47 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -36,7 +36,7 @@ type HttpOption struct { } func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) { - c, err := net.DialTimeout("tcp", h.addr, tcpTimeout) + c, err := dialTimeout("tcp", h.addr, tcpTimeout) if err == nil && h.tls { cc := tls.Client(c, h.tlsConfig) err = cc.Handshake() diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 7d42272305..b05f5830f3 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -58,7 +58,7 @@ type v2rayObfsOption struct { } func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) { - c, err := net.DialTimeout("tcp", ss.server, tcpTimeout) + c, err := dialTimeout("tcp", ss.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) } @@ -87,7 +87,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, return nil, nil, err } - addr, err := net.ResolveUDPAddr("udp", ss.server) + addr, err := resolveUDPAddr("udp", ss.server) if err != nil { return nil, nil, err } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 7f52b58eb1..6ec611c65e 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -32,7 +32,7 @@ type Socks5Option struct { } func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { - c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) + c, err := dialTimeout("tcp", ss.addr, tcpTimeout) if err == nil && ss.tls { cc := tls.Client(c, ss.tlsConfig) @@ -58,7 +58,7 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { } func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { - c, err := net.DialTimeout("tcp", ss.addr, tcpTimeout) + c, err := dialTimeout("tcp", ss.addr, tcpTimeout) if err == nil && ss.tls { cc := tls.Client(c, ss.tlsConfig) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index d7c134b311..0cccf5d9df 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -12,6 +12,7 @@ import ( "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/dns" ) const ( @@ -96,3 +97,30 @@ func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { n, err := fuc.Conn.Read(b) return n, fuc.RemoteAddr(), err } + +func dialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + ip, err := dns.ResolveIP(host) + if err != nil { + return nil, err + } + + return net.DialTimeout(network, net.JoinHostPort(ip.String(), port), timeout) +} + +func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + ip, err := dns.ResolveIP(host) + if err != nil { + return nil, err + } + return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) +} diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index e1113bd187..7d74936db6 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -32,7 +32,7 @@ type VmessOption struct { } func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) { - c, err := net.DialTimeout("tcp", v.server, tcpTimeout) + c, err := dialTimeout("tcp", v.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", v.server) } @@ -42,7 +42,7 @@ func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) { } func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { - c, err := net.DialTimeout("tcp", v.server, tcpTimeout) + c, err := dialTimeout("tcp", v.server, tcpTimeout) if err != nil { return nil, nil, fmt.Errorf("%s connect error", v.server) } diff --git a/config/config.go b/config/config.go index 598ac83412..fdf55db0b3 100644 --- a/config/config.go +++ b/config/config.go @@ -506,6 +506,7 @@ func parseDNS(cfg rawDNS) (*DNS, error) { dnsCfg := &DNS{ Enable: cfg.Enable, Listen: cfg.Listen, + IPv6: cfg.IPv6, EnhancedMode: cfg.EnhancedMode, } var err error diff --git a/dns/iputil.go b/dns/iputil.go new file mode 100644 index 0000000000..9d8c704882 --- /dev/null +++ b/dns/iputil.go @@ -0,0 +1,32 @@ +package dns + +import ( + "errors" + "net" +) + +var ( + errIPNotFound = errors.New("ip not found") +) + +// ResolveIP with a host, return ip +func ResolveIP(host string) (net.IP, error) { + if DefaultResolver != nil { + if DefaultResolver.ipv6 { + return DefaultResolver.ResolveIP(host) + } + return DefaultResolver.ResolveIPv4(host) + } + + ip := net.ParseIP(host) + if ip != nil { + return ip, nil + } + + ipAddr, err := net.ResolveIPAddr("ip", host) + if err != nil { + return nil, err + } + + return ipAddr.IP, nil +} diff --git a/dns/resolver.go b/dns/resolver.go index bb0710d285..7821e570b6 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -18,6 +18,11 @@ import ( geoip2 "github.com/oschwald/geoip2-golang" ) +var ( + // DefaultResolver aim to resolve ip with host + DefaultResolver *Resolver +) + var ( globalSessionCache = tls.NewLRUClientSessionCache(64) @@ -47,11 +52,16 @@ type Resolver struct { // ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { + ip = net.ParseIP(host) + if ip != nil { + return ip, nil + } + ch := make(chan net.IP) go func() { + defer close(ch) ip, err := r.resolveIP(host, D.TypeA) if err != nil { - close(ch) return } ch <- ip @@ -65,8 +75,8 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { return } - ip, closed := <-ch - if closed { + ip, open := <-ch + if !open { return nil, errors.New("can't found ip") } @@ -275,8 +285,8 @@ func New(config Config) *Resolver { }) r := &Resolver{ - main: transform(config.Main), ipv6: config.IPv6, + main: transform(config.Main), cache: cache.New(time.Second * 60), mapping: config.EnhancedMode == MAPPING, fakeip: config.EnhancedMode == FAKEIP, diff --git a/hub/executor/executor.go b/hub/executor/executor.go index a0bd1c8663..f295108f06 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -59,7 +59,7 @@ func updateExperimental(c *config.Experimental) { func updateDNS(c *config.DNS) { if c.Enable == false { - T.Instance().SetResolver(nil) + dns.DefaultResolver = nil dns.ReCreateServer("", nil) return } @@ -70,12 +70,15 @@ func updateDNS(c *config.DNS) { EnhancedMode: c.EnhancedMode, Pool: c.FakeIPRange, }) - T.Instance().SetResolver(r) + dns.DefaultResolver = r if err := dns.ReCreateServer(c.Listen, r); err != nil { log.Errorln("Start DNS server error: %s", err.Error()) return } - log.Infoln("DNS server listening at: %s", c.Listen) + + if c.Listen != "" { + log.Infoln("DNS server listening at: %s", c.Listen) + } } func updateProxies(proxies map[string]C.Proxy) { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index faeaf46ddf..fedde7f21e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -26,7 +26,6 @@ type Tunnel struct { proxies map[string]C.Proxy configMux *sync.RWMutex traffic *C.Traffic - resolver *dns.Resolver // experimental features ignoreResolveFail bool @@ -86,15 +85,6 @@ func (t *Tunnel) SetMode(mode Mode) { t.mode = mode } -// SetResolver change the resolver of tunnel -func (t *Tunnel) SetResolver(resolver *dns.Resolver) { - t.resolver = resolver -} - -func (t *Tunnel) hasResolver() bool { - return t.resolver != nil -} - func (t *Tunnel) process() { queue := t.queue.Out() for { @@ -105,20 +95,11 @@ func (t *Tunnel) process() { } func (t *Tunnel) resolveIP(host string) (net.IP, error) { - if t.resolver == nil { - ipAddr, err := net.ResolveIPAddr("ip", host) - if err != nil { - return nil, err - } - - return ipAddr.IP, nil - } - - return t.resolver.ResolveIP(host) + return dns.ResolveIP(host) } func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { - return t.hasResolver() && (t.resolver.IsMapping() || t.resolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil + return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil } func (t *Tunnel) handleConn(localConn C.ServerAdapter) { @@ -132,11 +113,11 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { // preprocess enhanced-mode metadata if t.needLookupIP(metadata) { - host, exist := t.resolver.IPToHost(*metadata.DstIP) + host, exist := dns.DefaultResolver.IPToHost(*metadata.DstIP) if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName - if t.resolver.IsFakeIP() { + if dns.DefaultResolver.IsFakeIP() { metadata.DstIP = nil } } From 34338e7107c1868124f8aab2446f6b71c9b0640f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 29 Jun 2019 16:48:48 +0800 Subject: [PATCH 199/535] Chore: update dependencies & fix typo --- config/config.go | 4 ++-- dns/iputil.go | 2 +- dns/resolver.go | 8 ++++---- go.mod | 8 ++++---- go.sum | 19 ++++++++++--------- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/config/config.go b/config/config.go index fdf55db0b3..2bbaf9bd1f 100644 --- a/config/config.go +++ b/config/config.go @@ -234,7 +234,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { } var proxy C.ProxyAdapter - err := fmt.Errorf("can't parse") + err := fmt.Errorf("cannot parse") switch proxyType { case "ss": ssOption := &adapters.ShadowSocksOption{} @@ -293,7 +293,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { var group C.ProxyAdapter ps := []C.Proxy{} - err := fmt.Errorf("can't parse") + err := fmt.Errorf("cannot parse") switch groupType { case "url-test": urlTestOption := &adapters.URLTestOption{} diff --git a/dns/iputil.go b/dns/iputil.go index 9d8c704882..106c25d8bc 100644 --- a/dns/iputil.go +++ b/dns/iputil.go @@ -6,7 +6,7 @@ import ( ) var ( - errIPNotFound = errors.New("ip not found") + errIPNotFound = errors.New("cannot found ip") ) // ResolveIP with a host, return ip diff --git a/dns/resolver.go b/dns/resolver.go index 7821e570b6..75c3796351 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -77,7 +77,7 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { ip, open := <-ch if !open { - return nil, errors.New("can't found ip") + return nil, errIPNotFound } return ip, nil @@ -100,7 +100,7 @@ func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) { ips := r.msgToIP(msg) if len(ips) == 0 { - return nil, errors.New("can't found ip") + return nil, errIPNotFound } ip = ips[0] @@ -207,7 +207,7 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { res := <-msgCh if res.Error == nil { if mmdb == nil { - return nil, errors.New("GeoIP can't use") + return nil, errors.New("GeoIP cannot use") } if ips := r.msgToIP(res.Msg); len(ips) != 0 { @@ -236,7 +236,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) ips := r.msgToIP(msg) if len(ips) == 0 { - return nil, errors.New("can't found ip") + return nil, errIPNotFound } ip = ips[0] diff --git a/go.mod b/go.mod index 6ffd572eb3..c64d6c20bb 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,12 @@ require ( github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.0 github.com/miekg/dns v1.1.14 - github.com/oschwald/geoip2-golang v1.2.1 - github.com/oschwald/maxminddb-golang v1.3.0 // indirect - github.com/sirupsen/logrus v1.4.1 + github.com/oschwald/geoip2-golang v1.3.0 + github.com/oschwald/maxminddb-golang v1.3.1 // indirect + github.com/sirupsen/logrus v1.4.2 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 - golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect + golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index f4ca045a93..afbc3bf886 100644 --- a/go.sum +++ b/go.sum @@ -20,14 +20,14 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGi github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA= github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/oschwald/geoip2-golang v1.2.1 h1:3iz+jmeJc6fuCyWeKgtXSXu7+zvkxJbHFXkMT5FVebU= -github.com/oschwald/geoip2-golang v1.2.1/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= -github.com/oschwald/maxminddb-golang v1.3.0 h1:oTh8IBSj10S5JNlUDg5WjJ1QdBMdeaZIkPEVfESSWgE= -github.com/oschwald/maxminddb-golang v1.3.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= +github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8= +github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= +github.com/oschwald/maxminddb-golang v1.3.1 h1:kPc5+ieL5CC/Zn0IaXJPxDFlUxKTQEU8QBTtmfQDAIo= +github.com/oschwald/maxminddb-golang v1.3.1/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= 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/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -36,12 +36,13 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/Le golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20190215142949-d0b11bdaac8a/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-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 0eff8516c0f2fce1ff1c497947c26a90646b8c24 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 30 Jun 2019 15:10:56 +0800 Subject: [PATCH 200/535] Chore: improve Dockerfile --- Dockerfile | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index c36a4ffc48..bd0d05c904 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,18 @@ -FROM golang:latest as builder +FROM golang:alpine as builder -RUN wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \ +RUN apk add --no-cache make git && \ + wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \ tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \ mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb - WORKDIR /clash-src - COPY . /clash-src - RUN go mod download && \ - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-w -s' -o /clash + make linux-amd64 && \ + mv ./bin/clash-linux-amd64 /clash FROM alpine:latest RUN apk add --no-cache ca-certificates - COPY --from=builder /Country.mmdb /root/.config/clash/ COPY --from=builder /clash / - ENTRYPOINT ["/clash"] From 7c6c147a184a2b7832afc3a080a573c37db83a1c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 2 Jul 2019 19:18:03 +0800 Subject: [PATCH 201/535] Optimization: refactor picker --- adapters/outbound/base.go | 13 +++++-- adapters/outbound/fallback.go | 3 +- adapters/outbound/loadbalance.go | 3 +- adapters/outbound/urltest.go | 38 +++++++------------- common/picker/picker.go | 59 ++++++++++++++++++++++++-------- common/picker/picker_test.go | 44 ++++++++++++------------ constant/adapters.go | 3 +- dns/resolver.go | 24 ++++--------- hub/route/proxies.go | 40 ++++++++++++---------- 9 files changed, 123 insertions(+), 104 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 5887712119..7c2254984a 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "encoding/json" "errors" "net" @@ -99,7 +100,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { } // URLTest get the delay for the specified URL -func (p *Proxy) URLTest(url string) (t uint16, err error) { +func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { defer func() { p.alive = err == nil record := C.DelayHistory{Time: time.Now()} @@ -123,6 +124,13 @@ func (p *Proxy) URLTest(url string) (t uint16, err error) { return } defer instance.Close() + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return + } + req = req.WithContext(ctx) + transport := &http.Transport{ Dial: func(string, string) (net.Conn, error) { return instance, nil @@ -133,8 +141,9 @@ func (p *Proxy) URLTest(url string) (t uint16, err error) { TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, } + client := http.Client{Transport: transport} - resp, err := client.Get(url) + resp, err := client.Do(req) if err != nil { return } diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 913383a448..67a6ab0866 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "encoding/json" "errors" "net" @@ -90,7 +91,7 @@ func (f *Fallback) validTest() { for _, p := range f.proxies { go func(p C.Proxy) { - p.URLTest(f.rawURL) + p.URLTest(context.Background(), f.rawURL) wg.Done() }(p) } diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index e870271347..9418863f0a 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "encoding/json" "errors" "net" @@ -95,7 +96,7 @@ func (lb *LoadBalance) validTest() { for _, p := range lb.proxies { go func(p C.Proxy) { - p.URLTest(lb.rawURL) + p.URLTest(context.Background(), lb.rawURL) wg.Done() }(p) } diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 9a219cc095..cfe6b5ba52 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "net" - "sync" "sync/atomic" "time" @@ -103,35 +102,22 @@ func (u *URLTest) speedTest() { } defer atomic.StoreInt32(&u.once, 0) - wg := sync.WaitGroup{} - wg.Add(len(u.proxies)) - c := make(chan interface{}) - fast := picker.SelectFast(context.Background(), c) - timer := time.NewTimer(u.interval) - + ctx, cancel := context.WithTimeout(context.Background(), u.interval) + defer cancel() + picker, ctx := picker.WithContext(ctx) for _, p := range u.proxies { - go func(p C.Proxy) { - _, err := p.URLTest(u.rawURL) - if err == nil { - c <- p + picker.Go(func() (interface{}, error) { + _, err := p.URLTest(ctx, u.rawURL) + if err != nil { + return nil, err } - wg.Done() - }(p) + return p, nil + }) } - go func() { - wg.Wait() - close(c) - }() - - select { - case <-timer.C: - // Wait for fast to return or close. - <-fast - case p, open := <-fast: - if open { - u.fast = p.(C.Proxy) - } + fast := picker.Wait() + if fast != nil { + u.fast = fast.(C.Proxy) } } diff --git a/common/picker/picker.go b/common/picker/picker.go index 07e2076d15..f420679ddd 100644 --- a/common/picker/picker.go +++ b/common/picker/picker.go @@ -1,22 +1,53 @@ package picker -import "context" +import ( + "context" + "sync" +) + +// Picker provides synchronization, and Context cancelation +// for groups of goroutines working on subtasks of a common task. +// Inspired by errGroup +type Picker struct { + cancel func() + + wg sync.WaitGroup + + once sync.Once + result interface{} +} + +// WithContext returns a new Picker and an associated Context derived from ctx. +func WithContext(ctx context.Context) (*Picker, context.Context) { + ctx, cancel := context.WithCancel(ctx) + return &Picker{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, +// then returns the first nil error result (if any) from them. +func (p *Picker) Wait() interface{} { + p.wg.Wait() + if p.cancel != nil { + p.cancel() + } + return p.result +} + +// Go calls the given function in a new goroutine. +// The first call to return a nil error cancels the group; its result will be returned by Wait. +func (p *Picker) Go(f func() (interface{}, error)) { + p.wg.Add(1) -func SelectFast(ctx context.Context, in <-chan interface{}) <-chan interface{} { - out := make(chan interface{}) go func() { - select { - case p, open := <-in: - if open { - out <- p - } - case <-ctx.Done(): - } + defer p.wg.Done() - close(out) - for range in { + if ret, err := f(); err == nil { + p.once.Do(func() { + p.result = ret + if p.cancel != nil { + p.cancel() + } + }) } }() - - return out } diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go index f33627f743..7b225d3940 100644 --- a/common/picker/picker_test.go +++ b/common/picker/picker_test.go @@ -6,39 +6,37 @@ import ( "time" ) -func sleepAndSend(delay int, in chan<- interface{}, input interface{}) { - time.Sleep(time.Millisecond * time.Duration(delay)) - in <- input -} - -func sleepAndClose(delay int, in chan interface{}) { - time.Sleep(time.Millisecond * time.Duration(delay)) - close(in) +func sleepAndSend(ctx context.Context, delay int, input interface{}) func() (interface{}, error) { + return func() (interface{}, error) { + timer := time.NewTimer(time.Millisecond * time.Duration(delay)) + select { + case <-timer.C: + return input, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + } } func TestPicker_Basic(t *testing.T) { - in := make(chan interface{}) - fast := SelectFast(context.Background(), in) - go sleepAndSend(20, in, 1) - go sleepAndSend(30, in, 2) - go sleepAndClose(40, in) + picker, ctx := WithContext(context.Background()) + picker.Go(sleepAndSend(ctx, 30, 2)) + picker.Go(sleepAndSend(ctx, 20, 1)) - number, exist := <-fast - if !exist || number != 1 { - t.Error("should recv 1", exist, number) + number := picker.Wait() + if number != nil && number.(int) != 1 { + t.Error("should recv 1", number) } } func TestPicker_Timeout(t *testing.T) { - in := make(chan interface{}) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*5) defer cancel() - fast := SelectFast(ctx, in) - go sleepAndSend(20, in, 1) - go sleepAndClose(30, in) + picker, ctx := WithContext(ctx) + picker.Go(sleepAndSend(ctx, 20, 1)) - _, exist := <-fast - if exist { - t.Error("should recv false") + number := picker.Wait() + if number != nil { + t.Error("should recv nil") } } diff --git a/constant/adapters.go b/constant/adapters.go index e05cd58ed8..11844bc77c 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -1,6 +1,7 @@ package constant import ( + "context" "net" "time" ) @@ -44,7 +45,7 @@ type Proxy interface { Alive() bool DelayHistory() []DelayHistory LastDelay() uint16 - URLTest(url string) (uint16, error) + URLTest(ctx context.Context, url string) (uint16, error) } // AdapterType is enum of adapter type diff --git a/dns/resolver.go b/dns/resolver.go index 75c3796351..044976f8a7 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -163,32 +163,22 @@ func (r *Resolver) IsFakeIP() bool { } func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) { - in := make(chan interface{}) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - fast := picker.SelectFast(ctx, in) + fast, ctx := picker.WithContext(ctx) - wg := sync.WaitGroup{} - wg.Add(len(clients)) for _, r := range clients { - go func(r resolver) { - defer wg.Done() + fast.Go(func() (interface{}, error) { msg, err := r.ExchangeContext(ctx, m) if err != nil || msg.Rcode != D.RcodeSuccess { - return + return nil, errors.New("resolve error") } - in <- msg - }(r) + return msg, nil + }) } - // release in channel - go func() { - wg.Wait() - close(in) - }() - - elm, exist := <-fast - if !exist { + elm := fast.Wait() + if elm == nil { return nil, errors.New("All DNS requests failed") } diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 7122191707..e4c77dc03e 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -9,6 +9,7 @@ import ( "time" A "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/common/picker" C "github.com/Dreamacro/clash/constant" T "github.com/Dreamacro/clash/tunnel" @@ -110,27 +111,28 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - sigCh := make(chan uint16) - go func() { - t, err := proxy.URLTest(url) - if err != nil { - sigCh <- 0 - } - sigCh <- t - }() + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) + defer cancel() + picker, ctx := picker.WithContext(ctx) + picker.Go(func() (interface{}, error) { + return proxy.URLTest(ctx, url) + }) - select { - case <-time.After(time.Millisecond * time.Duration(timeout)): + elm := picker.Wait() + if elm == nil { render.Status(r, http.StatusRequestTimeout) render.JSON(w, r, ErrRequestTimeout) - case t := <-sigCh: - if t == 0 { - render.Status(r, http.StatusServiceUnavailable) - render.JSON(w, r, newError("An error occurred in the delay test")) - } else { - render.JSON(w, r, render.M{ - "delay": t, - }) - } + return + } + + delay := elm.(uint16) + if delay == 0 { + render.Status(r, http.StatusServiceUnavailable) + render.JSON(w, r, newError("An error occurred in the delay test")) + return } + + render.JSON(w, r, render.M{ + "delay": delay, + }) } From f867f0254678342113d06e47f8f5951626b37f59 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 12 Jul 2019 15:44:12 +0800 Subject: [PATCH 202/535] Feature(API): logs and traffic support websocket --- hub/route/server.go | 83 ++++++++++++++++++++++++++++++++++----------- log/log.go | 5 +++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/hub/route/server.go b/hub/route/server.go index d530684a19..7c3a233476 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -1,6 +1,7 @@ package route import ( + "bytes" "encoding/json" "net/http" "strings" @@ -12,6 +13,7 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/cors" "github.com/go-chi/render" + "github.com/gorilla/websocket" ) var ( @@ -19,6 +21,8 @@ var ( serverAddr = "" uiPath = "" + + upgrader = websocket.Upgrader{} ) type Traffic struct { @@ -47,15 +51,12 @@ func Start(addr string, secret string) { MaxAge: 300, }) - root := chi.NewRouter().With(jsonContentType) - root.Get("/traffic", traffic) - root.Get("/logs", getLogs) - r.Get("/", hello) r.Group(func(r chi.Router) { r.Use(cors.Handler, authentication) - r.Mount("/", root) + r.Get("/logs", getLogs) + r.Get("/traffic", traffic) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) @@ -78,14 +79,6 @@ func Start(addr string, secret string) { } } -func jsonContentType(next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - next.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} - func authentication(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { header := r.Header.Get("Authorization") @@ -113,19 +106,44 @@ func hello(w http.ResponseWriter, r *http.Request) { } func traffic(w http.ResponseWriter, r *http.Request) { - render.Status(r, http.StatusOK) + var wsConn *websocket.Conn + if websocket.IsWebSocketUpgrade(r) { + var err error + wsConn, err = upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + } + + if wsConn == nil { + w.Header().Set("Content-Type", "application/json") + render.Status(r, http.StatusOK) + } tick := time.NewTicker(time.Second) t := T.Instance().Traffic() + buf := &bytes.Buffer{} + var err error for range tick.C { + buf.Reset() up, down := t.Now() - if err := json.NewEncoder(w).Encode(Traffic{ + if err := json.NewEncoder(buf).Encode(Traffic{ Up: up, Down: down, }); err != nil { break } - w.(http.Flusher).Flush() + + if wsConn == nil { + _, err = w.Write(buf.Bytes()) + w.(http.Flusher).Flush() + } else { + err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) + } + + if err != nil { + break + } } } @@ -147,20 +165,47 @@ func getLogs(w http.ResponseWriter, r *http.Request) { return } + var wsConn *websocket.Conn + if websocket.IsWebSocketUpgrade(r) { + var err error + wsConn, err = upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + } + + if wsConn == nil { + w.Header().Set("Content-Type", "application/json") + render.Status(r, http.StatusOK) + } + sub := log.Subscribe() - render.Status(r, http.StatusOK) + defer log.UnSubscribe(sub) + buf := &bytes.Buffer{} + var err error for elm := range sub { + buf.Reset() log := elm.(*log.Event) if log.LogLevel < level { continue } - if err := json.NewEncoder(w).Encode(Log{ + if err := json.NewEncoder(buf).Encode(Log{ Type: log.Type(), Payload: log.Payload, }); err != nil { break } - w.(http.Flusher).Flush() + + if wsConn == nil { + _, err = w.Write(buf.Bytes()) + w.(http.Flusher).Flush() + } else { + err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) + } + + if err != nil { + break + } } } diff --git a/log/log.go b/log/log.go index 251929de1d..7190cc8989 100644 --- a/log/log.go +++ b/log/log.go @@ -62,6 +62,11 @@ func Subscribe() observable.Subscription { return sub } +func UnSubscribe(sub observable.Subscription) { + source.UnSubscribe(sub) + return +} + func Level() LogLevel { return level } From 1a21c8ebfdf6e35f47158eed90993b9e46ed42aa Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 14 Jul 2019 19:29:58 +0800 Subject: [PATCH 203/535] Feature(dns): support custom hosts --- README.md | 6 ++ component/domain-trie/node.go | 26 +++++++ component/domain-trie/tire.go | 84 ++++++++++++++++++++ component/domain-trie/trie_test.go | 69 ++++++++++++++++ config/config.go | 30 +++++-- dns/middleware.go | 121 +++++++++++++++++++++++++++++ dns/resolver.go | 23 ++++-- dns/server.go | 74 +++--------------- go.mod | 2 +- hub/executor/executor.go | 1 + 10 files changed, 358 insertions(+), 78 deletions(-) create mode 100644 component/domain-trie/node.go create mode 100644 component/domain-trie/tire.go create mode 100644 component/domain-trie/trie_test.go create mode 100644 dns/middleware.go diff --git a/README.md b/README.md index a031ecea54..f0d4f2d7ec 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,12 @@ experimental: # listen: 0.0.0.0:53 # enhanced-mode: redir-host # or fake-ip # # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it + # # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) + # # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com) + # # NOTE: hosts don't work with `fake-ip` + # hosts: + # '*.clash.dev': 127.0.0.1 + # 'alpha.clash.dev': '::1' # nameserver: # - 114.114.114.114 # - tls://dns.rubyfish.cn:853 # dns over tls diff --git a/component/domain-trie/node.go b/component/domain-trie/node.go new file mode 100644 index 0000000000..be8ba91fe4 --- /dev/null +++ b/component/domain-trie/node.go @@ -0,0 +1,26 @@ +package trie + +// Node is the trie's node +type Node struct { + Data interface{} + children map[string]*Node +} + +func (n *Node) getChild(s string) *Node { + return n.children[s] +} + +func (n *Node) hasChild(s string) bool { + return n.getChild(s) != nil +} + +func (n *Node) addChild(s string, child *Node) { + n.children[s] = child +} + +func newNode(data interface{}) *Node { + return &Node{ + Data: data, + children: map[string]*Node{}, + } +} diff --git a/component/domain-trie/tire.go b/component/domain-trie/tire.go new file mode 100644 index 0000000000..8d57021431 --- /dev/null +++ b/component/domain-trie/tire.go @@ -0,0 +1,84 @@ +package trie + +import ( + "errors" + "strings" +) + +const ( + wildcard = "*" + domainStep = "." +) + +var ( + // ErrInvalidDomain means insert domain is invalid + ErrInvalidDomain = errors.New("invalid domain") +) + +// Trie contains the main logic for adding and searching nodes for domain segments. +// support wildcard domain (e.g *.google.com) +type Trie struct { + root *Node +} + +// Insert adds a node to the trie. +// Support +// 1. www.example.com +// 2. *.example.com +// 3. subdomain.*.example.com +func (t *Trie) Insert(domain string, data interface{}) error { + parts := strings.Split(domain, domainStep) + if len(parts) < 2 { + return ErrInvalidDomain + } + + node := t.root + // reverse storage domain part to save space + for i := len(parts) - 1; i >= 0; i-- { + part := parts[i] + if !node.hasChild(part) { + node.addChild(part, newNode(nil)) + } + + node = node.getChild(part) + } + + node.Data = data + return nil +} + +// Search is the most important part of the Trie. +// Priority as: +// 1. static part +// 2. wildcard domain +func (t *Trie) Search(domain string) *Node { + parts := strings.Split(domain, domainStep) + if len(parts) < 2 { + return nil + } + + n := t.root + for i := len(parts) - 1; i >= 0; i-- { + part := parts[i] + + var child *Node + if !n.hasChild(part) { + if !n.hasChild(wildcard) { + return nil + } + + child = n.getChild(wildcard) + } else { + child = n.getChild(part) + } + + n = child + } + + return n +} + +// New returns a new, empty Trie. +func New() *Trie { + return &Trie{root: newNode(nil)} +} diff --git a/component/domain-trie/trie_test.go b/component/domain-trie/trie_test.go new file mode 100644 index 0000000000..cd80ce3da3 --- /dev/null +++ b/component/domain-trie/trie_test.go @@ -0,0 +1,69 @@ +package trie + +import ( + "net" + "testing" +) + +func TestTrie_Basic(t *testing.T) { + tree := New() + domains := []string{ + "example.com", + "google.com", + } + + for _, domain := range domains { + tree.Insert(domain, net.ParseIP("127.0.0.1")) + } + + node := tree.Search("example.com") + if node == nil { + t.Error("should not recv nil") + } + + if !node.Data.(net.IP).Equal(net.IP{127, 0, 0, 1}) { + t.Error("should equal 127.0.0.1") + } +} + +func TestTrie_Wildcard(t *testing.T) { + tree := New() + domains := []string{ + "*.example.com", + "sub.*.example.com", + "*.dev", + } + + for _, domain := range domains { + tree.Insert(domain, nil) + } + + if tree.Search("sub.example.com") == nil { + t.Error("should not recv nil") + } + + if tree.Search("sub.foo.example.com") == nil { + t.Error("should not recv nil") + } + + if tree.Search("foo.sub.example.com") != nil { + t.Error("should recv nil") + } + + if tree.Search("foo.example.dev") != nil { + t.Error("should recv nil") + } +} + +func TestTrie_Boundary(t *testing.T) { + tree := New() + tree.Insert("*.dev", nil) + + if err := tree.Insert("com", nil); err == nil { + t.Error("should recv err") + } + + if tree.Search("dev") != nil { + t.Error("should recv nil") + } +} diff --git a/config/config.go b/config/config.go index 2bbaf9bd1f..d11e243cdd 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ import ( adapters "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/auth" + trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/fakeip" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -42,6 +43,7 @@ type DNS struct { IPv6 bool `yaml:"ipv6"` NameServer []dns.NameServer `yaml:"nameserver"` Fallback []dns.NameServer `yaml:"fallback"` + Hosts *trie.Trie `yaml:"-"` Listen string `yaml:"listen"` EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` FakeIPRange *fakeip.Pool @@ -63,13 +65,14 @@ type Config struct { } type rawDNS struct { - Enable bool `yaml:"enable"` - IPv6 bool `yaml:"ipv6"` - NameServer []string `yaml:"nameserver"` - Fallback []string `yaml:"fallback"` - Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` - FakeIPRange string `yaml:"fake-ip-range"` + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []string `yaml:"nameserver"` + Hosts map[string]string `yaml:"hosts"` + Fallback []string `yaml:"fallback"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + FakeIPRange string `yaml:"fake-ip-range"` } type rawConfig struct { @@ -134,6 +137,7 @@ func readConfig(path string) (*rawConfig, error) { DNS: rawDNS{ Enable: false, FakeIPRange: "198.18.0.1/16", + Hosts: map[string]string{}, }, } err = yaml.Unmarshal([]byte(data), &rawConfig) @@ -518,6 +522,18 @@ func parseDNS(cfg rawDNS) (*DNS, error) { return nil, err } + if len(cfg.Hosts) != 0 { + tree := trie.New() + for domain, ipStr := range cfg.Hosts { + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, fmt.Errorf("%s is not a valid IP", ipStr) + } + tree.Insert(domain, ip) + } + dnsCfg.Hosts = tree + } + if cfg.EnhancedMode == dns.FAKEIP { _, ipnet, err := net.ParseCIDR(cfg.FakeIPRange) if err != nil { diff --git a/dns/middleware.go b/dns/middleware.go new file mode 100644 index 0000000000..1b9198bdb9 --- /dev/null +++ b/dns/middleware.go @@ -0,0 +1,121 @@ +package dns + +import ( + "fmt" + "net" + "strings" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/fakeip" + "github.com/Dreamacro/clash/log" + + D "github.com/miekg/dns" +) + +type handler func(w D.ResponseWriter, r *D.Msg) + +func withFakeIP(cache *cache.Cache, pool *fakeip.Pool) handler { + return func(w D.ResponseWriter, r *D.Msg) { + q := r.Question[0] + + cacheItem := cache.Get("fakeip:" + q.String()) + if cache != nil { + msg := cacheItem.(*D.Msg).Copy() + setMsgTTL(msg, 1) + msg.SetReply(r) + w.WriteMsg(msg) + return + } + + rr := &D.A{} + rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} + ip := pool.Get() + rr.A = ip + msg := r.Copy() + msg.Answer = []D.RR{rr} + putMsgToCache(cache, "fakeip:"+q.String(), msg) + putMsgToCache(cache, ip.String(), msg) + + setMsgTTL(msg, 1) + return + } +} + +func withResolver(resolver *Resolver) handler { + return func(w D.ResponseWriter, r *D.Msg) { + msg, err := resolver.Exchange(r) + + if err != nil { + q := r.Question[0] + qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String()) + log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err) + D.HandleFailed(w, r) + return + } + msg.SetReply(r) + w.WriteMsg(msg) + return + } +} + +func withHost(resolver *Resolver, next handler) handler { + hosts := resolver.hosts + if hosts == nil { + panic("dns/withHost: hosts should not be nil") + } + + return func(w D.ResponseWriter, r *D.Msg) { + q := r.Question[0] + if q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA { + next(w, r) + return + } + + domain := strings.TrimRight(q.Name, ".") + host := hosts.Search(domain) + if host == nil { + next(w, r) + return + } + + ip := host.Data.(net.IP) + if q.Qtype == D.TypeAAAA && ip.To16() == nil { + next(w, r) + return + } else if q.Qtype == D.TypeA && ip.To4() == nil { + next(w, r) + return + } + + var rr D.RR + if q.Qtype == D.TypeAAAA { + record := &D.AAAA{} + record.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: dnsDefaultTTL} + record.AAAA = ip + rr = record + } else { + record := &D.A{} + record.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} + record.A = ip + rr = record + } + + msg := r.Copy() + msg.Answer = []D.RR{rr} + msg.SetReply(r) + w.WriteMsg(msg) + return + } +} + +func newHandler(resolver *Resolver) handler { + if resolver.IsFakeIP() { + return withFakeIP(resolver.cache, resolver.pool) + } + + if resolver.hosts != nil { + return withHost(resolver, withResolver(resolver)) + } + + return withResolver(resolver) +} diff --git a/dns/resolver.go b/dns/resolver.go index 044976f8a7..a41e68fd20 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -11,11 +11,13 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/picker" + trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/fakeip" C "github.com/Dreamacro/clash/constant" D "github.com/miekg/dns" geoip2 "github.com/oschwald/geoip2-golang" + "golang.org/x/sync/singleflight" ) var ( @@ -44,9 +46,11 @@ type Resolver struct { ipv6 bool mapping bool fakeip bool + hosts *trie.Trie pool *fakeip.Pool fallback []resolver main []resolver + group singleflight.Group cache *cache.Cache } @@ -134,13 +138,20 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { } }() - isIPReq := isIPRequest(q) - if isIPReq { - msg, err = r.fallbackExchange(m) - return + ret, err, _ := r.group.Do(q.String(), func() (interface{}, error) { + isIPReq := isIPRequest(q) + if isIPReq { + msg, err := r.fallbackExchange(m) + return msg, err + } + + return r.batchExchange(r.main, m) + }) + + if err == nil { + msg = ret.(*D.Msg) } - msg, err = r.batchExchange(r.main, m) return } @@ -266,6 +277,7 @@ type Config struct { Main, Fallback []NameServer IPv6 bool EnhancedMode EnhancedMode + Hosts *trie.Trie Pool *fakeip.Pool } @@ -280,6 +292,7 @@ func New(config Config) *Resolver { cache: cache.New(time.Second * 60), mapping: config.EnhancedMode == MAPPING, fakeip: config.EnhancedMode == FAKEIP, + hosts: config.Hosts, pool: config.Pool, } if len(config.Fallback) != 0 { diff --git a/dns/server.go b/dns/server.go index 21dcac0935..9649587d0b 100644 --- a/dns/server.go +++ b/dns/server.go @@ -1,12 +1,8 @@ package dns import ( - "errors" - "fmt" "net" - "github.com/Dreamacro/clash/log" - "github.com/miekg/dns" D "github.com/miekg/dns" ) @@ -19,79 +15,26 @@ var ( type Server struct { *D.Server - r *Resolver + handler handler } func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { - if s.r.IsFakeIP() { - msg, err := s.handleFakeIP(r) - if err != nil { - D.HandleFailed(w, r) - return - } - msg.SetReply(r) - w.WriteMsg(msg) - return - } - - msg, err := s.r.Exchange(r) - - if err != nil { - if len(r.Question) > 0 { - q := r.Question[0] - qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String()) - log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err) - } - D.HandleFailed(w, r) - return - } - msg.SetReply(r) - w.WriteMsg(msg) -} - -func (s *Server) handleFakeIP(r *D.Msg) (msg *D.Msg, err error) { if len(r.Question) == 0 { - err = errors.New("should have one question at least") - return - } - - q := r.Question[0] - - cache := s.r.cache.Get("fakeip:" + q.String()) - if cache != nil { - msg = cache.(*D.Msg).Copy() - setMsgTTL(msg, 1) + D.HandleFailed(w, r) return } - var ip net.IP - defer func() { - if msg == nil { - return - } - - putMsgToCache(s.r.cache, "fakeip:"+q.String(), msg) - putMsgToCache(s.r.cache, ip.String(), msg) - - setMsgTTL(msg, 1) - }() - - rr := &D.A{} - rr.Hdr = dns.RR_Header{Name: r.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: dnsDefaultTTL} - ip = s.r.pool.Get() - rr.A = ip - msg = r.Copy() - msg.Answer = []D.RR{rr} - return + s.handler(w, r) } -func (s *Server) setReslover(r *Resolver) { - s.r = r +func (s *Server) setHandler(handler handler) { + s.handler = handler } func ReCreateServer(addr string, resolver *Resolver) error { if addr == address { - server.setReslover(resolver) + handler := newHandler(resolver) + server.setHandler(handler) return nil } @@ -116,7 +59,8 @@ func ReCreateServer(addr string, resolver *Resolver) error { } address = addr - server = &Server{r: resolver} + handler := newHandler(resolver) + server = &Server{handler: handler} server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} go func() { diff --git a/go.mod b/go.mod index c64d6c20bb..23fe1c71dc 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/sirupsen/logrus v1.4.2 golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 - golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect + golang.org/x/sync v0.0.0-20190423024810-112230192c58 gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index f295108f06..82d5f6e0bc 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -67,6 +67,7 @@ func updateDNS(c *config.DNS) { Main: c.NameServer, Fallback: c.Fallback, IPv6: c.IPv6, + Hosts: c.Hosts, EnhancedMode: c.EnhancedMode, Pool: c.FakeIPRange, }) From c1b5e4f5611a5f2f90bcc442b364b084b01554ef Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 14 Jul 2019 23:16:52 +0800 Subject: [PATCH 204/535] Fix(dns): Incorrect variable name --- dns/middleware.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/middleware.go b/dns/middleware.go index 1b9198bdb9..edc42c42fd 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -19,7 +19,7 @@ func withFakeIP(cache *cache.Cache, pool *fakeip.Pool) handler { q := r.Question[0] cacheItem := cache.Get("fakeip:" + q.String()) - if cache != nil { + if cacheItem != nil { msg := cacheItem.(*D.Msg).Copy() setMsgTTL(msg, 1) msg.SetReply(r) From 0dd2a6dee5fdbf8598f47328ae44f81338b3fe8a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 15 Jul 2019 10:18:42 +0800 Subject: [PATCH 205/535] Fix(dns): set handler when resolver not nil --- dns/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/server.go b/dns/server.go index 9649587d0b..2f8585c07b 100644 --- a/dns/server.go +++ b/dns/server.go @@ -32,7 +32,7 @@ func (s *Server) setHandler(handler handler) { } func ReCreateServer(addr string, resolver *Resolver) error { - if addr == address { + if addr == address && resolver != nil { handler := newHandler(resolver) server.setHandler(handler) return nil From 6077e825c5f2f706f0e4c0b80af3f71acae0af35 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 15 Jul 2019 17:44:55 +0800 Subject: [PATCH 206/535] Fix(dns): miss response --- dns/middleware.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dns/middleware.go b/dns/middleware.go index edc42c42fd..b456f855b5 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -37,6 +37,7 @@ func withFakeIP(cache *cache.Cache, pool *fakeip.Pool) handler { putMsgToCache(cache, ip.String(), msg) setMsgTTL(msg, 1) + w.WriteMsg(msg) return } } From 3497fdaf45e06e75b558646735b7e335c58b6ffd Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 15 Jul 2019 18:00:51 +0800 Subject: [PATCH 207/535] Fix(domain-trie): Incorrect result --- component/domain-trie/tire.go | 4 ++++ component/domain-trie/trie_test.go | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/component/domain-trie/tire.go b/component/domain-trie/tire.go index 8d57021431..13567a98b0 100644 --- a/component/domain-trie/tire.go +++ b/component/domain-trie/tire.go @@ -75,6 +75,10 @@ func (t *Trie) Search(domain string) *Node { n = child } + if n.Data == nil { + return nil + } + return n } diff --git a/component/domain-trie/trie_test.go b/component/domain-trie/trie_test.go index cd80ce3da3..bd594e97aa 100644 --- a/component/domain-trie/trie_test.go +++ b/component/domain-trie/trie_test.go @@ -5,6 +5,8 @@ import ( "testing" ) +var localIP = net.IP{127, 0, 0, 1} + func TestTrie_Basic(t *testing.T) { tree := New() domains := []string{ @@ -13,7 +15,7 @@ func TestTrie_Basic(t *testing.T) { } for _, domain := range domains { - tree.Insert(domain, net.ParseIP("127.0.0.1")) + tree.Insert(domain, localIP) } node := tree.Search("example.com") @@ -21,7 +23,7 @@ func TestTrie_Basic(t *testing.T) { t.Error("should not recv nil") } - if !node.Data.(net.IP).Equal(net.IP{127, 0, 0, 1}) { + if !node.Data.(net.IP).Equal(localIP) { t.Error("should equal 127.0.0.1") } } @@ -35,7 +37,7 @@ func TestTrie_Wildcard(t *testing.T) { } for _, domain := range domains { - tree.Insert(domain, nil) + tree.Insert(domain, localIP) } if tree.Search("sub.example.com") == nil { @@ -53,13 +55,17 @@ func TestTrie_Wildcard(t *testing.T) { if tree.Search("foo.example.dev") != nil { t.Error("should recv nil") } + + if tree.Search("example.com") != nil { + t.Error("should recv nil") + } } func TestTrie_Boundary(t *testing.T) { tree := New() - tree.Insert("*.dev", nil) + tree.Insert("*.dev", localIP) - if err := tree.Insert("com", nil); err == nil { + if err := tree.Insert("com", localIP); err == nil { t.Error("should recv err") } From 9e77c650d9e5df80c367b5a50517c6aaf07abef2 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 16 Jul 2019 00:57:08 +0800 Subject: [PATCH 208/535] Fix(domain-trie): domain could without dot --- component/domain-trie/tire.go | 12 ++++++++---- component/domain-trie/trie_test.go | 10 +++++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/component/domain-trie/tire.go b/component/domain-trie/tire.go index 13567a98b0..26062fc2e2 100644 --- a/component/domain-trie/tire.go +++ b/component/domain-trie/tire.go @@ -21,17 +21,21 @@ type Trie struct { root *Node } +func isValidDomain(domain string) bool { + return domain[0] != '.' && domain[len(domain)-1] != '.' +} + // Insert adds a node to the trie. // Support // 1. www.example.com // 2. *.example.com // 3. subdomain.*.example.com func (t *Trie) Insert(domain string, data interface{}) error { - parts := strings.Split(domain, domainStep) - if len(parts) < 2 { + if !isValidDomain(domain) { return ErrInvalidDomain } + parts := strings.Split(domain, domainStep) node := t.root // reverse storage domain part to save space for i := len(parts) - 1; i >= 0; i-- { @@ -52,10 +56,10 @@ func (t *Trie) Insert(domain string, data interface{}) error { // 1. static part // 2. wildcard domain func (t *Trie) Search(domain string) *Node { - parts := strings.Split(domain, domainStep) - if len(parts) < 2 { + if !isValidDomain(domain) { return nil } + parts := strings.Split(domain, domainStep) n := t.root for i := len(parts) - 1; i >= 0; i-- { diff --git a/component/domain-trie/trie_test.go b/component/domain-trie/trie_test.go index bd594e97aa..5303596bc4 100644 --- a/component/domain-trie/trie_test.go +++ b/component/domain-trie/trie_test.go @@ -65,11 +65,19 @@ func TestTrie_Boundary(t *testing.T) { tree := New() tree.Insert("*.dev", localIP) - if err := tree.Insert("com", localIP); err == nil { + if err := tree.Insert(".", localIP); err == nil { + t.Error("should recv err") + } + + if err := tree.Insert(".com", localIP); err == nil { t.Error("should recv err") } if tree.Search("dev") != nil { t.Error("should recv nil") } + + if tree.Search(".dev") != nil { + t.Error("should recv nil") + } } From 067027553373a3f9d13c8a73e64ed455ded3140c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 17 Jul 2019 22:24:26 +0800 Subject: [PATCH 209/535] Fix(url-test): incorrect result --- adapters/outbound/urltest.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index cfe6b5ba52..9c6bd7d5c4 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -106,12 +106,13 @@ func (u *URLTest) speedTest() { defer cancel() picker, ctx := picker.WithContext(ctx) for _, p := range u.proxies { + proxy := p picker.Go(func() (interface{}, error) { - _, err := p.URLTest(ctx, u.rawURL) - if err != nil { - return nil, err + t, err := proxy.URLTest(ctx, u.rawURL) + if err != nil || t == 0 { + return nil, errors.New("speed test error") } - return p, nil + return proxy, nil }) } From f00dfdd34d6903f5aba191432d70d493099b8c1a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 18 Jul 2019 00:12:01 +0800 Subject: [PATCH 210/535] Fix(picker): add WithTimeout for some situation --- adapters/outbound/base.go | 4 ++++ adapters/outbound/urltest.go | 11 ++++++----- common/picker/picker.go | 9 +++++++++ common/picker/picker_test.go | 4 ++-- hub/route/proxies.go | 3 +-- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 7c2254984a..6fb18069e8 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -12,6 +12,10 @@ import ( C "github.com/Dreamacro/clash/constant" ) +var ( + defaultURLTestTimeout = time.Second * 5 +) + type Base struct { name string tp C.AdapterType diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 9c6bd7d5c4..351c2e4cb2 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -102,15 +102,14 @@ func (u *URLTest) speedTest() { } defer atomic.StoreInt32(&u.once, 0) - ctx, cancel := context.WithTimeout(context.Background(), u.interval) + picker, ctx, cancel := picker.WithTimeout(context.Background(), defaultURLTestTimeout) defer cancel() - picker, ctx := picker.WithContext(ctx) for _, p := range u.proxies { proxy := p picker.Go(func() (interface{}, error) { - t, err := proxy.URLTest(ctx, u.rawURL) - if err != nil || t == 0 { - return nil, errors.New("speed test error") + _, err := proxy.URLTest(ctx, u.rawURL) + if err != nil { + return nil, err } return proxy, nil }) @@ -120,6 +119,8 @@ func (u *URLTest) speedTest() { if fast != nil { u.fast = fast.(C.Proxy) } + + <-ctx.Done() } func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { diff --git a/common/picker/picker.go b/common/picker/picker.go index f420679ddd..a66b9bc600 100644 --- a/common/picker/picker.go +++ b/common/picker/picker.go @@ -3,6 +3,7 @@ package picker import ( "context" "sync" + "time" ) // Picker provides synchronization, and Context cancelation @@ -18,11 +19,19 @@ type Picker struct { } // WithContext returns a new Picker and an associated Context derived from ctx. +// and cancel when first element return. func WithContext(ctx context.Context) (*Picker, context.Context) { ctx, cancel := context.WithCancel(ctx) return &Picker{cancel: cancel}, ctx } +// WithTimeout returns a new Picker and an associated Context derived from ctx with timeout, +// but it doesn't cancel when first element return. +func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context, context.CancelFunc) { + ctx, cancel := context.WithTimeout(ctx, timeout) + return &Picker{}, ctx, cancel +} + // Wait blocks until all function calls from the Go method have returned, // then returns the first nil error result (if any) from them. func (p *Picker) Wait() interface{} { diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go index 7b225d3940..7ff3712fab 100644 --- a/common/picker/picker_test.go +++ b/common/picker/picker_test.go @@ -30,9 +30,9 @@ func TestPicker_Basic(t *testing.T) { } func TestPicker_Timeout(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*5) + picker, ctx, cancel := WithTimeout(context.Background(), time.Millisecond*5) defer cancel() - picker, ctx := WithContext(ctx) + picker.Go(sleepAndSend(ctx, 20, 1)) number := picker.Wait() diff --git a/hub/route/proxies.go b/hub/route/proxies.go index e4c77dc03e..7044f05718 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -111,9 +111,8 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) + picker, ctx, cancel := picker.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) defer cancel() - picker, ctx := picker.WithContext(ctx) picker.Go(func() (interface{}, error) { return proxy.URLTest(ctx, url) }) From 183e776970c65a7a11a2852585a51265ddf97980 Mon Sep 17 00:00:00 2001 From: Siji Date: Thu, 25 Jul 2019 17:06:26 +0800 Subject: [PATCH 211/535] Chore: use lower case cipher format (#238) --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f0d4f2d7ec..b025af2ef2 100644 --- a/README.md +++ b/README.md @@ -136,17 +136,20 @@ experimental: Proxy: # shadowsocks -# The types of cipher are consistent with go-shadowsocks2 -# support AEAD_AES_128_GCM AEAD_AES_192_GCM AEAD_AES_256_GCM AEAD_CHACHA20_POLY1305 AES-128-CTR AES-192-CTR AES-256-CTR AES-128-CFB AES-192-CFB AES-256-CFB CHACHA20-IETF XCHACHA20 -# In addition to what go-shadowsocks2 supports, it also supports chacha20 rc4-md5 xchacha20-ietf-poly1305 -- { name: "ss1", type: ss, server: server, port: 443, cipher: AEAD_CHACHA20_POLY1305, password: "password", udp: true } +# The supported ciphers(encrypt methods): +# aes-128-gcm aes-192-gcm aes-256-gcm +# aes-128-cfb aes-192-cfb aes-256-cfb +# aes-128-ctr aes-192-ctr aes-256-ctr +# rc4-md5 chacha20 chacha20-ietf xchacha20 +# chacha20-ietf-poly1305 xchacha20-ietf-poly1305 +- { name: "ss1", type: ss, server: server, port: 443, cipher: chacha20-ietf-poly1305, password: "password", udp: true } # old obfs configuration remove after prerelease - name: "ss2" type: ss server: server port: 443 - cipher: AEAD_CHACHA20_POLY1305 + cipher: chacha20-ietf-poly1305 password: "password" plugin: obfs plugin-opts: @@ -157,7 +160,7 @@ Proxy: type: ss server: server port: 443 - cipher: AEAD_CHACHA20_POLY1305 + cipher: chacha20-ietf-poly1305 password: "password" plugin: v2ray-plugin plugin-opts: From 1fd8f690fea1d8bd4f858e44ee44f2d1c8b67b11 Mon Sep 17 00:00:00 2001 From: "X. Jason Lyu" Date: Thu, 25 Jul 2019 17:47:39 +0800 Subject: [PATCH 212/535] Fix(socks5): fully udp associate support (#233) --- adapters/outbound/shadowsocks.go | 4 +- adapters/outbound/socks5.go | 76 +++++++++++++++++++--- adapters/outbound/vmess.go | 7 +- component/nat-table/nat.go | 98 ++++++++++++++++++++++++++++ component/socks5/socks5.go | 107 +++++++++++++++++++++++++------ hub/executor/executor.go | 1 - proxy/listener.go | 31 ++++++++- proxy/socks/tcp.go | 5 +- proxy/socks/udp.go | 65 +++++++++++++++++++ proxy/socks/utils.go | 41 ++++++++++++ tunnel/connection.go | 55 +++++++--------- tunnel/session.go | 22 +++++++ tunnel/tunnel.go | 40 ++++++++++-- 13 files changed, 473 insertions(+), 79 deletions(-) create mode 100644 component/nat-table/nat.go create mode 100644 proxy/socks/udp.go create mode 100644 proxy/socks/utils.go create mode 100644 tunnel/session.go diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index b05f5830f3..a77c1a9aec 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -92,13 +92,13 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, return nil, nil, err } - remoteAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) + targetAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, nil, err } pc = ss.cipher.PacketConn(pc) - return &ssUDPConn{PacketConn: pc, rAddr: remoteAddr}, addr, nil + return &ssUDPConn{PacketConn: pc, rAddr: targetAddr}, addr, nil } func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 6ec611c65e..1b08bf874e 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -3,6 +3,8 @@ package adapters import ( "crypto/tls" "fmt" + "io" + "io/ioutil" "net" "strconv" @@ -51,24 +53,31 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { Password: ss.pass, } } - if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { + if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { return nil, err } return c, nil } -func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { +func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ net.PacketConn, _ net.Addr, err error) { c, err := dialTimeout("tcp", ss.addr, tcpTimeout) + if err != nil { + err = fmt.Errorf("%s connect error", ss.addr) + return + } - if err == nil && ss.tls { + if ss.tls { cc := tls.Client(c, ss.tlsConfig) err = cc.Handshake() c = cc } - if err != nil { - return nil, nil, fmt.Errorf("%s connect error", ss.addr) - } + defer func() { + if err != nil { + c.Close() + } + }() + tcpKeepAlive(c) var user *socks5.User if ss.user != "" { @@ -78,10 +87,36 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error } } - if err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user); err != nil { - return nil, nil, err + bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user) + if err != nil { + err = fmt.Errorf("%v client hanshake error", err) + return } - return &fakeUDPConn{Conn: c}, c.LocalAddr(), nil + + addr, err := net.ResolveUDPAddr("udp", bindAddr.String()) + if err != nil { + return + } + + targetAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) + if err != nil { + return + } + + pc, err := net.ListenPacket("udp", "") + if err != nil { + return + } + + go func() { + io.Copy(ioutil.Discard, c) + c.Close() + // A UDP association terminates when the TCP connection that the UDP + // ASSOCIATE request arrived on terminates. RFC1928 + pc.Close() + }() + + return &socksUDPConn{PacketConn: pc, rAddr: targetAddr}, addr, nil } func NewSocks5(option Socks5Option) *Socks5 { @@ -108,3 +143,26 @@ func NewSocks5(option Socks5Option) *Socks5 { tlsConfig: tlsConfig, } } + +type socksUDPConn struct { + net.PacketConn + rAddr net.Addr +} + +func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + packet, err := socks5.EncodeUDPPacket(uc.rAddr.String(), b) + if err != nil { + return + } + return uc.PacketConn.WriteTo(packet, addr) +} + +func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, a, e := uc.PacketConn.ReadFrom(b) + addr, payload, err := socks5.DecodeUDPPacket(b) + if err != nil { + return 0, nil, err + } + copy(b, payload) + return n - len(addr) - 3, a, e +} diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 7d74936db6..825af42cae 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -48,7 +48,10 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) } tcpKeepAlive(c) c, err = v.client.New(c, parseVmessAddr(metadata)) - return &fakeUDPConn{Conn: c}, c.LocalAddr(), err + if err != nil { + return nil, nil, fmt.Errorf("new vmess client error: %v", err) + } + return &fakeUDPConn{Conn: c}, c.RemoteAddr(), nil } func NewVmess(option VmessOption) (*Vmess, error) { @@ -74,7 +77,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { Base: &Base{ name: option.Name, tp: C.Vmess, - udp: option.UDP, + udp: true, }, server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), client: client, diff --git a/component/nat-table/nat.go b/component/nat-table/nat.go new file mode 100644 index 0000000000..06b3867126 --- /dev/null +++ b/component/nat-table/nat.go @@ -0,0 +1,98 @@ +package nat + +import ( + "net" + "runtime" + "sync" + "time" +) + +type Table struct { + *table +} + +type table struct { + mapping sync.Map + janitor *janitor + timeout time.Duration +} + +type element struct { + Expired time.Time + RemoteAddr net.Addr + RemoteConn net.PacketConn +} + +func (t *table) Set(key net.Addr, rConn net.PacketConn, rAddr net.Addr) { + // set conn read timeout + rConn.SetReadDeadline(time.Now().Add(t.timeout)) + t.mapping.Store(key, &element{ + RemoteAddr: rAddr, + RemoteConn: rConn, + Expired: time.Now().Add(t.timeout), + }) +} + +func (t *table) Get(key net.Addr) (rConn net.PacketConn, rAddr net.Addr) { + item, exist := t.mapping.Load(key) + if !exist { + return + } + elm := item.(*element) + // expired + if time.Since(elm.Expired) > 0 { + t.mapping.Delete(key) + elm.RemoteConn.Close() + return + } + // reset expired time + elm.Expired = time.Now().Add(t.timeout) + return elm.RemoteConn, elm.RemoteAddr +} + +func (t *table) cleanup() { + t.mapping.Range(func(k, v interface{}) bool { + key := k.(net.Addr) + elm := v.(*element) + if time.Since(elm.Expired) > 0 { + t.mapping.Delete(key) + elm.RemoteConn.Close() + } + return true + }) +} + +type janitor struct { + interval time.Duration + stop chan struct{} +} + +func (j *janitor) process(t *table) { + ticker := time.NewTicker(j.interval) + for { + select { + case <-ticker.C: + t.cleanup() + case <-j.stop: + ticker.Stop() + return + } + } +} + +func stopJanitor(t *Table) { + t.janitor.stop <- struct{}{} +} + +// New return *Cache +func New(interval time.Duration) *Table { + j := &janitor{ + interval: interval, + stop: make(chan struct{}), + } + t := &table{janitor: j, timeout: interval} + go j.process(t) + T := &Table{t} + runtime.SetFinalizer(T, stopJanitor) + return T +} diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index e945fe2cdd..b16c3b69ad 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -41,7 +41,25 @@ const MaxAddrLen = 1 + 1 + 255 + 2 const MaxAuthLen = 255 // Addr represents a SOCKS address as defined in RFC 1928 section 5. -type Addr = []byte +type Addr []byte + +func (a Addr) String() string { + var host, port string + + switch a[0] { + case AtypDomainName: + host = string(a[2 : 2+int(a[1])]) + port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) + case AtypIPv4: + host = net.IP(a[1 : 1+net.IPv4len]).String() + port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1])) + case AtypIPv6: + host = net.IP(a[1 : 1+net.IPv6len]).String() + port = strconv.Itoa((int(a[1+net.IPv6len]) << 8) | int(a[1+net.IPv6len+1])) + } + + return net.JoinHostPort(host, port) +} // SOCKS errors as defined in RFC 1928 section 6. const ( @@ -138,23 +156,33 @@ func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, return } - if buf[1] != CmdConnect && buf[1] != CmdUDPAssociate { - err = ErrCommandNotSupported - return - } - command = buf[1] addr, err = readAddr(rw, buf) if err != nil { return } - // write VER REP RSV ATYP BND.ADDR BND.PORT - _, err = rw.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0}) + + switch command { + case CmdConnect, CmdUDPAssociate: + // Acquire server listened address info + localAddr := ParseAddr(rw.LocalAddr().String()) + if localAddr == nil { + err = ErrAddressNotSupported + } else { + // write VER REP RSV ATYP BND.ADDR BND.PORT + _, err = rw.Write(bytes.Join([][]byte{{5, 0, 0}, localAddr}, []byte{})) + } + case CmdBind: + fallthrough + default: + err = ErrCommandNotSupported + } + return } // ClientHandshake fast-tracks SOCKS initialization to get target address to connect on client side. -func ClientHandshake(rw io.ReadWriter, addr Addr, cammand Command, user *User) error { +func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) (Addr, error) { buf := make([]byte, MaxAddrLen) var err error @@ -165,16 +193,16 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, cammand Command, user *User) e _, err = rw.Write([]byte{5, 1, 0}) } if err != nil { - return err + return nil, err } // VER, METHOD if _, err := io.ReadFull(rw, buf[:2]); err != nil { - return err + return nil, err } if buf[0] != 5 { - return errors.New("SOCKS version error") + return nil, errors.New("SOCKS version error") } if buf[1] == 2 { @@ -187,30 +215,31 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, cammand Command, user *User) e authMsg.WriteString(user.Password) if _, err := rw.Write(authMsg.Bytes()); err != nil { - return err + return nil, err } if _, err := io.ReadFull(rw, buf[:2]); err != nil { - return err + return nil, err } if buf[1] != 0 { - return errors.New("rejected username/password") + return nil, errors.New("rejected username/password") } } else if buf[1] != 0 { - return errors.New("SOCKS need auth") + return nil, errors.New("SOCKS need auth") } // VER, CMD, RSV, ADDR - if _, err := rw.Write(bytes.Join([][]byte{{5, cammand, 0}, addr}, []byte(""))); err != nil { - return err + if _, err := rw.Write(bytes.Join([][]byte{{5, command, 0}, addr}, []byte{})); err != nil { + return nil, err } - if _, err := io.ReadFull(rw, buf[:10]); err != nil { - return err + // VER, REP, RSV + if _, err := io.ReadFull(rw, buf[:3]); err != nil { + return nil, err } - return nil + return readAddr(rw, buf) } func readAddr(r io.Reader, b []byte) (Addr, error) { @@ -307,3 +336,39 @@ func ParseAddr(s string) Addr { return addr } + +func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { + if len(packet) < 5 { + err = errors.New("insufficient length of packet") + return + } + + // packet[0] and packet[1] are reserved + if !bytes.Equal(packet[:2], []byte{0, 0}) { + err = errors.New("reserved fields should be zero") + return + } + + if packet[2] != 0 /* fragments */ { + err = errors.New("discarding fragmented payload") + return + } + + addr = SplitAddr(packet[3:]) + if addr == nil { + err = errors.New("failed to read UDP header") + } + + payload = bytes.Join([][]byte{packet[3+len(addr):]}, []byte{}) + return +} + +func EncodeUDPPacket(addr string, payload []byte) (packet []byte, err error) { + rAddr := ParseAddr(addr) + if rAddr == nil { + err = errors.New("cannot parse addr") + return + } + packet = bytes.Join([][]byte{{0, 0, 0}, rAddr, payload}, []byte{}) + return +} diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 82d5f6e0bc..361afe1e6b 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -103,7 +103,6 @@ func updateGeneral(general *config.General) { T.Instance().SetMode(general.Mode) allowLan := general.AllowLan - P.SetAllowLan(allowLan) if err := P.ReCreateHTTP(general.Port); err != nil { diff --git a/proxy/listener.go b/proxy/listener.go index 7fbcfd7793..98ef4ec835 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -13,9 +13,10 @@ import ( var ( allowLan = false - socksListener *socks.SockListener - httpListener *http.HttpListener - redirListener *redir.RedirListener + socksListener *socks.SockListener + socksUDPListener *socks.SockUDPListener + httpListener *http.HttpListener + redirListener *redir.RedirListener ) type listener interface { @@ -82,6 +83,30 @@ func ReCreateSocks(port int) error { return err } + return reCreateSocksUDP(port) +} + +func reCreateSocksUDP(port int) error { + addr := genAddr(port, allowLan) + + if socksUDPListener != nil { + if socksUDPListener.Address() == addr { + return nil + } + socksUDPListener.Close() + socksUDPListener = nil + } + + if portIsZero(addr) { + return nil + } + + var err error + socksUDPListener, err = socks.NewSocksUDPProxy(addr) + if err != nil { + return err + } + return nil } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 47bfa0373d..1d080dd6b7 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -1,6 +1,8 @@ package socks import ( + "io" + "io/ioutil" "net" adapters "github.com/Dreamacro/clash/adapters/inbound" @@ -62,7 +64,8 @@ func handleSocks(conn net.Conn) { } conn.(*net.TCPConn).SetKeepAlive(true) if command == socks5.CmdUDPAssociate { - tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP)) + defer conn.Close() + io.Copy(ioutil.Discard, conn) return } tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go new file mode 100644 index 0000000000..63b63ba54b --- /dev/null +++ b/proxy/socks/udp.go @@ -0,0 +1,65 @@ +package socks + +import ( + "net" + + adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/socks5" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" +) + +var ( + _ = tunnel.NATInstance() +) + +type SockUDPListener struct { + net.PacketConn + address string + closed bool +} + +func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { + l, err := net.ListenPacket("udp", addr) + if err != nil { + return nil, err + } + + sl := &SockUDPListener{l, addr, false} + go func() { + buf := pool.BufPool.Get().([]byte) + defer pool.BufPool.Put(buf[:cap(buf)]) + for { + n, remoteAddr, err := l.ReadFrom(buf) + if err != nil { + if sl.closed { + break + } + continue + } + go handleSocksUDP(l, buf[:n], remoteAddr) + } + }() + + return sl, nil +} + +func (l *SockUDPListener) Close() error { + l.closed = true + return l.PacketConn.Close() +} + +func (l *SockUDPListener) Address() string { + return l.address +} + +func handleSocksUDP(c net.PacketConn, packet []byte, remoteAddr net.Addr) { + target, payload, err := socks5.DecodeUDPPacket(packet) + if err != nil { + // Unresolved UDP packet, do nothing + return + } + conn := newfakeConn(c, target.String(), remoteAddr, payload) + tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP)) +} diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go new file mode 100644 index 0000000000..2ecee3c31b --- /dev/null +++ b/proxy/socks/utils.go @@ -0,0 +1,41 @@ +package socks + +import ( + "bytes" + "net" + + "github.com/Dreamacro/clash/component/socks5" +) + +type fakeConn struct { + net.PacketConn + target string + remoteAddr net.Addr + buffer *bytes.Buffer +} + +func newfakeConn(conn net.PacketConn, target string, remoteAddr net.Addr, buf []byte) *fakeConn { + buffer := bytes.NewBuffer(buf) + return &fakeConn{ + PacketConn: conn, + target: target, + buffer: buffer, + remoteAddr: remoteAddr, + } +} + +func (c *fakeConn) Read(b []byte) (n int, err error) { + return c.buffer.Read(b) +} + +func (c *fakeConn) Write(b []byte) (n int, err error) { + packet, err := socks5.EncodeUDPPacket(c.target, b) + if err != nil { + return + } + return c.PacketConn.WriteTo(packet, c.remoteAddr) +} + +func (c *fakeConn) RemoteAddr() net.Addr { + return c.remoteAddr +} diff --git a/tunnel/connection.go b/tunnel/connection.go index a8928a624e..36c698d2ae 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -63,52 +63,41 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } } -func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { - conn := newTrafficTrack(outbound, t.traffic) - relay(request, conn) -} - -func (t *Tunnel) handleUDPOverTCP(conn net.Conn, pc net.PacketConn, addr net.Addr) error { - ch := make(chan error, 1) +func (t *Tunnel) handleUDPToRemote(conn net.Conn, pc net.PacketConn, addr net.Addr) { + buf := pool.BufPool.Get().([]byte) + defer pool.BufPool.Put(buf[:cap(buf)]) - go func() { - buf := pool.BufPool.Get().([]byte) - defer pool.BufPool.Put(buf) - for { - n, err := conn.Read(buf) - if err != nil { - ch <- err - return - } - pc.SetReadDeadline(time.Now().Add(120 * time.Second)) - if _, err = pc.WriteTo(buf[:n], addr); err != nil { - ch <- err - return - } - t.traffic.Up() <- int64(n) - ch <- nil - } - }() + n, err := conn.Read(buf) + if err != nil { + return + } + if _, err = pc.WriteTo(buf[:n], addr); err != nil { + return + } + t.traffic.Up() <- int64(n) +} +func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn) { buf := pool.BufPool.Get().([]byte) - defer pool.BufPool.Put(buf) + defer pool.BufPool.Put(buf[:cap(buf)]) for { - pc.SetReadDeadline(time.Now().Add(120 * time.Second)) n, _, err := pc.ReadFrom(buf) if err != nil { - break + return } - if _, err := conn.Write(buf[:n]); err != nil { - break + n, err = conn.Write(buf[:n]) + if err != nil { + return } - t.traffic.Down() <- int64(n) } +} - <-ch - return nil +func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { + conn := newTrafficTrack(outbound, t.traffic) + relay(request, conn) } // relay copies between left and right bidirectionally. diff --git a/tunnel/session.go b/tunnel/session.go new file mode 100644 index 0000000000..4433deae8c --- /dev/null +++ b/tunnel/session.go @@ -0,0 +1,22 @@ +package tunnel + +import ( + "sync" + "time" + + nat "github.com/Dreamacro/clash/component/nat-table" +) + +var ( + natTable *nat.Table + natOnce sync.Once + + natTimeout = 120 * time.Second +) + +func NATInstance() *nat.Table { + natOnce.Do(func() { + natTable = nat.New(natTimeout) + }) + return natTable +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index fedde7f21e..984af7b9a0 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -103,9 +103,20 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { } func (t *Tunnel) handleConn(localConn C.ServerAdapter) { - defer localConn.Close() - metadata := localConn.Metadata() + defer func() { + var conn net.Conn + switch adapter := localConn.(type) { + case *InboundAdapter.HTTPAdapter: + conn = adapter.Conn + case *InboundAdapter.SocketAdapter: + conn = adapter.Conn + } + if _, ok := conn.(*net.TCPConn); ok { + localConn.Close() + } + }() + metadata := localConn.Metadata() if !metadata.Valid() { log.Warnln("[Metadata] not valid: %#v", metadata) return @@ -138,18 +149,32 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } } - if metadata.NetWork == C.UDP { - pc, addr, err := proxy.DialUDP(metadata) + switch metadata.NetWork { + case C.TCP: + t.handleTCPConn(localConn, metadata, proxy) + case C.UDP: + t.handleUDPConn(localConn, metadata, proxy) + } +} + +func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy) { + pc, addr := natTable.Get(localConn.RemoteAddr()) + if pc == nil { + var err error + pc, addr, err = proxy.DialUDP(metadata) if err != nil { log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) return } - defer pc.Close() - t.handleUDPOverTCP(localConn, pc, addr) - return + natTable.Set(localConn.RemoteAddr(), pc, addr) + go t.handleUDPToLocal(localConn, pc) } + t.handleUDPToRemote(localConn, pc, addr) +} + +func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy) { remoConn, err := proxy.Dial(metadata) if err != nil { log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) @@ -196,6 +221,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { } if metadata.NetWork == C.UDP && !adapter.SupportUDP() { + log.Debugln("%v UDP is not supported", adapter.Name()) continue } From 1702e7ddb47a1844de27b8a92e1436dde3733b8c Mon Sep 17 00:00:00 2001 From: Leo Yao Date: Fri, 26 Jul 2019 18:45:50 +0800 Subject: [PATCH 213/535] Chore(build): add mipsle-softfloat (#240) --- Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 6899d9015f..ce0626b986 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,8 @@ PLATFORM_LIST = \ linux-armv8 \ linux-mips-softfloat \ linux-mips-hardfloat \ - linux-mipsle \ + linux-mipsle-softfloat \ + linux-mipsle-hardfloat \ linux-mips64 \ linux-mips64le \ freebsd-386 \ @@ -55,8 +56,11 @@ linux-mips-softfloat: linux-mips-hardfloat: GOARCH=mips GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ -linux-mipsle: - GOARCH=mipsle GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ +linux-mipsle-softfloat: + GOARCH=mipsle GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + +linux-mipsle-hardfloat: + GOARCH=mipsle GOMIPS=hardfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ linux-mips64: GOARCH=mips64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ From 271ed2b9c16c967f265d353a4768f45f9dad6494 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 26 Jul 2019 19:09:13 +0800 Subject: [PATCH 214/535] Improve(fakeip): use lru cache to avoid outdate --- common/cache/cache_test.go | 30 +++---- common/cache/lrucache.go | 148 ++++++++++++++++++++++++++++++++++ common/cache/lrucache_test.go | 117 +++++++++++++++++++++++++++ component/fakeip/pool.go | 80 ++++++++++++++---- component/fakeip/pool_test.go | 51 +++++++----- config/config.go | 2 +- dns/middleware.go | 20 ++--- dns/resolver.go | 4 + go.mod | 1 + 9 files changed, 386 insertions(+), 67 deletions(-) create mode 100644 common/cache/lrucache.go create mode 100644 common/cache/lrucache_test.go diff --git a/common/cache/cache_test.go b/common/cache/cache_test.go index 101ca869e3..cf4a391483 100644 --- a/common/cache/cache_test.go +++ b/common/cache/cache_test.go @@ -4,6 +4,8 @@ import ( "runtime" "testing" "time" + + "github.com/stretchr/testify/assert" ) func TestCache_Basic(t *testing.T) { @@ -14,32 +16,30 @@ func TestCache_Basic(t *testing.T) { c.Put("string", "a", ttl) i := c.Get("int") - if i.(int) != 1 { - t.Error("should recv 1") - } + assert.Equal(t, i.(int), 1, "should recv 1") s := c.Get("string") - if s.(string) != "a" { - t.Error("should recv 'a'") - } + assert.Equal(t, s.(string), "a", "should recv 'a'") } func TestCache_TTL(t *testing.T) { interval := 200 * time.Millisecond ttl := 20 * time.Millisecond + now := time.Now() c := New(interval) c.Put("int", 1, ttl) + c.Put("int2", 2, ttl) i := c.Get("int") - if i.(int) != 1 { - t.Error("should recv 1") - } + _, expired := c.GetWithExpire("int2") + assert.Equal(t, i.(int), 1, "should recv 1") + assert.True(t, now.Before(expired)) time.Sleep(ttl * 2) i = c.Get("int") - if i != nil { - t.Error("should recv nil") - } + j, _ := c.GetWithExpire("int2") + assert.Nil(t, i, "should recv nil") + assert.Nil(t, j, "should recv nil") } func TestCache_AutoCleanup(t *testing.T) { @@ -50,9 +50,9 @@ func TestCache_AutoCleanup(t *testing.T) { time.Sleep(ttl * 2) i := c.Get("int") - if i != nil { - t.Error("should recv nil") - } + j, _ := c.GetWithExpire("int") + assert.Nil(t, i, "should recv nil") + assert.Nil(t, j, "should recv nil") } func TestCache_AutoGC(t *testing.T) { diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go new file mode 100644 index 0000000000..5a139bf714 --- /dev/null +++ b/common/cache/lrucache.go @@ -0,0 +1,148 @@ +package cache + +// Modified by https://github.com/die-net/lrucache + +import ( + "container/list" + "sync" + "time" +) + +// Option is part of Functional Options Pattern +type Option func(*LruCache) + +// WithUpdateAgeOnGet update expires when Get element +func WithUpdateAgeOnGet() Option { + return func(l *LruCache) { + l.updateAgeOnGet = true + } +} + +// WithAge defined element max age (second) +func WithAge(maxAge int64) Option { + return func(l *LruCache) { + l.maxAge = maxAge + } +} + +// WithSize defined max length of LruCache +func WithSize(maxSize int) Option { + return func(l *LruCache) { + l.maxSize = maxSize + } +} + +// LruCache is a thread-safe, in-memory lru-cache that evicts the +// least recently used entries from memory when (if set) the entries are +// older than maxAge (in seconds). Use the New constructor to create one. +type LruCache struct { + maxAge int64 + maxSize int + mu sync.Mutex + cache map[interface{}]*list.Element + lru *list.List // Front is least-recent + updateAgeOnGet bool +} + +// NewLRUCache creates an LruCache +func NewLRUCache(options ...Option) *LruCache { + lc := &LruCache{ + lru: list.New(), + cache: make(map[interface{}]*list.Element), + } + + for _, option := range options { + option(lc) + } + + return lc +} + +// Get returns the interface{} representation of a cached response and a bool +// set to true if the key was found. +func (c *LruCache) Get(key interface{}) (interface{}, bool) { + c.mu.Lock() + defer c.mu.Unlock() + + le, ok := c.cache[key] + if !ok { + return nil, false + } + + if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() { + c.deleteElement(le) + c.maybeDeleteOldest() + + return nil, false + } + + c.lru.MoveToBack(le) + entry := le.Value.(*entry) + if c.maxAge > 0 && c.updateAgeOnGet { + entry.expires = time.Now().Unix() + c.maxAge + } + value := entry.value + + return value, true +} + +// Set stores the interface{} representation of a response for a given key. +func (c *LruCache) Set(key interface{}, value interface{}) { + c.mu.Lock() + defer c.mu.Unlock() + + expires := int64(0) + if c.maxAge > 0 { + expires = time.Now().Unix() + c.maxAge + } + + if le, ok := c.cache[key]; ok { + c.lru.MoveToBack(le) + e := le.Value.(*entry) + e.value = value + e.expires = expires + } else { + e := &entry{key: key, value: value, expires: expires} + c.cache[key] = c.lru.PushBack(e) + + if c.maxSize > 0 { + if len := c.lru.Len(); len > c.maxSize { + c.deleteElement(c.lru.Front()) + } + } + } + + c.maybeDeleteOldest() +} + +// Delete removes the value associated with a key. +func (c *LruCache) Delete(key string) { + c.mu.Lock() + + if le, ok := c.cache[key]; ok { + c.deleteElement(le) + } + + c.mu.Unlock() +} + +func (c *LruCache) maybeDeleteOldest() { + if c.maxAge > 0 { + now := time.Now().Unix() + for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() { + c.deleteElement(le) + } + } +} + +func (c *LruCache) deleteElement(le *list.Element) { + c.lru.Remove(le) + e := le.Value.(*entry) + delete(c.cache, e.key) +} + +type entry struct { + key interface{} + value interface{} + expires int64 +} diff --git a/common/cache/lrucache_test.go b/common/cache/lrucache_test.go new file mode 100644 index 0000000000..31f9a919e9 --- /dev/null +++ b/common/cache/lrucache_test.go @@ -0,0 +1,117 @@ +package cache + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +var entries = []struct { + key string + value string +}{ + {"1", "one"}, + {"2", "two"}, + {"3", "three"}, + {"4", "four"}, + {"5", "five"}, +} + +func TestLRUCache(t *testing.T) { + c := NewLRUCache() + + for _, e := range entries { + c.Set(e.key, e.value) + } + + c.Delete("missing") + _, ok := c.Get("missing") + assert.False(t, ok) + + for _, e := range entries { + value, ok := c.Get(e.key) + if assert.True(t, ok) { + assert.Equal(t, e.value, value.(string)) + } + } + + for _, e := range entries { + c.Delete(e.key) + + _, ok := c.Get(e.key) + assert.False(t, ok) + } +} + +func TestLRUMaxAge(t *testing.T) { + c := NewLRUCache(WithAge(86400)) + + now := time.Now().Unix() + expected := now + 86400 + + // Add one expired entry + c.Set("foo", "bar") + c.lru.Back().Value.(*entry).expires = now + + // Reset + c.Set("foo", "bar") + e := c.lru.Back().Value.(*entry) + assert.True(t, e.expires >= now) + c.lru.Back().Value.(*entry).expires = now + + // Set a few and verify expiration times + for _, s := range entries { + c.Set(s.key, s.value) + e := c.lru.Back().Value.(*entry) + assert.True(t, e.expires >= expected && e.expires <= expected+10) + } + + // Make sure we can get them all + for _, s := range entries { + _, ok := c.Get(s.key) + assert.True(t, ok) + } + + // Expire all entries + for _, s := range entries { + le, ok := c.cache[s.key] + if assert.True(t, ok) { + le.Value.(*entry).expires = now + } + } + + // Get one expired entry, which should clear all expired entries + _, ok := c.Get("3") + assert.False(t, ok) + assert.Equal(t, c.lru.Len(), 0) +} + +func TestLRUpdateOnGet(t *testing.T) { + c := NewLRUCache(WithAge(86400), WithUpdateAgeOnGet()) + + now := time.Now().Unix() + expires := now + 86400/2 + + // Add one expired entry + c.Set("foo", "bar") + c.lru.Back().Value.(*entry).expires = expires + + _, ok := c.Get("foo") + assert.True(t, ok) + assert.True(t, c.lru.Back().Value.(*entry).expires > expires) +} + +func TestMaxSize(t *testing.T) { + c := NewLRUCache(WithSize(2)) + // Add one expired entry + c.Set("foo", "bar") + _, ok := c.Get("foo") + assert.True(t, ok) + + c.Set("bar", "foo") + c.Set("baz", "foo") + + _, ok = c.Get("foo") + assert.False(t, ok) +} diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 32d5d574e4..8b7a27667d 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -4,22 +4,72 @@ import ( "errors" "net" "sync" + + "github.com/Dreamacro/clash/common/cache" ) // Pool is a implementation about fake ip generator without storage type Pool struct { - max uint32 - min uint32 - offset uint32 - mux *sync.Mutex + max uint32 + min uint32 + gateway uint32 + offset uint32 + mux *sync.Mutex + cache *cache.LruCache +} + +// Lookup return a fake ip with host +func (p *Pool) Lookup(host string) net.IP { + p.mux.Lock() + defer p.mux.Unlock() + if ip, exist := p.cache.Get(host); exist { + return ip.(net.IP) + } + + ip := p.get(host) + p.cache.Set(host, ip) + return ip } -// Get return a new fake ip -func (p *Pool) Get() net.IP { +// LookBack return host with the fake ip +func (p *Pool) LookBack(ip net.IP) (string, bool) { p.mux.Lock() defer p.mux.Unlock() - ip := uintToIP(p.min + p.offset) - p.offset = (p.offset + 1) % (p.max - p.min) + + if ip = ip.To4(); ip == nil { + return "", false + } + + n := ipToUint(ip.To4()) + offset := n - p.min + 1 + + if host, exist := p.cache.Get(offset); exist { + return host.(string), true + } + + return "", false +} + +// Gateway return gateway ip +func (p *Pool) Gateway() net.IP { + return uintToIP(p.gateway) +} + +func (p *Pool) get(host string) net.IP { + current := p.offset + for { + p.offset = (p.offset + 1) % (p.max - p.min) + // Avoid infinite loops + if p.offset == current { + break + } + + if _, exist := p.cache.Get(p.offset); !exist { + break + } + } + ip := uintToIP(p.min + p.offset - 1) + p.cache.Set(p.offset, host) return ip } @@ -36,8 +86,8 @@ func uintToIP(v uint32) net.IP { } // New return Pool instance -func New(ipnet *net.IPNet) (*Pool, error) { - min := ipToUint(ipnet.IP) + 1 +func New(ipnet *net.IPNet, size int) (*Pool, error) { + min := ipToUint(ipnet.IP) + 2 ones, bits := ipnet.Mask.Size() total := 1< Date: Mon, 29 Jul 2019 10:12:10 +0800 Subject: [PATCH 215/535] Fix: typo (#246) --- tunnel/mode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tunnel/mode.go b/tunnel/mode.go index 69d0d6f8af..5f7d963909 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -60,6 +60,6 @@ func (m Mode) String() string { case Direct: return "Direct" default: - return "Unknow" + return "Unknown" } } From f6acbaac7b3529617cef0250ee7e2cf1cf30b9a1 Mon Sep 17 00:00:00 2001 From: "X. Jason Lyu" Date: Mon, 29 Jul 2019 12:25:29 +0800 Subject: [PATCH 216/535] Fix(vmess): typo (#248) --- adapters/outbound/vmess.go | 2 +- component/vmess/vmess.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 825af42cae..2d6d69c84d 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -67,7 +67,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { WebSocketPath: option.WSPath, WebSocketHeaders: option.WSHeaders, SkipCertVerify: option.SkipCertVerify, - SessionCacahe: getClientSessionCache(), + SessionCache: getClientSessionCache(), }) if err != nil { return nil, err diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 23a7b508da..6e2767ce0c 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -86,7 +86,7 @@ type Config struct { WebSocketPath string WebSocketHeaders map[string]string SkipCertVerify bool - SessionCacahe tls.ClientSessionCache + SessionCache tls.ClientSessionCache } // New return a Conn with net.Conn and DstAddr @@ -139,7 +139,7 @@ func NewClient(config Config) (*Client, error) { tlsConfig = &tls.Config{ ServerName: config.HostName, InsecureSkipVerify: config.SkipCertVerify, - ClientSessionCache: config.SessionCacahe, + ClientSessionCache: config.SessionCache, } if tlsConfig.ClientSessionCache == nil { tlsConfig.ClientSessionCache = getClientSessionCache() From 85128a634d99dd038571d04a09dce81c1e78ab4b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 31 Jul 2019 11:13:49 +0800 Subject: [PATCH 217/535] Fix(vmess): set current server name in tls --- component/v2ray-plugin/websocket.go | 8 +++++++- component/vmess/vmess.go | 11 ++++++++++- component/vmess/websocket.go | 8 ++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/component/v2ray-plugin/websocket.go b/component/v2ray-plugin/websocket.go index 1e53cb9b61..96909aff1d 100644 --- a/component/v2ray-plugin/websocket.go +++ b/component/v2ray-plugin/websocket.go @@ -3,6 +3,7 @@ package obfs import ( "crypto/tls" "net" + "net/http" "github.com/Dreamacro/clash/component/vmess" ) @@ -17,11 +18,16 @@ type WebsocketOption struct { // NewWebsocketObfs return a HTTPObfs func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) { + header := http.Header{} + for k, v := range option.Headers { + header.Add(k, v) + } + config := &vmess.WebsocketConfig{ Host: option.Host, Path: option.Path, TLS: option.TLSConfig != nil, - Headers: option.Headers, + Headers: header, TLSConfig: option.TLSConfig, } diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 6e2767ce0c..49890354b7 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "net" + "net/http" "runtime" "sync" @@ -132,6 +133,11 @@ func NewClient(config Config) (*Client, error) { return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) } + header := http.Header{} + for k, v := range config.WebSocketHeaders { + header.Add(k, v) + } + host := net.JoinHostPort(config.HostName, config.Port) var tlsConfig *tls.Config @@ -144,6 +150,9 @@ func NewClient(config Config) (*Client, error) { if tlsConfig.ClientSessionCache == nil { tlsConfig.ClientSessionCache = getClientSessionCache() } + if host := header.Get("Host"); host != "" { + tlsConfig.ServerName = host + } } var wsConfig *WebsocketConfig @@ -151,7 +160,7 @@ func NewClient(config Config) (*Client, error) { wsConfig = &WebsocketConfig{ Host: host, Path: config.WebSocketPath, - Headers: config.WebSocketHeaders, + Headers: header, TLS: config.TLS, TLSConfig: tlsConfig, } diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index bd06c39d9c..ae737d944b 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -22,7 +22,7 @@ type websocketConn struct { type WebsocketConfig struct { Host string Path string - Headers map[string]string + Headers http.Header TLS bool TLSConfig *tls.Config } @@ -131,14 +131,14 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { headers := http.Header{} if c.Headers != nil { - for k, v := range c.Headers { - headers.Set(k, v) + for k := range c.Headers { + headers.Add(k, c.Headers.Get(k)) } } wsConn, resp, err := dialer.Dial(uri.String(), headers) if err != nil { - var reason string + reason := err.Error() if resp != nil { reason = resp.Status } From 528fbd10e412ad811425a542515d337974efb13d Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 2 Aug 2019 09:22:09 +0800 Subject: [PATCH 218/535] Fix(dns): use closure client (#251) --- dns/resolver.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dns/resolver.go b/dns/resolver.go index ecea9adf49..4e66f17c90 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -182,7 +182,8 @@ func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err defer cancel() fast, ctx := picker.WithContext(ctx) - for _, r := range clients { + for _, client := range clients { + r := client fast.Go(func() (interface{}, error) { msg, err := r.ExchangeContext(ctx, m) if err != nil || msg.Rcode != D.RcodeSuccess { From 288afd1308b95f61585233dd5bdc9bbebe8f96de Mon Sep 17 00:00:00 2001 From: Siji Date: Wed, 7 Aug 2019 14:21:39 +0800 Subject: [PATCH 219/535] Fix: don't read yml if not exist (#253) --- config/config.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index a735a9dbac..a996216490 100644 --- a/config/config.go +++ b/config/config.go @@ -106,7 +106,11 @@ func readRawConfig(path string) ([]byte, error) { } path = path[:len(path)-5] + ".yml" - return ioutil.ReadFile(path) + if _, err = os.Stat(path); err == nil { + return ioutil.ReadFile(path) + } + + return data, nil } func readConfig(path string) (*rawConfig, error) { From 5829c3d5be39a5472f04d4bd9a640cfcc77b3323 Mon Sep 17 00:00:00 2001 From: Siji Date: Thu, 8 Aug 2019 13:45:07 +0800 Subject: [PATCH 220/535] Feature: support customizing bind-address when allow-lan is true (#255) --- README.md | 6 ++++++ config/config.go | 5 +++++ hub/executor/executor.go | 4 ++++ hub/route/configs.go | 17 +++++++++++------ proxy/listener.go | 36 ++++++++++++++++++++++-------------- 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b025af2ef2..921f752508 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,12 @@ socks-port: 7891 allow-lan: false +# Only applicable when setting allow-lan to true +# "*": bind all IP addresses +# 192.168.122.11: bind a single IPv4 address +# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address +bind-address: "*" + # Rule / Global/ Direct (default is Rule) mode: Rule diff --git a/config/config.go b/config/config.go index a996216490..296c9761cb 100644 --- a/config/config.go +++ b/config/config.go @@ -30,6 +30,7 @@ type General struct { RedirPort int `json:"redir-port"` Authentication []string `json:"authentication"` AllowLan bool `json:"allow-lan"` + BindAddress string `json:"bind-address"` Mode T.Mode `json:"mode"` LogLevel log.LogLevel `json:"log-level"` ExternalController string `json:"-"` @@ -81,6 +82,7 @@ type rawConfig struct { RedirPort int `yaml:"redir-port"` Authentication []string `yaml:"authentication"` AllowLan bool `yaml:"allow-lan"` + BindAddress string `yaml:"bind-address"` Mode T.Mode `yaml:"mode"` LogLevel log.LogLevel `yaml:"log-level"` ExternalController string `yaml:"external-controller"` @@ -129,6 +131,7 @@ func readConfig(path string) (*rawConfig, error) { // config with some default value rawConfig := &rawConfig{ AllowLan: false, + BindAddress: "*", Mode: T.Rule, Authentication: []string{}, LogLevel: log.INFO, @@ -192,6 +195,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) { socksPort := cfg.SocksPort redirPort := cfg.RedirPort allowLan := cfg.AllowLan + bindAddress := cfg.BindAddress externalController := cfg.ExternalController externalUI := cfg.ExternalUI secret := cfg.Secret @@ -213,6 +217,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) { SocksPort: socksPort, RedirPort: redirPort, AllowLan: allowLan, + BindAddress: bindAddress, Mode: mode, LogLevel: logLevel, ExternalController: externalController, diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 361afe1e6b..b00ae2d411 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -46,6 +46,7 @@ func GetGeneral() *config.General { RedirPort: ports.RedirPort, Authentication: authenticator, AllowLan: P.AllowLan(), + BindAddress: P.BindAddress(), Mode: T.Instance().Mode(), LogLevel: log.Level(), } @@ -105,6 +106,9 @@ func updateGeneral(general *config.General) { allowLan := general.AllowLan P.SetAllowLan(allowLan) + bindAddress := general.BindAddress + P.SetBindAddress(bindAddress) + if err := P.ReCreateHTTP(general.Port); err != nil { log.Errorln("Start HTTP server error: %s", err.Error()) } diff --git a/hub/route/configs.go b/hub/route/configs.go index 3895c955e6..08856a4335 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -22,12 +22,13 @@ func configRouter() http.Handler { } type configSchema struct { - Port *int `json:"port"` - SocksPort *int `json:"socks-port"` - RedirPort *int `json:"redir-port"` - AllowLan *bool `json:"allow-lan"` - Mode *T.Mode `json:"mode"` - LogLevel *log.LogLevel `json:"log-level"` + Port *int `json:"port"` + SocksPort *int `json:"socks-port"` + RedirPort *int `json:"redir-port"` + AllowLan *bool `json:"allow-lan"` + BindAddress *string `json:"bind-address"` + Mode *T.Mode `json:"mode"` + LogLevel *log.LogLevel `json:"log-level"` } func getConfigs(w http.ResponseWriter, r *http.Request) { @@ -55,6 +56,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { P.SetAllowLan(*general.AllowLan) } + if general.BindAddress != nil { + P.SetBindAddress(*general.BindAddress) + } + ports := P.GetPorts() P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) diff --git a/proxy/listener.go b/proxy/listener.go index 98ef4ec835..1966b0bfdc 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -11,7 +11,8 @@ import ( ) var ( - allowLan = false + allowLan = false + bindAddress = "*" socksListener *socks.SockListener socksUDPListener *socks.SockUDPListener @@ -34,12 +35,20 @@ func AllowLan() bool { return allowLan } +func BindAddress() string { + return bindAddress +} + func SetAllowLan(al bool) { allowLan = al } +func SetBindAddress(host string) { + bindAddress = host +} + func ReCreateHTTP(port int) error { - addr := genAddr(port, allowLan) + addr := genAddr(bindAddress, port, allowLan) if httpListener != nil { if httpListener.Address() == addr { @@ -63,7 +72,7 @@ func ReCreateHTTP(port int) error { } func ReCreateSocks(port int) error { - addr := genAddr(port, allowLan) + addr := genAddr(bindAddress, port, allowLan) if socksListener != nil { if socksListener.Address() == addr { @@ -83,12 +92,10 @@ func ReCreateSocks(port int) error { return err } - return reCreateSocksUDP(port) + return reCreateSocksUDP(addr) } -func reCreateSocksUDP(port int) error { - addr := genAddr(port, allowLan) - +func reCreateSocksUDP(addr string) error { if socksUDPListener != nil { if socksUDPListener.Address() == addr { return nil @@ -97,10 +104,6 @@ func reCreateSocksUDP(port int) error { socksUDPListener = nil } - if portIsZero(addr) { - return nil - } - var err error socksUDPListener, err = socks.NewSocksUDPProxy(addr) if err != nil { @@ -111,7 +114,7 @@ func reCreateSocksUDP(port int) error { } func ReCreateRedir(port int) error { - addr := genAddr(port, allowLan) + addr := genAddr(bindAddress, port, allowLan) if redirListener != nil { if redirListener.Address() == addr { @@ -167,9 +170,14 @@ func portIsZero(addr string) bool { return false } -func genAddr(port int, allowLan bool) string { +func genAddr(host string, port int, allowLan bool) string { if allowLan { - return fmt.Sprintf(":%d", port) + if host == "*" { + return fmt.Sprintf(":%d", port) + } else { + return fmt.Sprintf("%s:%d", host, port) + } } + return fmt.Sprintf("127.0.0.1:%d", port) } From b926f4cf09d6fc4a7983bf830628d9d70e769152 Mon Sep 17 00:00:00 2001 From: Yunhao Zhang Date: Fri, 9 Aug 2019 01:28:37 +0800 Subject: [PATCH 221/535] Feature: trace adapters when dialing (#170) --- adapters/outbound/base.go | 38 +++++++++++++++++++++++++-- adapters/outbound/direct.go | 8 +++--- adapters/outbound/fallback.go | 16 +++++++++--- adapters/outbound/http.go | 4 +-- adapters/outbound/loadbalance.go | 23 +++++++++++++---- adapters/outbound/reject.go | 4 +-- adapters/outbound/selector.go | 16 +++++++++--- adapters/outbound/shadowsocks.go | 8 +++--- adapters/outbound/socks5.go | 8 +++--- adapters/outbound/urltest.go | 12 ++++++--- adapters/outbound/vmess.go | 8 +++--- constant/adapters.go | 33 ++++++++++++++++++++++-- tunnel/tunnel.go | 44 ++++++++++++++++++++------------ 13 files changed, 166 insertions(+), 56 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 6fb18069e8..3254209b6d 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -30,7 +30,7 @@ func (b *Base) Type() C.AdapterType { return b.tp } -func (b *Base) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { +func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { return nil, nil, errors.New("no support") } @@ -46,6 +46,40 @@ func (b *Base) MarshalJSON() ([]byte, error) { }) } +type conn struct { + net.Conn + chain C.Chain +} + +func (c *conn) Chains() C.Chain { + return c.chain +} + +func (c *conn) AppendToChains(a C.ProxyAdapter) { + c.chain = append(c.chain, a.Name()) +} + +func newConn(c net.Conn, a C.ProxyAdapter) C.Conn { + return &conn{c, []string{a.Name()}} +} + +type packetConn struct { + net.PacketConn + chain C.Chain +} + +func (c *packetConn) Chains() C.Chain { + return c.chain +} + +func (c *packetConn) AppendToChains(a C.ProxyAdapter) { + c.chain = append(c.chain, a.Name()) +} + +func newPacketConn(c net.PacketConn, a C.ProxyAdapter) C.PacketConn { + return &packetConn{c, []string{a.Name()}} +} + type Proxy struct { C.ProxyAdapter history *queue.Queue @@ -56,7 +90,7 @@ func (p *Proxy) Alive() bool { return p.alive } -func (p *Proxy) Dial(metadata *C.Metadata) (net.Conn, error) { +func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { conn, err := p.ProxyAdapter.Dial(metadata) if err != nil { p.alive = false diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 491c170efc..0f3e6dc42d 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -10,7 +10,7 @@ type Direct struct { *Base } -func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) { +func (d *Direct) Dial(metadata *C.Metadata) (C.Conn, error) { address := net.JoinHostPort(metadata.Host, metadata.DstPort) if metadata.DstIP != nil { address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) @@ -21,10 +21,10 @@ func (d *Direct) Dial(metadata *C.Metadata) (net.Conn, error) { return nil, err } tcpKeepAlive(c) - return c, nil + return newConn(c, d), nil } -func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { +func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { pc, err := net.ListenPacket("udp", "") if err != nil { return nil, nil, err @@ -34,7 +34,7 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) if err != nil { return nil, nil, err } - return pc, addr, nil + return newPacketConn(pc, d), addr, nil } func NewDirect() *Direct { diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 67a6ab0866..9b44edebde 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -31,14 +31,22 @@ func (f *Fallback) Now() string { return proxy.Name() } -func (f *Fallback) Dial(metadata *C.Metadata) (net.Conn, error) { +func (f *Fallback) Dial(metadata *C.Metadata) (C.Conn, error) { proxy := f.findAliveProxy() - return proxy.Dial(metadata) + c, err := proxy.Dial(metadata) + if err == nil { + c.AppendToChains(f) + } + return c, err } -func (f *Fallback) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { +func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { proxy := f.findAliveProxy() - return proxy.DialUDP(metadata) + pc, addr, err := proxy.DialUDP(metadata) + if err == nil { + pc.AppendToChains(f) + } + return pc, addr, err } func (f *Fallback) SupportUDP() bool { diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index a617de5b47..9b3791f048 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -35,7 +35,7 @@ type HttpOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) { +func (h *Http) Dial(metadata *C.Metadata) (C.Conn, error) { c, err := dialTimeout("tcp", h.addr, tcpTimeout) if err == nil && h.tls { cc := tls.Client(c, h.tlsConfig) @@ -51,7 +51,7 @@ func (h *Http) Dial(metadata *C.Metadata) (net.Conn, error) { return nil, err } - return c, nil + return newConn(c, h), nil } func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index 9418863f0a..c719e8b84f 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -54,21 +54,34 @@ func jumpHash(key uint64, buckets int32) int32 { return int32(b) } -func (lb *LoadBalance) Dial(metadata *C.Metadata) (net.Conn, error) { +func (lb *LoadBalance) Dial(metadata *C.Metadata) (c C.Conn, err error) { + defer func() { + if err == nil { + c.AppendToChains(lb) + } + }() + key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) buckets := int32(len(lb.proxies)) for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { idx := jumpHash(key, buckets) proxy := lb.proxies[idx] if proxy.Alive() { - return proxy.Dial(metadata) + c, err = proxy.Dial(metadata) + return } } - - return lb.proxies[0].Dial(metadata) + c, err = lb.proxies[0].Dial(metadata) + return } -func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { +func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) { + defer func() { + if err == nil { + pc.AppendToChains(lb) + } + }() + key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) buckets := int32(len(lb.proxies)) for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index f78d4f4556..de395d5822 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -12,8 +12,8 @@ type Reject struct { *Base } -func (r *Reject) Dial(metadata *C.Metadata) (net.Conn, error) { - return &NopConn{}, nil +func (r *Reject) Dial(metadata *C.Metadata) (C.Conn, error) { + return newConn(&NopConn{}, r), nil } func NewReject() *Reject { diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index a14a7ce3a5..31d5a0a563 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -20,12 +20,20 @@ type SelectorOption struct { Proxies []string `proxy:"proxies"` } -func (s *Selector) Dial(metadata *C.Metadata) (net.Conn, error) { - return s.selected.Dial(metadata) +func (s *Selector) Dial(metadata *C.Metadata) (C.Conn, error) { + c, err := s.selected.Dial(metadata) + if err == nil { + c.AppendToChains(s) + } + return c, err } -func (s *Selector) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { - return s.selected.DialUDP(metadata) +func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { + pc, addr, err := s.selected.DialUDP(metadata) + if err == nil { + pc.AppendToChains(s) + } + return pc, addr, err } func (s *Selector) SupportUDP() bool { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index a77c1a9aec..a958191f27 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -57,7 +57,7 @@ type v2rayObfsOption struct { SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` } -func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) { +func (ss *ShadowSocks) Dial(metadata *C.Metadata) (C.Conn, error) { c, err := dialTimeout("tcp", ss.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) @@ -78,10 +78,10 @@ func (ss *ShadowSocks) Dial(metadata *C.Metadata) (net.Conn, error) { } c = ss.cipher.StreamConn(c) _, err = c.Write(serializesSocksAddr(metadata)) - return c, err + return newConn(c, ss), err } -func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { +func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { pc, err := net.ListenPacket("udp", "") if err != nil { return nil, nil, err @@ -98,7 +98,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, } pc = ss.cipher.PacketConn(pc) - return &ssUDPConn{PacketConn: pc, rAddr: targetAddr}, addr, nil + return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil } func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 1b08bf874e..d3f7a3a93a 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -33,7 +33,7 @@ type Socks5Option struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { +func (ss *Socks5) Dial(metadata *C.Metadata) (C.Conn, error) { c, err := dialTimeout("tcp", ss.addr, tcpTimeout) if err == nil && ss.tls { @@ -56,10 +56,10 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (net.Conn, error) { if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { return nil, err } - return c, nil + return newConn(c, ss), nil } -func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ net.PacketConn, _ net.Addr, err error) { +func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) { c, err := dialTimeout("tcp", ss.addr, tcpTimeout) if err != nil { err = fmt.Errorf("%s connect error", ss.addr) @@ -116,7 +116,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ net.PacketConn, _ net.Addr, e pc.Close() }() - return &socksUDPConn{PacketConn: pc, rAddr: targetAddr}, addr, nil + return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil } func NewSocks5(option Socks5Option) *Socks5 { diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 351c2e4cb2..4e9dcc7428 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -33,16 +33,22 @@ func (u *URLTest) Now() string { return u.fast.Name() } -func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) { +func (u *URLTest) Dial(metadata *C.Metadata) (C.Conn, error) { a, err := u.fast.Dial(metadata) if err != nil { u.fallback() + } else { + a.AppendToChains(u) } return a, err } -func (u *URLTest) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { - return u.fast.DialUDP(metadata) +func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { + pc, addr, err := u.fast.DialUDP(metadata) + if err == nil { + pc.AppendToChains(u) + } + return pc, addr, err } func (u *URLTest) SupportUDP() bool { diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 2d6d69c84d..0121feb4ed 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -31,17 +31,17 @@ type VmessOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (v *Vmess) Dial(metadata *C.Metadata) (net.Conn, error) { +func (v *Vmess) Dial(metadata *C.Metadata) (C.Conn, error) { c, err := dialTimeout("tcp", v.server, tcpTimeout) if err != nil { return nil, fmt.Errorf("%s connect error", v.server) } tcpKeepAlive(c) c, err = v.client.New(c, parseVmessAddr(metadata)) - return c, err + return newConn(c, v), err } -func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) { +func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { c, err := dialTimeout("tcp", v.server, tcpTimeout) if err != nil { return nil, nil, fmt.Errorf("%s connect error", v.server) @@ -51,7 +51,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (net.PacketConn, net.Addr, error) if err != nil { return nil, nil, fmt.Errorf("new vmess client error: %v", err) } - return &fakeUDPConn{Conn: c}, c.RemoteAddr(), nil + return newPacketConn(&fakeUDPConn{Conn: c}, v), c.RemoteAddr(), nil } func NewVmess(option VmessOption) (*Vmess, error) { diff --git a/constant/adapters.go b/constant/adapters.go index 11844bc77c..30ea501242 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -2,6 +2,7 @@ package constant import ( "context" + "fmt" "net" "time" ) @@ -25,11 +26,39 @@ type ServerAdapter interface { Metadata() *Metadata } +type Connection interface { + Chains() Chain + AppendToChains(adapter ProxyAdapter) +} + +type Chain []string + +func (c Chain) String() string { + switch len(c) { + case 0: + return "" + case 1: + return c[0] + default: + return fmt.Sprintf("%s[%s]", c[len(c)-1], c[0]) + } +} + +type Conn interface { + net.Conn + Connection +} + +type PacketConn interface { + net.PacketConn + Connection +} + type ProxyAdapter interface { Name() string Type() AdapterType - Dial(metadata *Metadata) (net.Conn, error) - DialUDP(metadata *Metadata) (net.PacketConn, net.Addr, error) + Dial(metadata *Metadata) (Conn, error) + DialUDP(metadata *Metadata) (PacketConn, net.Addr, error) SupportUDP() bool Destroy() MarshalJSON() ([]byte, error) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 984af7b9a0..593686152b 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -135,6 +135,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { } var proxy C.Proxy + var rule C.Rule switch t.mode { case Direct: proxy = t.proxies["DIRECT"] @@ -143,7 +144,7 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { // Rule default: var err error - proxy, err = t.match(metadata) + proxy, rule, err = t.match(metadata) if err != nil { return } @@ -151,22 +152,29 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { switch metadata.NetWork { case C.TCP: - t.handleTCPConn(localConn, metadata, proxy) + t.handleTCPConn(localConn, metadata, proxy, rule) case C.UDP: - t.handleUDPConn(localConn, metadata, proxy) + t.handleUDPConn(localConn, metadata, proxy, rule) } } -func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy) { +func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) { pc, addr := natTable.Get(localConn.RemoteAddr()) if pc == nil { - var err error - pc, addr, err = proxy.DialUDP(metadata) + rawpc, naddr, err := proxy.DialUDP(metadata) + addr = naddr + pc = rawpc if err != nil { - log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) + log.Warnln("%s --> %v match %s using %s error: %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter(), err.Error()) return } + if rule != nil { + log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawpc.Chains().String()) + } else { + log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) + } + natTable.Set(localConn.RemoteAddr(), pc, addr) go t.handleUDPToLocal(localConn, pc) } @@ -174,14 +182,21 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, t.handleUDPToRemote(localConn, pc, addr) } -func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy) { +func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) { remoConn, err := proxy.Dial(metadata) + if err != nil { - log.Warnln("Proxy[%s] connect [%s --> %s] error: %s", proxy.Name(), metadata.SrcIP.String(), metadata.String(), err.Error()) + log.Warnln("%s --> %v match %s using %s error: %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter(), err.Error()) return } defer remoConn.Close() + if rule != nil { + log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), remoConn.Chains().String()) + } else { + log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) + } + switch adapter := localConn.(type) { case *InboundAdapter.HTTPAdapter: t.handleHTTP(adapter, remoConn) @@ -194,7 +209,7 @@ func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.DstIP == nil } -func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { +func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { t.configMux.RLock() defer t.configMux.RUnlock() @@ -204,7 +219,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { ip, err := t.resolveIP(metadata.Host) if err != nil { if !t.ignoreResolveFail { - return nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error()) + return nil, nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error()) } log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) } else { @@ -224,13 +239,10 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, error) { log.Debugln("%v UDP is not supported", adapter.Name()) continue } - - log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter()) - return adapter, nil + return adapter, rule, nil } } - log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) - return t.proxies["DIRECT"], nil + return t.proxies["DIRECT"], nil, nil } func newTunnel() *Tunnel { From f75cd04181b9a8fbcb04d25de5b05299bfee5073 Mon Sep 17 00:00:00 2001 From: Fndroid <18825176954@163.com> Date: Fri, 9 Aug 2019 15:39:13 +0800 Subject: [PATCH 222/535] Change: speedtest with HEAD instead of GET (#259) --- adapters/outbound/base.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 3254209b6d..e8f44f4592 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -163,7 +163,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { } defer instance.Close() - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequest(http.MethodHead, url, nil) if err != nil { return } From b137a50d8531e6e8c9708ed8b6cb978cec86a4a8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 10 Aug 2019 20:14:24 +0800 Subject: [PATCH 223/535] Fix: crash in handleConn --- tunnel/tunnel.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 593686152b..8681e5c2db 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -165,7 +165,7 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, addr = naddr pc = rawpc if err != nil { - log.Warnln("%s --> %v match %s using %s error: %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter(), err.Error()) + log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) return } @@ -184,9 +184,8 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) { remoConn, err := proxy.Dial(metadata) - if err != nil { - log.Warnln("%s --> %v match %s using %s error: %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rule.Adapter(), err.Error()) + log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) return } defer remoConn.Close() From d59e98dc832a3b723c08ec055830d0cd98b9b304 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Mon, 12 Aug 2019 10:11:44 +0800 Subject: [PATCH 224/535] Feature: allow arbitrary order in proxy group (#89) --- config/config.go | 5 +- config/utils.go | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 296c9761cb..942ee87617 100644 --- a/config/config.go +++ b/config/config.go @@ -293,10 +293,13 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { } // parse proxy group + if err := proxyGroupsDagSort(groupsConfig); err != nil { + return nil, err + } for idx, mapping := range groupsConfig { groupType, existType := mapping["type"].(string) groupName, existName := mapping["name"].(string) - if !existType && existName { + if !(existType && existName) { return nil, fmt.Errorf("ProxyGroup %d: missing type or name", idx) } diff --git a/config/utils.go b/config/utils.go index 54e205f8f8..7c6b69f34b 100644 --- a/config/utils.go +++ b/config/utils.go @@ -34,3 +34,128 @@ func or(pointers ...*int) *int { } return pointers[len(pointers)-1] } + +// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. +// Meanwhile, record the original index in the config file. +// If loop is detected, return an error with location of loop. +func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { + + type Node struct { + indegree int + // topological order + topo int + // the origional data in `groupsConfig` + data map[string]interface{} + // `outdegree` and `from` are used in loop locating + outdegree int + from []string + } + + graph := make(map[string]*Node) + + // Step 1.1 build dependency graph + for idx, mapping := range groupsConfig { + // record original order in config file. + // this field can be used determinate the display order in FrontEnd. + mapping["configIdx"] = idx + groupName, existName := mapping["name"].(string) + if !existName { + return fmt.Errorf("ProxyGroup %d: missing name", idx) + } + if node, ok := graph[groupName]; ok { + if node.data != nil { + return fmt.Errorf("ProxyGroup %s: duplicate group name", groupName) + } + node.data = mapping + } else { + graph[groupName] = &Node{0, -1, mapping, 0, nil} + } + proxies, existProxies := mapping["proxies"] + if !existProxies { + return fmt.Errorf("ProxyGroup %s: the `proxies` field is requried", groupName) + } + for _, proxy := range proxies.([]interface{}) { + proxy := proxy.(string) + if node, ex := graph[proxy]; ex { + node.indegree++ + } else { + graph[proxy] = &Node{1, -1, nil, 0, nil} + } + } + } + // Step 1.2 Topological Sort + // topological index of **ProxyGroup** + index := 0 + queue := make([]string, 0) + for name, node := range graph { + // in the begning, put nodes that have `node.indegree == 0` into queue. + if node.indegree == 0 { + queue = append(queue, name) + } + } + // every element in queue have indegree == 0 + for ; len(queue) > 0; queue = queue[1:] { + name := queue[0] + node := graph[name] + if node.data != nil { + index++ + groupsConfig[len(groupsConfig)-index] = node.data + for _, proxy := range node.data["proxies"].([]interface{}) { + child := graph[proxy.(string)] + child.indegree-- + if child.indegree == 0 { + queue = append(queue, proxy.(string)) + } + } + } + delete(graph, name) + } + + // no loop is detected, return sorted ProxyGroup + if len(graph) == 0 { + return nil + } + + // if loop is detected, locate the loop and throw an error + // Step 2.1 rebuild the graph, fill `outdegree` and `from` filed + for name, node := range graph { + if node.data == nil { + continue + } + for _, proxy := range node.data["proxies"].([]interface{}) { + node.outdegree++ + child := graph[proxy.(string)] + if child.from == nil { + child.from = make([]string, 0, child.indegree) + } + child.from = append(child.from, name) + } + } + // Step 2.2 remove nodes outside the loop. so that we have only the loops remain in `graph` + queue = make([]string, 0) + // initialize queue with node have outdegree == 0 + for name, node := range graph { + if node.outdegree == 0 { + queue = append(queue, name) + } + } + // every element in queue have outdegree == 0 + for ; len(queue) > 0; queue = queue[1:] { + name := queue[0] + node := graph[name] + for _, f := range node.from { + graph[f].outdegree-- + if graph[f].outdegree == 0 { + queue = append(queue, f) + } + } + delete(graph, name) + } + // Step 2.3 report the elements in loop + loopElements := make([]string, 0, len(graph)) + for name := range graph { + loopElements = append(loopElements, name) + delete(graph, name) + } + return fmt.Errorf("Loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements) +} From 0f7f0a9b1a0bb48d9fb268a03701686576c00025 Mon Sep 17 00:00:00 2001 From: "X. Jason Lyu" Date: Mon, 12 Aug 2019 14:01:32 +0800 Subject: [PATCH 225/535] Optimization: socks UDP & fix typo (#261) --- .gitignore | 6 ++++++ adapters/outbound/socks5.go | 10 ++++++++-- tunnel/tunnel.go | 18 +++++++++--------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 9a9bbe1904..0593cfd054 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,9 @@ bin/* # dep vendor + +# GoLand +.idea/* + +# macOS file +.DS_Store diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index d3f7a3a93a..a72c938645 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -116,7 +116,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err pc.Close() }() - return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil + return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr, tcpConn: c}, ss), addr, nil } func NewSocks5(option Socks5Option) *Socks5 { @@ -146,7 +146,8 @@ func NewSocks5(option Socks5Option) *Socks5 { type socksUDPConn struct { net.PacketConn - rAddr net.Addr + rAddr net.Addr + tcpConn net.Conn } func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { @@ -166,3 +167,8 @@ func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { copy(b, payload) return n - len(addr) - 3, a, e } + +func (uc *socksUDPConn) Close() error { + uc.tcpConn.Close() + return uc.PacketConn.Close() +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 8681e5c2db..837374ad01 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -161,16 +161,16 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) { pc, addr := natTable.Get(localConn.RemoteAddr()) if pc == nil { - rawpc, naddr, err := proxy.DialUDP(metadata) - addr = naddr - pc = rawpc + rawPc, nAddr, err := proxy.DialUDP(metadata) + addr = nAddr + pc = rawPc if err != nil { log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) return } if rule != nil { - log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawpc.Chains().String()) + log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) } else { log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) } @@ -183,24 +183,24 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, } func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) { - remoConn, err := proxy.Dial(metadata) + remoteConn, err := proxy.Dial(metadata) if err != nil { log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) return } - defer remoConn.Close() + defer remoteConn.Close() if rule != nil { - log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), remoConn.Chains().String()) + log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String()) } else { log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) } switch adapter := localConn.(type) { case *InboundAdapter.HTTPAdapter: - t.handleHTTP(adapter, remoConn) + t.handleHTTP(adapter, remoteConn) case *InboundAdapter.SocketAdapter: - t.handleSocket(adapter, remoConn) + t.handleSocket(adapter, remoteConn) } } From 6f3a654d6c054665c0c519bdc91dc9da322a0b4b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 15 Aug 2019 11:40:40 +0800 Subject: [PATCH 226/535] Chore: ship github action --- .github/workflows/go.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000000..a8f3819924 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,21 @@ +name: Go +on: [push, pull_request] +jobs: + + build: + name: Build + runs-on: ubuntu-latest + steps: + + - name: Set up Go 1.12 + uses: actions/setup-go@v1 + with: + go-version: 1.12 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + + - name: Get dependencies + run: | + go test ./... From 48a2013d9c8985e5a0d78133852f048b387044b4 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 16 Aug 2019 21:38:27 +0800 Subject: [PATCH 227/535] Fix: socks address stringify buffer overflow --- component/socks5/socks5.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index b16c3b69ad..1dd60391ae 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -48,8 +48,9 @@ func (a Addr) String() string { switch a[0] { case AtypDomainName: - host = string(a[2 : 2+int(a[1])]) - port = strconv.Itoa((int(a[2+int(a[1])]) << 8) | int(a[2+int(a[1])+1])) + hostLen := uint16(a[1]) + host = string(a[2 : 2+hostLen]) + port = strconv.Itoa((int(a[2+hostLen]) << 8) | int(a[2+hostLen+1])) case AtypIPv4: host = net.IP(a[1 : 1+net.IPv4len]).String() port = strconv.Itoa((int(a[1+net.IPv4len]) << 8) | int(a[1+net.IPv4len+1])) From 71f0a4e35ea01b332387ac5ee78e900abd0abb04 Mon Sep 17 00:00:00 2001 From: comwrg Date: Mon, 26 Aug 2019 12:26:14 +0800 Subject: [PATCH 228/535] Fix: typo (#281) --- constant/adapters.go | 2 +- constant/rule.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/constant/adapters.go b/constant/adapters.go index 30ea501242..4593fed044 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -103,6 +103,6 @@ func (at AdapterType) String() string { case LoadBalance: return "LoadBalance" default: - return "Unknow" + return "Unknown" } } diff --git a/constant/rule.go b/constant/rule.go index fa8a12c798..40756264b9 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -36,7 +36,7 @@ func (rt RuleType) String() string { case MATCH: return "MATCH" default: - return "Unknow" + return "Unknown" } } From 5cc66e51dbe3ddc3b94022b3c75b0626c308d074 Mon Sep 17 00:00:00 2001 From: comwrg Date: Tue, 27 Aug 2019 16:23:44 +0800 Subject: [PATCH 229/535] Chore: remove unused code (#282) --- adapters/outbound/urltest.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 4e9dcc7428..6f61d8d778 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -125,8 +125,6 @@ func (u *URLTest) speedTest() { if fast != nil { u.fast = fast.(C.Proxy) } - - <-ctx.Done() } func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { From e34090c39a5dfaa9f7730ec5154c6ae8cf78d3fa Mon Sep 17 00:00:00 2001 From: comwrg Date: Wed, 28 Aug 2019 22:28:02 +0800 Subject: [PATCH 230/535] Improve: url-test retry dial when failed (#283) --- adapters/outbound/urltest.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 6f61d8d778..b9efd8d250 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -33,14 +33,16 @@ func (u *URLTest) Now() string { return u.fast.Name() } -func (u *URLTest) Dial(metadata *C.Metadata) (C.Conn, error) { - a, err := u.fast.Dial(metadata) - if err != nil { +func (u *URLTest) Dial(metadata *C.Metadata) (c C.Conn, err error) { + for i := 0; i < 3; i++ { + c, err = u.fast.Dial(metadata) + if err == nil { + c.AppendToChains(u) + return + } u.fallback() - } else { - a.AppendToChains(u) } - return a, err + return } func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { From 0d51877fcdab1392c4dedfce0fabfb494998d6cb Mon Sep 17 00:00:00 2001 From: Comzyh Date: Wed, 28 Aug 2019 23:44:32 +0800 Subject: [PATCH 231/535] Fix: should keep the original order of proxy groups (#284) --- config/config.go | 22 ++++++++++++++++------ config/utils.go | 3 --- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/config/config.go b/config/config.go index 942ee87617..080a6e499b 100644 --- a/config/config.go +++ b/config/config.go @@ -292,15 +292,26 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { proxyList = append(proxyList, proxy.Name()) } - // parse proxy group + // keep the origional order of ProxyGroups in config file + for idx, mapping := range groupsConfig { + groupName, existName := mapping["name"].(string) + if !existName { + return nil, fmt.Errorf("ProxyGroup %d: missing name", idx) + } + proxyList = append(proxyList, groupName) + } + + // check if any loop exists and sort the ProxyGroups if err := proxyGroupsDagSort(groupsConfig); err != nil { return nil, err } - for idx, mapping := range groupsConfig { + + // parse proxy group + for _, mapping := range groupsConfig { groupType, existType := mapping["type"].(string) - groupName, existName := mapping["name"].(string) - if !(existType && existName) { - return nil, fmt.Errorf("ProxyGroup %d: missing type or name", idx) + groupName, _ := mapping["name"].(string) + if !existType { + return nil, fmt.Errorf("ProxyGroup %s: missing type", groupName) } if _, exist := proxies[groupName]; exist { @@ -364,7 +375,6 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) } proxies[groupName] = adapters.NewProxy(group) - proxyList = append(proxyList, groupName) } ps := []C.Proxy{} diff --git a/config/utils.go b/config/utils.go index 7c6b69f34b..f2832f563a 100644 --- a/config/utils.go +++ b/config/utils.go @@ -55,9 +55,6 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { // Step 1.1 build dependency graph for idx, mapping := range groupsConfig { - // record original order in config file. - // this field can be used determinate the display order in FrontEnd. - mapping["configIdx"] = idx groupName, existName := mapping["name"].(string) if !existName { return fmt.Errorf("ProxyGroup %d: missing name", idx) From 9e0bd627900f3118c4a5da3e9c343b329aac2ed1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 4 Sep 2019 23:26:20 +0900 Subject: [PATCH 232/535] Migration: go 1.13 --- .github/workflows/go.yml | 4 ++-- .travis.yml | 2 +- README.md | 2 +- constant/path.go | 12 ++---------- go.mod | 14 ++++++++------ go.sum | 26 ++++++++++++++++---------- main.go | 3 --- 7 files changed, 30 insertions(+), 33 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a8f3819924..ed5dd784f3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -7,10 +7,10 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.12 + - name: Set up Go 1.13 uses: actions/setup-go@v1 with: - go-version: 1.12 + go-version: 1.13 id: go - name: Check out code into the Go module directory diff --git a/.travis.yml b/.travis.yml index 32d4e3db03..8f88b68403 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go sudo: false go: - - '1.12' + - '1.13' install: - "go mod download" env: diff --git a/README.md b/README.md index 921f752508..bd6281b648 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ go get -u -v github.com/Dreamacro/clash Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases) -Requires Go >= 1.12. +Requires Go >= 1.13. Checkout Clash version: diff --git a/constant/path.go b/constant/path.go index bfe158a1cd..4eff23f7af 100644 --- a/constant/path.go +++ b/constant/path.go @@ -2,7 +2,6 @@ package constant import ( "os" - "os/user" P "path" ) @@ -16,16 +15,9 @@ type path struct { } func init() { - currentUser, err := user.Current() - var homedir string + homedir, err := os.UserHomeDir() if err != nil { - dir := os.Getenv("HOME") - if dir == "" { - dir, _ = os.Getwd() - } - homedir = dir - } else { - homedir = currentUser.HomeDir + homedir, _ = os.Getwd() } homedir = P.Join(homedir, ".config", Name) diff --git a/go.mod b/go.mod index a39e7adad2..eca282deb0 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,22 @@ module github.com/Dreamacro/clash +go 1.13 + require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.3 + github.com/Dreamacro/go-shadowsocks2 v0.1.4 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible - github.com/gorilla/websocket v1.4.0 - github.com/miekg/dns v1.1.14 + github.com/gorilla/websocket v1.4.1 + github.com/miekg/dns v1.1.16 github.com/oschwald/geoip2-golang v1.3.0 - github.com/oschwald/maxminddb-golang v1.3.1 // indirect + github.com/oschwald/maxminddb-golang v1.4.0 // indirect github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.2.2 - golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 - golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 + golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 + golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 golang.org/x/sync v0.0.0-20190423024810-112230192c58 gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.2 diff --git a/go.sum b/go.sum index afbc3bf886..a1b83ba577 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.3 h1:1ffY/q4e3o+MnztYgIq1iZiX1BWoWQ6D3AIO1kkb8bc= -github.com/Dreamacro/go-shadowsocks2 v0.1.3/go.mod h1:0x17IhQ+mlY6q/ffKRpzaE7u4aHMxxnitTRSrV5G6TU= +github.com/Dreamacro/go-shadowsocks2 v0.1.4 h1:SqSVFdtBHVx2rrMOK+qipO10rRSQQKEeAF9Igm8qB9g= +github.com/Dreamacro/go-shadowsocks2 v0.1.4/go.mod h1:RPU9lnoesqJ/0jFzntFrYpbQI0+NoYiJVu7mWn3Y0cs= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -14,16 +14,16 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA= -github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.16 h1:iMEQ/IVHxPTtx2Q07JP/k4CKRvSjiAZjZ0hnhgYEDmE= +github.com/miekg/dns v1.1.16/go.mod h1:YNV562EiewvSmpCB6/W4c6yqjK7Z+M/aIS1JHsIVeg8= github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8= github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= -github.com/oschwald/maxminddb-golang v1.3.1 h1:kPc5+ieL5CC/Zn0IaXJPxDFlUxKTQEU8QBTtmfQDAIo= -github.com/oschwald/maxminddb-golang v1.3.1/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= +github.com/oschwald/maxminddb-golang v1.4.0 h1:5/rpmW41qrgSed4wK32rdznbkTSXHcraY2LOMJX4DMc= +github.com/oschwald/maxminddb-golang v1.4.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= 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/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -31,13 +31,19 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= +golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20180928133829-e4b3c5e90611/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-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/main.go b/main.go index 2e9c4b25e4..cb8b89bca6 100644 --- a/main.go +++ b/main.go @@ -33,9 +33,6 @@ func main() { return } - // enable tls 1.3 and remove when go 1.13 - os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1") - if homedir != "" { if !filepath.IsAbs(homedir) { currentDir, _ := os.Getwd() From 9875f8ea6ed5cb813c54a55d37155cdfdc1a9ba8 Mon Sep 17 00:00:00 2001 From: comwrg Date: Sat, 7 Sep 2019 16:23:43 +0800 Subject: [PATCH 233/535] Fix: typo (#287) --- adapters/outbound/base.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index e8f44f4592..f255111b94 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -107,7 +107,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory { return histories } -// LastDelay return last history record. if proxy is not alive, return the max value of int16. +// LastDelay return last history record. if proxy is not alive, return the max value of uint16. func (p *Proxy) LastDelay() (delay uint16) { var max uint16 = 0xffff if !p.alive { From 981501013194772dd77168ebd014a20aaf1a7734 Mon Sep 17 00:00:00 2001 From: Soar Qin Date: Sun, 8 Sep 2019 11:21:28 +0800 Subject: [PATCH 234/535] Fix: HTTP status code `100 Continue` support" (#288) * Fix: HTTP Status Code `100 Continue` support * Style: code style adjustment --- tunnel/connection.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 36c698d2ae..327a1c6c94 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -17,6 +17,9 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { req := request.R host := req.Host + inboundReeder := bufio.NewReader(request) + outboundReeder := bufio.NewReader(conn) + for { keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" @@ -27,8 +30,9 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { if err != nil { break } - br := bufio.NewReader(conn) - resp, err := http.ReadResponse(br, req) + + handleResponse: + resp, err := http.ReadResponse(outboundReeder, req) if err != nil { break } @@ -50,7 +54,11 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } - req, err = http.ReadRequest(bufio.NewReader(request)) + if resp.StatusCode == http.StatusContinue { + goto handleResponse + } + + req, err = http.ReadRequest(inboundReeder) if err != nil { break } From 60fdd82e2b42ace367e70df199a296fff6a6e0d1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 8 Sep 2019 22:33:52 +0800 Subject: [PATCH 235/535] Fix(API): use right status code --- hub/route/proxies.go | 2 +- hub/route/server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 7044f05718..d485495e99 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -119,7 +119,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { elm := picker.Wait() if elm == nil { - render.Status(r, http.StatusRequestTimeout) + render.Status(r, http.StatusGatewayTimeout) render.JSON(w, r, ErrRequestTimeout) return } diff --git a/hub/route/server.go b/hub/route/server.go index 7c3a233476..a309944e71 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -65,7 +65,7 @@ func Start(addr string, secret string) { if uiPath != "" { r.Group(func(r chi.Router) { fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath))) - r.Get("/ui", http.RedirectHandler("/ui/", 301).ServeHTTP) + r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP) r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) { fs.ServeHTTP(w, r) }) From 112b3e5a6c254cb293bb616604c9db6e254b8f1e Mon Sep 17 00:00:00 2001 From: Soar Qin Date: Thu, 12 Sep 2019 10:22:09 +0800 Subject: [PATCH 236/535] Fix: don't close connection on status `100 Continue` and header `Proxy-Connection: Keep-Alive` (#294) --- tunnel/connection.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 327a1c6c94..b5beb043bc 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -37,7 +37,16 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } adapters.RemoveHopByHopHeaders(resp.Header) - if resp.ContentLength >= 0 { + + if resp.StatusCode == http.StatusContinue { + err = resp.Write(request) + if err != nil { + break + } + goto handleResponse + } + + if keepAlive || resp.ContentLength >= 0 { resp.Header.Set("Proxy-Connection", "keep-alive") resp.Header.Set("Connection", "keep-alive") resp.Header.Set("Keep-Alive", "timeout=4") @@ -50,14 +59,6 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } - if !keepAlive { - break - } - - if resp.StatusCode == http.StatusContinue { - goto handleResponse - } - req, err = http.ReadRequest(inboundReeder) if err != nil { break From b3e10c05e6b2d72a9be75a267d7b87df375c37dd Mon Sep 17 00:00:00 2001 From: Comzyh Date: Fri, 13 Sep 2019 15:04:51 +0800 Subject: [PATCH 237/535] Fix: parse error in proxyGroupsDagSort (#298) --- adapters/outbound/base.go | 6 ++++++ config/config.go | 2 +- config/utils.go | 31 ++++++++++++++++--------------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index f255111b94..710afad070 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -193,3 +193,9 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { func NewProxy(adapter C.ProxyAdapter) *Proxy { return &Proxy{adapter, queue.New(10), true} } + +// ProxyGroupOption contain the common options for all kind of ProxyGroup +type ProxyGroupOption struct { + Name string `proxy:"name"` + Proxies []string `proxy:"proxies"` +} diff --git a/config/config.go b/config/config.go index 080a6e499b..e1baac8f67 100644 --- a/config/config.go +++ b/config/config.go @@ -302,7 +302,7 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { } // check if any loop exists and sort the ProxyGroups - if err := proxyGroupsDagSort(groupsConfig); err != nil { + if err := proxyGroupsDagSort(groupsConfig, decoder); err != nil { return nil, err } diff --git a/config/utils.go b/config/utils.go index f2832f563a..e263d68be8 100644 --- a/config/utils.go +++ b/config/utils.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" + adapters "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" ) @@ -38,9 +40,9 @@ func or(pointers ...*int) *int { // Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. // Meanwhile, record the original index in the config file. // If loop is detected, return an error with location of loop. -func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { +func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structure.Decoder) error { - type Node struct { + type graphNode struct { indegree int // topological order topo int @@ -51,32 +53,31 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { from []string } - graph := make(map[string]*Node) + graph := make(map[string]*graphNode) // Step 1.1 build dependency graph - for idx, mapping := range groupsConfig { - groupName, existName := mapping["name"].(string) - if !existName { - return fmt.Errorf("ProxyGroup %d: missing name", idx) + for _, mapping := range groupsConfig { + option := &adapters.ProxyGroupOption{} + err := decoder.Decode(mapping, option) + groupName := option.Name + if err != nil { + return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) } + if node, ok := graph[groupName]; ok { if node.data != nil { return fmt.Errorf("ProxyGroup %s: duplicate group name", groupName) } node.data = mapping } else { - graph[groupName] = &Node{0, -1, mapping, 0, nil} - } - proxies, existProxies := mapping["proxies"] - if !existProxies { - return fmt.Errorf("ProxyGroup %s: the `proxies` field is requried", groupName) + graph[groupName] = &graphNode{0, -1, mapping, 0, nil} } - for _, proxy := range proxies.([]interface{}) { - proxy := proxy.(string) + + for _, proxy := range option.Proxies { if node, ex := graph[proxy]; ex { node.indegree++ } else { - graph[proxy] = &Node{1, -1, nil, 0, nil} + graph[proxy] = &graphNode{1, -1, nil, 0, nil} } } } From 16e3090ee85140b8f9370680cde407204cda86b6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 13 Sep 2019 17:44:30 +0800 Subject: [PATCH 238/535] Feature: add version api --- hub/route/server.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hub/route/server.go b/hub/route/server.go index a309944e71..a1bbcde985 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -7,6 +7,7 @@ import ( "strings" "time" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" T "github.com/Dreamacro/clash/tunnel" @@ -57,6 +58,7 @@ func Start(addr string, secret string) { r.Get("/logs", getLogs) r.Get("/traffic", traffic) + r.Get("/version", version) r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) @@ -209,3 +211,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) { } } } + +func version(w http.ResponseWriter, r *http.Request) { + render.JSON(w, r, render.M{"version": C.Version}) +} From 96a4abf46c3b9073c98e45079e6a51a2aad2a70b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 11 Sep 2019 17:00:55 +0800 Subject: [PATCH 239/535] Feature: move hosts to the top --- config/config.go | 53 +++++++++++-------- dns/iputil.go | 66 ++++++++++++++++++++++++ dns/middleware.go | 109 +++++++++++++-------------------------- dns/resolver.go | 48 +++++++---------- hub/executor/executor.go | 7 ++- tunnel/tunnel.go | 7 +++ 6 files changed, 166 insertions(+), 124 deletions(-) diff --git a/config/config.go b/config/config.go index e1baac8f67..6d0942ddb9 100644 --- a/config/config.go +++ b/config/config.go @@ -44,7 +44,6 @@ type DNS struct { IPv6 bool `yaml:"ipv6"` NameServer []dns.NameServer `yaml:"nameserver"` Fallback []dns.NameServer `yaml:"fallback"` - Hosts *trie.Trie `yaml:"-"` Listen string `yaml:"listen"` EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` FakeIPRange *fakeip.Pool @@ -60,20 +59,20 @@ type Config struct { General *General DNS *DNS Experimental *Experimental + Hosts *trie.Trie Rules []C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy } type rawDNS struct { - Enable bool `yaml:"enable"` - IPv6 bool `yaml:"ipv6"` - NameServer []string `yaml:"nameserver"` - Hosts map[string]string `yaml:"hosts"` - Fallback []string `yaml:"fallback"` - Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` - FakeIPRange string `yaml:"fake-ip-range"` + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []string `yaml:"nameserver"` + Fallback []string `yaml:"fallback"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + FakeIPRange string `yaml:"fake-ip-range"` } type rawConfig struct { @@ -89,6 +88,7 @@ type rawConfig struct { ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` + Hosts map[string]string `yaml:"hosts"` DNS rawDNS `yaml:"dns"` Experimental Experimental `yaml:"experimental"` Proxy []map[string]interface{} `yaml:"Proxy"` @@ -135,6 +135,7 @@ func readConfig(path string) (*rawConfig, error) { Mode: T.Rule, Authentication: []string{}, LogLevel: log.INFO, + Hosts: map[string]string{}, Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, @@ -144,7 +145,6 @@ func readConfig(path string) (*rawConfig, error) { DNS: rawDNS{ Enable: false, FakeIPRange: "198.18.0.1/16", - Hosts: map[string]string{}, }, } err = yaml.Unmarshal([]byte(data), &rawConfig) @@ -185,6 +185,12 @@ func Parse(path string) (*Config, error) { } config.DNS = dnsCfg + hosts, err := parseHosts(rawCfg) + if err != nil { + return nil, err + } + config.Hosts = hosts + config.Users = parseAuthentication(rawCfg.Authentication) return config, nil @@ -460,6 +466,21 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { return rules, nil } +func parseHosts(cfg *rawConfig) (*trie.Trie, error) { + tree := trie.New() + if len(cfg.Hosts) != 0 { + for domain, ipStr := range cfg.Hosts { + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, fmt.Errorf("%s is not a valid IP", ipStr) + } + tree.Insert(domain, ip) + } + } + + return tree, nil +} + func hostWithDefaultPort(host string, defPort string) (string, error) { if !strings.Contains(host, ":") { host += ":" @@ -544,18 +565,6 @@ func parseDNS(cfg rawDNS) (*DNS, error) { return nil, err } - if len(cfg.Hosts) != 0 { - tree := trie.New() - for domain, ipStr := range cfg.Hosts { - ip := net.ParseIP(ipStr) - if ip == nil { - return nil, fmt.Errorf("%s is not a valid IP", ipStr) - } - tree.Insert(domain, ip) - } - dnsCfg.Hosts = tree - } - if cfg.EnhancedMode == dns.FAKEIP { _, ipnet, err := net.ParseCIDR(cfg.FakeIPRange) if err != nil { diff --git a/dns/iputil.go b/dns/iputil.go index 106c25d8bc..66dd0c0edc 100644 --- a/dns/iputil.go +++ b/dns/iputil.go @@ -9,8 +9,74 @@ var ( errIPNotFound = errors.New("cannot found ip") ) +// ResolveIPv4 with a host, return ipv4 +func ResolveIPv4(host string) (net.IP, error) { + if node := DefaultHosts.Search(host); node != nil { + if ip := node.Data.(net.IP).To4(); ip != nil { + return ip, nil + } + } + + ip := net.ParseIP(host) + if ip4 := ip.To4(); ip4 != nil { + return ip4, nil + } + + if DefaultResolver != nil { + return DefaultResolver.ResolveIPv4(host) + } + + ipAddrs, err := net.LookupIP(host) + if err != nil { + return nil, err + } + + for _, ip := range ipAddrs { + if ip4 := ip.To4(); ip4 != nil { + return ip4, nil + } + } + + return nil, errIPNotFound +} + +// ResolveIPv6 with a host, return ipv6 +func ResolveIPv6(host string) (net.IP, error) { + if node := DefaultHosts.Search(host); node != nil { + if ip := node.Data.(net.IP).To16(); ip != nil { + return ip, nil + } + } + + ip := net.ParseIP(host) + if ip6 := ip.To16(); ip6 != nil { + return ip6, nil + } + + if DefaultResolver != nil { + return DefaultResolver.ResolveIPv6(host) + } + + ipAddrs, err := net.LookupIP(host) + if err != nil { + return nil, err + } + + for _, ip := range ipAddrs { + if ip6 := ip.To16(); ip6 != nil { + return ip6, nil + } + } + + return nil, errIPNotFound +} + // ResolveIP with a host, return ip func ResolveIP(host string) (net.IP, error) { + if node := DefaultHosts.Search(host); node != nil { + return node.Data.(net.IP), nil + } + if DefaultResolver != nil { if DefaultResolver.ipv6 { return DefaultResolver.ResolveIP(host) diff --git a/dns/middleware.go b/dns/middleware.go index 4c7e1ec299..6d30f8d643 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -1,8 +1,6 @@ package dns import ( - "fmt" - "net" "strings" "github.com/Dreamacro/clash/component/fakeip" @@ -12,34 +10,40 @@ import ( ) type handler func(w D.ResponseWriter, r *D.Msg) +type middleware func(next handler) handler -func withFakeIP(pool *fakeip.Pool) handler { - return func(w D.ResponseWriter, r *D.Msg) { - q := r.Question[0] - host := strings.TrimRight(q.Name, ".") - - rr := &D.A{} - rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} - ip := pool.Lookup(host) - rr.A = ip - msg := r.Copy() - msg.Answer = []D.RR{rr} - - setMsgTTL(msg, 1) - msg.SetReply(r) - w.WriteMsg(msg) - return +func withFakeIP(fakePool *fakeip.Pool) middleware { + return func(next handler) handler { + return func(w D.ResponseWriter, r *D.Msg) { + q := r.Question[0] + if q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA { + next(w, r) + return + } + + host := strings.TrimRight(q.Name, ".") + + rr := &D.A{} + rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} + ip := fakePool.Lookup(host) + rr.A = ip + msg := r.Copy() + msg.Answer = []D.RR{rr} + + setMsgTTL(msg, 1) + msg.SetReply(r) + w.WriteMsg(msg) + return + } } } func withResolver(resolver *Resolver) handler { return func(w D.ResponseWriter, r *D.Msg) { msg, err := resolver.Exchange(r) - if err != nil { q := r.Question[0] - qString := fmt.Sprintf("%s %s %s", q.Name, D.Class(q.Qclass).String(), D.Type(q.Qtype).String()) - log.Debugln("[DNS Server] Exchange %s failed: %v", qString, err) + log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err) D.HandleFailed(w, r) return } @@ -49,64 +53,23 @@ func withResolver(resolver *Resolver) handler { } } -func withHost(resolver *Resolver, next handler) handler { - hosts := resolver.hosts - if hosts == nil { - panic("dns/withHost: hosts should not be nil") +func compose(middlewares []middleware, endpoint handler) handler { + length := len(middlewares) + h := endpoint + for i := length - 1; i >= 0; i-- { + middleware := middlewares[i] + h = middleware(h) } - return func(w D.ResponseWriter, r *D.Msg) { - q := r.Question[0] - if q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA { - next(w, r) - return - } - - domain := strings.TrimRight(q.Name, ".") - host := hosts.Search(domain) - if host == nil { - next(w, r) - return - } - - ip := host.Data.(net.IP) - if q.Qtype == D.TypeAAAA && ip.To16() == nil { - next(w, r) - return - } else if q.Qtype == D.TypeA && ip.To4() == nil { - next(w, r) - return - } - - var rr D.RR - if q.Qtype == D.TypeAAAA { - record := &D.AAAA{} - record.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: dnsDefaultTTL} - record.AAAA = ip - rr = record - } else { - record := &D.A{} - record.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} - record.A = ip - rr = record - } - - msg := r.Copy() - msg.Answer = []D.RR{rr} - msg.SetReply(r) - w.WriteMsg(msg) - return - } + return h } func newHandler(resolver *Resolver) handler { - if resolver.IsFakeIP() { - return withFakeIP(resolver.pool) - } + middlewares := []middleware{} - if resolver.hosts != nil { - return withHost(resolver, withResolver(resolver)) + if resolver.IsFakeIP() { + middlewares = append(middlewares, withFakeIP(resolver.pool)) } - return withResolver(resolver) + return compose(middlewares, withResolver(resolver)) } diff --git a/dns/resolver.go b/dns/resolver.go index 4e66f17c90..6ed6576496 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -21,8 +21,11 @@ import ( ) var ( - // DefaultResolver aim to resolve ip with host + // DefaultResolver aim to resolve ip DefaultResolver *Resolver + + // DefaultHosts aim to resolve hosts + DefaultHosts = trie.New() ) var ( @@ -46,7 +49,6 @@ type Resolver struct { ipv6 bool mapping bool fakeip bool - hosts *trie.Trie pool *fakeip.Pool fallback []resolver main []resolver @@ -56,11 +58,6 @@ type Resolver struct { // ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { - ip = net.ParseIP(host) - if ip != nil { - return ip, nil - } - ch := make(chan net.IP) go func() { defer close(ch) @@ -89,26 +86,12 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { // ResolveIPv4 request with TypeA func (r *Resolver) ResolveIPv4(host string) (ip net.IP, err error) { - ip = net.ParseIP(host) - if ip != nil { - return ip, nil - } - - query := &D.Msg{} - query.SetQuestion(D.Fqdn(host), D.TypeA) - - msg, err := r.Exchange(query) - if err != nil { - return nil, err - } - - ips := r.msgToIP(msg) - if len(ips) == 0 { - return nil, errIPNotFound - } + return r.resolveIP(host, D.TypeA) +} - ip = ips[0] - return +// ResolveIPv6 request with TypeAAAA +func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) { + return r.resolveIP(host, D.TypeAAAA) } // Exchange a batch of dns request, and it use cache @@ -232,6 +215,17 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { } func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) { + ip = net.ParseIP(host) + if dnsType == D.TypeAAAA { + if ip6 := ip.To16(); ip6 != nil { + return ip6, nil + } + } else { + if ip4 := ip.To4(); ip4 != nil { + return ip4, nil + } + } + query := &D.Msg{} query.SetQuestion(D.Fqdn(host), dnsType) @@ -282,7 +276,6 @@ type Config struct { Main, Fallback []NameServer IPv6 bool EnhancedMode EnhancedMode - Hosts *trie.Trie Pool *fakeip.Pool } @@ -297,7 +290,6 @@ func New(config Config) *Resolver { cache: cache.New(time.Second * 60), mapping: config.EnhancedMode == MAPPING, fakeip: config.EnhancedMode == FAKEIP, - hosts: config.Hosts, pool: config.Pool, } if len(config.Fallback) != 0 { diff --git a/hub/executor/executor.go b/hub/executor/executor.go index b00ae2d411..2883d5fcf0 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -2,6 +2,7 @@ package executor import ( "github.com/Dreamacro/clash/component/auth" + trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -30,6 +31,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateProxies(cfg.Proxies) updateRules(cfg.Rules) updateDNS(cfg.DNS) + updateHosts(cfg.Hosts) updateExperimental(cfg.Experimental) } @@ -68,7 +70,6 @@ func updateDNS(c *config.DNS) { Main: c.NameServer, Fallback: c.Fallback, IPv6: c.IPv6, - Hosts: c.Hosts, EnhancedMode: c.EnhancedMode, Pool: c.FakeIPRange, }) @@ -83,6 +84,10 @@ func updateDNS(c *config.DNS) { } } +func updateHosts(tree *trie.Trie) { + dns.DefaultHosts = tree +} + func updateProxies(proxies map[string]C.Proxy) { tunnel := T.Instance() oldProxies := tunnel.Proxies() diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 837374ad01..87aafec323 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -213,6 +213,13 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { defer t.configMux.RUnlock() var resolved bool + + if node := dns.DefaultHosts.Search(metadata.Host); node != nil { + ip := node.Data.(net.IP) + metadata.DstIP = &ip + resolved = true + } + for _, rule := range t.rules { if !resolved && t.shouldResolveIP(rule, metadata) { ip, err := t.resolveIP(metadata.Host) From 09917a2a9519d1acd42b2e1b4031d169eb50203e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 14 Sep 2019 20:00:40 +0800 Subject: [PATCH 240/535] Fix: tcp dual stack dial --- adapters/outbound/util.go | 72 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 0cccf5d9df..b3670b3e52 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -2,6 +2,7 @@ package adapters import ( "bytes" + "context" "crypto/tls" "fmt" "net" @@ -104,12 +105,75 @@ func dialTimeout(network, address string, timeout time.Duration) (net.Conn, erro return nil, err } - ip, err := dns.ResolveIP(host) - if err != nil { - return nil, err + dialer := net.Dialer{Timeout: timeout} + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + returned := make(chan struct{}) + defer close(returned) + + type dialResult struct { + net.Conn + error + ipv6 bool + done bool } + results := make(chan dialResult) + var primary, fallback dialResult + + startRacer := func(ctx context.Context, host string, ipv6 bool) { + var err error + + var ip net.IP + if ipv6 { + ip, err = dns.ResolveIPv6(host) + } else { + ip, err = dns.ResolveIPv4(host) + } + if err != nil { + return + } - return net.DialTimeout(network, net.JoinHostPort(ip.String(), port), timeout) + var c net.Conn + if ipv6 { + c, err = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port)) + } else { + c, err = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) + } + if err != nil { + return + } + + select { + case results <- dialResult{Conn: c, error: err, ipv6: ipv6}: + case <-returned: + if c != nil { + c.Close() + } + } + } + + go startRacer(ctx, host, false) + go startRacer(ctx, host, true) + + for { + select { + case res := <-results: + if res.error == nil { + return res.Conn, nil + } + + if res.ipv6 { + primary = res + } else { + fallback = res + } + + if primary.done && fallback.done { + return nil, primary.error + } + } + } } func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { From 3dd9ea52d8ecd11feabc81f21c88f15bb6db3344 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 14 Sep 2019 21:42:40 +0800 Subject: [PATCH 241/535] Fix(domain-trie): crash when insert --- component/domain-trie/tire.go | 2 +- component/domain-trie/trie_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/component/domain-trie/tire.go b/component/domain-trie/tire.go index 26062fc2e2..f4e87ff5e4 100644 --- a/component/domain-trie/tire.go +++ b/component/domain-trie/tire.go @@ -22,7 +22,7 @@ type Trie struct { } func isValidDomain(domain string) bool { - return domain[0] != '.' && domain[len(domain)-1] != '.' + return domain != "" && domain[0] != '.' && domain[len(domain)-1] != '.' } // Insert adds a node to the trie. diff --git a/component/domain-trie/trie_test.go b/component/domain-trie/trie_test.go index 5303596bc4..228106cd9a 100644 --- a/component/domain-trie/trie_test.go +++ b/component/domain-trie/trie_test.go @@ -26,6 +26,10 @@ func TestTrie_Basic(t *testing.T) { if !node.Data.(net.IP).Equal(localIP) { t.Error("should equal 127.0.0.1") } + + if tree.Insert("", localIP) == nil { + t.Error("should return error") + } } func TestTrie_Wildcard(t *testing.T) { From 09f435d928692546a15458619ef5ccba342710bf Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 14 Sep 2019 21:45:11 +0800 Subject: [PATCH 242/535] Chore: update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd6281b648..9f692a7ab8 100644 --- a/README.md +++ b/README.md @@ -120,18 +120,18 @@ experimental: # - "user1:pass1" # - "user2:pass2" +# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) +# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com) +# hosts: +# '*.clash.dev': 127.0.0.1 +# 'alpha.clash.dev': '::1' + # dns: # enable: true # set true to enable dns (default is false) # ipv6: false # default is false # listen: 0.0.0.0:53 # enhanced-mode: redir-host # or fake-ip # # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it - # # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) - # # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com) - # # NOTE: hosts don't work with `fake-ip` - # hosts: - # '*.clash.dev': 127.0.0.1 - # 'alpha.clash.dev': '::1' # nameserver: # - 114.114.114.114 # - tls://dns.rubyfish.cn:853 # dns over tls From b76737bdbbce7cb2e6b380e33b77e5cb37c70c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E8=BE=B0=E6=96=87?= Date: Sun, 15 Sep 2019 13:36:45 +0800 Subject: [PATCH 243/535] Feature: add fallback filters (#105) --- README.md | 4 +++ config/config.go | 67 +++++++++++++++++++++++++++++++--------- dns/filters.go | 26 ++++++++++++++++ dns/resolver.go | 58 +++++++++++++++++++++++----------- hub/executor/executor.go | 4 +++ 5 files changed, 127 insertions(+), 32 deletions(-) create mode 100644 dns/filters.go diff --git a/README.md b/README.md index 9f692a7ab8..1af142fa3f 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,10 @@ experimental: # - https://1.1.1.1/dns-query # dns over https # fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN # - tcp://1.1.1.1 + # fallback-filter: + # geoip: true # default + # ipcidr: # ips in these subnets will be considered polluted + # - 240.0.0.0/4 Proxy: diff --git a/config/config.go b/config/config.go index 6d0942ddb9..1644511116 100644 --- a/config/config.go +++ b/config/config.go @@ -40,13 +40,20 @@ type General struct { // DNS config type DNS struct { - Enable bool `yaml:"enable"` - IPv6 bool `yaml:"ipv6"` - NameServer []dns.NameServer `yaml:"nameserver"` - Fallback []dns.NameServer `yaml:"fallback"` - Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` - FakeIPRange *fakeip.Pool + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []dns.NameServer `yaml:"nameserver"` + Fallback []dns.NameServer `yaml:"fallback"` + FallbackFilter FallbackFilter `yaml:"fallback-filter"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + FakeIPRange *fakeip.Pool +} + +// FallbackFilter config +type FallbackFilter struct { + GeoIP bool `yaml:"geoip"` + IPCIDR []*net.IPNet `yaml:"ipcidr"` } // Experimental config @@ -66,13 +73,19 @@ type Config struct { } type rawDNS struct { - Enable bool `yaml:"enable"` - IPv6 bool `yaml:"ipv6"` - NameServer []string `yaml:"nameserver"` - Fallback []string `yaml:"fallback"` - Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` - FakeIPRange string `yaml:"fake-ip-range"` + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []string `yaml:"nameserver"` + Fallback []string `yaml:"fallback"` + FallbackFilter rawFallbackFilter `yaml:"fallback-filter"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + FakeIPRange string `yaml:"fake-ip-range"` +} + +type rawFallbackFilter struct { + GeoIP bool `yaml:"geoip"` + IPCIDR []string `yaml:"ipcidr"` } type rawConfig struct { @@ -145,6 +158,10 @@ func readConfig(path string) (*rawConfig, error) { DNS: rawDNS{ Enable: false, FakeIPRange: "198.18.0.1/16", + FallbackFilter: rawFallbackFilter{ + GeoIP: true, + IPCIDR: []string{}, + }, }, } err = yaml.Unmarshal([]byte(data), &rawConfig) @@ -545,6 +562,20 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { return nameservers, nil } +func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { + ipNets := []*net.IPNet{} + + for idx, ip := range ips { + _, ipnet, err := net.ParseCIDR(ip) + if err != nil { + return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error()) + } + ipNets = append(ipNets, ipnet) + } + + return ipNets, nil +} + func parseDNS(cfg rawDNS) (*DNS, error) { if cfg.Enable && len(cfg.NameServer) == 0 { return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") @@ -555,6 +586,9 @@ func parseDNS(cfg rawDNS) (*DNS, error) { Listen: cfg.Listen, IPv6: cfg.IPv6, EnhancedMode: cfg.EnhancedMode, + FallbackFilter: FallbackFilter{ + IPCIDR: []*net.IPNet{}, + }, } var err error if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil { @@ -578,6 +612,11 @@ func parseDNS(cfg rawDNS) (*DNS, error) { dnsCfg.FakeIPRange = pool } + dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP + if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil { + dnsCfg.FallbackFilter.IPCIDR = fallbackip + } + return dnsCfg, nil } diff --git a/dns/filters.go b/dns/filters.go new file mode 100644 index 0000000000..abb99f4771 --- /dev/null +++ b/dns/filters.go @@ -0,0 +1,26 @@ +package dns + +import "net" + +type fallbackFilter interface { + Match(net.IP) bool +} + +type geoipFilter struct{} + +func (gf *geoipFilter) Match(ip net.IP) bool { + if mmdb == nil { + return false + } + + record, _ := mmdb.Country(ip) + return record.Country.IsoCode == "CN" || record.Country.IsoCode == "" +} + +type ipnetFilter struct { + ipnet *net.IPNet +} + +func (inf *ipnetFilter) Match(ip net.IP) bool { + return inf.ipnet.Contains(ip) +} diff --git a/dns/resolver.go b/dns/resolver.go index 6ed6576496..a9cd283e39 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -46,14 +46,15 @@ type result struct { } type Resolver struct { - ipv6 bool - mapping bool - fakeip bool - pool *fakeip.Pool - fallback []resolver - main []resolver - group singleflight.Group - cache *cache.Cache + ipv6 bool + mapping bool + fakeip bool + pool *fakeip.Pool + main []resolver + fallback []resolver + fallbackFilters []fallbackFilter + group singleflight.Group + cache *cache.Cache } // ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA @@ -94,6 +95,15 @@ func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) { return r.resolveIP(host, D.TypeAAAA) } +func (r *Resolver) shouldFallback(ip net.IP) bool { + for _, filter := range r.fallbackFilters { + if filter.Match(ip) { + return true + } + } + return false +} + // Exchange a batch of dns request, and it use cache func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { if len(m.Question) == 0 { @@ -195,13 +205,8 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { fallbackMsg := r.asyncExchange(r.fallback, m) res := <-msgCh if res.Error == nil { - if mmdb == nil { - return nil, errors.New("GeoIP cannot use") - } - if ips := r.msgToIP(res.Msg); len(ips) != 0 { - if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" { - // release channel + if r.shouldFallback(ips[0]) { go func() { <-fallbackMsg }() msg = res.Msg return msg, err @@ -272,18 +277,20 @@ type NameServer struct { Addr string } +type FallbackFilter struct { + GeoIP bool + IPCIDR []*net.IPNet +} + type Config struct { Main, Fallback []NameServer IPv6 bool EnhancedMode EnhancedMode + FallbackFilter FallbackFilter Pool *fakeip.Pool } func New(config Config) *Resolver { - once.Do(func() { - mmdb, _ = geoip2.Open(C.Path.MMDB()) - }) - r := &Resolver{ ipv6: config.IPv6, main: transform(config.Main), @@ -292,8 +299,23 @@ func New(config Config) *Resolver { fakeip: config.EnhancedMode == FAKEIP, pool: config.Pool, } + if len(config.Fallback) != 0 { r.fallback = transform(config.Fallback) } + + fallbackFilters := []fallbackFilter{} + if config.FallbackFilter.GeoIP { + once.Do(func() { + mmdb, _ = geoip2.Open(C.Path.MMDB()) + }) + + fallbackFilters = append(fallbackFilters, &geoipFilter{}) + } + for _, ipnet := range config.FallbackFilter.IPCIDR { + fallbackFilters = append(fallbackFilters, &ipnetFilter{ipnet: ipnet}) + } + r.fallbackFilters = fallbackFilters + return r } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 2883d5fcf0..321d68304b 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -72,6 +72,10 @@ func updateDNS(c *config.DNS) { IPv6: c.IPv6, EnhancedMode: c.EnhancedMode, Pool: c.FakeIPRange, + FallbackFilter: dns.FallbackFilter{ + GeoIP: c.FallbackFilter.GeoIP, + IPCIDR: c.FallbackFilter.IPCIDR, + }, }) dns.DefaultResolver = r if err := dns.ReCreateServer(c.Listen, r); err != nil { From 8adcc4d83b487c6ba143a5f162d26b113a8ae26f Mon Sep 17 00:00:00 2001 From: Comzyh Date: Tue, 17 Sep 2019 20:11:49 +0800 Subject: [PATCH 244/535] Fix: TCP dial error should not return early (#307) --- adapters/outbound/util.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index b3670b3e52..3b1731195e 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -140,12 +140,9 @@ func dialTimeout(network, address string, timeout time.Duration) (net.Conn, erro } else { c, err = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) } - if err != nil { - return - } select { - case results <- dialResult{Conn: c, error: err, ipv6: ipv6}: + case results <- dialResult{Conn: c, error: err, ipv6: ipv6, done: true}: case <-returned: if c != nil { c.Close() From 5e6ab99403f1206f263fe91277ff5e2979318e3d Mon Sep 17 00:00:00 2001 From: comwrg Date: Fri, 20 Sep 2019 16:04:06 +0800 Subject: [PATCH 245/535] Fix: dial should return when dns failed (#311) --- adapters/outbound/util.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 3b1731195e..7922991674 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -105,7 +105,7 @@ func dialTimeout(network, address string, timeout time.Duration) (net.Conn, erro return nil, err } - dialer := net.Dialer{Timeout: timeout} + dialer := net.Dialer{} ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -122,31 +122,31 @@ func dialTimeout(network, address string, timeout time.Duration) (net.Conn, erro var primary, fallback dialResult startRacer := func(ctx context.Context, host string, ipv6 bool) { - var err error + result := dialResult{ipv6: ipv6, done: true} + defer func() { + select { + case results <- result: + case <-returned: + if result.Conn != nil { + result.Conn.Close() + } + } + }() var ip net.IP if ipv6 { - ip, err = dns.ResolveIPv6(host) + ip, result.error = dns.ResolveIPv6(host) } else { - ip, err = dns.ResolveIPv4(host) + ip, result.error = dns.ResolveIPv4(host) } - if err != nil { + if result.error != nil { return } - var c net.Conn if ipv6 { - c, err = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port)) + result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port)) } else { - c, err = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) - } - - select { - case results <- dialResult{Conn: c, error: err, ipv6: ipv6, done: true}: - case <-returned: - if c != nil { - c.Close() - } + result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) } } From 8f60d61ff910f4f09703aef29ffa56f28a74f34c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 21 Sep 2019 10:30:43 +0800 Subject: [PATCH 246/535] Fix(fake-ip): return failed when type is AAAA --- dns/middleware.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dns/middleware.go b/dns/middleware.go index 6d30f8d643..bd0aa7d9fe 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -16,7 +16,11 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { return func(next handler) handler { return func(w D.ResponseWriter, r *D.Msg) { q := r.Question[0] - if q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA { + + if q.Qtype == D.TypeAAAA { + D.HandleFailed(w, r) + return + } else if q.Qtype != D.TypeA { next(w, r) return } From e0c8aed5c71ffb6969fd23181607f3f883fbae61 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 21 Sep 2019 21:28:02 +0800 Subject: [PATCH 247/535] Fix(API): cors middleware hoisting because it doesn't work with r.Group --- hub/route/server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hub/route/server.go b/hub/route/server.go index a1bbcde985..7736a3c9c4 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -52,9 +52,10 @@ func Start(addr string, secret string) { MaxAge: 300, }) + r.Use(cors.Handler) r.Get("/", hello) r.Group(func(r chi.Router) { - r.Use(cors.Handler, authentication) + r.Use(authentication) r.Get("/logs", getLogs) r.Get("/traffic", traffic) From 1a8a6d0b5d28161069c23560464be9f1e0eb6fd7 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 21 Sep 2019 23:49:00 +0800 Subject: [PATCH 248/535] Feature: v2ray-plugin support disable mux --- README.md | 1 + adapters/outbound/shadowsocks.go | 22 ++++++++++++---------- component/v2ray-plugin/websocket.go | 22 +++++++++++++--------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 1af142fa3f..63724d2662 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,7 @@ Proxy: # skip-cert-verify: true # host: bing.com # path: "/" + # mux: true # headers: # custom: value diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index a958191f27..58c76bc981 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -23,9 +23,9 @@ type ShadowSocks struct { cipher core.Cipher // obfs - obfsMode string - obfsOption *simpleObfsOption - wsOption *v2rayObfs.WebsocketOption + obfsMode string + obfsOption *simpleObfsOption + v2rayOption *v2rayObfs.Option } type ShadowSocksOption struct { @@ -55,6 +55,7 @@ type v2rayObfsOption struct { TLS bool `obfs:"tls,omitempty"` Headers map[string]string `obfs:"headers,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` + Mux bool `obfs:"mux,omitempty"` } func (ss *ShadowSocks) Dial(metadata *C.Metadata) (C.Conn, error) { @@ -71,7 +72,7 @@ func (ss *ShadowSocks) Dial(metadata *C.Metadata) (C.Conn, error) { c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) case "websocket": var err error - c, err = v2rayObfs.NewWebsocketObfs(c, ss.wsOption) + c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption) if err != nil { return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) } @@ -116,7 +117,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) } - var wsOption *v2rayObfs.WebsocketOption + var v2rayOption *v2rayObfs.Option var obfsOption *simpleObfsOption obfsMode := "" @@ -144,7 +145,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { obfsMode = opts.Mode obfsOption = &opts } else if option.Plugin == "v2ray-plugin" { - opts := v2rayObfsOption{Host: "bing.com"} + opts := v2rayObfsOption{Host: "bing.com", Mux: true} if err := decoder.Decode(option.PluginOpts, &opts); err != nil { return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error()) } @@ -162,11 +163,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { ClientSessionCache: getClientSessionCache(), } } - wsOption = &v2rayObfs.WebsocketOption{ + v2rayOption = &v2rayObfs.Option{ Host: opts.Host, Path: opts.Path, Headers: opts.Headers, TLSConfig: tlsConfig, + Mux: opts.Mux, } } @@ -179,9 +181,9 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { server: server, cipher: ciph, - obfsMode: obfsMode, - wsOption: wsOption, - obfsOption: obfsOption, + obfsMode: obfsMode, + v2rayOption: v2rayOption, + obfsOption: obfsOption, }, nil } diff --git a/component/v2ray-plugin/websocket.go b/component/v2ray-plugin/websocket.go index 96909aff1d..df6d05da2f 100644 --- a/component/v2ray-plugin/websocket.go +++ b/component/v2ray-plugin/websocket.go @@ -8,16 +8,17 @@ import ( "github.com/Dreamacro/clash/component/vmess" ) -// WebsocketOption is options of websocket obfs -type WebsocketOption struct { +// Option is options of websocket obfs +type Option struct { Host string Path string Headers map[string]string TLSConfig *tls.Config + Mux bool } -// NewWebsocketObfs return a HTTPObfs -func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) { +// NewV2rayObfs return a HTTPObfs +func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { header := http.Header{} for k, v := range option.Headers { header.Add(k, v) @@ -36,10 +37,13 @@ func NewWebsocketObfs(conn net.Conn, option *WebsocketOption) (net.Conn, error) if err != nil { return nil, err } - conn = NewMux(conn, MuxOption{ - ID: [2]byte{0, 0}, - Host: "127.0.0.1", - Port: 0, - }) + + if option.Mux { + conn = NewMux(conn, MuxOption{ + ID: [2]byte{0, 0}, + Host: "127.0.0.1", + Port: 0, + }) + } return conn, nil } From 904c354ee4c45a25849e7b821f0ae2ff83cf76a0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 26 Sep 2019 10:08:50 +0800 Subject: [PATCH 249/535] Fix: use correctly last record --- adapters/outbound/base.go | 6 +++--- common/queue/queue.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 710afad070..b560fc7197 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -114,11 +114,11 @@ func (p *Proxy) LastDelay() (delay uint16) { return max } - head := p.history.First() - if head == nil { + last := p.history.Last() + if last == nil { return max } - history := head.(C.DelayHistory) + history := last.(C.DelayHistory) if history.Delay == 0 { return max } diff --git a/common/queue/queue.go b/common/queue/queue.go index 7cb51b8513..f1a0c6fcae 100644 --- a/common/queue/queue.go +++ b/common/queue/queue.go @@ -34,16 +34,16 @@ func (q *Queue) Pop() interface{} { return head } -// First returns the head of items without deleting. -func (q *Queue) First() interface{} { +// Last returns the last of item. +func (q *Queue) Last() interface{} { if len(q.items) == 0 { return nil } q.lock.RLock() - head := q.items[0] + last := q.items[len(q.items)-1] q.lock.RUnlock() - return head + return last } // Copy get the copy of queue. From 045c3a3ad4d6b50bab47818ef4f3e24166347529 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Fri, 27 Sep 2019 10:33:37 +0800 Subject: [PATCH 250/535] Fix: clearer error and ipv6 string parse (#325) --- adapters/outbound/util.go | 16 ++++++++++++---- dns/iputil.go | 24 ++++++++++++++++-------- dns/resolver.go | 12 +++++------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 7922991674..8d61796859 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -115,8 +115,9 @@ func dialTimeout(network, address string, timeout time.Duration) (net.Conn, erro type dialResult struct { net.Conn error - ipv6 bool - done bool + resolved bool + ipv6 bool + done bool } results := make(chan dialResult) var primary, fallback dialResult @@ -142,6 +143,7 @@ func dialTimeout(network, address string, timeout time.Duration) (net.Conn, erro if result.error != nil { return } + result.resolved = true if ipv6 { result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port)) @@ -160,14 +162,20 @@ func dialTimeout(network, address string, timeout time.Duration) (net.Conn, erro return res.Conn, nil } - if res.ipv6 { + if !res.ipv6 { primary = res } else { fallback = res } if primary.done && fallback.done { - return nil, primary.error + if primary.resolved { + return nil, primary.error + } else if fallback.resolved { + return nil, fallback.error + } else { + return nil, primary.error + } } } } diff --git a/dns/iputil.go b/dns/iputil.go index 66dd0c0edc..7967b44aa9 100644 --- a/dns/iputil.go +++ b/dns/iputil.go @@ -3,10 +3,12 @@ package dns import ( "errors" "net" + "strings" ) var ( errIPNotFound = errors.New("cannot found ip") + errIPVersion = errors.New("ip version error") ) // ResolveIPv4 with a host, return ipv4 @@ -18,8 +20,11 @@ func ResolveIPv4(host string) (net.IP, error) { } ip := net.ParseIP(host) - if ip4 := ip.To4(); ip4 != nil { - return ip4, nil + if ip != nil { + if !strings.Contains(host, ":") { + return ip, nil + } + return nil, errIPVersion } if DefaultResolver != nil { @@ -32,8 +37,8 @@ func ResolveIPv4(host string) (net.IP, error) { } for _, ip := range ipAddrs { - if ip4 := ip.To4(); ip4 != nil { - return ip4, nil + if len(ip) == net.IPv4len { + return ip, nil } } @@ -49,8 +54,11 @@ func ResolveIPv6(host string) (net.IP, error) { } ip := net.ParseIP(host) - if ip6 := ip.To16(); ip6 != nil { - return ip6, nil + if ip != nil { + if strings.Contains(host, ":") { + return ip, nil + } + return nil, errIPVersion } if DefaultResolver != nil { @@ -63,8 +71,8 @@ func ResolveIPv6(host string) (net.IP, error) { } for _, ip := range ipAddrs { - if ip6 := ip.To16(); ip6 != nil { - return ip6, nil + if len(ip) == net.IPv6len { + return ip, nil } } diff --git a/dns/resolver.go b/dns/resolver.go index a9cd283e39..0ba2342053 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -221,13 +221,11 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) { ip = net.ParseIP(host) - if dnsType == D.TypeAAAA { - if ip6 := ip.To16(); ip6 != nil { - return ip6, nil - } - } else { - if ip4 := ip.To4(); ip4 != nil { - return ip4, nil + if ip != nil { + if dnsType == D.TypeAAAA && len(ip) == net.IPv6len { + return ip, nil + } else if dnsType == D.TypeA && len(ip) == net.IPv4len { + return ip, nil } } From c38469330dac9f2dd23f76a234957a51808e17db Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 27 Sep 2019 15:26:07 +0800 Subject: [PATCH 251/535] Fix: ip version check --- dns/iputil.go | 6 +++--- dns/resolver.go | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dns/iputil.go b/dns/iputil.go index 7967b44aa9..3c34cf1655 100644 --- a/dns/iputil.go +++ b/dns/iputil.go @@ -37,8 +37,8 @@ func ResolveIPv4(host string) (net.IP, error) { } for _, ip := range ipAddrs { - if len(ip) == net.IPv4len { - return ip, nil + if ip4 := ip.To4(); ip4 != nil { + return ip4, nil } } @@ -71,7 +71,7 @@ func ResolveIPv6(host string) (net.IP, error) { } for _, ip := range ipAddrs { - if len(ip) == net.IPv6len { + if ip.To4() == nil { return ip, nil } } diff --git a/dns/resolver.go b/dns/resolver.go index 0ba2342053..2276f789f1 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -222,9 +222,10 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) { ip = net.ParseIP(host) if ip != nil { - if dnsType == D.TypeAAAA && len(ip) == net.IPv6len { + isIPv4 := ip.To4() != nil + if dnsType == D.TypeAAAA && !isIPv4 { return ip, nil - } else if dnsType == D.TypeA && len(ip) == net.IPv4len { + } else if dnsType == D.TypeA && isIPv4 { return ip, nil } } From 50d2e082d55d8205b1eced3e83976a7d6f541dbc Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 30 Sep 2019 14:13:29 +0800 Subject: [PATCH 252/535] Feature: websocket api support browser --- hub/route/server.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/hub/route/server.go b/hub/route/server.go index 7736a3c9c4..394ad40636 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -23,7 +23,11 @@ var ( uiPath = "" - upgrader = websocket.Upgrader{} + upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } ) type Traffic struct { @@ -84,14 +88,26 @@ func Start(addr string, secret string) { func authentication(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { - header := r.Header.Get("Authorization") - text := strings.SplitN(header, " ", 2) - if serverSecret == "" { next.ServeHTTP(w, r) return } + // Browser websocket not support custom header + if websocket.IsWebSocketUpgrade(r) && r.URL.Query().Get("token") != "" { + token := r.URL.Query().Get("token") + if token != serverSecret { + render.Status(r, http.StatusUnauthorized) + render.JSON(w, r, ErrUnauthorized) + return + } + next.ServeHTTP(w, r) + return + } + + header := r.Header.Get("Authorization") + text := strings.SplitN(header, " ", 2) + hasUnvalidHeader := text[0] != "Bearer" hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret if hasUnvalidHeader || hasUnvalidSecret { From d3c50cf89fb74f02e91211226009a2cd924a72ec Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 5 Oct 2019 09:33:40 +0800 Subject: [PATCH 253/535] Fix: throw error when CONNECT return 5xx --- adapters/outbound/http.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 9b3791f048..12b80a5b17 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -81,17 +81,21 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { return err } - if resp.StatusCode == 200 { + if resp.StatusCode == http.StatusOK { return nil } - if resp.StatusCode == 407 { + if resp.StatusCode == http.StatusProxyAuthRequired { return errors.New("HTTP need auth") } - if resp.StatusCode == 405 { + if resp.StatusCode == http.StatusMethodNotAllowed { return errors.New("CONNECT method not allowed by proxy") } + + if resp.StatusCode >= http.StatusInternalServerError { + return errors.New(resp.Status) + } return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) } From 54386ccda39decfda7ad417fc689bf4ab673af1b Mon Sep 17 00:00:00 2001 From: Birkhoff Lee Date: Tue, 8 Oct 2019 10:59:24 +0800 Subject: [PATCH 254/535] Chore: Improve grammar and wording (#337) --- README.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 63724d2662..6f60bcfef3 100644 --- a/README.md +++ b/README.md @@ -20,40 +20,39 @@ ## Features -- HTTP/HTTPS and SOCKS protocol -- Surge like configuration +- HTTP, HTTPS and SOCKS protocol +- Surge-like configuration format - GeoIP rule support -- Support Vmess/Shadowsocks/Socks5 -- Support for Netfilter TCP redirect +- Supports Vmess, Shadowsocks and SOCKS5 +- Supports Netfilter TCP redirecting +- Comprehensive API ## Install -You can build from source: +Clash Requires Go >= 1.13. You can build it from source: ```sh -go get -u -v github.com/Dreamacro/clash +$ go get -u -v github.com/Dreamacro/clash ``` -Pre-built binaries are available: [release](https://github.com/Dreamacro/clash/releases) +Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) -Requires Go >= 1.13. - -Checkout Clash version: +Check Clash version with: ```sh -clash -v +$ clash -v ``` ## Daemon -Unfortunately, there is no native elegant way to implement golang's daemon. +Unfortunately, there is no native and elegant way to implement daemons on Golang. -So we can use third-party daemon tools like pm2, supervisor, and so on. +So we can use third-party daemon tools like PM2, Supervisor or the like. In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way: ```sh -pm2 start clash +$ pm2 start clash ``` If you have Docker installed, you can run clash directly using `docker-compose`. @@ -62,19 +61,19 @@ If you have Docker installed, you can run clash directly using `docker-compose`. ## Config -The default configuration directory is `$HOME/.config/clash` +The default configuration directory is `$HOME/.config/clash`. -The name of the configuration file is `config.yaml` +The name of the configuration file is `config.yaml`. -If you want to use another directory, you can use `-d` to control the configuration directory +If you want to use another directory, use `-d` to control the configuration directory. -For example, you can use the current directory as the configuration directory +For example, you can use the current directory as the configuration directory: ```sh -clash -d . +$ clash -d . ``` -Below is a simple demo configuration file: +Below is an example configuration file: ```yml # port of HTTP @@ -101,7 +100,7 @@ mode: Rule # info / warning / error / debug / silent log-level: info -# A RESTful API for clash +# RESTful API for clash external-controller: 127.0.0.1:9090 # you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui` From 06c9dfdb8000d4bcfc1b745a8ae17240dec7366d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 9 Oct 2019 18:46:23 +0800 Subject: [PATCH 255/535] Feature: experimental support snell --- README.md | 115 +++++++++++++++++++++++++++---------- adapters/outbound/snell.go | 71 +++++++++++++++++++++++ component/snell/cipher.go | 21 +++++++ component/snell/snell.go | 91 +++++++++++++++++++++++++++++ config/config.go | 7 +++ constant/adapters.go | 3 + 6 files changed, 277 insertions(+), 31 deletions(-) create mode 100644 adapters/outbound/snell.go create mode 100644 component/snell/cipher.go create mode 100644 component/snell/snell.go diff --git a/README.md b/README.md index 6f60bcfef3..ab44890f67 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,8 @@ For example, you can use the current directory as the configuration directory: $ clash -d . ``` -Below is an example configuration file: +
+ This is an example configuration file ```yml # port of HTTP @@ -91,7 +92,7 @@ allow-lan: false # "*": bind all IP addresses # 192.168.122.11: bind a single IPv4 address # "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address -bind-address: "*" +# bind-address: "*" # Rule / Global/ Direct (default is Rule) mode: Rule @@ -151,9 +152,15 @@ Proxy: # aes-128-ctr aes-192-ctr aes-256-ctr # rc4-md5 chacha20 chacha20-ietf xchacha20 # chacha20-ietf-poly1305 xchacha20-ietf-poly1305 -- { name: "ss1", type: ss, server: server, port: 443, cipher: chacha20-ietf-poly1305, password: "password", udp: true } +- name: "ss1" + type: ss + server: server + port: 443 + cipher: chacha20-ietf-poly1305 + password: "password" + # udp: true -# old obfs configuration remove after prerelease +# old obfs configuration format remove after prerelease - name: "ss2" type: ss server: server @@ -184,47 +191,92 @@ Proxy: # vmess # cipher support auto/aes-128-gcm/chacha20-poly1305/none -- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto } -# with tls -- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true } -# with tls and skip-cert-verify -- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, tls: true, skip-cert-verify: true } -# with ws-path and ws-headers -- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, ws-headers: { Host: v2ray.com } } -# with ws + tls -- { name: "vmess", type: vmess, server: server, port: 443, uuid: uuid, alterId: 32, cipher: auto, network: ws, ws-path: /path, tls: true } +- name: "vmess" + type: vmess + server: server + port: 443 + uuid: uuid + alterId: 32 + cipher: auto + # udp: true + # tls: true + # skip-cert-verify: true + # network: ws + # ws-path: /path + # ws-headers: + # Host: v2ray.com # socks5 -- { name: "socks", type: socks5, server: server, port: 443 } -# socks5 with authentication -- { name: "socks", type: socks5, server: server, port: 443, username: "username", password: "password" } -# with tls -- { name: "socks", type: socks5, server: server, port: 443, tls: true } -# with tls and skip-cert-verify -- { name: "socks", type: socks5, server: server, port: 443, tls: true, skip-cert-verify: true } +- name: "socks" + type: socks5 + server: server + port: 443 + # username: username + # password: password + # tls: true + # skip-cert-verify: true + # udp: true # http -- { name: "http", type: http, server: server, port: 443 } -# http with authentication -- { name: "http", type: http, server: server, port: 443, username: "username", password: "password" } -# with tls (https) -- { name: "http", type: http, server: server, port: 443, tls: true } -# with tls (https) and skip-cert-verify -- { name: "http", type: http, server: server, port: 443, tls: true, skip-cert-verify: true } +- name: "http" + type: http + server: server + port: 443 + # username: username + # password: password + # tls: true # https + # skip-cert-verify: true + +# snell +- name: "snell" + type: snell + server: server + port: 44046 + psk: yourpsk + # obfs-opts: + # mode: http # or tls + # host: bing.com Proxy Group: # url-test select which proxy will be used by benchmarking speed to a URL. -- { name: "auto", type: url-test, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } +- name: "auto" + type: url-test + proxies: + - ss1 + - ss2 + - vmess1 + url: 'http://www.gstatic.com/generate_204' + interval: 300 # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. -- { name: "fallback-auto", type: fallback, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } +- name: "fallback-auto" + type: fallback + proxies: + - ss1 + - ss2 + - vmess1 + url: 'http://www.gstatic.com/generate_204' + interval: 300 # load-balance: The request of the same eTLD will be dial on the same proxy. -- { name: "load-balance", type: load-balance, proxies: ["ss1", "ss2", "vmess1"], url: "http://www.gstatic.com/generate_204", interval: 300 } +- name: "load-balance" + type: load-balance + proxies: + - ss1 + - ss2 + - vmess1 + url: 'http://www.gstatic.com/generate_204' + interval: 300 # select is used for selecting proxy or proxy group # you can use RESTful API to switch proxy, is recommended for use in GUI. -- { name: "Proxy", type: select, proxies: ["ss1", "ss2", "vmess1", "auto"] } +- name: Proxy + type: select + proxies: + - ss1 + - ss2 + - vmess1 + - auto Rule: - DOMAIN-SUFFIX,google.com,auto @@ -241,6 +293,7 @@ Rule: # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now - MATCH,auto ``` +
## Documentations https://clash.gitbook.io/ diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go new file mode 100644 index 0000000000..b4131199b9 --- /dev/null +++ b/adapters/outbound/snell.go @@ -0,0 +1,71 @@ +package adapters + +import ( + "fmt" + "net" + "strconv" + + "github.com/Dreamacro/clash/common/structure" + obfs "github.com/Dreamacro/clash/component/simple-obfs" + "github.com/Dreamacro/clash/component/snell" + C "github.com/Dreamacro/clash/constant" +) + +type Snell struct { + *Base + server string + psk []byte + obfsOption *simpleObfsOption +} + +type SnellOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Psk string `proxy:"psk"` + ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` +} + +func (s *Snell) Dial(metadata *C.Metadata) (C.Conn, error) { + c, err := dialTimeout("tcp", s.server, tcpTimeout) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", s.server, err.Error()) + } + tcpKeepAlive(c) + switch s.obfsOption.Mode { + case "tls": + c = obfs.NewTLSObfs(c, s.obfsOption.Host) + case "http": + _, port, _ := net.SplitHostPort(s.server) + c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port) + } + c = snell.StreamConn(c, s.psk) + port, _ := strconv.Atoi(metadata.DstPort) + err = snell.WriteHeader(c, metadata.String(), uint(port)) + return newConn(c, s), err +} + +func NewSnell(option SnellOption) (*Snell, error) { + server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + psk := []byte(option.Psk) + + decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) + obfsOption := &simpleObfsOption{Host: "bing.com"} + if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { + return nil, fmt.Errorf("snell %s initialize obfs error: %s", server, err.Error()) + } + + if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { + return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode) + } + + return &Snell{ + Base: &Base{ + name: option.Name, + tp: C.Snell, + }, + server: server, + psk: psk, + obfsOption: obfsOption, + }, nil +} diff --git a/component/snell/cipher.go b/component/snell/cipher.go new file mode 100644 index 0000000000..f66f0801ce --- /dev/null +++ b/component/snell/cipher.go @@ -0,0 +1,21 @@ +package snell + +import ( + "crypto/cipher" + + "golang.org/x/crypto/argon2" +) + +type snellCipher struct { + psk []byte + makeAEAD func(key []byte) (cipher.AEAD, error) +} + +func (sc *snellCipher) KeySize() int { return 32 } +func (sc *snellCipher) SaltSize() int { return 16 } +func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { + return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) +} +func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { + return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) +} diff --git a/component/snell/snell.go b/component/snell/snell.go new file mode 100644 index 0000000000..6b40b383d1 --- /dev/null +++ b/component/snell/snell.go @@ -0,0 +1,91 @@ +package snell + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net" + "sync" + + "github.com/Dreamacro/go-shadowsocks2/shadowaead" + "golang.org/x/crypto/chacha20poly1305" +) + +const ( + CommandPing byte = 0 + CommandConnect byte = 1 + + CommandTunnel byte = 0 + CommandError byte = 2 + + Version byte = 1 +) + +var ( + bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} +) + +type Snell struct { + net.Conn + buffer [1]byte + reply bool +} + +func (s *Snell) Read(b []byte) (int, error) { + if s.reply { + return s.Conn.Read(b) + } + + s.reply = true + if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { + return 0, err + } + + if s.buffer[0] == CommandTunnel { + return s.Conn.Read(b) + } else if s.buffer[0] != CommandError { + return 0, errors.New("Command not support") + } + + // CommandError + if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { + return 0, err + } + + length := int(s.buffer[0]) + msg := make([]byte, length) + + if _, err := io.ReadFull(s.Conn, msg); err != nil { + return 0, err + } + + return 0, errors.New(string(msg)) +} + +func WriteHeader(conn net.Conn, host string, port uint) error { + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() + defer bufferPool.Put(buf) + buf.WriteByte(Version) + buf.WriteByte(CommandConnect) + + // clientID length & id + buf.WriteByte(0) + + // host & port + buf.WriteByte(uint8(len(host))) + buf.WriteString(host) + binary.Write(buf, binary.BigEndian, uint16(port)) + + if _, err := conn.Write(buf.Bytes()); err != nil { + return err + } + + return nil +} + +func StreamConn(conn net.Conn, psk []byte) net.Conn { + cipher := &snellCipher{psk, chacha20poly1305.New} + return &Snell{Conn: shadowaead.NewConn(conn, cipher)} +} diff --git a/config/config.go b/config/config.go index 1644511116..7a5bce4aca 100644 --- a/config/config.go +++ b/config/config.go @@ -300,6 +300,13 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { break } proxy, err = adapters.NewVmess(*vmessOption) + case "snell": + snellOption := &adapters.SnellOption{} + err = decoder.Decode(mapping, snellOption) + if err != nil { + break + } + proxy, err = adapters.NewSnell(*snellOption) default: return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) } diff --git a/constant/adapters.go b/constant/adapters.go index 4593fed044..2e155ac8aa 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -14,6 +14,7 @@ const ( Reject Selector Shadowsocks + Snell Socks5 Http URLTest @@ -92,6 +93,8 @@ func (at AdapterType) String() string { return "Selector" case Shadowsocks: return "Shadowsocks" + case Snell: + return "Snell" case Socks5: return "Socks5" case Http: From 52125a3992ec2c3dd18afd33b4689b82680fd2ea Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 11 Oct 2019 14:01:16 +0800 Subject: [PATCH 256/535] Fix: fakeip missing host --- common/cache/lrucache.go | 9 +++++++++ component/fakeip/pool.go | 20 +++++++++++++++----- component/fakeip/pool_test.go | 27 +++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 5a139bf714..1cb278d9c3 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -86,6 +86,15 @@ func (c *LruCache) Get(key interface{}) (interface{}, bool) { return value, true } +// Exist returns if key exist in cache but not put item to the head of linked list +func (c *LruCache) Exist(key interface{}) bool { + c.mu.Lock() + defer c.mu.Unlock() + + _, ok := c.cache[key] + return ok +} + // Set stores the interface{} representation of a response for a given key. func (c *LruCache) Set(key interface{}, value interface{}) { c.mu.Lock() diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 8b7a27667d..e71ddfaa32 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -22,8 +22,14 @@ type Pool struct { func (p *Pool) Lookup(host string) net.IP { p.mux.Lock() defer p.mux.Unlock() - if ip, exist := p.cache.Get(host); exist { - return ip.(net.IP) + if elm, exist := p.cache.Get(host); exist { + ip := elm.(net.IP) + + // ensure ip --> host on head of linked list + n := ipToUint(ip.To4()) + offset := n - p.min + 1 + p.cache.Get(offset) + return ip } ip := p.get(host) @@ -43,8 +49,12 @@ func (p *Pool) LookBack(ip net.IP) (string, bool) { n := ipToUint(ip.To4()) offset := n - p.min + 1 - if host, exist := p.cache.Get(offset); exist { - return host.(string), true + if elm, exist := p.cache.Get(offset); exist { + host := elm.(string) + + // ensure host --> ip on head of linked list + p.cache.Get(host) + return host, true } return "", false @@ -64,7 +74,7 @@ func (p *Pool) get(host string) net.IP { break } - if _, exist := p.cache.Get(p.offset); !exist { + if !p.cache.Exist(p.offset) { break } } diff --git a/component/fakeip/pool_test.go b/component/fakeip/pool_test.go index fd62349e04..d68c101129 100644 --- a/component/fakeip/pool_test.go +++ b/component/fakeip/pool_test.go @@ -43,6 +43,33 @@ func TestPool_MaxCacheSize(t *testing.T) { assert.False(t, first.Equal(next)) } +func TestPool_DoubleMapping(t *testing.T) { + _, ipnet, _ := net.ParseCIDR("192.168.0.1/24") + pool, _ := New(ipnet, 2) + + // fill cache + fooIP := pool.Lookup("foo.com") + bazIP := pool.Lookup("baz.com") + + // make foo.com hot + pool.Lookup("foo.com") + + // should drop baz.com + barIP := pool.Lookup("bar.com") + + _, fooExist := pool.LookBack(fooIP) + _, bazExist := pool.LookBack(bazIP) + _, barExist := pool.LookBack(barIP) + + newBazIP := pool.Lookup("baz.com") + + assert.True(t, fooExist) + assert.False(t, bazExist) + assert.True(t, barExist) + + assert.False(t, bazIP.Equal(newBazIP)) +} + func TestPool_Error(t *testing.T) { _, ipnet, _ := net.ParseCIDR("192.168.0.1/31") _, err := New(ipnet, 10) From 0f63682bdfaf0c79ed01a3b21c7dc6207b7c8f3e Mon Sep 17 00:00:00 2001 From: Birkhoff Lee Date: Fri, 11 Oct 2019 20:10:19 +0800 Subject: [PATCH 257/535] Chore: update README.md (#353) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ab44890f67..a1d38d2507 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,12 @@ ## Features -- HTTP, HTTPS and SOCKS protocol +- Local HTTP/HTTPS/SOCKS server - Surge-like configuration format - GeoIP rule support -- Supports Vmess, Shadowsocks and SOCKS5 +- Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol - Supports Netfilter TCP redirecting -- Comprehensive API +- Comprehensive HTTP API ## Install From 4cd8b6f24fb3c5f2b344f4cdc7a8552abb00435a Mon Sep 17 00:00:00 2001 From: Jason Lyu Date: Fri, 11 Oct 2019 20:11:18 +0800 Subject: [PATCH 258/535] Fix: some UDP issues (#265) --- adapters/outbound/direct.go | 2 +- adapters/outbound/http.go | 2 +- adapters/outbound/shadowsocks.go | 22 +++-- adapters/outbound/socks5.go | 17 ++-- adapters/outbound/util.go | 13 --- adapters/outbound/vmess.go | 15 +++- component/nat-table/nat.go | 98 -------------------- component/nat/table.go | 46 ++++++++++ component/socks5/socks5.go | 12 +-- constant/metadata.go | 11 ++- proxy/socks/udp.go | 27 +++--- proxy/socks/utils.go | 22 +++-- tunnel/connection.go | 5 +- tunnel/session.go | 22 ----- tunnel/tunnel.go | 149 ++++++++++++++++++++----------- 15 files changed, 224 insertions(+), 239 deletions(-) delete mode 100644 component/nat-table/nat.go create mode 100644 component/nat/table.go delete mode 100644 tunnel/session.go diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 0f3e6dc42d..2b0a2a47d1 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -30,7 +30,7 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { return nil, nil, err } - addr, err := resolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) + addr, err := resolveUDPAddr("udp", metadata.RemoteAddress()) if err != nil { return nil, nil, err } diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 12b80a5b17..357ed5dfb3 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -58,7 +58,7 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { var buf bytes.Buffer var err error - addr := net.JoinHostPort(metadata.String(), metadata.DstPort) + addr := metadata.RemoteAddress() buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") buf.WriteString("Host: " + metadata.String() + "\r\n") buf.WriteString("Proxy-Connection: Keep-Alive\r\n") diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 58c76bc981..c46f8fc924 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -7,7 +7,6 @@ import ( "net" "strconv" - "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/common/structure" obfs "github.com/Dreamacro/clash/component/simple-obfs" "github.com/Dreamacro/clash/component/socks5" @@ -93,9 +92,9 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, er return nil, nil, err } - targetAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) - if err != nil { - return nil, nil, err + targetAddr := socks5.ParseAddr(metadata.RemoteAddress()) + if targetAddr == nil { + return nil, nil, fmt.Errorf("parse address error: %v:%v", metadata.String(), metadata.DstPort) } pc = ss.cipher.PacketConn(pc) @@ -189,16 +188,15 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { type ssUDPConn struct { net.PacketConn - rAddr net.Addr + rAddr socks5.Addr } -func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { - buf := pool.BufPool.Get().([]byte) - defer pool.BufPool.Put(buf[:cap(buf)]) - rAddr := socks5.ParseAddr(uc.rAddr.String()) - copy(buf[len(rAddr):], b) - copy(buf, rAddr) - return uc.PacketConn.WriteTo(buf[:len(rAddr)+len(b)], addr) +func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + packet, err := socks5.EncodeUDPPacket(uc.rAddr, b) + if err != nil { + return + } + return uc.PacketConn.WriteTo(packet[3:], addr) } func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index a72c938645..d99daa9327 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -98,9 +98,9 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err return } - targetAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(metadata.String(), metadata.DstPort)) - if err != nil { - return + targetAddr := socks5.ParseAddr(metadata.RemoteAddress()) + if targetAddr == nil { + return nil, nil, fmt.Errorf("parse address error: %v:%v", metadata.String(), metadata.DstPort) } pc, err := net.ListenPacket("udp", "") @@ -146,12 +146,12 @@ func NewSocks5(option Socks5Option) *Socks5 { type socksUDPConn struct { net.PacketConn - rAddr net.Addr + rAddr socks5.Addr tcpConn net.Conn } func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - packet, err := socks5.EncodeUDPPacket(uc.rAddr.String(), b) + packet, err := socks5.EncodeUDPPacket(uc.rAddr, b) if err != nil { return } @@ -160,12 +160,17 @@ func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { n, a, e := uc.PacketConn.ReadFrom(b) + if e != nil { + return 0, nil, e + } addr, payload, err := socks5.DecodeUDPPacket(b) if err != nil { return 0, nil, err } + // due to DecodeUDPPacket is mutable, record addr length + addrLength := len(addr) copy(b, payload) - return n - len(addr) - 3, a, e + return n - addrLength - 3, a, nil } func (uc *socksUDPConn) Close() error { diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 8d61796859..46c4581ad1 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -86,19 +86,6 @@ func serializesSocksAddr(metadata *C.Metadata) []byte { return bytes.Join(buf, nil) } -type fakeUDPConn struct { - net.Conn -} - -func (fuc *fakeUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { - return fuc.Conn.Write(b) -} - -func (fuc *fakeUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { - n, err := fuc.Conn.Read(b) - return n, fuc.RemoteAddr(), err -} - func dialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 0121feb4ed..5b5337a004 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -51,7 +51,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { if err != nil { return nil, nil, fmt.Errorf("new vmess client error: %v", err) } - return newPacketConn(&fakeUDPConn{Conn: c}, v), c.RemoteAddr(), nil + return newPacketConn(&vmessUDPConn{Conn: c}, v), c.RemoteAddr(), nil } func NewVmess(option VmessOption) (*Vmess, error) { @@ -111,3 +111,16 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { Port: uint(port), } } + +type vmessUDPConn struct { + net.Conn +} + +func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return uc.Conn.Write(b) +} + +func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, err := uc.Conn.Read(b) + return n, uc.RemoteAddr(), err +} diff --git a/component/nat-table/nat.go b/component/nat-table/nat.go deleted file mode 100644 index 06b3867126..0000000000 --- a/component/nat-table/nat.go +++ /dev/null @@ -1,98 +0,0 @@ -package nat - -import ( - "net" - "runtime" - "sync" - "time" -) - -type Table struct { - *table -} - -type table struct { - mapping sync.Map - janitor *janitor - timeout time.Duration -} - -type element struct { - Expired time.Time - RemoteAddr net.Addr - RemoteConn net.PacketConn -} - -func (t *table) Set(key net.Addr, rConn net.PacketConn, rAddr net.Addr) { - // set conn read timeout - rConn.SetReadDeadline(time.Now().Add(t.timeout)) - t.mapping.Store(key, &element{ - RemoteAddr: rAddr, - RemoteConn: rConn, - Expired: time.Now().Add(t.timeout), - }) -} - -func (t *table) Get(key net.Addr) (rConn net.PacketConn, rAddr net.Addr) { - item, exist := t.mapping.Load(key) - if !exist { - return - } - elm := item.(*element) - // expired - if time.Since(elm.Expired) > 0 { - t.mapping.Delete(key) - elm.RemoteConn.Close() - return - } - // reset expired time - elm.Expired = time.Now().Add(t.timeout) - return elm.RemoteConn, elm.RemoteAddr -} - -func (t *table) cleanup() { - t.mapping.Range(func(k, v interface{}) bool { - key := k.(net.Addr) - elm := v.(*element) - if time.Since(elm.Expired) > 0 { - t.mapping.Delete(key) - elm.RemoteConn.Close() - } - return true - }) -} - -type janitor struct { - interval time.Duration - stop chan struct{} -} - -func (j *janitor) process(t *table) { - ticker := time.NewTicker(j.interval) - for { - select { - case <-ticker.C: - t.cleanup() - case <-j.stop: - ticker.Stop() - return - } - } -} - -func stopJanitor(t *Table) { - t.janitor.stop <- struct{}{} -} - -// New return *Cache -func New(interval time.Duration) *Table { - j := &janitor{ - interval: interval, - stop: make(chan struct{}), - } - t := &table{janitor: j, timeout: interval} - go j.process(t) - T := &Table{t} - runtime.SetFinalizer(T, stopJanitor) - return T -} diff --git a/component/nat/table.go b/component/nat/table.go new file mode 100644 index 0000000000..eac9846771 --- /dev/null +++ b/component/nat/table.go @@ -0,0 +1,46 @@ +package nat + +import ( + "net" + "sync" +) + +type Table struct { + mapping sync.Map +} + +type element struct { + RemoteAddr net.Addr + RemoteConn net.PacketConn +} + +func (t *Table) Set(key string, pc net.PacketConn, addr net.Addr) { + // set conn read timeout + t.mapping.Store(key, &element{ + RemoteConn: pc, + RemoteAddr: addr, + }) +} + +func (t *Table) Get(key string) (net.PacketConn, net.Addr) { + item, exist := t.mapping.Load(key) + if !exist { + return nil, nil + } + elm := item.(*element) + return elm.RemoteConn, elm.RemoteAddr +} + +func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) { + item, loaded := t.mapping.LoadOrStore(key, &sync.WaitGroup{}) + return item.(*sync.WaitGroup), loaded +} + +func (t *Table) Delete(key string) { + t.mapping.Delete(key) +} + +// New return *Cache +func New() *Table { + return &Table{} +} diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index 1dd60391ae..243f34cb4a 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -338,6 +338,7 @@ func ParseAddr(s string) Addr { return addr } +// DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet` func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { if len(packet) < 5 { err = errors.New("insufficient length of packet") @@ -360,16 +361,15 @@ func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { err = errors.New("failed to read UDP header") } - payload = bytes.Join([][]byte{packet[3+len(addr):]}, []byte{}) + payload = packet[3+len(addr):] return } -func EncodeUDPPacket(addr string, payload []byte) (packet []byte, err error) { - rAddr := ParseAddr(addr) - if rAddr == nil { - err = errors.New("cannot parse addr") +func EncodeUDPPacket(addr Addr, payload []byte) (packet []byte, err error) { + if addr == nil { + err = errors.New("address is invalid") return } - packet = bytes.Join([][]byte{{0, 0, 0}, rAddr, payload}, []byte{}) + packet = bytes.Join([][]byte{{0, 0, 0}, addr, payload}, []byte{}) return } diff --git a/constant/metadata.go b/constant/metadata.go index f95bea586f..cc8703619b 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -41,11 +41,18 @@ type Metadata struct { Host string } +func (m *Metadata) RemoteAddress() string { + return net.JoinHostPort(m.String(), m.DstPort) +} + func (m *Metadata) String() string { - if m.Host == "" { + if m.Host != "" { + return m.Host + } else if m.DstIP != nil { return m.DstIP.String() + } else { + return "" } - return m.Host } func (m *Metadata) Valid() bool { diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 63b63ba54b..36228ad563 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -1,17 +1,13 @@ package socks import ( + "bytes" "net" adapters "github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/tunnel" -) - -var ( - _ = tunnel.NATInstance() ) type SockUDPListener struct { @@ -28,17 +24,17 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { sl := &SockUDPListener{l, addr, false} go func() { - buf := pool.BufPool.Get().([]byte) - defer pool.BufPool.Put(buf[:cap(buf)]) for { + buf := pool.BufPool.Get().([]byte) n, remoteAddr, err := l.ReadFrom(buf) if err != nil { + pool.BufPool.Put(buf[:cap(buf)]) if sl.closed { break } continue } - go handleSocksUDP(l, buf[:n], remoteAddr) + handleSocksUDP(l, buf[:n], remoteAddr) } }() @@ -54,12 +50,19 @@ func (l *SockUDPListener) Address() string { return l.address } -func handleSocksUDP(c net.PacketConn, packet []byte, remoteAddr net.Addr) { - target, payload, err := socks5.DecodeUDPPacket(packet) +func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { + target, payload, err := socks5.DecodeUDPPacket(buf) if err != nil { - // Unresolved UDP packet, do nothing + // Unresolved UDP packet, return buffer to the pool + pool.BufPool.Put(buf[:cap(buf)]) return } - conn := newfakeConn(c, target.String(), remoteAddr, payload) + conn := &fakeConn{ + PacketConn: pc, + remoteAddr: addr, + targetAddr: target, + buffer: bytes.NewBuffer(payload), + bufRef: buf, + } tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP)) } diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index 2ecee3c31b..ea05961eb5 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -4,24 +4,16 @@ import ( "bytes" "net" + "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/socks5" ) type fakeConn struct { net.PacketConn - target string remoteAddr net.Addr + targetAddr socks5.Addr buffer *bytes.Buffer -} - -func newfakeConn(conn net.PacketConn, target string, remoteAddr net.Addr, buf []byte) *fakeConn { - buffer := bytes.NewBuffer(buf) - return &fakeConn{ - PacketConn: conn, - target: target, - buffer: buffer, - remoteAddr: remoteAddr, - } + bufRef []byte } func (c *fakeConn) Read(b []byte) (n int, err error) { @@ -29,7 +21,7 @@ func (c *fakeConn) Read(b []byte) (n int, err error) { } func (c *fakeConn) Write(b []byte) (n int, err error) { - packet, err := socks5.EncodeUDPPacket(c.target, b) + packet, err := socks5.EncodeUDPPacket(c.targetAddr, b) if err != nil { return } @@ -39,3 +31,9 @@ func (c *fakeConn) Write(b []byte) (n int, err error) { func (c *fakeConn) RemoteAddr() net.Addr { return c.remoteAddr } + +func (c *fakeConn) Close() error { + err := c.PacketConn.Close() + pool.BufPool.Put(c.bufRef[:cap(c.bufRef)]) + return err +} diff --git a/tunnel/connection.go b/tunnel/connection.go index b5beb043bc..e48167f295 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -86,11 +86,14 @@ func (t *Tunnel) handleUDPToRemote(conn net.Conn, pc net.PacketConn, addr net.Ad t.traffic.Up() <- int64(n) } -func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn) { +func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn, key string, timeout time.Duration) { buf := pool.BufPool.Get().([]byte) defer pool.BufPool.Put(buf[:cap(buf)]) + defer t.natTable.Delete(key) + defer pc.Close() for { + pc.SetReadDeadline(time.Now().Add(timeout)) n, _, err := pc.ReadFrom(buf) if err != nil { return diff --git a/tunnel/session.go b/tunnel/session.go deleted file mode 100644 index 4433deae8c..0000000000 --- a/tunnel/session.go +++ /dev/null @@ -1,22 +0,0 @@ -package tunnel - -import ( - "sync" - "time" - - nat "github.com/Dreamacro/clash/component/nat-table" -) - -var ( - natTable *nat.Table - natOnce sync.Once - - natTimeout = 120 * time.Second -) - -func NATInstance() *nat.Table { - natOnce.Do(func() { - natTable = nat.New(natTimeout) - }) - return natTable -} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 87aafec323..32647f1a6d 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -7,6 +7,7 @@ import ( "time" InboundAdapter "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/component/nat" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" @@ -17,11 +18,16 @@ import ( var ( tunnel *Tunnel once sync.Once + + // default timeout for UDP session + udpTimeout = 60 * time.Second ) // Tunnel handle relay inbound proxy and outbound proxy type Tunnel struct { - queue *channels.InfiniteChannel + tcpQueue *channels.InfiniteChannel + udpQueue *channels.InfiniteChannel + natTable *nat.Table rules []C.Rule proxies map[string]C.Proxy configMux *sync.RWMutex @@ -36,7 +42,12 @@ type Tunnel struct { // Add request to queue func (t *Tunnel) Add(req C.ServerAdapter) { - t.queue.In() <- req + switch req.Metadata().NetWork { + case C.TCP: + t.tcpQueue.In() <- req + case C.UDP: + t.udpQueue.In() <- req + } } // Traffic return traffic of all connections @@ -86,11 +97,18 @@ func (t *Tunnel) SetMode(mode Mode) { } func (t *Tunnel) process() { - queue := t.queue.Out() - for { - elm := <-queue + go func() { + queue := t.udpQueue.Out() + for elm := range queue { + conn := elm.(C.ServerAdapter) + t.handleUDPConn(conn) + } + }() + + queue := t.tcpQueue.Out() + for elm := range queue { conn := elm.(C.ServerAdapter) - go t.handleConn(conn) + go t.handleTCPConn(conn) } } @@ -102,26 +120,7 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil } -func (t *Tunnel) handleConn(localConn C.ServerAdapter) { - defer func() { - var conn net.Conn - switch adapter := localConn.(type) { - case *InboundAdapter.HTTPAdapter: - conn = adapter.Conn - case *InboundAdapter.SocketAdapter: - conn = adapter.Conn - } - if _, ok := conn.(*net.TCPConn); ok { - localConn.Close() - } - }() - - metadata := localConn.Metadata() - if !metadata.Valid() { - log.Warnln("[Metadata] not valid: %#v", metadata) - return - } - +func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { // preprocess enhanced-mode metadata if t.needLookupIP(metadata) { host, exist := dns.DefaultResolver.IPToHost(*metadata.DstIP) @@ -146,43 +145,87 @@ func (t *Tunnel) handleConn(localConn C.ServerAdapter) { var err error proxy, rule, err = t.match(metadata) if err != nil { - return + return nil, nil, err } } + return proxy, rule, nil +} - switch metadata.NetWork { - case C.TCP: - t.handleTCPConn(localConn, metadata, proxy, rule) - case C.UDP: - t.handleUDPConn(localConn, metadata, proxy, rule) +func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) { + metadata := localConn.Metadata() + if !metadata.Valid() { + log.Warnln("[Metadata] not valid: %#v", metadata) + return } -} -func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) { - pc, addr := natTable.Get(localConn.RemoteAddr()) - if pc == nil { - rawPc, nAddr, err := proxy.DialUDP(metadata) - addr = nAddr - pc = rawPc - if err != nil { - log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) - return + src := localConn.RemoteAddr().String() + dst := metadata.RemoteAddress() + key := src + "-" + dst + + pc, addr := t.natTable.Get(key) + if pc != nil { + t.handleUDPToRemote(localConn, pc, addr) + return + } + + lockKey := key + "-lock" + wg, loaded := t.natTable.GetOrCreateLock(lockKey) + go func() { + if !loaded { + wg.Add(1) + proxy, rule, err := t.resolveMetadata(metadata) + if err != nil { + log.Warnln("Parse metadata failed: %s", err.Error()) + t.natTable.Delete(lockKey) + wg.Done() + return + } + + rawPc, nAddr, err := proxy.DialUDP(metadata) + if err != nil { + log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) + t.natTable.Delete(lockKey) + wg.Done() + return + } + pc = rawPc + addr = nAddr + + if rule != nil { + log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) + } else { + log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) + } + + t.natTable.Set(key, pc, addr) + t.natTable.Delete(lockKey) + wg.Done() + go t.handleUDPToLocal(localConn, pc, key, udpTimeout) } - if rule != nil { - log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) - } else { - log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) + wg.Wait() + pc, addr := t.natTable.Get(key) + if pc != nil { + t.handleUDPToRemote(localConn, pc, addr) } + }() +} - natTable.Set(localConn.RemoteAddr(), pc, addr) - go t.handleUDPToLocal(localConn, pc) +func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { + defer localConn.Close() + + metadata := localConn.Metadata() + if !metadata.Valid() { + log.Warnln("[Metadata] not valid: %#v", metadata) + return } - t.handleUDPToRemote(localConn, pc, addr) -} + proxy, rule, err := t.resolveMetadata(metadata) + if err != nil { + log.Warnln("Parse metadata failed: %v", err) + return + } -func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter, metadata *C.Metadata, proxy C.Proxy, rule C.Rule) { remoteConn, err := proxy.Dial(metadata) if err != nil { log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) @@ -253,7 +296,9 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { func newTunnel() *Tunnel { return &Tunnel{ - queue: channels.NewInfiniteChannel(), + tcpQueue: channels.NewInfiniteChannel(), + udpQueue: channels.NewInfiniteChannel(), + natTable: nat.New(), proxies: make(map[string]C.Proxy), configMux: &sync.RWMutex{}, traffic: C.NewTraffic(time.Second), From 0cdc40beb3cfe3465547d3d5f2a0da913f6e3c4c Mon Sep 17 00:00:00 2001 From: comwrg Date: Sat, 12 Oct 2019 23:29:00 +0800 Subject: [PATCH 259/535] Fix: urltest get fastest node ehavior (#326) --- adapters/outbound/urltest.go | 7 ++++-- common/picker/picker.go | 37 ++++++++++++++++++++++++++----- common/picker/picker_test.go | 42 ++++++++++++++++++++++++++++-------- dns/resolver.go | 5 +---- hub/route/proxies.go | 13 ++++------- 5 files changed, 75 insertions(+), 29 deletions(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index b9efd8d250..60b4beda0d 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -110,8 +110,9 @@ func (u *URLTest) speedTest() { } defer atomic.StoreInt32(&u.once, 0) - picker, ctx, cancel := picker.WithTimeout(context.Background(), defaultURLTestTimeout) + ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) defer cancel() + picker := picker.WithoutAutoCancel(ctx) for _, p := range u.proxies { proxy := p picker.Go(func() (interface{}, error) { @@ -123,10 +124,12 @@ func (u *URLTest) speedTest() { }) } - fast := picker.Wait() + fast := picker.WaitWithoutCancel() if fast != nil { u.fast = fast.(C.Proxy) } + + picker.Wait() } func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { diff --git a/common/picker/picker.go b/common/picker/picker.go index a66b9bc600..a30202df4e 100644 --- a/common/picker/picker.go +++ b/common/picker/picker.go @@ -10,26 +10,42 @@ import ( // for groups of goroutines working on subtasks of a common task. // Inspired by errGroup type Picker struct { + ctx context.Context cancel func() wg sync.WaitGroup once sync.Once result interface{} + + firstDone chan struct{} +} + +func newPicker(ctx context.Context, cancel func()) *Picker { + return &Picker{ + ctx: ctx, + cancel: cancel, + firstDone: make(chan struct{}, 1), + } } // WithContext returns a new Picker and an associated Context derived from ctx. // and cancel when first element return. func WithContext(ctx context.Context) (*Picker, context.Context) { ctx, cancel := context.WithCancel(ctx) - return &Picker{cancel: cancel}, ctx + return newPicker(ctx, cancel), ctx } -// WithTimeout returns a new Picker and an associated Context derived from ctx with timeout, -// but it doesn't cancel when first element return. -func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context, context.CancelFunc) { +// WithTimeout returns a new Picker and an associated Context derived from ctx with timeout. +func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.Context) { ctx, cancel := context.WithTimeout(ctx, timeout) - return &Picker{}, ctx, cancel + return newPicker(ctx, cancel), ctx +} + +// WithoutAutoCancel returns a new Picker and an associated Context derived from ctx, +// but it wouldn't cancel context when the first element return. +func WithoutAutoCancel(ctx context.Context) *Picker { + return newPicker(ctx, nil) } // Wait blocks until all function calls from the Go method have returned, @@ -42,6 +58,16 @@ func (p *Picker) Wait() interface{} { return p.result } +// WaitWithoutCancel blocks until the first result return, if timeout will return nil. +func (p *Picker) WaitWithoutCancel() interface{} { + select { + case <-p.firstDone: + return p.result + case <-p.ctx.Done(): + return p.result + } +} + // Go calls the given function in a new goroutine. // The first call to return a nil error cancels the group; its result will be returned by Wait. func (p *Picker) Go(f func() (interface{}, error)) { @@ -53,6 +79,7 @@ func (p *Picker) Go(f func() (interface{}, error)) { if ret, err := f(); err == nil { p.once.Do(func() { p.result = ret + p.firstDone <- struct{}{} if p.cancel != nil { p.cancel() } diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go index 7ff3712fab..9e1650096d 100644 --- a/common/picker/picker_test.go +++ b/common/picker/picker_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" "time" + + "github.com/stretchr/testify/assert" ) func sleepAndSend(ctx context.Context, delay int, input interface{}) func() (interface{}, error) { @@ -24,19 +26,41 @@ func TestPicker_Basic(t *testing.T) { picker.Go(sleepAndSend(ctx, 20, 1)) number := picker.Wait() - if number != nil && number.(int) != 1 { - t.Error("should recv 1", number) - } + assert.NotNil(t, number) + assert.Equal(t, number.(int), 1) } func TestPicker_Timeout(t *testing.T) { - picker, ctx, cancel := WithTimeout(context.Background(), time.Millisecond*5) - defer cancel() - + picker, ctx := WithTimeout(context.Background(), time.Millisecond*5) picker.Go(sleepAndSend(ctx, 20, 1)) number := picker.Wait() - if number != nil { - t.Error("should recv nil") - } + assert.Nil(t, number) +} + +func TestPicker_WaitWithoutAutoCancel(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*60) + defer cancel() + picker := WithoutAutoCancel(ctx) + + trigger := false + picker.Go(sleepAndSend(ctx, 10, 1)) + picker.Go(func() (interface{}, error) { + timer := time.NewTimer(time.Millisecond * time.Duration(30)) + select { + case <-timer.C: + trigger = true + return 2, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + }) + elm := picker.WaitWithoutCancel() + + assert.NotNil(t, elm) + assert.Equal(t, elm.(int), 1) + + elm = picker.Wait() + assert.True(t, trigger) + assert.Equal(t, elm.(int), 1) } diff --git a/dns/resolver.go b/dns/resolver.go index 2276f789f1..1b68d7d6ec 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -171,10 +171,7 @@ func (r *Resolver) IsFakeIP() bool { } func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - fast, ctx := picker.WithContext(ctx) - + fast, ctx := picker.WithTimeout(context.Background(), time.Second) for _, client := range clients { r := client fast.Go(func() (interface{}, error) { diff --git a/hub/route/proxies.go b/hub/route/proxies.go index d485495e99..201fd03f6e 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -9,7 +9,6 @@ import ( "time" A "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/common/picker" C "github.com/Dreamacro/clash/constant" T "github.com/Dreamacro/clash/tunnel" @@ -111,21 +110,17 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(C.Proxy) - picker, ctx, cancel := picker.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout)) defer cancel() - picker.Go(func() (interface{}, error) { - return proxy.URLTest(ctx, url) - }) - elm := picker.Wait() - if elm == nil { + delay, err := proxy.URLTest(ctx, url) + if ctx.Err() != nil { render.Status(r, http.StatusGatewayTimeout) render.JSON(w, r, ErrRequestTimeout) return } - delay := elm.(uint16) - if delay == 0 { + if err != nil || delay == 0 { render.Status(r, http.StatusServiceUnavailable) render.JSON(w, r, newError("An error occurred in the delay test")) return From 7c4a359a2b611175542004fb46c81a1489e75dc6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 12 Oct 2019 23:55:39 +0800 Subject: [PATCH 260/535] Fix: dial tcp with context to avoid margin of error --- adapters/outbound/base.go | 10 ++++++++-- adapters/outbound/direct.go | 5 +++-- adapters/outbound/fallback.go | 4 ++-- adapters/outbound/http.go | 5 +++-- adapters/outbound/loadbalance.go | 6 +++--- adapters/outbound/reject.go | 3 ++- adapters/outbound/selector.go | 5 +++-- adapters/outbound/shadowsocks.go | 5 +++-- adapters/outbound/snell.go | 5 +++-- adapters/outbound/socks5.go | 9 ++++++--- adapters/outbound/urltest.go | 4 ++-- adapters/outbound/util.go | 4 +--- adapters/outbound/vmess.go | 9 ++++++--- constant/adapters.go | 3 ++- 14 files changed, 47 insertions(+), 30 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index b560fc7197..505489b480 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -91,7 +91,13 @@ func (p *Proxy) Alive() bool { } func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { - conn, err := p.ProxyAdapter.Dial(metadata) + ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) + defer cancel() + return p.DialContext(ctx, metadata) +} + +func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + conn, err := p.ProxyAdapter.DialContext(ctx, metadata) if err != nil { p.alive = false } @@ -157,7 +163,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { } start := time.Now() - instance, err := p.Dial(&addr) + instance, err := p.DialContext(ctx, &addr) if err != nil { return } diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 2b0a2a47d1..22a4171cb9 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "net" C "github.com/Dreamacro/clash/constant" @@ -10,13 +11,13 @@ type Direct struct { *Base } -func (d *Direct) Dial(metadata *C.Metadata) (C.Conn, error) { +func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { address := net.JoinHostPort(metadata.Host, metadata.DstPort) if metadata.DstIP != nil { address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) } - c, err := dialTimeout("tcp", address, tcpTimeout) + c, err := dialContext(ctx, "tcp", address) if err != nil { return nil, err } diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 9b44edebde..3e43e63cda 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -31,9 +31,9 @@ func (f *Fallback) Now() string { return proxy.Name() } -func (f *Fallback) Dial(metadata *C.Metadata) (C.Conn, error) { +func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { proxy := f.findAliveProxy() - c, err := proxy.Dial(metadata) + c, err := proxy.DialContext(ctx, metadata) if err == nil { c.AppendToChains(f) } diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 357ed5dfb3..77b835b6ba 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -3,6 +3,7 @@ package adapters import ( "bufio" "bytes" + "context" "crypto/tls" "encoding/base64" "errors" @@ -35,8 +36,8 @@ type HttpOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (h *Http) Dial(metadata *C.Metadata) (C.Conn, error) { - c, err := dialTimeout("tcp", h.addr, tcpTimeout) +func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialContext(ctx, "tcp", h.addr) if err == nil && h.tls { cc := tls.Client(c, h.tlsConfig) err = cc.Handshake() diff --git a/adapters/outbound/loadbalance.go b/adapters/outbound/loadbalance.go index c719e8b84f..d27beecece 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outbound/loadbalance.go @@ -54,7 +54,7 @@ func jumpHash(key uint64, buckets int32) int32 { return int32(b) } -func (lb *LoadBalance) Dial(metadata *C.Metadata) (c C.Conn, err error) { +func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { defer func() { if err == nil { c.AppendToChains(lb) @@ -67,11 +67,11 @@ func (lb *LoadBalance) Dial(metadata *C.Metadata) (c C.Conn, err error) { idx := jumpHash(key, buckets) proxy := lb.proxies[idx] if proxy.Alive() { - c, err = proxy.Dial(metadata) + c, err = proxy.DialContext(ctx, metadata) return } } - c, err = lb.proxies[0].Dial(metadata) + c, err = lb.proxies[0].DialContext(ctx, metadata) return } diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index de395d5822..65ab11921e 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "io" "net" "time" @@ -12,7 +13,7 @@ type Reject struct { *Base } -func (r *Reject) Dial(metadata *C.Metadata) (C.Conn, error) { +func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { return newConn(&NopConn{}, r), nil } diff --git a/adapters/outbound/selector.go b/adapters/outbound/selector.go index 31d5a0a563..b7ed661cc0 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outbound/selector.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "encoding/json" "errors" "net" @@ -20,8 +21,8 @@ type SelectorOption struct { Proxies []string `proxy:"proxies"` } -func (s *Selector) Dial(metadata *C.Metadata) (C.Conn, error) { - c, err := s.selected.Dial(metadata) +func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := s.selected.DialContext(ctx, metadata) if err == nil { c.AppendToChains(s) } diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index c46f8fc924..22d160ba4c 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "crypto/tls" "encoding/json" "fmt" @@ -57,8 +58,8 @@ type v2rayObfsOption struct { Mux bool `obfs:"mux,omitempty"` } -func (ss *ShadowSocks) Dial(metadata *C.Metadata) (C.Conn, error) { - c, err := dialTimeout("tcp", ss.server, tcpTimeout) +func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialContext(ctx, "tcp", ss.server) if err != nil { return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) } diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go index b4131199b9..6b95aace89 100644 --- a/adapters/outbound/snell.go +++ b/adapters/outbound/snell.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "fmt" "net" "strconv" @@ -26,8 +27,8 @@ type SnellOption struct { ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` } -func (s *Snell) Dial(metadata *C.Metadata) (C.Conn, error) { - c, err := dialTimeout("tcp", s.server, tcpTimeout) +func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialContext(ctx, "tcp", s.server) if err != nil { return nil, fmt.Errorf("%s connect error: %s", s.server, err.Error()) } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index d99daa9327..9355cff138 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "crypto/tls" "fmt" "io" @@ -33,8 +34,8 @@ type Socks5Option struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (ss *Socks5) Dial(metadata *C.Metadata) (C.Conn, error) { - c, err := dialTimeout("tcp", ss.addr, tcpTimeout) +func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialContext(ctx, "tcp", ss.addr) if err == nil && ss.tls { cc := tls.Client(c, ss.tlsConfig) @@ -60,7 +61,9 @@ func (ss *Socks5) Dial(metadata *C.Metadata) (C.Conn, error) { } func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) { - c, err := dialTimeout("tcp", ss.addr, tcpTimeout) + ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) + defer cancel() + c, err := dialContext(ctx, "tcp", ss.addr) if err != nil { err = fmt.Errorf("%s connect error", ss.addr) return diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 60b4beda0d..2bdb872daf 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -33,9 +33,9 @@ func (u *URLTest) Now() string { return u.fast.Name() } -func (u *URLTest) Dial(metadata *C.Metadata) (c C.Conn, err error) { +func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { for i := 0; i < 3; i++ { - c, err = u.fast.Dial(metadata) + c, err = u.fast.DialContext(ctx, metadata) if err == nil { c.AppendToChains(u) return diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 46c4581ad1..22b2d95355 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -86,15 +86,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte { return bytes.Join(buf, nil) } -func dialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { +func dialContext(ctx context.Context, network, address string) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err } dialer := net.Dialer{} - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() returned := make(chan struct{}) defer close(returned) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 5b5337a004..d61172e542 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -1,6 +1,7 @@ package adapters import ( + "context" "fmt" "net" "strconv" @@ -31,8 +32,8 @@ type VmessOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (v *Vmess) Dial(metadata *C.Metadata) (C.Conn, error) { - c, err := dialTimeout("tcp", v.server, tcpTimeout) +func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialContext(ctx, "tcp", v.server) if err != nil { return nil, fmt.Errorf("%s connect error", v.server) } @@ -42,7 +43,9 @@ func (v *Vmess) Dial(metadata *C.Metadata) (C.Conn, error) { } func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - c, err := dialTimeout("tcp", v.server, tcpTimeout) + ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) + defer cancel() + c, err := dialContext(ctx, "tcp", v.server) if err != nil { return nil, nil, fmt.Errorf("%s connect error", v.server) } diff --git a/constant/adapters.go b/constant/adapters.go index 2e155ac8aa..97d65a50c1 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -58,7 +58,7 @@ type PacketConn interface { type ProxyAdapter interface { Name() string Type() AdapterType - Dial(metadata *Metadata) (Conn, error) + DialContext(ctx context.Context, metadata *Metadata) (Conn, error) DialUDP(metadata *Metadata) (PacketConn, net.Addr, error) SupportUDP() bool Destroy() @@ -74,6 +74,7 @@ type Proxy interface { ProxyAdapter Alive() bool DelayHistory() []DelayHistory + Dial(metadata *Metadata) (Conn, error) LastDelay() uint16 URLTest(ctx context.Context, url string) (uint16, error) } From d1fb442bd51ca697f8d9f7b3d451c9bdd83401cd Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 13 Oct 2019 00:22:39 +0800 Subject: [PATCH 261/535] Chore: update dependencies --- go.mod | 14 +++++++------- go.sum | 43 +++++++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index eca282deb0..151a61842a 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,21 @@ module github.com/Dreamacro/clash go 1.13 require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.4 + github.com/Dreamacro/go-shadowsocks2 v0.1.5-0.20191012162057-46254afc8b68 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.1 - github.com/miekg/dns v1.1.16 + github.com/miekg/dns v1.1.22 github.com/oschwald/geoip2-golang v1.3.0 - github.com/oschwald/maxminddb-golang v1.4.0 // indirect + github.com/oschwald/maxminddb-golang v1.5.0 // indirect github.com/sirupsen/logrus v1.4.2 - github.com/stretchr/testify v1.2.2 - golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 - golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 - golang.org/x/sync v0.0.0-20190423024810-112230192c58 + github.com/stretchr/testify v1.4.0 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/net v0.0.0-20191011234655-491137f69257 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index a1b83ba577..77f5e12f4e 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.4 h1:SqSVFdtBHVx2rrMOK+qipO10rRSQQKEeAF9Igm8qB9g= -github.com/Dreamacro/go-shadowsocks2 v0.1.4/go.mod h1:RPU9lnoesqJ/0jFzntFrYpbQI0+NoYiJVu7mWn3Y0cs= +github.com/Dreamacro/go-shadowsocks2 v0.1.5-0.20191012162057-46254afc8b68 h1:UBDLpj1IGVkUcUBuZWE6DmZApPTZcnmcV6AfyDN/yhg= +github.com/Dreamacro/go-shadowsocks2 v0.1.5-0.20191012162057-46254afc8b68/go.mod h1:Y8obOtHDOqxMGHjPglfCiXZBKExOA9VL6I6sJagOwYM= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= @@ -18,38 +19,52 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.16 h1:iMEQ/IVHxPTtx2Q07JP/k4CKRvSjiAZjZ0hnhgYEDmE= -github.com/miekg/dns v1.1.16/go.mod h1:YNV562EiewvSmpCB6/W4c6yqjK7Z+M/aIS1JHsIVeg8= +github.com/miekg/dns v1.1.22 h1:Jm64b3bO9kP43ddLjL2EY3Io6bmy1qGb9Xxz6TqS6rc= +github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8= github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= -github.com/oschwald/maxminddb-golang v1.4.0 h1:5/rpmW41qrgSed4wK32rdznbkTSXHcraY2LOMJX4DMc= -github.com/oschwald/maxminddb-golang v1.4.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= +github.com/oschwald/maxminddb-golang v1.5.0 h1:rmyoIV6z2/s9TCJedUuDiKht2RN12LWJ1L7iRGtWY64= +github.com/oschwald/maxminddb-golang v1.5.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= 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/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +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/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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/net v0.0.0-20191011234655-491137f69257 h1:ry8e2D+cwaV6hk7lb3aRTjjZo24shrbK0e11QEOkTIg= +golang.org/x/net v0.0.0-20191011234655-491137f69257/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-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= From 461e0a68735ab1cf79f6f2160aa68eb2ff8402a5 Mon Sep 17 00:00:00 2001 From: oasiscifr <55550764+oasiscifr@users.noreply.github.com> Date: Sun, 13 Oct 2019 05:19:46 +0200 Subject: [PATCH 262/535] Fix: UDP socks recreate behavior (#355) --- proxy/listener.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/proxy/listener.go b/proxy/listener.go index 1966b0bfdc..9e7d0311d0 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -75,41 +75,37 @@ func ReCreateSocks(port int) error { addr := genAddr(bindAddress, port, allowLan) if socksListener != nil { - if socksListener.Address() == addr { - return nil + if socksListener.Address() != addr { + socksListener.Close() + socksListener = nil + } + } + + if socksUDPListener != nil { + if socksUDPListener.Address() != addr { + socksUDPListener.Close() + socksUDPListener = nil } - socksListener.Close() - socksListener = nil } if portIsZero(addr) { return nil } - var err error - socksListener, err = socks.NewSocksProxy(addr) + tcpListener, err := socks.NewSocksProxy(addr) if err != nil { return err } - return reCreateSocksUDP(addr) -} - -func reCreateSocksUDP(addr string) error { - if socksUDPListener != nil { - if socksUDPListener.Address() == addr { - return nil - } - socksUDPListener.Close() - socksUDPListener = nil - } - - var err error - socksUDPListener, err = socks.NewSocksUDPProxy(addr) + udpListener, err := socks.NewSocksUDPProxy(addr) if err != nil { + tcpListener.Close() return err } + socksListener = tcpListener + socksUDPListener = udpListener + return nil } From 2c82a2bfc84fd112ae6227ca3465203ec311616d Mon Sep 17 00:00:00 2001 From: comwrg Date: Sun, 13 Oct 2019 12:11:26 +0800 Subject: [PATCH 263/535] Optimization: use context in urltest speed test (#356) --- adapters/outbound/urltest.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go index 2bdb872daf..33f8071714 100644 --- a/adapters/outbound/urltest.go +++ b/adapters/outbound/urltest.go @@ -75,12 +75,15 @@ func (u *URLTest) Destroy() { func (u *URLTest) loop() { tick := time.NewTicker(u.interval) - go u.speedTest() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go u.speedTest(ctx) Loop: for { select { case <-tick.C: - go u.speedTest() + go u.speedTest(ctx) case <-u.done: break Loop } @@ -104,13 +107,13 @@ func (u *URLTest) fallback() { u.fast = fast } -func (u *URLTest) speedTest() { +func (u *URLTest) speedTest(ctx context.Context) { if !atomic.CompareAndSwapInt32(&u.once, 0, 1) { return } defer atomic.StoreInt32(&u.once, 0) - ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) + ctx, cancel := context.WithTimeout(ctx, defaultURLTestTimeout) defer cancel() picker := picker.WithoutAutoCancel(ctx) for _, p := range u.proxies { From e22ff74e79c25a9f35095bc2133d3e8ef8e45326 Mon Sep 17 00:00:00 2001 From: comwrg Date: Sun, 13 Oct 2019 18:11:02 +0800 Subject: [PATCH 264/535] Optimization: use context in fallback speed test (#357) --- adapters/outbound/fallback.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go index 3e43e63cda..46247c8647 100644 --- a/adapters/outbound/fallback.go +++ b/adapters/outbound/fallback.go @@ -5,9 +5,10 @@ import ( "encoding/json" "errors" "net" - "sync" + "sync/atomic" "time" + "github.com/Dreamacro/clash/common/picker" C "github.com/Dreamacro/clash/constant" ) @@ -17,6 +18,7 @@ type Fallback struct { rawURL string interval time.Duration done chan struct{} + once int32 } type FallbackOption struct { @@ -72,12 +74,15 @@ func (f *Fallback) Destroy() { func (f *Fallback) loop() { tick := time.NewTicker(f.interval) - go f.validTest() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go f.validTest(ctx) Loop: for { select { case <-tick.C: - go f.validTest() + go f.validTest(ctx) case <-f.done: break Loop } @@ -93,18 +98,24 @@ func (f *Fallback) findAliveProxy() C.Proxy { return f.proxies[0] } -func (f *Fallback) validTest() { - wg := sync.WaitGroup{} - wg.Add(len(f.proxies)) +func (f *Fallback) validTest(ctx context.Context) { + if !atomic.CompareAndSwapInt32(&f.once, 0, 1) { + return + } + defer atomic.StoreInt32(&f.once, 0) + + ctx, cancel := context.WithTimeout(ctx, defaultURLTestTimeout) + defer cancel() + picker := picker.WithoutAutoCancel(ctx) for _, p := range f.proxies { - go func(p C.Proxy) { - p.URLTest(context.Background(), f.rawURL) - wg.Done() - }(p) + proxy := p + picker.Go(func() (interface{}, error) { + return proxy.URLTest(ctx, f.rawURL) + }) } - wg.Wait() + picker.Wait() } func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { @@ -128,6 +139,7 @@ func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { rawURL: option.URL, interval: interval, done: make(chan struct{}), + once: 0, } go Fallback.loop() return Fallback, nil From 521a190b0f93a36e438a31ecd303342a43484ce0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 13 Oct 2019 23:51:26 +0800 Subject: [PATCH 265/535] Fix: hot recreate socks should ignore when receive same address --- proxy/listener.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/proxy/listener.go b/proxy/listener.go index 9e7d0311d0..79741581a4 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -74,10 +74,15 @@ func ReCreateHTTP(port int) error { func ReCreateSocks(port int) error { addr := genAddr(bindAddress, port, allowLan) + shouldTCPIgnore := false + shouldUDPIgnore := false + if socksListener != nil { if socksListener.Address() != addr { socksListener.Close() socksListener = nil + } else { + shouldTCPIgnore = true } } @@ -85,9 +90,15 @@ func ReCreateSocks(port int) error { if socksUDPListener.Address() != addr { socksUDPListener.Close() socksUDPListener = nil + } else { + shouldUDPIgnore = true } } + if shouldTCPIgnore && shouldUDPIgnore { + return nil + } + if portIsZero(addr) { return nil } From 710cd5aed26d10a4d6165c1dba21993bcb0bfe35 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 14 Oct 2019 10:40:00 +0800 Subject: [PATCH 266/535] Migration: use actions upload release --- .github/workflows/go.yml | 23 +++++++++++++++++++---- .travis.yml | 26 -------------------------- README.md | 4 ---- 3 files changed, 19 insertions(+), 34 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ed5dd784f3..d9db9e1ad3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -6,12 +6,10 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - - name: Set up Go 1.13 + - name: Setup Go uses: actions/setup-go@v1 with: - go-version: 1.13 - id: go + go-version: 1.13.x - name: Check out code into the Go module directory uses: actions/checkout@v1 @@ -19,3 +17,20 @@ jobs: - name: Get dependencies run: | go test ./... + + - name: Build + if: startsWith(github.ref, 'refs/tags/') + env: + NAME: clash + BINDIR: bin + run: make -j releases + + - name: Upload Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: bin/* + draft: true + prerelease: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8f88b68403..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: go -sudo: false -go: - - '1.13' -install: - - "go mod download" -env: - global: - - NAME=clash - - BINDIR=bin - - GO111MODULE=on -script: - - go test ./... -before_deploy: make -j releases -deploy: - provider: releases - prerelease: true - skip_cleanup: true - api_key: - secure: dp1tc1h0er7aaAZ1hY0Xk/cUKwB0ifsAjg6e0M/Ad5NC87oucP6ESNFkDu0e9rUS1yB826/VnVGzNE/Z5zdjXVzPft+g5v5oRxzI4BKLhf07t9s+x8Z+3sApTxdsC5BvcN9x+5yRbpDLQ3biDPxSFu86j7m2pkEWw6XYNZO3/5y+RZXX7zu+d4MzTLUaA2kWl7KQAP0tEJNuw9ACDhpkw7LYbU/8q3E76prOTeme5/AT6Gxj7XhKUNP27lazhhqBSWM14ybPANqojNLEfMFHN/Eu2phYO07MuLTd4zuOIuw9y65kgvTFcHRlORjwUhnviXyA69obQejjgDI1WDOtU4PqpFaSLrxWtKI6k5VNWHARYggDm/wKl0WG7F0Kgio1KiGGhDg2yrbseXr/zBNaDhBtTFh6XJffqqwmgby1PXB6PWwfvWXooJMaQiFZczLWeMBl8v6XbSN6jtMTh/PQlKai6BcDd4LM8GQ7VHpSeff4qXEU4Vpnadjgs8VDPOHng6/HV+wDs8q2LrlMbnxLWxbCjOMUB6w7YnSrwH9owzKSoUs/531I4tTCRQIgipJtTK2b881/8osVjdMGS1mDXhBWO+OM0LCAdORJz+kN4PIkXXvKLt6jX74k6z4M3swFaqqtlTduN2Yy/ErsjguQO1VZfHmcpNssmJXI5QB9sxA= - file: bin/* - file_glob: true - on: - repo: Dreamacro/clash - branch: master - tags: true diff --git a/README.md b/README.md index a1d38d2507..b3aae22d5b 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,6 @@

A rule-based tunnel in Go.

- - Travis-CI - From f4326daaa4b2ed3019a65e9594178b7dda71fa0f Mon Sep 17 00:00:00 2001 From: Kirill Motkov Date: Mon, 14 Oct 2019 12:13:23 +0300 Subject: [PATCH 267/535] Chore: code style improvements (#361) --- adapters/outbound/util.go | 7 ++++--- common/observable/observable_test.go | 4 ++-- component/v2ray-plugin/mux.go | 2 +- dns/util.go | 9 +++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 22b2d95355..4fdbe0035b 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -33,11 +33,12 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) { port := u.Port() if port == "" { - if u.Scheme == "https" { + switch u.Scheme { + case "https": port = "443" - } else if u.Scheme == "http" { + case "http": port = "80" - } else { + default: err = fmt.Errorf("%s scheme not Support", rawURL) return } diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index c3d178dbcb..41ee272dae 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -28,7 +28,7 @@ func TestObservable(t *testing.T) { } count := 0 for range data { - count = count + 1 + count++ } if count != 5 { t.Error("Revc number error") @@ -46,7 +46,7 @@ func TestObservable_MutilSubscribe(t *testing.T) { wg.Add(2) waitCh := func(ch <-chan interface{}) { for range ch { - count = count + 1 + count++ } wg.Done() } diff --git a/component/v2ray-plugin/mux.go b/component/v2ray-plugin/mux.go index 7191331b17..6bff27bfb2 100644 --- a/component/v2ray-plugin/mux.go +++ b/component/v2ray-plugin/mux.go @@ -52,7 +52,7 @@ func (m *Mux) Read(b []byte) (int, error) { if err != nil { return 0, err } - m.remain = m.remain - n + m.remain -= n return n, nil } diff --git a/dns/util.go b/dns/util.go index e29ea1b003..cb6a1c4e6e 100644 --- a/dns/util.go +++ b/dns/util.go @@ -81,13 +81,14 @@ func (e EnhancedMode) String() string { func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) { var ttl time.Duration - if len(msg.Answer) != 0 { + switch { + case len(msg.Answer) != 0: ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second - } else if len(msg.Ns) != 0 { + case len(msg.Ns) != 0: ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second - } else if len(msg.Extra) != 0 { + case len(msg.Extra) != 0: ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second - } else { + default: log.Debugln("[DNS] response msg error: %#v", msg) return } From 0a3595414e2659756075b3fd1703336c0fcaff1e Mon Sep 17 00:00:00 2001 From: Zephyr Date: Mon, 14 Oct 2019 18:11:22 +0800 Subject: [PATCH 268/535] Feature: can set specify config file path in cli (#360) --- constant/path.go | 24 +++++++++++++++--------- main.go | 27 ++++++++++++++++++++------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/constant/path.go b/constant/path.go index 4eff23f7af..087a677776 100644 --- a/constant/path.go +++ b/constant/path.go @@ -11,32 +11,38 @@ const Name = "clash" var Path *path type path struct { - homedir string + homeDir string + configFile string } func init() { - homedir, err := os.UserHomeDir() + homeDir, err := os.UserHomeDir() if err != nil { - homedir, _ = os.Getwd() + homeDir, _ = os.Getwd() } - homedir = P.Join(homedir, ".config", Name) - Path = &path{homedir: homedir} + homeDir = P.Join(homeDir, ".config", Name) + Path = &path{homeDir: homeDir, configFile: "config.yaml"} } // SetHomeDir is used to set the configuration path func SetHomeDir(root string) { - Path = &path{homedir: root} + Path.homeDir = root +} + +// SetConfig is used to set the configuration file +func SetConfig(file string) { + Path.configFile = file } func (p *path) HomeDir() string { - return p.homedir + return p.homeDir } func (p *path) Config() string { - return P.Join(p.homedir, "config.yaml") + return p.configFile } func (p *path) MMDB() string { - return P.Join(p.homedir, "Country.mmdb") + return P.Join(p.homeDir, "Country.mmdb") } diff --git a/main.go b/main.go index cb8b89bca6..a08a5eb5d5 100644 --- a/main.go +++ b/main.go @@ -17,12 +17,14 @@ import ( ) var ( - version bool - homedir string + version bool + homeDir string + configFile string ) func init() { - flag.StringVar(&homedir, "d", "", "set configuration directory") + flag.StringVar(&homeDir, "d", "", "set configuration directory") + flag.StringVar(&configFile, "f", "", "specify configuration file") flag.BoolVar(&version, "v", false, "show current version of clash") flag.Parse() } @@ -33,12 +35,23 @@ func main() { return } - if homedir != "" { - if !filepath.IsAbs(homedir) { + if homeDir != "" { + if !filepath.IsAbs(homeDir) { currentDir, _ := os.Getwd() - homedir = filepath.Join(currentDir, homedir) + homeDir = filepath.Join(currentDir, homeDir) } - C.SetHomeDir(homedir) + C.SetHomeDir(homeDir) + } + + if configFile != "" { + if !filepath.IsAbs(configFile) { + currentDir, _ := os.Getwd() + configFile = filepath.Join(currentDir, configFile) + } + C.SetConfig(configFile) + } else { + configFile := filepath.Join(C.Path.HomeDir(), C.Path.Config()) + C.SetConfig(configFile) } if err := config.Init(C.Path.HomeDir()); err != nil { From e5284cf647717a8087a185d88d15a01096274bc2 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 18 Oct 2019 11:12:35 +0800 Subject: [PATCH 269/535] License: use GPL 3.0 --- LICENSE | 695 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 674 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index ec13360b54..f288702d2f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,674 @@ -MIT License - -Copyright (c) 2018 Dreamacro - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 65f4a35e7f357efc12180ee2a31c860f04aec39e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 23 Oct 2019 12:35:41 +0800 Subject: [PATCH 270/535] Fix: use difference dialer --- adapters/outbound/util.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 4fdbe0035b..3af093c464 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -93,8 +93,6 @@ func dialContext(ctx context.Context, network, address string) (net.Conn, error) return nil, err } - dialer := net.Dialer{} - returned := make(chan struct{}) defer close(returned) @@ -109,6 +107,7 @@ func dialContext(ctx context.Context, network, address string) (net.Conn, error) var primary, fallback dialResult startRacer := func(ctx context.Context, host string, ipv6 bool) { + dialer := net.Dialer{} result := dialResult{ipv6: ipv6, done: true} defer func() { select { From 52cfa946522067c170084c89fc31c85cc25915e8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 26 Oct 2019 22:12:33 +0800 Subject: [PATCH 271/535] Fix: HTTP proxy should copy body --- tunnel/connection.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tunnel/connection.go b/tunnel/connection.go index e48167f295..1f9f335bef 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -36,6 +36,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { if err != nil { break } + defer resp.Body.Close() adapters.RemoveHopByHopHeaders(resp.Header) if resp.StatusCode == http.StatusContinue { @@ -59,6 +60,13 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } + buf := pool.BufPool.Get().([]byte) + _, err = io.CopyBuffer(request, resp.Body, buf) + pool.BufPool.Put(buf[:cap(buf)]) + if err != nil && err != io.EOF { + break + } + req, err = http.ReadRequest(inboundReeder) if err != nil { break From 207371aeaeec4cac5a8ca695a7aaf8a846138260 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 27 Oct 2019 21:44:07 +0800 Subject: [PATCH 272/535] Feature: add experimental connections API --- adapters/inbound/http.go | 1 + adapters/inbound/https.go | 3 + adapters/inbound/util.go | 11 ++-- component/fakeip/pool.go | 3 +- constant/metadata.go | 41 ++++++++++--- constant/rule.go | 4 +- constant/traffic.go | 55 ----------------- hub/route/connections.go | 91 ++++++++++++++++++++++++++++ hub/route/server.go | 3 +- rules/geoip.go | 2 +- rules/ipcidr.go | 2 +- tunnel/connection.go | 12 ++-- tunnel/manager.go | 87 +++++++++++++++++++++++++++ tunnel/tracker.go | 122 ++++++++++++++++++++++++++++++++++++++ tunnel/tunnel.go | 29 ++++----- tunnel/util.go | 29 --------- 16 files changed, 365 insertions(+), 130 deletions(-) delete mode 100644 constant/traffic.go create mode 100644 hub/route/connections.go create mode 100644 tunnel/manager.go create mode 100644 tunnel/tracker.go delete mode 100644 tunnel/util.go diff --git a/adapters/inbound/http.go b/adapters/inbound/http.go index f60544f8c7..7aeb4c6546 100644 --- a/adapters/inbound/http.go +++ b/adapters/inbound/http.go @@ -23,6 +23,7 @@ func (h *HTTPAdapter) Metadata() *C.Metadata { // NewHTTP is HTTPAdapter generator func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { metadata := parseHTTPAddr(request) + metadata.Type = C.HTTP if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { metadata.SrcIP = ip metadata.SrcPort = port diff --git a/adapters/inbound/https.go b/adapters/inbound/https.go index a880af74a3..124b80c22f 100644 --- a/adapters/inbound/https.go +++ b/adapters/inbound/https.go @@ -3,11 +3,14 @@ package adapters import ( "net" "net/http" + + C "github.com/Dreamacro/clash/constant" ) // NewHTTPS is HTTPAdapter generator func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { metadata := parseHTTPAddr(request) + metadata.Type = C.HTTPCONNECT if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { metadata.SrcIP = ip metadata.SrcPort = port diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index 9d977cf835..9a16a6cf6e 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -20,11 +20,11 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata { metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) case socks5.AtypIPv4: ip := net.IP(target[1 : 1+net.IPv4len]) - metadata.DstIP = &ip + metadata.DstIP = ip metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) case socks5.AtypIPv6: ip := net.IP(target[1 : 1+net.IPv6len]) - metadata.DstIP = &ip + metadata.DstIP = ip metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) } @@ -40,7 +40,6 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { metadata := &C.Metadata{ NetWork: C.TCP, - Type: C.HTTP, AddrType: C.AtypDomainName, Host: host, DstIP: nil, @@ -55,18 +54,18 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { default: metadata.AddrType = C.AtypIPv4 } - metadata.DstIP = &ip + metadata.DstIP = ip } return metadata } -func parseAddr(addr string) (*net.IP, string, error) { +func parseAddr(addr string) (net.IP, string, error) { host, port, err := net.SplitHostPort(addr) if err != nil { return nil, "", err } ip := net.ParseIP(host) - return &ip, port, nil + return ip, port, nil } diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index e71ddfaa32..86a92ee2ab 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -14,7 +14,7 @@ type Pool struct { min uint32 gateway uint32 offset uint32 - mux *sync.Mutex + mux sync.Mutex cache *cache.LruCache } @@ -111,7 +111,6 @@ func New(ipnet *net.IPNet, size int) (*Pool, error) { min: min, max: max, gateway: min - 1, - mux: &sync.Mutex{}, cache: cache.NewLRUCache(cache.WithSize(size * 2)), }, nil } diff --git a/constant/metadata.go b/constant/metadata.go index cc8703619b..afcd344317 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -1,6 +1,7 @@ package constant import ( + "encoding/json" "net" ) @@ -14,6 +15,7 @@ const ( UDP HTTP Type = iota + HTTPCONNECT SOCKS REDIR ) @@ -27,18 +29,41 @@ func (n *NetWork) String() string { return "udp" } +func (n NetWork) MarshalJSON() ([]byte, error) { + return json.Marshal(n.String()) +} + type Type int +func (t Type) String() string { + switch t { + case HTTP: + return "HTTP" + case HTTPCONNECT: + return "HTTP Connect" + case SOCKS: + return "Socks5" + case REDIR: + return "Redir" + default: + return "Unknown" + } +} + +func (t Type) MarshalJSON() ([]byte, error) { + return json.Marshal(t.String()) +} + // Metadata is used to store connection address type Metadata struct { - NetWork NetWork - Type Type - SrcIP *net.IP - DstIP *net.IP - SrcPort string - DstPort string - AddrType int - Host string + NetWork NetWork `json:"network"` + Type Type `json:"type"` + SrcIP net.IP `json:"sourceIP"` + DstIP net.IP `json:"destinationIP"` + SrcPort string `json:"sourcePort"` + DstPort string `json:"destinationPort"` + AddrType int `json:"-"` + Host string `json:"host"` } func (m *Metadata) RemoteAddress() string { diff --git a/constant/rule.go b/constant/rule.go index 40756264b9..1ac599dd67 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -24,7 +24,7 @@ func (rt RuleType) String() string { case DomainKeyword: return "DomainKeyword" case GEOIP: - return "GEOIP" + return "GeoIP" case IPCIDR: return "IPCIDR" case SrcIPCIDR: @@ -34,7 +34,7 @@ func (rt RuleType) String() string { case DstPort: return "DstPort" case MATCH: - return "MATCH" + return "Match" default: return "Unknown" } diff --git a/constant/traffic.go b/constant/traffic.go deleted file mode 100644 index edf67368f4..0000000000 --- a/constant/traffic.go +++ /dev/null @@ -1,55 +0,0 @@ -package constant - -import ( - "time" -) - -type Traffic struct { - up chan int64 - down chan int64 - upCount int64 - downCount int64 - upTotal int64 - downTotal int64 - interval time.Duration -} - -func (t *Traffic) Up() chan<- int64 { - return t.up -} - -func (t *Traffic) Down() chan<- int64 { - return t.down -} - -func (t *Traffic) Now() (up int64, down int64) { - return t.upTotal, t.downTotal -} - -func (t *Traffic) handle() { - go t.handleCh(t.up, &t.upCount, &t.upTotal) - go t.handleCh(t.down, &t.downCount, &t.downTotal) -} - -func (t *Traffic) handleCh(ch <-chan int64, count *int64, total *int64) { - ticker := time.NewTicker(t.interval) - for { - select { - case n := <-ch: - *count += n - case <-ticker.C: - *total = *count - *count = 0 - } - } -} - -func NewTraffic(interval time.Duration) *Traffic { - t := &Traffic{ - up: make(chan int64), - down: make(chan int64), - interval: interval, - } - go t.handle() - return t -} diff --git a/hub/route/connections.go b/hub/route/connections.go new file mode 100644 index 0000000000..6642b6f2db --- /dev/null +++ b/hub/route/connections.go @@ -0,0 +1,91 @@ +package route + +import ( + "bytes" + "encoding/json" + "net/http" + "strconv" + "time" + + T "github.com/Dreamacro/clash/tunnel" + "github.com/gorilla/websocket" + + "github.com/go-chi/chi" + "github.com/go-chi/render" +) + +func connectionRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getConnections) + r.Delete("/", closeAllConnections) + r.Delete("/{id}", closeConnection) + return r +} + +func getConnections(w http.ResponseWriter, r *http.Request) { + if !websocket.IsWebSocketUpgrade(r) { + snapshot := T.DefaultManager.Snapshot() + render.JSON(w, r, snapshot) + return + } + + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + + intervalStr := r.URL.Query().Get("interval") + interval := 1000 + if intervalStr != "" { + t, err := strconv.Atoi(intervalStr) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, ErrBadRequest) + return + } + + interval = t + } + + buf := &bytes.Buffer{} + sendSnapshot := func() error { + buf.Reset() + snapshot := T.DefaultManager.Snapshot() + if err := json.NewEncoder(buf).Encode(snapshot); err != nil { + return err + } + + return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) + } + + if err := sendSnapshot(); err != nil { + return + } + + tick := time.NewTicker(time.Millisecond * time.Duration(interval)) + for range tick.C { + if err := sendSnapshot(); err != nil { + break + } + } +} + +func closeConnection(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + snapshot := T.DefaultManager.Snapshot() + for _, c := range snapshot.Connections { + if id == c.ID() { + c.Close() + break + } + } + render.NoContent(w, r) +} + +func closeAllConnections(w http.ResponseWriter, r *http.Request) { + snapshot := T.DefaultManager.Snapshot() + for _, c := range snapshot.Connections { + c.Close() + } + render.NoContent(w, r) +} diff --git a/hub/route/server.go b/hub/route/server.go index 394ad40636..560c338fe8 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -67,6 +67,7 @@ func Start(addr string, secret string) { r.Mount("/configs", configRouter()) r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) + r.Mount("/connections", connectionRouter()) }) if uiPath != "" { @@ -140,7 +141,7 @@ func traffic(w http.ResponseWriter, r *http.Request) { } tick := time.NewTicker(time.Second) - t := T.Instance().Traffic() + t := T.DefaultManager buf := &bytes.Buffer{} var err error for range tick.C { diff --git a/rules/geoip.go b/rules/geoip.go index 07cc2eff9c..3ffce9e8c3 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -27,7 +27,7 @@ func (g *GEOIP) IsMatch(metadata *C.Metadata) bool { if metadata.DstIP == nil { return false } - record, _ := mmdb.Country(*metadata.DstIP) + record, _ := mmdb.Country(metadata.DstIP) return record.Country.IsoCode == g.country } diff --git a/rules/ipcidr.go b/rules/ipcidr.go index e81603008e..78a649aba9 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -24,7 +24,7 @@ func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { if i.isSourceIP { ip = metadata.SrcIP } - return ip != nil && i.ipnet.Contains(*ip) + return ip != nil && i.ipnet.Contains(ip) } func (i *IPCIDR) Adapter() string { diff --git a/tunnel/connection.go b/tunnel/connection.go index 1f9f335bef..87faa5bdef 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -13,12 +13,11 @@ import ( ) func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { - conn := newTrafficTrack(outbound, t.traffic) req := request.R host := req.Host inboundReeder := bufio.NewReader(request) - outboundReeder := bufio.NewReader(conn) + outboundReeder := bufio.NewReader(outbound) for { keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" @@ -26,7 +25,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { req.Header.Set("Connection", "close") req.RequestURI = "" adapters.RemoveHopByHopHeaders(req.Header) - err := req.Write(conn) + err := req.Write(outbound) if err != nil { break } @@ -91,7 +90,7 @@ func (t *Tunnel) handleUDPToRemote(conn net.Conn, pc net.PacketConn, addr net.Ad if _, err = pc.WriteTo(buf[:n], addr); err != nil { return } - t.traffic.Up() <- int64(n) + DefaultManager.Upload() <- int64(n) } func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn, key string, timeout time.Duration) { @@ -111,13 +110,12 @@ func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn, key string, if err != nil { return } - t.traffic.Down() <- int64(n) + DefaultManager.Download() <- int64(n) } } func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { - conn := newTrafficTrack(outbound, t.traffic) - relay(request, conn) + relay(request, outbound) } // relay copies between left and right bidirectionally. diff --git a/tunnel/manager.go b/tunnel/manager.go new file mode 100644 index 0000000000..d4d627e025 --- /dev/null +++ b/tunnel/manager.go @@ -0,0 +1,87 @@ +package tunnel + +import ( + "sync" + "time" +) + +var DefaultManager *Manager + +func init() { + DefaultManager = &Manager{ + upload: make(chan int64), + download: make(chan int64), + } + DefaultManager.handle() +} + +type Manager struct { + connections sync.Map + upload chan int64 + download chan int64 + uploadTemp int64 + downloadTemp int64 + uploadBlip int64 + downloadBlip int64 + uploadTotal int64 + downloadTotal int64 +} + +func (m *Manager) Join(c tracker) { + m.connections.Store(c.ID(), c) +} + +func (m *Manager) Leave(c tracker) { + m.connections.Delete(c.ID()) +} + +func (m *Manager) Upload() chan<- int64 { + return m.upload +} + +func (m *Manager) Download() chan<- int64 { + return m.download +} + +func (m *Manager) Now() (up int64, down int64) { + return m.uploadBlip, m.downloadBlip +} + +func (m *Manager) Snapshot() *Snapshot { + connections := []tracker{} + m.connections.Range(func(key, value interface{}) bool { + connections = append(connections, value.(tracker)) + return true + }) + + return &Snapshot{ + UploadTotal: m.uploadTotal, + DownloadTotal: m.downloadTotal, + Connections: connections, + } +} + +func (m *Manager) handle() { + go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal) + go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal) +} + +func (m *Manager) handleCh(ch <-chan int64, temp *int64, blip *int64, total *int64) { + ticker := time.NewTicker(time.Second) + for { + select { + case n := <-ch: + *temp += n + *total += n + case <-ticker.C: + *blip = *temp + *temp = 0 + } + } +} + +type Snapshot struct { + DownloadTotal int64 `json:"downloadTotal"` + UploadTotal int64 `json:"uploadTotal"` + Connections []tracker `json:"connections"` +} diff --git a/tunnel/tracker.go b/tunnel/tracker.go new file mode 100644 index 0000000000..77e442b86b --- /dev/null +++ b/tunnel/tracker.go @@ -0,0 +1,122 @@ +package tunnel + +import ( + "net" + "time" + + C "github.com/Dreamacro/clash/constant" + "github.com/gofrs/uuid" +) + +type tracker interface { + ID() string + Close() error +} + +type trackerInfo struct { + UUID uuid.UUID `json:"id"` + Metadata *C.Metadata `json:"metadata"` + UploadTotal int64 `json:"upload"` + DownloadTotal int64 `json:"download"` + Start time.Time `json:"start"` + Chain C.Chain `json:"chains"` + Rule string `json:"rule"` +} + +type tcpTracker struct { + C.Conn `json:"-"` + *trackerInfo + manager *Manager +} + +func (tt *tcpTracker) ID() string { + return tt.UUID.String() +} + +func (tt *tcpTracker) Read(b []byte) (int, error) { + n, err := tt.Conn.Read(b) + download := int64(n) + tt.manager.Download() <- download + tt.DownloadTotal += download + return n, err +} + +func (tt *tcpTracker) Write(b []byte) (int, error) { + n, err := tt.Conn.Write(b) + upload := int64(n) + tt.manager.Upload() <- upload + tt.UploadTotal += upload + return n, err +} + +func (tt *tcpTracker) Close() error { + tt.manager.Leave(tt) + return tt.Conn.Close() +} + +func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { + uuid, _ := uuid.NewV4() + t := &tcpTracker{ + Conn: conn, + manager: manager, + trackerInfo: &trackerInfo{ + UUID: uuid, + Start: time.Now(), + Metadata: metadata, + Chain: conn.Chains(), + Rule: rule.RuleType().String(), + }, + } + + manager.Join(t) + return t +} + +type udpTracker struct { + C.PacketConn `json:"-"` + *trackerInfo + manager *Manager +} + +func (ut *udpTracker) ID() string { + return ut.UUID.String() +} + +func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { + n, addr, err := ut.PacketConn.ReadFrom(b) + download := int64(n) + ut.manager.Download() <- download + ut.DownloadTotal += download + return n, addr, err +} + +func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { + n, err := ut.PacketConn.WriteTo(b, addr) + upload := int64(n) + ut.manager.Upload() <- upload + ut.UploadTotal += upload + return n, err +} + +func (ut *udpTracker) Close() error { + ut.manager.Leave(ut) + return ut.PacketConn.Close() +} + +func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { + uuid, _ := uuid.NewV4() + ut := &udpTracker{ + PacketConn: conn, + manager: manager, + trackerInfo: &trackerInfo{ + UUID: uuid, + Start: time.Now(), + Metadata: metadata, + Chain: conn.Chains(), + Rule: rule.RuleType().String(), + }, + } + + manager.Join(ut) + return ut +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 32647f1a6d..7eb16af13a 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -30,8 +30,7 @@ type Tunnel struct { natTable *nat.Table rules []C.Rule proxies map[string]C.Proxy - configMux *sync.RWMutex - traffic *C.Traffic + configMux sync.RWMutex // experimental features ignoreResolveFail bool @@ -50,11 +49,6 @@ func (t *Tunnel) Add(req C.ServerAdapter) { } } -// Traffic return traffic of all connections -func (t *Tunnel) Traffic() *C.Traffic { - return t.traffic -} - // Rules return all rules func (t *Tunnel) Rules() []C.Rule { return t.rules @@ -123,7 +117,7 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { // preprocess enhanced-mode metadata if t.needLookupIP(metadata) { - host, exist := dns.DefaultResolver.IPToHost(*metadata.DstIP) + host, exist := dns.DefaultResolver.IPToHost(metadata.DstIP) if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName @@ -188,8 +182,8 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) { wg.Done() return } - pc = rawPc addr = nAddr + pc = newUDPTracker(rawPc, DefaultManager, metadata, rule) if rule != nil { log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) @@ -231,6 +225,7 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) return } + remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule) defer remoteConn.Close() if rule != nil { @@ -259,7 +254,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { if node := dns.DefaultHosts.Search(metadata.Host); node != nil { ip := node.Data.(net.IP) - metadata.DstIP = &ip + metadata.DstIP = ip resolved = true } @@ -273,7 +268,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) } else { log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) - metadata.DstIP = &ip + metadata.DstIP = ip } resolved = true } @@ -296,13 +291,11 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { func newTunnel() *Tunnel { return &Tunnel{ - tcpQueue: channels.NewInfiniteChannel(), - udpQueue: channels.NewInfiniteChannel(), - natTable: nat.New(), - proxies: make(map[string]C.Proxy), - configMux: &sync.RWMutex{}, - traffic: C.NewTraffic(time.Second), - mode: Rule, + tcpQueue: channels.NewInfiniteChannel(), + udpQueue: channels.NewInfiniteChannel(), + natTable: nat.New(), + proxies: make(map[string]C.Proxy), + mode: Rule, } } diff --git a/tunnel/util.go b/tunnel/util.go deleted file mode 100644 index 15c4d3dacc..0000000000 --- a/tunnel/util.go +++ /dev/null @@ -1,29 +0,0 @@ -package tunnel - -import ( - "net" - - C "github.com/Dreamacro/clash/constant" -) - -// TrafficTrack record traffic of net.Conn -type TrafficTrack struct { - net.Conn - traffic *C.Traffic -} - -func (tt *TrafficTrack) Read(b []byte) (int, error) { - n, err := tt.Conn.Read(b) - tt.traffic.Down() <- int64(n) - return n, err -} - -func (tt *TrafficTrack) Write(b []byte) (int, error) { - n, err := tt.Conn.Write(b) - tt.traffic.Up() <- int64(n) - return n, err -} - -func newTrafficTrack(conn net.Conn, traffic *C.Traffic) *TrafficTrack { - return &TrafficTrack{traffic: traffic, Conn: conn} -} From 82a8c03953a508bb59c1a33660de05d2f4c9cadb Mon Sep 17 00:00:00 2001 From: Fndroid <18825176954@163.com> Date: Mon, 28 Oct 2019 00:02:23 +0800 Subject: [PATCH 273/535] Feature: add no-resolve for ip rules (#375) --- README.md | 3 ++- config/config.go | 42 +++++++++++++++++++++----------------- constant/rule.go | 3 ++- rules/base.go | 21 +++++++++++++++++++ rules/domain.go | 6 +++++- rules/domain_keyword.go | 6 +++++- rules/domain_suffix.go | 6 +++++- rules/final.go | 6 +++++- rules/geoip.go | 28 ++++++++++++++++--------- rules/ipcidr.go | 45 ++++++++++++++++++++++++++++++++--------- rules/port.go | 12 +++++++---- tunnel/tunnel.go | 9 +++++++-- 12 files changed, 137 insertions(+), 50 deletions(-) create mode 100644 rules/base.go diff --git a/README.md b/README.md index b3aae22d5b..b85e5f01f4 100644 --- a/README.md +++ b/README.md @@ -279,9 +279,10 @@ Rule: - DOMAIN-KEYWORD,google,auto - DOMAIN,google.com,auto - DOMAIN-SUFFIX,ad.com,REJECT -- IP-CIDR,127.0.0.0/8,DIRECT # rename SOURCE-IP-CIDR and would remove after prerelease - SRC-IP-CIDR,192.168.1.201/32,DIRECT +# optional param "no-resolve" for IP rules (GEOIP IP-CIDR) +- IP-CIDR,127.0.0.0/8,DIRECT - GEOIP,CN,DIRECT - DST-PORT,80,DIRECT - SRC-PORT,7777,DIRECT diff --git a/config/config.go b/config/config.go index 7a5bce4aca..1cccdccdb8 100644 --- a/config/config.go +++ b/config/config.go @@ -427,14 +427,19 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { var ( payload string target string + params = []string{} ) - switch len(rule) { - case 2: + switch l := len(rule); { + case l == 2: target = rule[1] - case 3: + case l == 3: payload = rule[1] target = rule[2] + case l >= 4: + payload = rule[1] + target = rule[2] + params = rule[3:] default: return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line) } @@ -444,7 +449,12 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { } rule = trimArr(rule) - var parsed C.Rule + params = trimArr(params) + var ( + parseErr error + parsed C.Rule + ) + switch rule[0] { case "DOMAIN": parsed = R.NewDomain(payload, target) @@ -453,26 +463,20 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { case "DOMAIN-KEYWORD": parsed = R.NewDomainKeyword(payload, target) case "GEOIP": - parsed = R.NewGEOIP(payload, target) + noResolve := R.HasNoResolve(params) + parsed = R.NewGEOIP(payload, target, noResolve) case "IP-CIDR", "IP-CIDR6": - if rule := R.NewIPCIDR(payload, target, false); rule != nil { - parsed = rule - } + noResolve := R.HasNoResolve(params) + parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRNoResolve(noResolve)) // deprecated when bump to 1.0 case "SOURCE-IP-CIDR": fallthrough case "SRC-IP-CIDR": - if rule := R.NewIPCIDR(payload, target, true); rule != nil { - parsed = rule - } + parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true)) case "SRC-PORT": - if rule := R.NewPort(payload, target, true); rule != nil { - parsed = rule - } + parsed, parseErr = R.NewPort(payload, target, true) case "DST-PORT": - if rule := R.NewPort(payload, target, false); rule != nil { - parsed = rule - } + parsed, parseErr = R.NewPort(payload, target, false) case "MATCH": fallthrough // deprecated when bump to 1.0 @@ -480,8 +484,8 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { parsed = R.NewMatch(target) } - if parsed == nil { - return nil, fmt.Errorf("Rules[%d] [%s] error: payload invalid", idx, line) + if parseErr != nil { + return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } rules = append(rules, parsed) diff --git a/constant/rule.go b/constant/rule.go index 1ac599dd67..4db333b40f 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -42,7 +42,8 @@ func (rt RuleType) String() string { type Rule interface { RuleType() RuleType - IsMatch(metadata *Metadata) bool + Match(metadata *Metadata) bool Adapter() string Payload() string + NoResolveIP() bool } diff --git a/rules/base.go b/rules/base.go new file mode 100644 index 0000000000..793cad7e76 --- /dev/null +++ b/rules/base.go @@ -0,0 +1,21 @@ +package rules + +import ( + "errors" +) + +var ( + errPayload = errors.New("payload error") + errParams = errors.New("params error") + + noResolve = "no-resolve" +) + +func HasNoResolve(params []string) bool { + for _, p := range params { + if p == noResolve { + return true + } + } + return false +} diff --git a/rules/domain.go b/rules/domain.go index 1c0fbdd1a5..373761e567 100644 --- a/rules/domain.go +++ b/rules/domain.go @@ -15,7 +15,7 @@ func (d *Domain) RuleType() C.RuleType { return C.Domain } -func (d *Domain) IsMatch(metadata *C.Metadata) bool { +func (d *Domain) Match(metadata *C.Metadata) bool { if metadata.AddrType != C.AtypDomainName { return false } @@ -30,6 +30,10 @@ func (d *Domain) Payload() string { return d.domain } +func (d *Domain) NoResolveIP() bool { + return false +} + func NewDomain(domain string, adapter string) *Domain { return &Domain{ domain: strings.ToLower(domain), diff --git a/rules/domain_keyword.go b/rules/domain_keyword.go index 0708b10189..3583362589 100644 --- a/rules/domain_keyword.go +++ b/rules/domain_keyword.go @@ -15,7 +15,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType { return C.DomainKeyword } -func (dk *DomainKeyword) IsMatch(metadata *C.Metadata) bool { +func (dk *DomainKeyword) Match(metadata *C.Metadata) bool { if metadata.AddrType != C.AtypDomainName { return false } @@ -31,6 +31,10 @@ func (dk *DomainKeyword) Payload() string { return dk.keyword } +func (dk *DomainKeyword) NoResolveIP() bool { + return false +} + func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { return &DomainKeyword{ keyword: strings.ToLower(keyword), diff --git a/rules/domain_suffix.go b/rules/domain_suffix.go index 710047738c..69bed9bb10 100644 --- a/rules/domain_suffix.go +++ b/rules/domain_suffix.go @@ -15,7 +15,7 @@ func (ds *DomainSuffix) RuleType() C.RuleType { return C.DomainSuffix } -func (ds *DomainSuffix) IsMatch(metadata *C.Metadata) bool { +func (ds *DomainSuffix) Match(metadata *C.Metadata) bool { if metadata.AddrType != C.AtypDomainName { return false } @@ -31,6 +31,10 @@ func (ds *DomainSuffix) Payload() string { return ds.suffix } +func (ds *DomainSuffix) NoResolveIP() bool { + return false +} + func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { return &DomainSuffix{ suffix: strings.ToLower(suffix), diff --git a/rules/final.go b/rules/final.go index 88df0680d2..dc97ffa56f 100644 --- a/rules/final.go +++ b/rules/final.go @@ -12,7 +12,7 @@ func (f *Match) RuleType() C.RuleType { return C.MATCH } -func (f *Match) IsMatch(metadata *C.Metadata) bool { +func (f *Match) Match(metadata *C.Metadata) bool { return true } @@ -24,6 +24,10 @@ func (f *Match) Payload() string { return "" } +func (f *Match) NoResolveIP() bool { + return false +} + func NewMatch(adapter string) *Match { return &Match{ adapter: adapter, diff --git a/rules/geoip.go b/rules/geoip.go index 3ffce9e8c3..56b75fea24 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -15,19 +15,21 @@ var ( ) type GEOIP struct { - country string - adapter string + country string + adapter string + noResolveIP bool } func (g *GEOIP) RuleType() C.RuleType { return C.GEOIP } -func (g *GEOIP) IsMatch(metadata *C.Metadata) bool { - if metadata.DstIP == nil { +func (g *GEOIP) Match(metadata *C.Metadata) bool { + ip := metadata.DstIP + if ip == nil { return false } - record, _ := mmdb.Country(metadata.DstIP) + record, _ := mmdb.Country(ip) return record.Country.IsoCode == g.country } @@ -39,7 +41,11 @@ func (g *GEOIP) Payload() string { return g.country } -func NewGEOIP(country string, adapter string) *GEOIP { +func (g *GEOIP) NoResolveIP() bool { + return g.noResolveIP +} + +func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { once.Do(func() { var err error mmdb, err = geoip2.Open(C.Path.MMDB()) @@ -47,8 +53,12 @@ func NewGEOIP(country string, adapter string) *GEOIP { log.Fatalf("Can't load mmdb: %s", err.Error()) } }) - return &GEOIP{ - country: country, - adapter: adapter, + + geoip := &GEOIP{ + country: country, + adapter: adapter, + noResolveIP: noResolveIP, } + + return geoip } diff --git a/rules/ipcidr.go b/rules/ipcidr.go index 78a649aba9..3e128f3e41 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -6,10 +6,25 @@ import ( C "github.com/Dreamacro/clash/constant" ) +type IPCIDROption func(*IPCIDR) + +func WithIPCIDRSourceIP(b bool) IPCIDROption { + return func(i *IPCIDR) { + i.isSourceIP = b + } +} + +func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { + return func(i *IPCIDR) { + i.noResolveIP = !noResolve + } +} + type IPCIDR struct { - ipnet *net.IPNet - adapter string - isSourceIP bool + ipnet *net.IPNet + adapter string + isSourceIP bool + noResolveIP bool } func (i *IPCIDR) RuleType() C.RuleType { @@ -19,7 +34,7 @@ func (i *IPCIDR) RuleType() C.RuleType { return C.IPCIDR } -func (i *IPCIDR) IsMatch(metadata *C.Metadata) bool { +func (i *IPCIDR) Match(metadata *C.Metadata) bool { ip := metadata.DstIP if i.isSourceIP { ip = metadata.SrcIP @@ -35,14 +50,24 @@ func (i *IPCIDR) Payload() string { return i.ipnet.String() } -func NewIPCIDR(s string, adapter string, isSourceIP bool) *IPCIDR { +func (i *IPCIDR) NoResolveIP() bool { + return i.noResolveIP +} + +func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { _, ipnet, err := net.ParseCIDR(s) if err != nil { - return nil + return nil, errPayload + } + + ipcidr := &IPCIDR{ + ipnet: ipnet, + adapter: adapter, } - return &IPCIDR{ - ipnet: ipnet, - adapter: adapter, - isSourceIP: isSourceIP, + + for _, o := range opts { + o(ipcidr) } + + return ipcidr, nil } diff --git a/rules/port.go b/rules/port.go index ba9bad57c2..54eb230623 100644 --- a/rules/port.go +++ b/rules/port.go @@ -19,7 +19,7 @@ func (p *Port) RuleType() C.RuleType { return C.DstPort } -func (p *Port) IsMatch(metadata *C.Metadata) bool { +func (p *Port) Match(metadata *C.Metadata) bool { if p.isSource { return metadata.SrcPort == p.port } @@ -34,14 +34,18 @@ func (p *Port) Payload() string { return p.port } -func NewPort(port string, adapter string, isSource bool) *Port { +func (p *Port) NoResolveIP() bool { + return false +} + +func NewPort(port string, adapter string, isSource bool) (*Port, error) { _, err := strconv.Atoi(port) if err != nil { - return nil + return nil, errPayload } return &Port{ adapter: adapter, port: port, isSource: isSource, - } + }, nil } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 7eb16af13a..29196556f1 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -115,6 +115,11 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { } func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { + // handle host equal IP string + if ip := net.ParseIP(metadata.Host); ip != nil { + metadata.DstIP = ip + } + // preprocess enhanced-mode metadata if t.needLookupIP(metadata) { host, exist := dns.DefaultResolver.IPToHost(metadata.DstIP) @@ -243,7 +248,7 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { } func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { - return (rule.RuleType() == C.GEOIP || rule.RuleType() == C.IPCIDR) && metadata.Host != "" && metadata.DstIP == nil + return !rule.NoResolveIP() && metadata.Host != "" && metadata.DstIP == nil } func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { @@ -273,7 +278,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { resolved = true } - if rule.IsMatch(metadata) { + if rule.Match(metadata) { adapter, ok := t.proxies[rule.Adapter()] if !ok { continue From 6d375ac37ea76429d04ba71434eaa5860e9ffede Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 28 Oct 2019 12:58:39 +0800 Subject: [PATCH 274/535] Fix: new tracker crash when rule is nil --- tunnel/tracker.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tunnel/tracker.go b/tunnel/tracker.go index 77e442b86b..e96c28356d 100644 --- a/tunnel/tracker.go +++ b/tunnel/tracker.go @@ -56,6 +56,11 @@ func (tt *tcpTracker) Close() error { func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { uuid, _ := uuid.NewV4() + ruleType := "" + if rule != nil { + ruleType = rule.RuleType().String() + } + t := &tcpTracker{ Conn: conn, manager: manager, @@ -64,7 +69,7 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), - Rule: rule.RuleType().String(), + Rule: ruleType, }, } @@ -105,6 +110,11 @@ func (ut *udpTracker) Close() error { func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { uuid, _ := uuid.NewV4() + ruleType := "" + if rule != nil { + ruleType = rule.RuleType().String() + } + ut := &udpTracker{ PacketConn: conn, manager: manager, @@ -113,7 +123,7 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), - Rule: rule.RuleType().String(), + Rule: ruleType, }, } From 1948ea11eff6b0b84f6e3b13ac8bfe1cfb27d698 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Wed, 30 Oct 2019 15:43:55 +0800 Subject: [PATCH 275/535] Fix: refactor observable to solve a small-probability crash --- common/observable/observable.go | 46 +++++++++++++--------------- common/observable/observable_test.go | 30 ++++++------------ 2 files changed, 31 insertions(+), 45 deletions(-) diff --git a/common/observable/observable.go b/common/observable/observable.go index 4b216d4f81..64bd0a0a5e 100644 --- a/common/observable/observable.go +++ b/common/observable/observable.go @@ -7,60 +7,58 @@ import ( type Observable struct { iterable Iterable - listener *sync.Map + listener map[Subscription]*Subscriber + mux sync.Mutex done bool - doneLock sync.RWMutex } func (o *Observable) process() { for item := range o.iterable { - o.listener.Range(func(key, value interface{}) bool { - elm := value.(*Subscriber) - elm.Emit(item) - return true - }) + o.mux.Lock() + for _, sub := range o.listener { + sub.Emit(item) + } + o.mux.Unlock() } o.close() } func (o *Observable) close() { - o.doneLock.Lock() - o.done = true - o.doneLock.Unlock() + o.mux.Lock() + defer o.mux.Unlock() - o.listener.Range(func(key, value interface{}) bool { - elm := value.(*Subscriber) - elm.Close() - return true - }) + o.done = true + for _, sub := range o.listener { + sub.Close() + } } func (o *Observable) Subscribe() (Subscription, error) { - o.doneLock.RLock() - done := o.done - o.doneLock.RUnlock() - if done == true { + o.mux.Lock() + defer o.mux.Unlock() + if o.done { return nil, errors.New("Observable is closed") } subscriber := newSubscriber() - o.listener.Store(subscriber.Out(), subscriber) + o.listener[subscriber.Out()] = subscriber return subscriber.Out(), nil } func (o *Observable) UnSubscribe(sub Subscription) { - elm, exist := o.listener.Load(sub) + o.mux.Lock() + defer o.mux.Unlock() + subscriber, exist := o.listener[sub] if !exist { return } - subscriber := elm.(*Subscriber) - o.listener.Delete(subscriber.Out()) + delete(o.listener, sub) subscriber.Close() } func NewObservable(any Iterable) *Observable { observable := &Observable{ iterable: any, - listener: &sync.Map{}, + listener: map[Subscription]*Subscriber{}, } go observable.process() return observable diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index 41ee272dae..d965fa3b0f 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -5,6 +5,8 @@ import ( "sync" "testing" "time" + + "github.com/stretchr/testify/assert" ) func iterator(item []interface{}) chan interface{} { @@ -23,16 +25,12 @@ func TestObservable(t *testing.T) { iter := iterator([]interface{}{1, 2, 3, 4, 5}) src := NewObservable(iter) data, err := src.Subscribe() - if err != nil { - t.Error(err) - } + assert.Nil(t, err) count := 0 for range data { count++ } - if count != 5 { - t.Error("Revc number error") - } + assert.Equal(t, count, 5) } func TestObservable_MutilSubscribe(t *testing.T) { @@ -53,23 +51,17 @@ func TestObservable_MutilSubscribe(t *testing.T) { go waitCh(ch1) go waitCh(ch2) wg.Wait() - if count != 10 { - t.Error("Revc number error") - } + assert.Equal(t, count, 10) } func TestObservable_UnSubscribe(t *testing.T) { iter := iterator([]interface{}{1, 2, 3, 4, 5}) src := NewObservable(iter) data, err := src.Subscribe() - if err != nil { - t.Error(err) - } + assert.Nil(t, err) src.UnSubscribe(data) _, open := <-data - if open { - t.Error("Revc number error") - } + assert.False(t, open) } func TestObservable_SubscribeClosedSource(t *testing.T) { @@ -79,9 +71,7 @@ func TestObservable_SubscribeClosedSource(t *testing.T) { <-data _, closed := src.Subscribe() - if closed == nil { - t.Error("Observable should be closed") - } + assert.NotNil(t, closed) } func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { @@ -118,7 +108,5 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) { } wg.Wait() now := runtime.NumGoroutine() - if init != now { - t.Errorf("Goroutine Leak: init %d now %d", init, now) - } + assert.Equal(t, init, now) } From e1030401582eacd7068d25b678477c39d7b2dc89 Mon Sep 17 00:00:00 2001 From: Fndroid <18825176954@163.com> Date: Mon, 4 Nov 2019 10:42:39 +0800 Subject: [PATCH 276/535] Fix: NoResolveIP should return current value (#390) --- config/config.go | 2 +- rules/domain.go | 2 +- rules/domain_keyword.go | 2 +- rules/domain_suffix.go | 2 +- rules/final.go | 2 +- rules/ipcidr.go | 2 +- rules/port.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/config/config.go b/config/config.go index 1cccdccdb8..aee3129eaa 100644 --- a/config/config.go +++ b/config/config.go @@ -472,7 +472,7 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { case "SOURCE-IP-CIDR": fallthrough case "SRC-IP-CIDR": - parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true)) + parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true), R.WithIPCIDRNoResolve(true)) case "SRC-PORT": parsed, parseErr = R.NewPort(payload, target, true) case "DST-PORT": diff --git a/rules/domain.go b/rules/domain.go index 373761e567..14c0ffb93b 100644 --- a/rules/domain.go +++ b/rules/domain.go @@ -31,7 +31,7 @@ func (d *Domain) Payload() string { } func (d *Domain) NoResolveIP() bool { - return false + return true } func NewDomain(domain string, adapter string) *Domain { diff --git a/rules/domain_keyword.go b/rules/domain_keyword.go index 3583362589..dc7578e3bf 100644 --- a/rules/domain_keyword.go +++ b/rules/domain_keyword.go @@ -32,7 +32,7 @@ func (dk *DomainKeyword) Payload() string { } func (dk *DomainKeyword) NoResolveIP() bool { - return false + return true } func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { diff --git a/rules/domain_suffix.go b/rules/domain_suffix.go index 69bed9bb10..6999f29f44 100644 --- a/rules/domain_suffix.go +++ b/rules/domain_suffix.go @@ -32,7 +32,7 @@ func (ds *DomainSuffix) Payload() string { } func (ds *DomainSuffix) NoResolveIP() bool { - return false + return true } func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { diff --git a/rules/final.go b/rules/final.go index dc97ffa56f..62484999d0 100644 --- a/rules/final.go +++ b/rules/final.go @@ -25,7 +25,7 @@ func (f *Match) Payload() string { } func (f *Match) NoResolveIP() bool { - return false + return true } func NewMatch(adapter string) *Match { diff --git a/rules/ipcidr.go b/rules/ipcidr.go index 3e128f3e41..18763f1ecf 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -16,7 +16,7 @@ func WithIPCIDRSourceIP(b bool) IPCIDROption { func WithIPCIDRNoResolve(noResolve bool) IPCIDROption { return func(i *IPCIDR) { - i.noResolveIP = !noResolve + i.noResolveIP = noResolve } } diff --git a/rules/port.go b/rules/port.go index 54eb230623..e6e3b0407b 100644 --- a/rules/port.go +++ b/rules/port.go @@ -35,7 +35,7 @@ func (p *Port) Payload() string { } func (p *Port) NoResolveIP() bool { - return false + return true } func NewPort(port string, adapter string, isSource bool) (*Port, error) { From e48ccdd4c89b714bef3a231233ccec64c03e4e50 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 4 Nov 2019 23:07:19 +0800 Subject: [PATCH 277/535] Fix: unsupported rule should throw error --- config/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/config.go b/config/config.go index aee3129eaa..3f4da5aa82 100644 --- a/config/config.go +++ b/config/config.go @@ -482,6 +482,8 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { // deprecated when bump to 1.0 case "FINAL": parsed = R.NewMatch(target) + default: + parseErr = fmt.Errorf("unsupported rule type %s", rule[0]) } if parseErr != nil { From 3b0cc8548c9422c3630cab58f9ead7eb278e71e4 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 6 Nov 2019 12:05:18 +0800 Subject: [PATCH 278/535] Chore: add cache for github actions --- .github/workflows/go.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d9db9e1ad3..7eaaca8d16 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,8 +13,16 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v1 + + - name: Cache go module + uses: actions/cache@v1 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- - - name: Get dependencies + - name: Get dependencies and run test run: | go test ./... From 8e10e67b89257cb257e788919ac230b5707cf9d1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 12 Nov 2019 10:09:12 +0800 Subject: [PATCH 279/535] Fix: throw correct error in read config --- config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index 3f4da5aa82..1e5ab5fc6a 100644 --- a/config/config.go +++ b/config/config.go @@ -121,11 +121,11 @@ func readRawConfig(path string) ([]byte, error) { } path = path[:len(path)-5] + ".yml" - if _, err = os.Stat(path); err == nil { + if _, fallbackErr := os.Stat(path); fallbackErr == nil { return ioutil.ReadFile(path) } - return data, nil + return data, err } func readConfig(path string) (*rawConfig, error) { From 3e4bc9f85cb2191ebe6994f74138ddf2e12f1758 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 1 Dec 2019 13:22:47 +0800 Subject: [PATCH 280/535] Feature: update config API support raw yaml payload --- config/config.go | 54 ++++++---------------------------------- constant/path.go | 10 ++++++++ hub/executor/executor.go | 52 +++++++++++++++++++++++++++++++++++++- hub/route/configs.go | 38 +++++++++++++++++++--------- 4 files changed, 94 insertions(+), 60 deletions(-) diff --git a/config/config.go b/config/config.go index 1e5ab5fc6a..e53580564e 100644 --- a/config/config.go +++ b/config/config.go @@ -2,11 +2,9 @@ package config import ( "fmt" - "io/ioutil" "net" "net/url" "os" - "path/filepath" "strings" adapters "github.com/Dreamacro/clash/adapters/outbound" @@ -109,40 +107,12 @@ type rawConfig struct { Rule []string `yaml:"Rule"` } -// forward compatibility before 1.0 -func readRawConfig(path string) ([]byte, error) { - data, err := ioutil.ReadFile(path) - if err == nil && len(data) != 0 { - return data, nil - } - - if filepath.Ext(path) != ".yaml" { - return nil, err - } - - path = path[:len(path)-5] + ".yml" - if _, fallbackErr := os.Stat(path); fallbackErr == nil { - return ioutil.ReadFile(path) - } - - return data, err -} - -func readConfig(path string) (*rawConfig, error) { - if _, err := os.Stat(path); os.IsNotExist(err) { - return nil, err - } - data, err := readRawConfig(path) - if err != nil { - return nil, err - } - - if len(data) == 0 { - return nil, fmt.Errorf("Configuration file %s is empty", path) - } +// Parse config +func Parse(buf []byte) (*Config, error) { + config := &Config{} // config with some default value - rawConfig := &rawConfig{ + rawCfg := &rawConfig{ AllowLan: false, BindAddress: "*", Mode: T.Rule, @@ -164,18 +134,10 @@ func readConfig(path string) (*rawConfig, error) { }, }, } - err = yaml.Unmarshal([]byte(data), &rawConfig) - return rawConfig, err -} - -// Parse config -func Parse(path string) (*Config, error) { - config := &Config{} - - rawCfg, err := readConfig(path) - if err != nil { + if err := yaml.Unmarshal(buf, &rawCfg); err != nil { return nil, err } + config.Experimental = &rawCfg.Experimental general, err := parseGeneral(rawCfg) @@ -226,9 +188,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) { logLevel := cfg.LogLevel if externalUI != "" { - if !filepath.IsAbs(externalUI) { - externalUI = filepath.Join(C.Path.HomeDir(), externalUI) - } + externalUI = C.Path.Reslove(externalUI) if _, err := os.Stat(externalUI); os.IsNotExist(err) { return nil, fmt.Errorf("external-ui: %s not exist", externalUI) diff --git a/constant/path.go b/constant/path.go index 087a677776..9af355b259 100644 --- a/constant/path.go +++ b/constant/path.go @@ -3,6 +3,7 @@ package constant import ( "os" P "path" + "path/filepath" ) const Name = "clash" @@ -43,6 +44,15 @@ func (p *path) Config() string { return p.configFile } +// Reslove return a absolute path or a relative path with homedir +func (p *path) Reslove(path string) string { + if !filepath.IsAbs(path) { + return filepath.Join(p.HomeDir(), path) + } + + return path +} + func (p *path) MMDB() string { return P.Join(p.homeDir, "Country.mmdb") } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 321d68304b..ddfa3e6d03 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -1,6 +1,11 @@ package executor import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "github.com/Dreamacro/clash/component/auth" trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/config" @@ -12,6 +17,41 @@ import ( T "github.com/Dreamacro/clash/tunnel" ) +// forward compatibility before 1.0 +func readRawConfig(path string) ([]byte, error) { + data, err := ioutil.ReadFile(path) + if err == nil && len(data) != 0 { + return data, nil + } + + if filepath.Ext(path) != ".yaml" { + return nil, err + } + + path = path[:len(path)-5] + ".yml" + if _, fallbackErr := os.Stat(path); fallbackErr == nil { + return ioutil.ReadFile(path) + } + + return data, err +} + +func readConfig(path string) ([]byte, error) { + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil, err + } + data, err := readRawConfig(path) + if err != nil { + return nil, err + } + + if len(data) == 0 { + return nil, fmt.Errorf("Configuration file %s is empty", path) + } + + return data, err +} + // Parse config with default config path func Parse() (*config.Config, error) { return ParseWithPath(C.Path.Config()) @@ -19,7 +59,17 @@ func Parse() (*config.Config, error) { // ParseWithPath parse config with custom config path func ParseWithPath(path string) (*config.Config, error) { - return config.Parse(path) + buf, err := readConfig(path) + if err != nil { + return nil, err + } + + return config.Parse(buf) +} + +// Parse config with default config path +func ParseWithBytes(buf []byte) (*config.Config, error) { + return ParseWithPath(C.Path.Config()) } // ApplyConfig dispatch configure to all parts diff --git a/hub/route/configs.go b/hub/route/configs.go index 08856a4335..ea720a7f79 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -4,6 +4,7 @@ import ( "net/http" "path/filepath" + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" P "github.com/Dreamacro/clash/proxy" @@ -77,7 +78,8 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { } type updateConfigRequest struct { - Path string `json:"path"` + Path string `json:"path"` + Payload string `json:"payload"` } func updateConfigs(w http.ResponseWriter, r *http.Request) { @@ -88,18 +90,30 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { return } - if !filepath.IsAbs(req.Path) { - render.Status(r, http.StatusBadRequest) - render.JSON(w, r, newError("path is not a absoluted path")) - return - } - force := r.URL.Query().Get("force") == "true" - cfg, err := executor.ParseWithPath(req.Path) - if err != nil { - render.Status(r, http.StatusBadRequest) - render.JSON(w, r, newError(err.Error())) - return + var cfg *config.Config + var err error + + if req.Payload != "" { + cfg, err = executor.ParseWithBytes([]byte(req.Payload)) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) + return + } + } else { + if !filepath.IsAbs(req.Path) { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError("path is not a absoluted path")) + return + } + + cfg, err = executor.ParseWithPath(req.Path) + if err != nil { + render.Status(r, http.StatusBadRequest) + render.JSON(w, r, newError(err.Error())) + return + } } executor.ApplyConfig(cfg, force) From 93e0dbdc785a8cd744d3ae88c9bb352c769ee719 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 5 Dec 2019 00:17:24 +0800 Subject: [PATCH 281/535] Feature: lru cache add evict callback --- common/cache/lrucache.go | 14 ++++++++++++++ common/cache/lrucache_test.go | 21 +++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 1cb278d9c3..a73b0b9eed 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -11,6 +11,16 @@ import ( // Option is part of Functional Options Pattern type Option func(*LruCache) +// EvictCallback is used to get a callback when a cache entry is evicted +type EvictCallback func(key interface{}, value interface{}) + +// WithEvict set the evict callback +func WithEvict(cb EvictCallback) Option { + return func(l *LruCache) { + l.onEvict = cb + } +} + // WithUpdateAgeOnGet update expires when Get element func WithUpdateAgeOnGet() Option { return func(l *LruCache) { @@ -42,6 +52,7 @@ type LruCache struct { cache map[interface{}]*list.Element lru *list.List // Front is least-recent updateAgeOnGet bool + onEvict EvictCallback } // NewLRUCache creates an LruCache @@ -148,6 +159,9 @@ func (c *LruCache) deleteElement(le *list.Element) { c.lru.Remove(le) e := le.Value.(*entry) delete(c.cache, e.key) + if c.onEvict != nil { + c.onEvict(e.key, e.value) + } } type entry struct { diff --git a/common/cache/lrucache_test.go b/common/cache/lrucache_test.go index 31f9a919e9..b296d6b984 100644 --- a/common/cache/lrucache_test.go +++ b/common/cache/lrucache_test.go @@ -115,3 +115,24 @@ func TestMaxSize(t *testing.T) { _, ok = c.Get("foo") assert.False(t, ok) } + +func TestExist(t *testing.T) { + c := NewLRUCache(WithSize(1)) + c.Set(1, 2) + assert.True(t, c.Exist(1)) + c.Set(2, 3) + assert.False(t, c.Exist(1)) +} + +func TestEvict(t *testing.T) { + temp := 0 + evict := func(key interface{}, value interface{}) { + temp = key.(int) + value.(int) + } + + c := NewLRUCache(WithEvict(evict), WithSize(1)) + c.Set(1, 2) + c.Set(2, 3) + + assert.Equal(t, temp, 3) +} From ad53b42a68715f390e851c0602f7c1fde13e2888 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 5 Dec 2019 14:12:29 +0800 Subject: [PATCH 282/535] Fix: vmess websocket udp crash --- component/vmess/websocket.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index ae737d944b..30d2c3ae7c 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "strings" + "sync" "time" "github.com/gorilla/websocket" @@ -17,6 +18,10 @@ type websocketConn struct { conn *websocket.Conn reader io.Reader remoteAddr net.Addr + + // https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency + rMux sync.Mutex + wMux sync.Mutex } type WebsocketConfig struct { @@ -29,6 +34,8 @@ type WebsocketConfig struct { // Read implements net.Conn.Read() func (wsc *websocketConn) Read(b []byte) (int, error) { + wsc.rMux.Lock() + defer wsc.rMux.Unlock() for { reader, err := wsc.getReader() if err != nil { @@ -46,6 +53,8 @@ func (wsc *websocketConn) Read(b []byte) (int, error) { // Write implements io.Writer. func (wsc *websocketConn) Write(b []byte) (int, error) { + wsc.wMux.Lock() + defer wsc.wMux.Unlock() if err := wsc.conn.WriteMessage(websocket.BinaryMessage, b); err != nil { return 0, err } From b8267a69f69380f5190097552549d2727964d82e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 5 Dec 2019 17:51:21 +0800 Subject: [PATCH 283/535] Chore: throw more detail dial error --- adapters/outbound/http.go | 2 +- adapters/outbound/shadowsocks.go | 12 ++++++------ adapters/outbound/snell.go | 4 ++-- adapters/outbound/socks5.go | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 77b835b6ba..9dc4cf494a 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -45,7 +45,7 @@ func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, e } if err != nil { - return nil, fmt.Errorf("%s connect error", h.addr) + return nil, fmt.Errorf("%s connect error: %w", h.addr, err) } tcpKeepAlive(c) if err := h.shakeHand(metadata, c); err != nil { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 22d160ba4c..091f6f375c 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -61,7 +61,7 @@ type v2rayObfsOption struct { func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { c, err := dialContext(ctx, "tcp", ss.server) if err != nil { - return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) + return nil, fmt.Errorf("%s connect error: %w", ss.server, err) } tcpKeepAlive(c) switch ss.obfsMode { @@ -74,7 +74,7 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C var err error c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption) if err != nil { - return nil, fmt.Errorf("%s connect error: %s", ss.server, err.Error()) + return nil, fmt.Errorf("%s connect error: %w", ss.server, err) } } c = ss.cipher.StreamConn(c) @@ -95,7 +95,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, er targetAddr := socks5.ParseAddr(metadata.RemoteAddress()) if targetAddr == nil { - return nil, nil, fmt.Errorf("parse address error: %v:%v", metadata.String(), metadata.DstPort) + return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort) } pc = ss.cipher.PacketConn(pc) @@ -114,7 +114,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { password := option.Password ciph, err := core.PickCipher(cipher, nil, password) if err != nil { - return nil, fmt.Errorf("ss %s initialize error: %s", server, err.Error()) + return nil, fmt.Errorf("ss %s initialize error: %w", server, err) } var v2rayOption *v2rayObfs.Option @@ -136,7 +136,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { if option.Plugin == "obfs" { opts := simpleObfsOption{Host: "bing.com"} if err := decoder.Decode(option.PluginOpts, &opts); err != nil { - return nil, fmt.Errorf("ss %s initialize obfs error: %s", server, err.Error()) + return nil, fmt.Errorf("ss %s initialize obfs error: %w", server, err) } if opts.Mode != "tls" && opts.Mode != "http" { @@ -147,7 +147,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } else if option.Plugin == "v2ray-plugin" { opts := v2rayObfsOption{Host: "bing.com", Mux: true} if err := decoder.Decode(option.PluginOpts, &opts); err != nil { - return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %s", server, err.Error()) + return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", server, err) } if opts.Mode != "websocket" { diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go index 6b95aace89..ecc0a685ce 100644 --- a/adapters/outbound/snell.go +++ b/adapters/outbound/snell.go @@ -30,7 +30,7 @@ type SnellOption struct { func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { c, err := dialContext(ctx, "tcp", s.server) if err != nil { - return nil, fmt.Errorf("%s connect error: %s", s.server, err.Error()) + return nil, fmt.Errorf("%s connect error: %w", s.server, err) } tcpKeepAlive(c) switch s.obfsOption.Mode { @@ -53,7 +53,7 @@ func NewSnell(option SnellOption) (*Snell, error) { decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) obfsOption := &simpleObfsOption{Host: "bing.com"} if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { - return nil, fmt.Errorf("snell %s initialize obfs error: %s", server, err.Error()) + return nil, fmt.Errorf("snell %s initialize obfs error: %w", server, err) } if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 9355cff138..6551df178d 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -44,7 +44,7 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn } if err != nil { - return nil, fmt.Errorf("%s connect error", ss.addr) + return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } tcpKeepAlive(c) var user *socks5.User @@ -65,7 +65,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err defer cancel() c, err := dialContext(ctx, "tcp", ss.addr) if err != nil { - err = fmt.Errorf("%s connect error", ss.addr) + err = fmt.Errorf("%s connect error: %w", ss.addr, err) return } @@ -92,7 +92,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user) if err != nil { - err = fmt.Errorf("%v client hanshake error", err) + err = fmt.Errorf("client hanshake error: %w", err) return } @@ -103,7 +103,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err targetAddr := socks5.ParseAddr(metadata.RemoteAddress()) if targetAddr == nil { - return nil, nil, fmt.Errorf("parse address error: %v:%v", metadata.String(), metadata.DstPort) + return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort) } pc, err := net.ListenPacket("udp", "") From 452570704872a939472bab89d06fa8ce67dcdffb Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 5 Dec 2019 18:22:07 +0800 Subject: [PATCH 284/535] Chore: remove unused http outbound proxy code --- adapters/outbound/http.go | 51 ++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 9dc4cf494a..03bd99cb47 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -2,7 +2,6 @@ package adapters import ( "bufio" - "bytes" "context" "crypto/tls" "encoding/base64" @@ -11,6 +10,7 @@ import ( "io" "net" "net/http" + "net/url" "strconv" C "github.com/Dreamacro/clash/constant" @@ -18,12 +18,10 @@ import ( type Http struct { *Base - addr string - user string - pass string - tls bool - skipCertVerify bool - tlsConfig *tls.Config + addr string + user string + pass string + tlsConfig *tls.Config } type HttpOption struct { @@ -38,7 +36,7 @@ type HttpOption struct { func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { c, err := dialContext(ctx, "tcp", h.addr) - if err == nil && h.tls { + if err == nil && h.tlsConfig != nil { cc := tls.Client(c, h.tlsConfig) err = cc.Handshake() c = cc @@ -56,28 +54,28 @@ func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, e } func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { - var buf bytes.Buffer - var err error - addr := metadata.RemoteAddress() - buf.WriteString("CONNECT " + addr + " HTTP/1.1\r\n") - buf.WriteString("Host: " + metadata.String() + "\r\n") - buf.WriteString("Proxy-Connection: Keep-Alive\r\n") + req := &http.Request{ + Method: http.MethodConnect, + URL: &url.URL{ + Host: addr, + }, + Host: addr, + Header: http.Header{ + "Proxy-Connection": []string{"Keep-Alive"}, + }, + } if h.user != "" && h.pass != "" { auth := h.user + ":" + h.pass - buf.WriteString("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + "\r\n") + req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) } - // header ended - buf.WriteString("\r\n") - _, err = rw.Write(buf.Bytes()) - if err != nil { + if err := req.Write(rw); err != nil { return err } - var req http.Request - resp, err := http.ReadResponse(bufio.NewReader(rw), &req) + resp, err := http.ReadResponse(bufio.NewReader(rw), req) if err != nil { return err } @@ -97,6 +95,7 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { if resp.StatusCode >= http.StatusInternalServerError { return errors.New(resp.Status) } + return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) } @@ -115,11 +114,9 @@ func NewHttp(option HttpOption) *Http { name: option.Name, tp: C.Http, }, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), - user: option.UserName, - pass: option.Password, - tls: option.TLS, - skipCertVerify: option.SkipCertVerify, - tlsConfig: tlsConfig, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), + user: option.UserName, + pass: option.Password, + tlsConfig: tlsConfig, } } From 4d7096f4512996287b3c231045a588593b3649f9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 7 Dec 2019 23:37:42 +0800 Subject: [PATCH 285/535] Fix: HTTP inbound proxy can't close correctly --- tunnel/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 87faa5bdef..2849bd9f39 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -35,7 +35,6 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { if err != nil { break } - defer resp.Body.Close() adapters.RemoveHopByHopHeaders(resp.Header) if resp.StatusCode == http.StatusContinue { @@ -59,6 +58,7 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } + // even if resp.Write write body to the connection, but some http request have to Copy to close it buf := pool.BufPool.Get().([]byte) _, err = io.CopyBuffer(request, resp.Body, buf) pool.BufPool.Put(buf[:cap(buf)]) From c427bc89ef2a59477cf960ec6f20ecff08501b7a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 8 Dec 2019 12:17:24 +0800 Subject: [PATCH 286/535] Feature: add experimental provider --- adapters/inbound/http.go | 2 +- adapters/inbound/https.go | 2 +- adapters/inbound/socket.go | 2 +- adapters/inbound/util.go | 2 +- adapters/outbound/base.go | 14 +- adapters/outbound/direct.go | 2 +- adapters/outbound/fallback.go | 146 --------- adapters/outbound/http.go | 2 +- adapters/outbound/parser.go | 64 ++++ adapters/outbound/reject.go | 2 +- adapters/outbound/shadowsocks.go | 2 +- adapters/outbound/snell.go | 2 +- adapters/outbound/socks5.go | 2 +- adapters/outbound/urltest.go | 162 ---------- adapters/outbound/util.go | 2 +- adapters/outbound/vmess.go | 2 +- adapters/outboundgroup/fallback.go | 84 +++++ .../loadbalance.go | 97 ++---- adapters/outboundgroup/parser.go | 139 +++++++++ .../{outbound => outboundgroup}/selector.go | 64 ++-- adapters/outboundgroup/urltest.go | 93 ++++++ adapters/provider/healthcheck.go | 53 ++++ adapters/provider/parser.go | 60 ++++ adapters/provider/provider.go | 293 ++++++++++++++++++ adapters/provider/vehicle.go | 109 +++++++ common/structure/structure.go | 155 +++++++++ config/config.go | 197 ++++-------- config/utils.go | 58 ++-- constant/adapters.go | 1 - hub/executor/executor.go | 15 +- hub/route/proxies.go | 7 +- proxy/redir/tcp.go | 2 +- tunnel/tunnel.go | 16 +- 33 files changed, 1240 insertions(+), 613 deletions(-) delete mode 100644 adapters/outbound/fallback.go create mode 100644 adapters/outbound/parser.go delete mode 100644 adapters/outbound/urltest.go create mode 100644 adapters/outboundgroup/fallback.go rename adapters/{outbound => outboundgroup}/loadbalance.go (54%) create mode 100644 adapters/outboundgroup/parser.go rename adapters/{outbound => outboundgroup}/selector.go (51%) create mode 100644 adapters/outboundgroup/urltest.go create mode 100644 adapters/provider/healthcheck.go create mode 100644 adapters/provider/parser.go create mode 100644 adapters/provider/provider.go create mode 100644 adapters/provider/vehicle.go diff --git a/adapters/inbound/http.go b/adapters/inbound/http.go index 7aeb4c6546..867dab22b2 100644 --- a/adapters/inbound/http.go +++ b/adapters/inbound/http.go @@ -1,4 +1,4 @@ -package adapters +package inbound import ( "net" diff --git a/adapters/inbound/https.go b/adapters/inbound/https.go index 124b80c22f..7a2199801b 100644 --- a/adapters/inbound/https.go +++ b/adapters/inbound/https.go @@ -1,4 +1,4 @@ -package adapters +package inbound import ( "net" diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 017f742439..43fbd0f5f0 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -1,4 +1,4 @@ -package adapters +package inbound import ( "net" diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index 9a16a6cf6e..16b440e917 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -1,4 +1,4 @@ -package adapters +package inbound import ( "net" diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 505489b480..0b9c90df77 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "context" @@ -38,14 +38,16 @@ func (b *Base) SupportUDP() bool { return b.udp } -func (b *Base) Destroy() {} - func (b *Base) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]string{ "type": b.Type().String(), }) } +func NewBase(name string, tp C.AdapterType, udp bool) *Base { + return &Base{name, tp, udp} +} + type conn struct { net.Conn chain C.Chain @@ -199,9 +201,3 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { func NewProxy(adapter C.ProxyAdapter) *Proxy { return &Proxy{adapter, queue.New(10), true} } - -// ProxyGroupOption contain the common options for all kind of ProxyGroup -type ProxyGroupOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` -} diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 22a4171cb9..061d03618d 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "context" diff --git a/adapters/outbound/fallback.go b/adapters/outbound/fallback.go deleted file mode 100644 index 46247c8647..0000000000 --- a/adapters/outbound/fallback.go +++ /dev/null @@ -1,146 +0,0 @@ -package adapters - -import ( - "context" - "encoding/json" - "errors" - "net" - "sync/atomic" - "time" - - "github.com/Dreamacro/clash/common/picker" - C "github.com/Dreamacro/clash/constant" -) - -type Fallback struct { - *Base - proxies []C.Proxy - rawURL string - interval time.Duration - done chan struct{} - once int32 -} - -type FallbackOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` - URL string `proxy:"url"` - Interval int `proxy:"interval"` -} - -func (f *Fallback) Now() string { - proxy := f.findAliveProxy() - return proxy.Name() -} - -func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - proxy := f.findAliveProxy() - c, err := proxy.DialContext(ctx, metadata) - if err == nil { - c.AppendToChains(f) - } - return c, err -} - -func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - proxy := f.findAliveProxy() - pc, addr, err := proxy.DialUDP(metadata) - if err == nil { - pc.AppendToChains(f) - } - return pc, addr, err -} - -func (f *Fallback) SupportUDP() bool { - proxy := f.findAliveProxy() - return proxy.SupportUDP() -} - -func (f *Fallback) MarshalJSON() ([]byte, error) { - var all []string - for _, proxy := range f.proxies { - all = append(all, proxy.Name()) - } - return json.Marshal(map[string]interface{}{ - "type": f.Type().String(), - "now": f.Now(), - "all": all, - }) -} - -func (f *Fallback) Destroy() { - f.done <- struct{}{} -} - -func (f *Fallback) loop() { - tick := time.NewTicker(f.interval) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - go f.validTest(ctx) -Loop: - for { - select { - case <-tick.C: - go f.validTest(ctx) - case <-f.done: - break Loop - } - } -} - -func (f *Fallback) findAliveProxy() C.Proxy { - for _, proxy := range f.proxies { - if proxy.Alive() { - return proxy - } - } - return f.proxies[0] -} - -func (f *Fallback) validTest(ctx context.Context) { - if !atomic.CompareAndSwapInt32(&f.once, 0, 1) { - return - } - defer atomic.StoreInt32(&f.once, 0) - - ctx, cancel := context.WithTimeout(ctx, defaultURLTestTimeout) - defer cancel() - picker := picker.WithoutAutoCancel(ctx) - - for _, p := range f.proxies { - proxy := p - picker.Go(func() (interface{}, error) { - return proxy.URLTest(ctx, f.rawURL) - }) - } - - picker.Wait() -} - -func NewFallback(option FallbackOption, proxies []C.Proxy) (*Fallback, error) { - _, err := urlToMetadata(option.URL) - if err != nil { - return nil, err - } - - if len(proxies) < 1 { - return nil, errors.New("The number of proxies cannot be 0") - } - - interval := time.Duration(option.Interval) * time.Second - - Fallback := &Fallback{ - Base: &Base{ - name: option.Name, - tp: C.Fallback, - }, - proxies: proxies, - rawURL: option.URL, - interval: interval, - done: make(chan struct{}), - once: 0, - } - go Fallback.loop() - return Fallback, nil -} diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 03bd99cb47..5223f27db2 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "bufio" diff --git a/adapters/outbound/parser.go b/adapters/outbound/parser.go new file mode 100644 index 0000000000..6aeec86641 --- /dev/null +++ b/adapters/outbound/parser.go @@ -0,0 +1,64 @@ +package outbound + +import ( + "fmt" + + "github.com/Dreamacro/clash/common/structure" + C "github.com/Dreamacro/clash/constant" +) + +func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { + decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) + proxyType, existType := mapping["type"].(string) + if !existType { + return nil, fmt.Errorf("Missing type") + } + + var proxy C.ProxyAdapter + err := fmt.Errorf("Cannot parse") + switch proxyType { + case "ss": + ssOption := &ShadowSocksOption{} + err = decoder.Decode(mapping, ssOption) + if err != nil { + break + } + proxy, err = NewShadowSocks(*ssOption) + case "socks5": + socksOption := &Socks5Option{} + err = decoder.Decode(mapping, socksOption) + if err != nil { + break + } + proxy = NewSocks5(*socksOption) + case "http": + httpOption := &HttpOption{} + err = decoder.Decode(mapping, httpOption) + if err != nil { + break + } + proxy = NewHttp(*httpOption) + case "vmess": + vmessOption := &VmessOption{} + err = decoder.Decode(mapping, vmessOption) + if err != nil { + break + } + proxy, err = NewVmess(*vmessOption) + case "snell": + snellOption := &SnellOption{} + err = decoder.Decode(mapping, snellOption) + if err != nil { + break + } + proxy, err = NewSnell(*snellOption) + default: + return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) + } + + if err != nil { + return nil, err + } + + return NewProxy(proxy), nil +} diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index 65ab11921e..b053508228 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "context" diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 091f6f375c..80aa6659f0 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "context" diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go index ecc0a685ce..4626bbef35 100644 --- a/adapters/outbound/snell.go +++ b/adapters/outbound/snell.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "context" diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 6551df178d..7632be3045 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "context" diff --git a/adapters/outbound/urltest.go b/adapters/outbound/urltest.go deleted file mode 100644 index 33f8071714..0000000000 --- a/adapters/outbound/urltest.go +++ /dev/null @@ -1,162 +0,0 @@ -package adapters - -import ( - "context" - "encoding/json" - "errors" - "net" - "sync/atomic" - "time" - - "github.com/Dreamacro/clash/common/picker" - C "github.com/Dreamacro/clash/constant" -) - -type URLTest struct { - *Base - proxies []C.Proxy - rawURL string - fast C.Proxy - interval time.Duration - done chan struct{} - once int32 -} - -type URLTestOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` - URL string `proxy:"url"` - Interval int `proxy:"interval"` -} - -func (u *URLTest) Now() string { - return u.fast.Name() -} - -func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { - for i := 0; i < 3; i++ { - c, err = u.fast.DialContext(ctx, metadata) - if err == nil { - c.AppendToChains(u) - return - } - u.fallback() - } - return -} - -func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - pc, addr, err := u.fast.DialUDP(metadata) - if err == nil { - pc.AppendToChains(u) - } - return pc, addr, err -} - -func (u *URLTest) SupportUDP() bool { - return u.fast.SupportUDP() -} - -func (u *URLTest) MarshalJSON() ([]byte, error) { - var all []string - for _, proxy := range u.proxies { - all = append(all, proxy.Name()) - } - return json.Marshal(map[string]interface{}{ - "type": u.Type().String(), - "now": u.Now(), - "all": all, - }) -} - -func (u *URLTest) Destroy() { - u.done <- struct{}{} -} - -func (u *URLTest) loop() { - tick := time.NewTicker(u.interval) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - go u.speedTest(ctx) -Loop: - for { - select { - case <-tick.C: - go u.speedTest(ctx) - case <-u.done: - break Loop - } - } -} - -func (u *URLTest) fallback() { - fast := u.proxies[0] - min := fast.LastDelay() - for _, proxy := range u.proxies[1:] { - if !proxy.Alive() { - continue - } - - delay := proxy.LastDelay() - if delay < min { - fast = proxy - min = delay - } - } - u.fast = fast -} - -func (u *URLTest) speedTest(ctx context.Context) { - if !atomic.CompareAndSwapInt32(&u.once, 0, 1) { - return - } - defer atomic.StoreInt32(&u.once, 0) - - ctx, cancel := context.WithTimeout(ctx, defaultURLTestTimeout) - defer cancel() - picker := picker.WithoutAutoCancel(ctx) - for _, p := range u.proxies { - proxy := p - picker.Go(func() (interface{}, error) { - _, err := proxy.URLTest(ctx, u.rawURL) - if err != nil { - return nil, err - } - return proxy, nil - }) - } - - fast := picker.WaitWithoutCancel() - if fast != nil { - u.fast = fast.(C.Proxy) - } - - picker.Wait() -} - -func NewURLTest(option URLTestOption, proxies []C.Proxy) (*URLTest, error) { - _, err := urlToMetadata(option.URL) - if err != nil { - return nil, err - } - if len(proxies) < 1 { - return nil, errors.New("The number of proxies cannot be 0") - } - - interval := time.Duration(option.Interval) * time.Second - urlTest := &URLTest{ - Base: &Base{ - name: option.Name, - tp: C.URLTest, - }, - proxies: proxies[:], - rawURL: option.URL, - fast: proxies[0], - interval: interval, - done: make(chan struct{}), - once: 0, - } - go urlTest.loop() - return urlTest, nil -} diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index 3af093c464..b3deb0a7c6 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "bytes" diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index d61172e542..c653b106fb 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -1,4 +1,4 @@ -package adapters +package outbound import ( "context" diff --git a/adapters/outboundgroup/fallback.go b/adapters/outboundgroup/fallback.go new file mode 100644 index 0000000000..7c0525b884 --- /dev/null +++ b/adapters/outboundgroup/fallback.go @@ -0,0 +1,84 @@ +package outboundgroup + +import ( + "context" + "encoding/json" + "net" + + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/provider" + C "github.com/Dreamacro/clash/constant" +) + +type Fallback struct { + *outbound.Base + providers []provider.ProxyProvider +} + +func (f *Fallback) Now() string { + proxy := f.findAliveProxy() + return proxy.Name() +} + +func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + proxy := f.findAliveProxy() + c, err := proxy.DialContext(ctx, metadata) + if err == nil { + c.AppendToChains(f) + } + return c, err +} + +func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { + proxy := f.findAliveProxy() + pc, addr, err := proxy.DialUDP(metadata) + if err == nil { + pc.AppendToChains(f) + } + return pc, addr, err +} + +func (f *Fallback) SupportUDP() bool { + proxy := f.findAliveProxy() + return proxy.SupportUDP() +} + +func (f *Fallback) MarshalJSON() ([]byte, error) { + var all []string + for _, proxy := range f.proxies() { + all = append(all, proxy.Name()) + } + return json.Marshal(map[string]interface{}{ + "type": f.Type().String(), + "now": f.Now(), + "all": all, + }) +} + +func (f *Fallback) proxies() []C.Proxy { + proxies := []C.Proxy{} + for _, provider := range f.providers { + proxies = append(proxies, provider.Proxies()...) + } + return proxies +} + +func (f *Fallback) findAliveProxy() C.Proxy { + for _, provider := range f.providers { + proxies := provider.Proxies() + for _, proxy := range proxies { + if proxy.Alive() { + return proxy + } + } + } + + return f.providers[0].Proxies()[0] +} + +func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { + return &Fallback{ + Base: outbound.NewBase(name, C.Fallback, false), + providers: providers, + } +} diff --git a/adapters/outbound/loadbalance.go b/adapters/outboundgroup/loadbalance.go similarity index 54% rename from adapters/outbound/loadbalance.go rename to adapters/outboundgroup/loadbalance.go index d27beecece..74a154cece 100644 --- a/adapters/outbound/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -1,13 +1,12 @@ -package adapters +package outboundgroup import ( "context" "encoding/json" - "errors" "net" - "sync" - "time" + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/common/murmur3" C "github.com/Dreamacro/clash/constant" @@ -15,12 +14,9 @@ import ( ) type LoadBalance struct { - *Base - proxies []C.Proxy - maxRetry int - rawURL string - interval time.Duration - done chan struct{} + *outbound.Base + maxRetry int + providers []provider.ProxyProvider } func getKey(metadata *C.Metadata) string { @@ -62,16 +58,17 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c }() key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) - buckets := int32(len(lb.proxies)) + proxies := lb.proxies() + buckets := int32(len(proxies)) for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { idx := jumpHash(key, buckets) - proxy := lb.proxies[idx] + proxy := proxies[idx] if proxy.Alive() { c, err = proxy.DialContext(ctx, metadata) return } } - c, err = lb.proxies[0].DialContext(ctx, metadata) + c, err = proxies[0].DialContext(ctx, metadata) return } @@ -83,57 +80,34 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net. }() key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) - buckets := int32(len(lb.proxies)) + proxies := lb.proxies() + buckets := int32(len(proxies)) for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { idx := jumpHash(key, buckets) - proxy := lb.proxies[idx] + proxy := proxies[idx] if proxy.Alive() { return proxy.DialUDP(metadata) } } - return lb.proxies[0].DialUDP(metadata) + return proxies[0].DialUDP(metadata) } func (lb *LoadBalance) SupportUDP() bool { return true } -func (lb *LoadBalance) Destroy() { - lb.done <- struct{}{} -} - -func (lb *LoadBalance) validTest() { - wg := sync.WaitGroup{} - wg.Add(len(lb.proxies)) - - for _, p := range lb.proxies { - go func(p C.Proxy) { - p.URLTest(context.Background(), lb.rawURL) - wg.Done() - }(p) - } - - wg.Wait() -} - -func (lb *LoadBalance) loop() { - tick := time.NewTicker(lb.interval) - go lb.validTest() -Loop: - for { - select { - case <-tick.C: - go lb.validTest() - case <-lb.done: - break Loop - } +func (lb *LoadBalance) proxies() []C.Proxy { + proxies := []C.Proxy{} + for _, provider := range lb.providers { + proxies = append(proxies, provider.Proxies()...) } + return proxies } func (lb *LoadBalance) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range lb.proxies { + for _, proxy := range lb.proxies() { all = append(all, proxy.Name()) } return json.Marshal(map[string]interface{}{ @@ -142,31 +116,10 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { }) } -type LoadBalanceOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` - URL string `proxy:"url"` - Interval int `proxy:"interval"` -} - -func NewLoadBalance(option LoadBalanceOption, proxies []C.Proxy) (*LoadBalance, error) { - if len(proxies) == 0 { - return nil, errors.New("Provide at least one proxy") - } - - interval := time.Duration(option.Interval) * time.Second - - lb := &LoadBalance{ - Base: &Base{ - name: option.Name, - tp: C.LoadBalance, - }, - proxies: proxies, - maxRetry: 3, - rawURL: option.URL, - interval: interval, - done: make(chan struct{}), +func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { + return &LoadBalance{ + Base: outbound.NewBase(name, C.LoadBalance, false), + maxRetry: 3, + providers: providers, } - go lb.loop() - return lb, nil } diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go new file mode 100644 index 0000000000..972305c27e --- /dev/null +++ b/adapters/outboundgroup/parser.go @@ -0,0 +1,139 @@ +package outboundgroup + +import ( + "errors" + "fmt" + + "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/common/structure" + C "github.com/Dreamacro/clash/constant" +) + +var ( + errFormat = errors.New("format error") + errType = errors.New("unsupport type") + errMissUse = errors.New("`use` field should not be empty") + errMissHealthCheck = errors.New("`url` or `interval` missing") + errDuplicateProvider = errors.New("`duplicate provider name") +) + +type GroupCommonOption struct { + Name string `group:"name"` + Type string `group:"type"` + Proxies []string `group:"proxies,omitempty"` + Use []string `group:"use,omitempty"` + URL string `group:"url,omitempty"` + Interval int `group:"interval,omitempty"` +} + +func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) { + decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) + + groupOption := &GroupCommonOption{} + if err := decoder.Decode(config, groupOption); err != nil { + return nil, errFormat + } + + if groupOption.Type == "" || groupOption.Name == "" { + return nil, errFormat + } + + groupName := groupOption.Name + + providers := []provider.ProxyProvider{} + if len(groupOption.Proxies) != 0 { + ps, err := getProxies(proxyMap, groupOption.Proxies) + if err != nil { + return nil, err + } + + // if Use not empty, drop health check options + if len(groupOption.Use) != 0 { + pd, err := provider.NewCompatibleProvier(groupName, ps, nil) + if err != nil { + return nil, err + } + + providers = append(providers, pd) + } else { + // select don't need health check + if groupOption.Type == "select" { + pd, err := provider.NewCompatibleProvier(groupName, ps, nil) + if err != nil { + return nil, err + } + + providers = append(providers, pd) + providersMap[groupName] = pd + } else { + if groupOption.URL == "" || groupOption.Interval == 0 { + return nil, errMissHealthCheck + } + + healthOption := &provider.HealthCheckOption{ + URL: groupOption.URL, + Interval: uint(groupOption.Interval), + } + pd, err := provider.NewCompatibleProvier(groupName, ps, healthOption) + if err != nil { + return nil, err + } + + providers = append(providers, pd) + providersMap[groupName] = pd + } + } + } + + if len(groupOption.Use) != 0 { + list, err := getProviders(providersMap, groupOption.Use) + if err != nil { + return nil, err + } + providers = append(providers, list...) + } + + var group C.ProxyAdapter + switch groupOption.Type { + case "url-test": + group = NewURLTest(groupName, providers) + case "select": + group = NewSelector(groupName, providers) + case "fallback": + group = NewFallback(groupName, providers) + case "load-balance": + group = NewLoadBalance(groupName, providers) + default: + return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) + } + + return group, nil +} + +func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { + var ps []C.Proxy + for _, name := range list { + p, ok := mapping[name] + if !ok { + return nil, fmt.Errorf("'%s' not found", name) + } + ps = append(ps, p) + } + return ps, nil +} + +func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) { + var ps []provider.ProxyProvider + for _, name := range list { + p, ok := mapping[name] + if !ok { + return nil, fmt.Errorf("'%s' not found", name) + } + + if p.VehicleType() == provider.Compatible { + return nil, fmt.Errorf("proxy group %s can't contains in `use`", name) + } + ps = append(ps, p) + } + return ps, nil +} diff --git a/adapters/outbound/selector.go b/adapters/outboundgroup/selector.go similarity index 51% rename from adapters/outbound/selector.go rename to adapters/outboundgroup/selector.go index b7ed661cc0..fc53be716b 100644 --- a/adapters/outbound/selector.go +++ b/adapters/outboundgroup/selector.go @@ -1,4 +1,4 @@ -package adapters +package outboundgroup import ( "context" @@ -6,19 +6,15 @@ import ( "errors" "net" + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/provider" C "github.com/Dreamacro/clash/constant" ) type Selector struct { - *Base + *outbound.Base selected C.Proxy - proxies map[string]C.Proxy - proxyList []string -} - -type SelectorOption struct { - Name string `proxy:"name"` - Proxies []string `proxy:"proxies"` + providers []provider.ProxyProvider } func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { @@ -42,10 +38,15 @@ func (s *Selector) SupportUDP() bool { } func (s *Selector) MarshalJSON() ([]byte, error) { + var all []string + for _, proxy := range s.proxies() { + all = append(all, proxy.Name()) + } + return json.Marshal(map[string]interface{}{ "type": s.Type().String(), "now": s.Now(), - "all": s.proxyList, + "all": all, }) } @@ -54,34 +55,29 @@ func (s *Selector) Now() string { } func (s *Selector) Set(name string) error { - proxy, exist := s.proxies[name] - if !exist { - return errors.New("Proxy does not exist") + for _, proxy := range s.proxies() { + if proxy.Name() == name { + s.selected = proxy + return nil + } } - s.selected = proxy - return nil -} -func NewSelector(name string, proxies []C.Proxy) (*Selector, error) { - if len(proxies) == 0 { - return nil, errors.New("Provide at least one proxy") - } + return errors.New("Proxy does not exist") +} - mapping := make(map[string]C.Proxy) - proxyList := make([]string, len(proxies)) - for idx, proxy := range proxies { - mapping[proxy.Name()] = proxy - proxyList[idx] = proxy.Name() +func (s *Selector) proxies() []C.Proxy { + proxies := []C.Proxy{} + for _, provider := range s.providers { + proxies = append(proxies, provider.Proxies()...) } + return proxies +} - s := &Selector{ - Base: &Base{ - name: name, - tp: C.Selector, - }, - proxies: mapping, - selected: proxies[0], - proxyList: proxyList, +func NewSelector(name string, providers []provider.ProxyProvider) *Selector { + selected := providers[0].Proxies()[0] + return &Selector{ + Base: outbound.NewBase(name, C.Selector, false), + providers: providers, + selected: selected, } - return s, nil } diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go new file mode 100644 index 0000000000..2e57e0f2b1 --- /dev/null +++ b/adapters/outboundgroup/urltest.go @@ -0,0 +1,93 @@ +package outboundgroup + +import ( + "context" + "encoding/json" + "net" + + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/provider" + C "github.com/Dreamacro/clash/constant" +) + +type URLTest struct { + *outbound.Base + fast C.Proxy + providers []provider.ProxyProvider +} + +func (u *URLTest) Now() string { + return u.fast.Name() +} + +func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { + for i := 0; i < 3; i++ { + c, err = u.fast.DialContext(ctx, metadata) + if err == nil { + c.AppendToChains(u) + return + } + u.fallback() + } + return +} + +func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { + pc, addr, err := u.fast.DialUDP(metadata) + if err == nil { + pc.AppendToChains(u) + } + return pc, addr, err +} + +func (u *URLTest) proxies() []C.Proxy { + proxies := []C.Proxy{} + for _, provider := range u.providers { + proxies = append(proxies, provider.Proxies()...) + } + return proxies +} + +func (u *URLTest) SupportUDP() bool { + return u.fast.SupportUDP() +} + +func (u *URLTest) MarshalJSON() ([]byte, error) { + var all []string + for _, proxy := range u.proxies() { + all = append(all, proxy.Name()) + } + return json.Marshal(map[string]interface{}{ + "type": u.Type().String(), + "now": u.Now(), + "all": all, + }) +} + +func (u *URLTest) fallback() { + proxies := u.proxies() + fast := proxies[0] + min := fast.LastDelay() + for _, proxy := range proxies[1:] { + if !proxy.Alive() { + continue + } + + delay := proxy.LastDelay() + if delay < min { + fast = proxy + min = delay + } + } + u.fast = fast +} + +func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest { + fast := providers[0].Proxies()[0] + + return &URLTest{ + Base: outbound.NewBase(name, C.URLTest, false), + fast: fast, + providers: providers, + } +} diff --git a/adapters/provider/healthcheck.go b/adapters/provider/healthcheck.go new file mode 100644 index 0000000000..00520666a0 --- /dev/null +++ b/adapters/provider/healthcheck.go @@ -0,0 +1,53 @@ +package provider + +import ( + "context" + "time" + + C "github.com/Dreamacro/clash/constant" +) + +const ( + defaultURLTestTimeout = time.Second * 5 +) + +type HealthCheckOption struct { + URL string + Interval uint +} + +type healthCheck struct { + url string + proxies []C.Proxy + ticker *time.Ticker +} + +func (hc *healthCheck) process() { + go hc.check() + for range hc.ticker.C { + hc.check() + } +} + +func (hc *healthCheck) check() { + ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) + for _, proxy := range hc.proxies { + go proxy.URLTest(ctx, hc.url) + } + + <-ctx.Done() + cancel() +} + +func (hc *healthCheck) close() { + hc.ticker.Stop() +} + +func newHealthCheck(proxies []C.Proxy, url string, interval uint) *healthCheck { + ticker := time.NewTicker(time.Duration(interval) * time.Second) + return &healthCheck{ + proxies: proxies, + url: url, + ticker: ticker, + } +} diff --git a/adapters/provider/parser.go b/adapters/provider/parser.go new file mode 100644 index 0000000000..aea22f8d39 --- /dev/null +++ b/adapters/provider/parser.go @@ -0,0 +1,60 @@ +package provider + +import ( + "errors" + "fmt" + "time" + + "github.com/Dreamacro/clash/common/structure" + C "github.com/Dreamacro/clash/constant" +) + +var ( + errVehicleType = errors.New("unsupport vehicle type") +) + +type healthCheckSchema struct { + Enable bool `provider:"enable"` + URL string `provider:"url"` + Interval int `provider:"interval"` +} + +type proxyProviderSchema struct { + Type string `provider:"type"` + Path string `provider:"path"` + URL string `provider:"url,omitempty"` + Interval int `provider:"interval,omitempty"` + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` +} + +func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) { + decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) + + schema := &proxyProviderSchema{} + if err := decoder.Decode(mapping, schema); err != nil { + return nil, err + } + + var healthCheckOption *HealthCheckOption + if schema.HealthCheck.Enable { + healthCheckOption = &HealthCheckOption{ + URL: schema.HealthCheck.URL, + Interval: uint(schema.HealthCheck.Interval), + } + } + + path := C.Path.Reslove(schema.Path) + + var vehicle Vehicle + switch schema.Type { + case "file": + vehicle = NewFileVehicle(path) + case "http": + vehicle = NewHTTPVehicle(schema.URL, path) + default: + return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) + } + + interval := time.Duration(uint(schema.Interval)) * time.Second + return NewProxySetProvider(name, interval, vehicle, healthCheckOption), nil +} diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go new file mode 100644 index 0000000000..1149273db3 --- /dev/null +++ b/adapters/provider/provider.go @@ -0,0 +1,293 @@ +package provider + +import ( + "bytes" + "crypto/md5" + "errors" + "fmt" + "io/ioutil" + "net/url" + "os" + "sync" + "time" + + "github.com/Dreamacro/clash/adapters/outbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + + "gopkg.in/yaml.v2" +) + +const ( + ReservedName = "default" + + fileMode = 0666 +) + +// Provider Type +const ( + Proxy ProviderType = iota + Rule +) + +// ProviderType defined +type ProviderType int + +func (pt ProviderType) String() string { + switch pt { + case Proxy: + return "Proxy" + case Rule: + return "Rule" + default: + return "Unknown" + } +} + +// Provider interface +type Provider interface { + Name() string + VehicleType() VehicleType + Type() ProviderType + Initial() error + Reload() error + Destroy() error +} + +// ProxyProvider interface +type ProxyProvider interface { + Provider + Proxies() []C.Proxy +} + +type ProxySchema struct { + Proxies []map[string]interface{} `yaml:"proxies"` +} + +type ProxySetProvider struct { + name string + vehicle Vehicle + hash [16]byte + proxies []C.Proxy + healthCheck *healthCheck + healthCheckOption *HealthCheckOption + ticker *time.Ticker + + // mux for avoiding creating new goroutines when pulling + mux sync.Mutex +} + +func (pp *ProxySetProvider) Name() string { + return pp.name +} + +func (pp *ProxySetProvider) Reload() error { + return nil +} + +func (pp *ProxySetProvider) Destroy() error { + pp.mux.Lock() + defer pp.mux.Unlock() + if pp.healthCheck != nil { + pp.healthCheck.close() + pp.healthCheck = nil + } + + if pp.ticker != nil { + pp.ticker.Stop() + } + + return nil +} + +func (pp *ProxySetProvider) Initial() error { + var buf []byte + var err error + if _, err := os.Stat(pp.vehicle.Path()); err == nil { + buf, err = ioutil.ReadFile(pp.vehicle.Path()) + } else { + buf, err = pp.vehicle.Read() + } + + if err != nil { + return err + } + + proxies, err := pp.parse(buf) + if err != nil { + return err + } + + if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil { + return err + } + + pp.hash = md5.Sum(buf) + pp.setProxies(proxies) + + // pull proxies automatically + if pp.ticker != nil { + go pp.pullLoop() + } + + return nil +} + +func (pp *ProxySetProvider) VehicleType() VehicleType { + return pp.vehicle.Type() +} + +func (pp *ProxySetProvider) Type() ProviderType { + return Proxy +} + +func (pp *ProxySetProvider) Proxies() []C.Proxy { + return pp.proxies +} + +func (pp *ProxySetProvider) pullLoop() { + for range pp.ticker.C { + if err := pp.pull(); err != nil { + log.Warnln("[Provider] %s pull error: %s", pp.Name(), err.Error()) + } + } +} + +func (pp *ProxySetProvider) pull() error { + buf, err := pp.vehicle.Read() + if err != nil { + return err + } + + hash := md5.Sum(buf) + if bytes.Equal(pp.hash[:], hash[:]) { + log.Debugln("[Provider] %s's proxies doesn't change", pp.Name()) + return nil + } + + proxies, err := pp.parse(buf) + if err != nil { + return err + } + log.Infoln("[Provider] %s's proxies update", pp.Name()) + + if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil { + return err + } + + pp.hash = hash + pp.setProxies(proxies) + + return nil +} + +func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) { + schema := &ProxySchema{} + + if err := yaml.Unmarshal(buf, schema); err != nil { + return nil, err + } + + if schema.Proxies == nil { + return nil, errors.New("File must have a `proxies` field") + } + + proxies := []C.Proxy{} + for idx, mapping := range schema.Proxies { + proxy, err := outbound.ParseProxy(mapping) + if err != nil { + return nil, fmt.Errorf("Proxy %d error: %w", idx, err) + } + proxies = append(proxies, proxy) + } + + return proxies, nil +} + +func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) { + pp.proxies = proxies + if pp.healthCheckOption != nil { + pp.mux.Lock() + if pp.healthCheck != nil { + pp.healthCheck.close() + pp.healthCheck = newHealthCheck(proxies, pp.healthCheckOption.URL, pp.healthCheckOption.Interval) + go pp.healthCheck.process() + } + pp.mux.Unlock() + } +} + +func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, option *HealthCheckOption) *ProxySetProvider { + var ticker *time.Ticker + if interval != 0 { + ticker = time.NewTicker(interval) + } + + return &ProxySetProvider{ + name: name, + vehicle: vehicle, + proxies: []C.Proxy{}, + healthCheckOption: option, + ticker: ticker, + } +} + +type CompatibleProvier struct { + name string + healthCheck *healthCheck + proxies []C.Proxy +} + +func (cp *CompatibleProvier) Name() string { + return cp.name +} + +func (cp *CompatibleProvier) Reload() error { + return nil +} + +func (cp *CompatibleProvier) Destroy() error { + if cp.healthCheck != nil { + cp.healthCheck.close() + } + return nil +} + +func (cp *CompatibleProvier) Initial() error { + if cp.healthCheck != nil { + go cp.healthCheck.process() + } + return nil +} + +func (cp *CompatibleProvier) VehicleType() VehicleType { + return Compatible +} + +func (cp *CompatibleProvier) Type() ProviderType { + return Proxy +} + +func (cp *CompatibleProvier) Proxies() []C.Proxy { + return cp.proxies +} + +func NewCompatibleProvier(name string, proxies []C.Proxy, option *HealthCheckOption) (*CompatibleProvier, error) { + if len(proxies) == 0 { + return nil, errors.New("Provider need one proxy at least") + } + + var hc *healthCheck + if option != nil { + if _, err := url.Parse(option.URL); err != nil { + return nil, fmt.Errorf("URL format error: %w", err) + } + hc = newHealthCheck(proxies, option.URL, option.Interval) + } + + return &CompatibleProvier{ + name: name, + proxies: proxies, + healthCheck: hc, + }, nil +} diff --git a/adapters/provider/vehicle.go b/adapters/provider/vehicle.go new file mode 100644 index 0000000000..8314319456 --- /dev/null +++ b/adapters/provider/vehicle.go @@ -0,0 +1,109 @@ +package provider + +import ( + "context" + "io/ioutil" + "net/http" + "time" +) + +// Vehicle Type +const ( + File VehicleType = iota + HTTP + Compatible +) + +// VehicleType defined +type VehicleType int + +func (v VehicleType) String() string { + switch v { + case File: + return "File" + case HTTP: + return "HTTP" + case Compatible: + return "Compatible" + default: + return "Unknown" + } +} + +type Vehicle interface { + Read() ([]byte, error) + Path() string + Type() VehicleType +} + +type FileVehicle struct { + path string +} + +func (f *FileVehicle) Type() VehicleType { + return File +} + +func (f *FileVehicle) Path() string { + return f.path +} + +func (f *FileVehicle) Read() ([]byte, error) { + return ioutil.ReadFile(f.path) +} + +func NewFileVehicle(path string) *FileVehicle { + return &FileVehicle{path: path} +} + +type HTTPVehicle struct { + url string + path string +} + +func (h *HTTPVehicle) Type() VehicleType { + return HTTP +} + +func (h *HTTPVehicle) Path() string { + return h.path +} + +func (h *HTTPVehicle) Read() ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) + defer cancel() + + req, err := http.NewRequest(http.MethodGet, h.url, nil) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + + transport := &http.Transport{ + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + client := http.Client{Transport: transport} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + buf, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if err := ioutil.WriteFile(h.path, buf, fileMode); err != nil { + return nil, err + } + + return buf, nil +} + +func NewHTTPVehicle(url string, path string) *HTTPVehicle { + return &HTTPVehicle{url, path} +} diff --git a/common/structure/structure.go b/common/structure/structure.go index 8e595a17fd..0a047a410b 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -76,6 +76,8 @@ func (d *Decoder) decode(name string, data interface{}, val reflect.Value) error return d.decodeMap(name, data, val) case reflect.Interface: return d.setInterface(name, data, val) + case reflect.Struct: + return d.decodeStruct(name, data, val) default: return fmt.Errorf("type %s not support", val.Kind().String()) } @@ -232,6 +234,159 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle return nil } +func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + + // If the type of the value to write to and the data match directly, + // then we just set it directly instead of recursing into the structure. + if dataVal.Type() == val.Type() { + val.Set(dataVal) + return nil + } + + dataValKind := dataVal.Kind() + switch dataValKind { + case reflect.Map: + return d.decodeStructFromMap(name, dataVal, val) + default: + return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind()) + } +} + +func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error { + dataValType := dataVal.Type() + if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface { + return fmt.Errorf( + "'%s' needs a map with string keys, has '%s' keys", + name, dataValType.Key().Kind()) + } + + dataValKeys := make(map[reflect.Value]struct{}) + dataValKeysUnused := make(map[interface{}]struct{}) + for _, dataValKey := range dataVal.MapKeys() { + dataValKeys[dataValKey] = struct{}{} + dataValKeysUnused[dataValKey.Interface()] = struct{}{} + } + + errors := make([]string, 0) + + // This slice will keep track of all the structs we'll be decoding. + // There can be more than one struct if there are embedded structs + // that are squashed. + structs := make([]reflect.Value, 1, 5) + structs[0] = val + + // Compile the list of all the fields that we're going to be decoding + // from all the structs. + type field struct { + field reflect.StructField + val reflect.Value + } + fields := []field{} + for len(structs) > 0 { + structVal := structs[0] + structs = structs[1:] + + structType := structVal.Type() + + for i := 0; i < structType.NumField(); i++ { + fieldType := structType.Field(i) + fieldKind := fieldType.Type.Kind() + + // If "squash" is specified in the tag, we squash the field down. + squash := false + tagParts := strings.Split(fieldType.Tag.Get(d.option.TagName), ",") + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + + if squash { + if fieldKind != reflect.Struct { + errors = append(errors, + fmt.Errorf("%s: unsupported type for squash: %s", fieldType.Name, fieldKind).Error()) + } else { + structs = append(structs, structVal.FieldByName(fieldType.Name)) + } + continue + } + + // Normal struct field, store it away + fields = append(fields, field{fieldType, structVal.Field(i)}) + } + } + + // for fieldType, field := range fields { + for _, f := range fields { + field, fieldValue := f.field, f.val + fieldName := field.Name + + tagValue := field.Tag.Get(d.option.TagName) + tagValue = strings.SplitN(tagValue, ",", 2)[0] + if tagValue != "" { + fieldName = tagValue + } + + rawMapKey := reflect.ValueOf(fieldName) + rawMapVal := dataVal.MapIndex(rawMapKey) + if !rawMapVal.IsValid() { + // Do a slower search by iterating over each key and + // doing case-insensitive search. + for dataValKey := range dataValKeys { + mK, ok := dataValKey.Interface().(string) + if !ok { + // Not a string key + continue + } + + if strings.EqualFold(mK, fieldName) { + rawMapKey = dataValKey + rawMapVal = dataVal.MapIndex(dataValKey) + break + } + } + + if !rawMapVal.IsValid() { + // There was no matching key in the map for the value in + // the struct. Just ignore. + continue + } + } + + // Delete the key we're using from the unused map so we stop tracking + delete(dataValKeysUnused, rawMapKey.Interface()) + + if !fieldValue.IsValid() { + // This should never happen + panic("field is not valid") + } + + // If we can't set the field, then it is unexported or something, + // and we just continue onwards. + if !fieldValue.CanSet() { + continue + } + + // If the name is empty string, then we're at the root, and we + // don't dot-join the fields. + if name != "" { + fieldName = fmt.Sprintf("%s.%s", name, fieldName) + } + + if err := d.decode(fieldName, rawMapVal.Interface(), fieldValue); err != nil { + errors = append(errors, err.Error()) + } + } + + if len(errors) > 0 { + return fmt.Errorf(strings.Join(errors, ",")) + } + + return nil +} + func (d *Decoder) setInterface(name string, data interface{}, val reflect.Value) (err error) { dataVal := reflect.ValueOf(data) val.Set(dataVal) diff --git a/config/config.go b/config/config.go index e53580564e..28d4b37786 100644 --- a/config/config.go +++ b/config/config.go @@ -7,8 +7,9 @@ import ( "os" "strings" - adapters "github.com/Dreamacro/clash/adapters/outbound" - "github.com/Dreamacro/clash/common/structure" + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/outboundgroup" + "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/fakeip" @@ -68,6 +69,7 @@ type Config struct { Rules []C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy + Providers map[string]provider.ProxyProvider } type rawDNS struct { @@ -99,12 +101,13 @@ type rawConfig struct { ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` - Hosts map[string]string `yaml:"hosts"` - DNS rawDNS `yaml:"dns"` - Experimental Experimental `yaml:"experimental"` - Proxy []map[string]interface{} `yaml:"Proxy"` - ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` - Rule []string `yaml:"Rule"` + ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"` + Hosts map[string]string `yaml:"hosts"` + DNS rawDNS `yaml:"dns"` + Experimental Experimental `yaml:"experimental"` + Proxy []map[string]interface{} `yaml:"Proxy"` + ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` + Rule []string `yaml:"Rule"` } // Parse config @@ -146,11 +149,12 @@ func Parse(buf []byte) (*Config, error) { } config.General = general - proxies, err := parseProxies(rawCfg) + proxies, providers, err := parseProxies(rawCfg) if err != nil { return nil, err } config.Proxies = proxies + config.Providers = providers rules, err := parseRules(rawCfg, proxies) if err != nil { @@ -171,7 +175,6 @@ func Parse(buf []byte) (*Config, error) { config.Hosts = hosts config.Users = parseAuthentication(rawCfg.Authentication) - return config, nil } @@ -210,75 +213,38 @@ func parseGeneral(cfg *rawConfig) (*General, error) { return general, nil } -func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { - proxies := make(map[string]C.Proxy) +func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) { + proxies = make(map[string]C.Proxy) + providersMap = make(map[string]provider.ProxyProvider) proxyList := []string{} proxiesConfig := cfg.Proxy groupsConfig := cfg.ProxyGroup + providersConfig := cfg.ProxyProvider - decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) + defer func() { + // Destroy already created provider when err != nil + if err != nil { + for _, provider := range providersMap { + provider.Destroy() + } + } + }() - proxies["DIRECT"] = adapters.NewProxy(adapters.NewDirect()) - proxies["REJECT"] = adapters.NewProxy(adapters.NewReject()) + proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) + proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) proxyList = append(proxyList, "DIRECT", "REJECT") // parse proxy for idx, mapping := range proxiesConfig { - proxyType, existType := mapping["type"].(string) - if !existType { - return nil, fmt.Errorf("Proxy %d missing type", idx) - } - - var proxy C.ProxyAdapter - err := fmt.Errorf("cannot parse") - switch proxyType { - case "ss": - ssOption := &adapters.ShadowSocksOption{} - err = decoder.Decode(mapping, ssOption) - if err != nil { - break - } - proxy, err = adapters.NewShadowSocks(*ssOption) - case "socks5": - socksOption := &adapters.Socks5Option{} - err = decoder.Decode(mapping, socksOption) - if err != nil { - break - } - proxy = adapters.NewSocks5(*socksOption) - case "http": - httpOption := &adapters.HttpOption{} - err = decoder.Decode(mapping, httpOption) - if err != nil { - break - } - proxy = adapters.NewHttp(*httpOption) - case "vmess": - vmessOption := &adapters.VmessOption{} - err = decoder.Decode(mapping, vmessOption) - if err != nil { - break - } - proxy, err = adapters.NewVmess(*vmessOption) - case "snell": - snellOption := &adapters.SnellOption{} - err = decoder.Decode(mapping, snellOption) - if err != nil { - break - } - proxy, err = adapters.NewSnell(*snellOption) - default: - return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) - } - + proxy, err := outbound.ParseProxy(mapping) if err != nil { - return nil, fmt.Errorf("Proxy [%d]: %s", idx, err.Error()) + return nil, nil, fmt.Errorf("Proxy %d: %w", idx, err) } if _, exist := proxies[proxy.Name()]; exist { - return nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) + return nil, nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) } - proxies[proxy.Name()] = adapters.NewProxy(proxy) + proxies[proxy.Name()] = proxy proxyList = append(proxyList, proxy.Name()) } @@ -286,95 +252,62 @@ func parseProxies(cfg *rawConfig) (map[string]C.Proxy, error) { for idx, mapping := range groupsConfig { groupName, existName := mapping["name"].(string) if !existName { - return nil, fmt.Errorf("ProxyGroup %d: missing name", idx) + return nil, nil, fmt.Errorf("ProxyGroup %d: missing name", idx) } proxyList = append(proxyList, groupName) } // check if any loop exists and sort the ProxyGroups - if err := proxyGroupsDagSort(groupsConfig, decoder); err != nil { - return nil, err + if err := proxyGroupsDagSort(groupsConfig); err != nil { + return nil, nil, err } - // parse proxy group - for _, mapping := range groupsConfig { - groupType, existType := mapping["type"].(string) - groupName, _ := mapping["name"].(string) - if !existType { - return nil, fmt.Errorf("ProxyGroup %s: missing type", groupName) + // parse and initial providers + for name, mapping := range providersConfig { + if name == provider.ReservedName { + return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName) } - if _, exist := proxies[groupName]; exist { - return nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) + pd, err := provider.ParseProxyProvider(name, mapping) + if err != nil { + return nil, nil, err } - var group C.ProxyAdapter - ps := []C.Proxy{} - - err := fmt.Errorf("cannot parse") - switch groupType { - case "url-test": - urlTestOption := &adapters.URLTestOption{} - err = decoder.Decode(mapping, urlTestOption) - if err != nil { - break - } - ps, err = getProxies(proxies, urlTestOption.Proxies) - if err != nil { - return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) - } - group, err = adapters.NewURLTest(*urlTestOption, ps) - case "select": - selectorOption := &adapters.SelectorOption{} - err = decoder.Decode(mapping, selectorOption) - if err != nil { - break - } - - ps, err = getProxies(proxies, selectorOption.Proxies) - if err != nil { - return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) - } - group, err = adapters.NewSelector(selectorOption.Name, ps) - case "fallback": - fallbackOption := &adapters.FallbackOption{} - err = decoder.Decode(mapping, fallbackOption) - if err != nil { - break - } - - ps, err = getProxies(proxies, fallbackOption.Proxies) - if err != nil { - return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) - } - group, err = adapters.NewFallback(*fallbackOption, ps) - case "load-balance": - loadBalanceOption := &adapters.LoadBalanceOption{} - err = decoder.Decode(mapping, loadBalanceOption) - if err != nil { - break - } + providersMap[name] = pd + } - ps, err = getProxies(proxies, loadBalanceOption.Proxies) - if err != nil { - return nil, fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) - } - group, err = adapters.NewLoadBalance(*loadBalanceOption, ps) + for _, provider := range providersMap { + log.Infoln("Start initial provider %s", provider.Name()) + if err := provider.Initial(); err != nil { + return nil, nil, err } + } + + // parse proxy group + for idx, mapping := range groupsConfig { + group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap) if err != nil { - return nil, fmt.Errorf("Proxy %s: %s", groupName, err.Error()) + return nil, nil, fmt.Errorf("ProxyGroup[%d]: %w", idx, err) } - proxies[groupName] = adapters.NewProxy(group) + + groupName := group.Name() + if _, exist := proxies[groupName]; exist { + return nil, nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) + } + + proxies[groupName] = outbound.NewProxy(group) } ps := []C.Proxy{} for _, v := range proxyList { ps = append(ps, proxies[v]) } + pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, nil) + providersMap[provider.ReservedName] = pd - global, _ := adapters.NewSelector("GLOBAL", ps) - proxies["GLOBAL"] = adapters.NewProxy(global) - return proxies, nil + global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd}) + proxies["GLOBAL"] = outbound.NewProxy(global) + return proxies, providersMap, nil } func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { diff --git a/config/utils.go b/config/utils.go index e263d68be8..79fae6c91e 100644 --- a/config/utils.go +++ b/config/utils.go @@ -4,9 +4,8 @@ import ( "fmt" "strings" - adapters "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/common/structure" - C "github.com/Dreamacro/clash/constant" ) func trimArr(arr []string) (r []string) { @@ -16,18 +15,6 @@ func trimArr(arr []string) (r []string) { return } -func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { - var ps []C.Proxy - for _, name := range list { - p, ok := mapping[name] - if !ok { - return nil, fmt.Errorf("'%s' not found", name) - } - ps = append(ps, p) - } - return ps, nil -} - func or(pointers ...*int) *int { for _, p := range pointers { if p != nil { @@ -40,8 +27,7 @@ func or(pointers ...*int) *int { // Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. // Meanwhile, record the original index in the config file. // If loop is detected, return an error with location of loop. -func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structure.Decoder) error { - +func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { type graphNode struct { indegree int // topological order @@ -50,34 +36,36 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structur data map[string]interface{} // `outdegree` and `from` are used in loop locating outdegree int + option *outboundgroup.GroupCommonOption from []string } + decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) graph := make(map[string]*graphNode) // Step 1.1 build dependency graph for _, mapping := range groupsConfig { - option := &adapters.ProxyGroupOption{} - err := decoder.Decode(mapping, option) - groupName := option.Name - if err != nil { - return fmt.Errorf("ProxyGroup %s: %s", groupName, err.Error()) + option := &outboundgroup.GroupCommonOption{} + if err := decoder.Decode(mapping, option); err != nil { + return fmt.Errorf("ProxyGroup %s: %s", option.Name, err.Error()) } + groupName := option.Name if node, ok := graph[groupName]; ok { if node.data != nil { return fmt.Errorf("ProxyGroup %s: duplicate group name", groupName) } node.data = mapping + node.option = option } else { - graph[groupName] = &graphNode{0, -1, mapping, 0, nil} + graph[groupName] = &graphNode{0, -1, mapping, 0, option, nil} } for _, proxy := range option.Proxies { if node, ex := graph[proxy]; ex { node.indegree++ } else { - graph[proxy] = &graphNode{1, -1, nil, 0, nil} + graph[proxy] = &graphNode{1, -1, nil, 0, nil, nil} } } } @@ -95,14 +83,19 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structur for ; len(queue) > 0; queue = queue[1:] { name := queue[0] node := graph[name] - if node.data != nil { + if node.option != nil { index++ groupsConfig[len(groupsConfig)-index] = node.data - for _, proxy := range node.data["proxies"].([]interface{}) { - child := graph[proxy.(string)] + if len(node.option.Proxies) == 0 { + delete(graph, name) + continue + } + + for _, proxy := range node.option.Proxies { + child := graph[proxy] child.indegree-- if child.indegree == 0 { - queue = append(queue, proxy.(string)) + queue = append(queue, proxy) } } } @@ -117,12 +110,17 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}, decoder *structur // if loop is detected, locate the loop and throw an error // Step 2.1 rebuild the graph, fill `outdegree` and `from` filed for name, node := range graph { - if node.data == nil { + if node.option == nil { + continue + } + + if len(node.option.Proxies) == 0 { continue } - for _, proxy := range node.data["proxies"].([]interface{}) { + + for _, proxy := range node.option.Proxies { node.outdegree++ - child := graph[proxy.(string)] + child := graph[proxy] if child.from == nil { child.from = make([]string, 0, child.indegree) } diff --git a/constant/adapters.go b/constant/adapters.go index 97d65a50c1..b51d083be7 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -61,7 +61,6 @@ type ProxyAdapter interface { DialContext(ctx context.Context, metadata *Metadata) (Conn, error) DialUDP(metadata *Metadata) (PacketConn, net.Addr, error) SupportUDP() bool - Destroy() MarshalJSON() ([]byte, error) } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index ddfa3e6d03..7d44d9740a 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/config" @@ -78,7 +79,7 @@ func ApplyConfig(cfg *config.Config, force bool) { if force { updateGeneral(cfg.General) } - updateProxies(cfg.Proxies) + updateProxies(cfg.Proxies, cfg.Providers) updateRules(cfg.Rules) updateDNS(cfg.DNS) updateHosts(cfg.Hosts) @@ -142,16 +143,16 @@ func updateHosts(tree *trie.Trie) { dns.DefaultHosts = tree } -func updateProxies(proxies map[string]C.Proxy) { +func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { tunnel := T.Instance() - oldProxies := tunnel.Proxies() + oldProviders := tunnel.Providers() - // close proxy group goroutine - for _, proxy := range oldProxies { - proxy.Destroy() + // close providers goroutine + for _, provider := range oldProviders { + provider.Destroy() } - tunnel.UpdateProxies(proxies) + tunnel.UpdateProxies(proxies, providers) } func updateRules(rules []C.Rule) { diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 201fd03f6e..bdf4eee0c1 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -8,7 +8,8 @@ import ( "strconv" "time" - A "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/outboundgroup" C "github.com/Dreamacro/clash/constant" T "github.com/Dreamacro/clash/tunnel" @@ -81,8 +82,8 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { return } - proxy := r.Context().Value(CtxKeyProxy).(*A.Proxy) - selector, ok := proxy.ProxyAdapter.(*A.Selector) + proxy := r.Context().Value(CtxKeyProxy).(*outbound.Proxy) + selector, ok := proxy.ProxyAdapter.(*outboundgroup.Selector) if !ok { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("Must be a Selector")) diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index 1c0fee6579..fd4556037d 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -59,5 +59,5 @@ func handleRedir(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(adapters.NewSocket(target, conn, C.REDIR, C.TCP)) + tun.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP)) } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 29196556f1..a578a490dd 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -6,7 +6,8 @@ import ( "sync" "time" - InboundAdapter "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/nat" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -30,6 +31,7 @@ type Tunnel struct { natTable *nat.Table rules []C.Rule proxies map[string]C.Proxy + providers map[string]provider.ProxyProvider configMux sync.RWMutex // experimental features @@ -66,10 +68,16 @@ func (t *Tunnel) Proxies() map[string]C.Proxy { return t.proxies } +// Providers return all compatible providers +func (t *Tunnel) Providers() map[string]provider.ProxyProvider { + return t.providers +} + // UpdateProxies handle update proxies -func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy) { +func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { t.configMux.Lock() t.proxies = proxies + t.providers = providers t.configMux.Unlock() } @@ -240,9 +248,9 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { } switch adapter := localConn.(type) { - case *InboundAdapter.HTTPAdapter: + case *inbound.HTTPAdapter: t.handleHTTP(adapter, remoteConn) - case *InboundAdapter.SocketAdapter: + case *inbound.SocketAdapter: t.handleSocket(adapter, remoteConn) } } From d8a1d88ded11dd87d50235aaa20b364d679a1c83 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 8 Dec 2019 12:34:05 +0800 Subject: [PATCH 287/535] Chore: update README.md --- README.md | 299 +++++++++++++++++++++++++++--------------------------- 1 file changed, 152 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index b85e5f01f4..4a761570b8 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@

A rule-based tunnel in Go.

+ + Github Actions + @@ -17,7 +20,6 @@ ## Features - Local HTTP/HTTPS/SOCKS server -- Surge-like configuration format - GeoIP rule support - Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol - Supports Netfilter TCP redirecting @@ -70,7 +72,7 @@ $ clash -d . ```

- This is an example configuration file + This is an example configuration file (click to expand) ```yml # port of HTTP @@ -140,158 +142,160 @@ experimental: # - 240.0.0.0/4 Proxy: - -# shadowsocks -# The supported ciphers(encrypt methods): -# aes-128-gcm aes-192-gcm aes-256-gcm -# aes-128-cfb aes-192-cfb aes-256-cfb -# aes-128-ctr aes-192-ctr aes-256-ctr -# rc4-md5 chacha20 chacha20-ietf xchacha20 -# chacha20-ietf-poly1305 xchacha20-ietf-poly1305 -- name: "ss1" - type: ss - server: server - port: 443 - cipher: chacha20-ietf-poly1305 - password: "password" - # udp: true - -# old obfs configuration format remove after prerelease -- name: "ss2" - type: ss - server: server - port: 443 - cipher: chacha20-ietf-poly1305 - password: "password" - plugin: obfs - plugin-opts: - mode: tls # or http - # host: bing.com - -- name: "ss3" - type: ss - server: server - port: 443 - cipher: chacha20-ietf-poly1305 - password: "password" - plugin: v2ray-plugin - plugin-opts: - mode: websocket # no QUIC now - # tls: true # wss + # shadowsocks + # The supported ciphers(encrypt methods): + # aes-128-gcm aes-192-gcm aes-256-gcm + # aes-128-cfb aes-192-cfb aes-256-cfb + # aes-128-ctr aes-192-ctr aes-256-ctr + # rc4-md5 chacha20 chacha20-ietf xchacha20 + # chacha20-ietf-poly1305 xchacha20-ietf-poly1305 + - name: "ss1" + type: ss + server: server + port: 443 + cipher: chacha20-ietf-poly1305 + password: "password" + # udp: true + + # old obfs configuration format remove after prerelease + - name: "ss2" + type: ss + server: server + port: 443 + cipher: chacha20-ietf-poly1305 + password: "password" + plugin: obfs + plugin-opts: + mode: tls # or http + # host: bing.com + + - name: "ss3" + type: ss + server: server + port: 443 + cipher: chacha20-ietf-poly1305 + password: "password" + plugin: v2ray-plugin + plugin-opts: + mode: websocket # no QUIC now + # tls: true # wss + # skip-cert-verify: true + # host: bing.com + # path: "/" + # mux: true + # headers: + # custom: value + + # vmess + # cipher support auto/aes-128-gcm/chacha20-poly1305/none + - name: "vmess" + type: vmess + server: server + port: 443 + uuid: uuid + alterId: 32 + cipher: auto + # udp: true + # tls: true + # skip-cert-verify: true + # network: ws + # ws-path: /path + # ws-headers: + # Host: v2ray.com + + # socks5 + - name: "socks" + type: socks5 + server: server + port: 443 + # username: username + # password: password + # tls: true # skip-cert-verify: true - # host: bing.com - # path: "/" - # mux: true - # headers: - # custom: value - -# vmess -# cipher support auto/aes-128-gcm/chacha20-poly1305/none -- name: "vmess" - type: vmess - server: server - port: 443 - uuid: uuid - alterId: 32 - cipher: auto - # udp: true - # tls: true - # skip-cert-verify: true - # network: ws - # ws-path: /path - # ws-headers: - # Host: v2ray.com - -# socks5 -- name: "socks" - type: socks5 - server: server - port: 443 - # username: username - # password: password - # tls: true - # skip-cert-verify: true - # udp: true - -# http -- name: "http" - type: http - server: server - port: 443 - # username: username - # password: password - # tls: true # https - # skip-cert-verify: true - -# snell -- name: "snell" - type: snell - server: server - port: 44046 - psk: yourpsk - # obfs-opts: - # mode: http # or tls - # host: bing.com + # udp: true + + # http + - name: "http" + type: http + server: server + port: 443 + # username: username + # password: password + # tls: true # https + # skip-cert-verify: true + + # snell + - name: "snell" + type: snell + server: server + port: 44046 + psk: yourpsk + # obfs-opts: + # mode: http # or tls + # host: bing.com Proxy Group: -# url-test select which proxy will be used by benchmarking speed to a URL. -- name: "auto" - type: url-test - proxies: - - ss1 - - ss2 - - vmess1 - url: 'http://www.gstatic.com/generate_204' - interval: 300 - -# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. -- name: "fallback-auto" - type: fallback - proxies: - - ss1 - - ss2 - - vmess1 - url: 'http://www.gstatic.com/generate_204' - interval: 300 - -# load-balance: The request of the same eTLD will be dial on the same proxy. -- name: "load-balance" - type: load-balance - proxies: - - ss1 - - ss2 - - vmess1 - url: 'http://www.gstatic.com/generate_204' - interval: 300 - -# select is used for selecting proxy or proxy group -# you can use RESTful API to switch proxy, is recommended for use in GUI. -- name: Proxy - type: select - proxies: - - ss1 - - ss2 - - vmess1 - - auto + # url-test select which proxy will be used by benchmarking speed to a URL. + - name: "auto" + type: url-test + proxies: + - ss1 + - ss2 + - vmess1 + url: 'http://www.gstatic.com/generate_204' + interval: 300 + + # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. + - name: "fallback-auto" + type: fallback + proxies: + - ss1 + - ss2 + - vmess1 + url: 'http://www.gstatic.com/generate_204' + interval: 300 + + # load-balance: The request of the same eTLD will be dial on the same proxy. + - name: "load-balance" + type: load-balance + proxies: + - ss1 + - ss2 + - vmess1 + url: 'http://www.gstatic.com/generate_204' + interval: 300 + + # select is used for selecting proxy or proxy group + # you can use RESTful API to switch proxy, is recommended for use in GUI. + - name: Proxy + type: select + proxies: + - ss1 + - ss2 + - vmess1 + - auto Rule: -- DOMAIN-SUFFIX,google.com,auto -- DOMAIN-KEYWORD,google,auto -- DOMAIN,google.com,auto -- DOMAIN-SUFFIX,ad.com,REJECT -# rename SOURCE-IP-CIDR and would remove after prerelease -- SRC-IP-CIDR,192.168.1.201/32,DIRECT -# optional param "no-resolve" for IP rules (GEOIP IP-CIDR) -- IP-CIDR,127.0.0.0/8,DIRECT -- GEOIP,CN,DIRECT -- DST-PORT,80,DIRECT -- SRC-PORT,7777,DIRECT -# FINAL would remove after prerelease -# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now -- MATCH,auto + - DOMAIN-SUFFIX,google.com,auto + - DOMAIN-KEYWORD,google,auto + - DOMAIN,google.com,auto + - DOMAIN-SUFFIX,ad.com,REJECT + # rename SOURCE-IP-CIDR and would remove after prerelease + - SRC-IP-CIDR,192.168.1.201/32,DIRECT + # optional param "no-resolve" for IP rules (GEOIP IP-CIDR) + - IP-CIDR,127.0.0.0/8,DIRECT + - GEOIP,CN,DIRECT + - DST-PORT,80,DIRECT + - SRC-PORT,7777,DIRECT + # FINAL would remove after prerelease + # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now + - MATCH,auto ```
+## Advanced +[Provider](https://github.com/Dreamacro/clash/wiki/Provider) + ## Documentations https://clash.gitbook.io/ @@ -310,4 +314,5 @@ https://clash.gitbook.io/ - [x] Complementing the necessary rule operators - [x] Redir proxy - [x] UDP support -- [ ] Connection manager +- [x] Connection manager +- [ ] Event API From bd4302e096d5e0b48a7a30de66688d4ee0a09eee Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 8 Dec 2019 13:05:05 +0800 Subject: [PATCH 288/535] Chore: update dependencies --- README.md | 2 +- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4a761570b8..afa0c3f151 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Proxy: # aes-128-gcm aes-192-gcm aes-256-gcm # aes-128-cfb aes-192-cfb aes-256-cfb # aes-128-ctr aes-192-ctr aes-256-ctr - # rc4-md5 chacha20 chacha20-ietf xchacha20 + # rc4-md5 chacha20-ietf xchacha20 # chacha20-ietf-poly1305 xchacha20-ietf-poly1305 - name: "ss1" type: ss diff --git a/go.mod b/go.mod index 151a61842a..1dc56b078b 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,21 @@ module github.com/Dreamacro/clash go 1.13 require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.5-0.20191012162057-46254afc8b68 + github.com/Dreamacro/go-shadowsocks2 v0.1.5 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.1 - github.com/miekg/dns v1.1.22 + github.com/miekg/dns v1.1.24 github.com/oschwald/geoip2-golang v1.3.0 github.com/oschwald/maxminddb-golang v1.5.0 // indirect github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 - golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 - golang.org/x/net v0.0.0-20191011234655-491137f69257 + golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 + golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/eapache/channels.v1 v1.1.0 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.7 ) diff --git a/go.sum b/go.sum index 77f5e12f4e..6e0954d34d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.5-0.20191012162057-46254afc8b68 h1:UBDLpj1IGVkUcUBuZWE6DmZApPTZcnmcV6AfyDN/yhg= -github.com/Dreamacro/go-shadowsocks2 v0.1.5-0.20191012162057-46254afc8b68/go.mod h1:Y8obOtHDOqxMGHjPglfCiXZBKExOA9VL6I6sJagOwYM= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= -github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/Dreamacro/go-shadowsocks2 v0.1.5 h1:BizWSjmwzAyQoslz6YhJYMiAGT99j9cnm9zlxVr+kyI= +github.com/Dreamacro/go-shadowsocks2 v0.1.5/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= 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= @@ -19,8 +17,8 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.22 h1:Jm64b3bO9kP43ddLjL2EY3Io6bmy1qGb9Xxz6TqS6rc= -github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.24 h1:6G8Eop/HM8hpagajbn0rFQvAKZWiiCa8P6N2I07+wwI= +github.com/miekg/dns v1.1.24/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8= github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= github.com/oschwald/maxminddb-golang v1.5.0 h1:rmyoIV6z2/s9TCJedUuDiKht2RN12LWJ1L7iRGtWY64= @@ -39,15 +37,15 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.0.0-20191011234655-491137f69257 h1:ry8e2D+cwaV6hk7lb3aRTjjZo24shrbK0e11QEOkTIg= -golang.org/x/net v0.0.0-20191011234655-491137f69257/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663 h1:Dd5RoEW+yQi+9DMybroBctIdyiwuNT7sJFMC27/6KxI= +golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663/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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= @@ -71,3 +69,5 @@ gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7Ooee gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 2334bafe68017ac421abe3d0352efe0b665898f2 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 10 Dec 2019 15:04:22 +0800 Subject: [PATCH 289/535] Change: proxy gruop strategy improvement --- adapters/outboundgroup/common.go | 20 +++++++ adapters/outboundgroup/fallback.go | 25 ++++---- adapters/outboundgroup/loadbalance.go | 13 +++-- adapters/outboundgroup/selector.go | 13 +++-- adapters/outboundgroup/urltest.go | 84 ++++++++++++++------------- common/singledo/singledo.go | 54 +++++++++++++++++ common/singledo/singledo_test.go | 52 +++++++++++++++++ 7 files changed, 198 insertions(+), 63 deletions(-) create mode 100644 adapters/outboundgroup/common.go create mode 100644 common/singledo/singledo.go create mode 100644 common/singledo/singledo_test.go diff --git a/adapters/outboundgroup/common.go b/adapters/outboundgroup/common.go new file mode 100644 index 0000000000..ce40072ce9 --- /dev/null +++ b/adapters/outboundgroup/common.go @@ -0,0 +1,20 @@ +package outboundgroup + +import ( + "time" + + "github.com/Dreamacro/clash/adapters/provider" + C "github.com/Dreamacro/clash/constant" +) + +const ( + defaultGetProxiesDuration = time.Second * 5 +) + +func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy { + proxies := []C.Proxy{} + for _, provider := range providers { + proxies = append(proxies, provider.Proxies()...) + } + return proxies +} diff --git a/adapters/outboundgroup/fallback.go b/adapters/outboundgroup/fallback.go index 7c0525b884..2104c39e1c 100644 --- a/adapters/outboundgroup/fallback.go +++ b/adapters/outboundgroup/fallback.go @@ -7,11 +7,13 @@ import ( "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/common/singledo" C "github.com/Dreamacro/clash/constant" ) type Fallback struct { *outbound.Base + single *singledo.Single providers []provider.ProxyProvider } @@ -56,29 +58,28 @@ func (f *Fallback) MarshalJSON() ([]byte, error) { } func (f *Fallback) proxies() []C.Proxy { - proxies := []C.Proxy{} - for _, provider := range f.providers { - proxies = append(proxies, provider.Proxies()...) - } - return proxies + elm, _, _ := f.single.Do(func() (interface{}, error) { + return getProvidersProxies(f.providers), nil + }) + + return elm.([]C.Proxy) } func (f *Fallback) findAliveProxy() C.Proxy { - for _, provider := range f.providers { - proxies := provider.Proxies() - for _, proxy := range proxies { - if proxy.Alive() { - return proxy - } + proxies := f.proxies() + for _, proxy := range proxies { + if proxy.Alive() { + return proxy } } - return f.providers[0].Proxies()[0] + return f.proxies()[0] } func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { return &Fallback{ Base: outbound.NewBase(name, C.Fallback, false), + single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, } } diff --git a/adapters/outboundgroup/loadbalance.go b/adapters/outboundgroup/loadbalance.go index 74a154cece..78a942e0a6 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -8,6 +8,7 @@ import ( "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/common/murmur3" + "github.com/Dreamacro/clash/common/singledo" C "github.com/Dreamacro/clash/constant" "golang.org/x/net/publicsuffix" @@ -15,6 +16,7 @@ import ( type LoadBalance struct { *outbound.Base + single *singledo.Single maxRetry int providers []provider.ProxyProvider } @@ -98,11 +100,11 @@ func (lb *LoadBalance) SupportUDP() bool { } func (lb *LoadBalance) proxies() []C.Proxy { - proxies := []C.Proxy{} - for _, provider := range lb.providers { - proxies = append(proxies, provider.Proxies()...) - } - return proxies + elm, _, _ := lb.single.Do(func() (interface{}, error) { + return getProvidersProxies(lb.providers), nil + }) + + return elm.([]C.Proxy) } func (lb *LoadBalance) MarshalJSON() ([]byte, error) { @@ -119,6 +121,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { return &LoadBalance{ Base: outbound.NewBase(name, C.LoadBalance, false), + single: singledo.NewSingle(defaultGetProxiesDuration), maxRetry: 3, providers: providers, } diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index fc53be716b..fd9ef041fc 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -8,11 +8,13 @@ import ( "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/common/singledo" C "github.com/Dreamacro/clash/constant" ) type Selector struct { *outbound.Base + single *singledo.Single selected C.Proxy providers []provider.ProxyProvider } @@ -66,17 +68,18 @@ func (s *Selector) Set(name string) error { } func (s *Selector) proxies() []C.Proxy { - proxies := []C.Proxy{} - for _, provider := range s.providers { - proxies = append(proxies, provider.Proxies()...) - } - return proxies + elm, _, _ := s.single.Do(func() (interface{}, error) { + return getProvidersProxies(s.providers), nil + }) + + return elm.([]C.Proxy) } func NewSelector(name string, providers []provider.ProxyProvider) *Selector { selected := providers[0].Proxies()[0] return &Selector{ Base: outbound.NewBase(name, C.Selector, false), + single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, selected: selected, } diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index 2e57e0f2b1..cf1ad13814 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -4,36 +4,35 @@ import ( "context" "encoding/json" "net" + "time" "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/common/singledo" C "github.com/Dreamacro/clash/constant" ) type URLTest struct { *outbound.Base - fast C.Proxy - providers []provider.ProxyProvider + single *singledo.Single + fastSingle *singledo.Single + providers []provider.ProxyProvider } func (u *URLTest) Now() string { - return u.fast.Name() + return u.fast().Name() } func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { - for i := 0; i < 3; i++ { - c, err = u.fast.DialContext(ctx, metadata) - if err == nil { - c.AppendToChains(u) - return - } - u.fallback() + c, err = u.fast().DialContext(ctx, metadata) + if err == nil { + c.AppendToChains(u) } - return + return c, err } func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - pc, addr, err := u.fast.DialUDP(metadata) + pc, addr, err := u.fast().DialUDP(metadata) if err == nil { pc.AppendToChains(u) } @@ -41,15 +40,37 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) } func (u *URLTest) proxies() []C.Proxy { - proxies := []C.Proxy{} - for _, provider := range u.providers { - proxies = append(proxies, provider.Proxies()...) - } - return proxies + elm, _, _ := u.single.Do(func() (interface{}, error) { + return getProvidersProxies(u.providers), nil + }) + + return elm.([]C.Proxy) +} + +func (u *URLTest) fast() C.Proxy { + elm, _, _ := u.fastSingle.Do(func() (interface{}, error) { + proxies := u.proxies() + fast := proxies[0] + min := fast.LastDelay() + for _, proxy := range proxies[1:] { + if !proxy.Alive() { + continue + } + + delay := proxy.LastDelay() + if delay < min { + fast = proxy + min = delay + } + } + return fast, nil + }) + + return elm.(C.Proxy) } func (u *URLTest) SupportUDP() bool { - return u.fast.SupportUDP() + return u.fast().SupportUDP() } func (u *URLTest) MarshalJSON() ([]byte, error) { @@ -64,30 +85,11 @@ func (u *URLTest) MarshalJSON() ([]byte, error) { }) } -func (u *URLTest) fallback() { - proxies := u.proxies() - fast := proxies[0] - min := fast.LastDelay() - for _, proxy := range proxies[1:] { - if !proxy.Alive() { - continue - } - - delay := proxy.LastDelay() - if delay < min { - fast = proxy - min = delay - } - } - u.fast = fast -} - func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest { - fast := providers[0].Proxies()[0] - return &URLTest{ - Base: outbound.NewBase(name, C.URLTest, false), - fast: fast, - providers: providers, + Base: outbound.NewBase(name, C.URLTest, false), + single: singledo.NewSingle(defaultGetProxiesDuration), + fastSingle: singledo.NewSingle(time.Second * 10), + providers: providers, } } diff --git a/common/singledo/singledo.go b/common/singledo/singledo.go new file mode 100644 index 0000000000..4828d55886 --- /dev/null +++ b/common/singledo/singledo.go @@ -0,0 +1,54 @@ +package singledo + +import ( + "sync" + "time" +) + +type call struct { + wg sync.WaitGroup + val interface{} + err error +} + +type Single struct { + mux sync.Mutex + last int64 + wait int64 + call *call + result *Result +} + +type Result struct { + Val interface{} + Err error +} + +func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) { + s.mux.Lock() + now := time.Now().Unix() + if now < s.last+s.wait { + s.mux.Unlock() + return s.result.Val, s.result.Err, true + } + + if call := s.call; call != nil { + s.mux.Unlock() + call.wg.Wait() + return call.val, call.err, true + } + + call := &call{} + call.wg.Add(1) + s.call = call + s.mux.Unlock() + call.val, call.err = fn() + s.call = nil + s.result = &Result{call.val, call.err} + s.last = now + return call.val, call.err, false +} + +func NewSingle(wait time.Duration) *Single { + return &Single{wait: int64(wait)} +} diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go new file mode 100644 index 0000000000..d65525802f --- /dev/null +++ b/common/singledo/singledo_test.go @@ -0,0 +1,52 @@ +package singledo + +import ( + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestBasic(t *testing.T) { + single := NewSingle(time.Millisecond * 30) + foo := 0 + shardCount := 0 + call := func() (interface{}, error) { + foo++ + return nil, nil + } + + var wg sync.WaitGroup + const n = 10 + wg.Add(n) + for i := 0; i < n; i++ { + go func() { + _, _, shard := single.Do(call) + if shard { + shardCount++ + } + wg.Done() + }() + } + + wg.Wait() + assert.Equal(t, 1, foo) + assert.Equal(t, 9, shardCount) +} + +func TestTimer(t *testing.T) { + single := NewSingle(time.Millisecond * 30) + foo := 0 + call := func() (interface{}, error) { + foo++ + return nil, nil + } + + single.Do(call) + time.Sleep(10 * time.Millisecond) + _, _, shard := single.Do(call) + + assert.Equal(t, 1, foo) + assert.True(t, shard) +} From 36716ca695b2c34fa584e1ea7129a1522097fcb9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 10 Dec 2019 16:26:15 +0800 Subject: [PATCH 290/535] Fix: singledo panic --- common/singledo/singledo.go | 11 ++++++----- common/singledo/singledo_test.go | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/common/singledo/singledo.go b/common/singledo/singledo.go index 4828d55886..849781151a 100644 --- a/common/singledo/singledo.go +++ b/common/singledo/singledo.go @@ -13,8 +13,8 @@ type call struct { type Single struct { mux sync.Mutex - last int64 - wait int64 + last time.Time + wait time.Duration call *call result *Result } @@ -26,8 +26,8 @@ type Result struct { func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) { s.mux.Lock() - now := time.Now().Unix() - if now < s.last+s.wait { + now := time.Now() + if now.Before(s.last.Add(s.wait)) { s.mux.Unlock() return s.result.Val, s.result.Err, true } @@ -43,6 +43,7 @@ func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, s s.call = call s.mux.Unlock() call.val, call.err = fn() + call.wg.Done() s.call = nil s.result = &Result{call.val, call.err} s.last = now @@ -50,5 +51,5 @@ func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, s } func NewSingle(wait time.Duration) *Single { - return &Single{wait: int64(wait)} + return &Single{wait: wait} } diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go index d65525802f..c1e48ca85e 100644 --- a/common/singledo/singledo_test.go +++ b/common/singledo/singledo_test.go @@ -14,6 +14,7 @@ func TestBasic(t *testing.T) { shardCount := 0 call := func() (interface{}, error) { foo++ + time.Sleep(time.Millisecond * 5) return nil, nil } From 29cf3ca0efbe9dd212224a73a3da7312347f9695 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 10 Dec 2019 17:27:07 +0800 Subject: [PATCH 291/535] Fix: should initial compatible provider --- config/config.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config/config.go b/config/config.go index 28d4b37786..d867282a52 100644 --- a/config/config.go +++ b/config/config.go @@ -298,6 +298,18 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[ proxies[groupName] = outbound.NewProxy(group) } + // initial compatible provier + for _, pd := range providersMap { + if pd.VehicleType() != provider.Compatible { + continue + } + + log.Infoln("Start initial compatible provider %s", pd.Name()) + if err := pd.Initial(); err != nil { + return nil, nil, err + } + } + ps := []C.Proxy{} for _, v := range proxyList { ps = append(ps, proxies[v]) From 95e9ae2d8d4d0bb474061dea5ce1c33528d4e210 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 11 Dec 2019 17:31:15 +0800 Subject: [PATCH 292/535] Feature: add basic api for proxy provider --- adapters/provider/provider.go | 42 ++++++++++++++++++++-- hub/route/common.go | 17 +++++++++ hub/route/ctxkeys.go | 6 ++-- hub/route/provider.go | 66 +++++++++++++++++++++++++++++++++++ hub/route/proxies.go | 7 +--- hub/route/server.go | 1 + 6 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 hub/route/common.go create mode 100644 hub/route/provider.go diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index 1149273db3..3d718cb6e1 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -3,6 +3,7 @@ package provider import ( "bytes" "crypto/md5" + "encoding/json" "errors" "fmt" "io/ioutil" @@ -58,6 +59,7 @@ type Provider interface { type ProxyProvider interface { Provider Proxies() []C.Proxy + HealthCheck() } type ProxySchema struct { @@ -72,11 +74,22 @@ type ProxySetProvider struct { healthCheck *healthCheck healthCheckOption *HealthCheckOption ticker *time.Ticker + updatedAt *time.Time // mux for avoiding creating new goroutines when pulling mux sync.Mutex } +func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "name": pp.Name(), + "type": pp.Type().String(), + "vehicleType": pp.VehicleType().String(), + "proxies": pp.Proxies(), + "updatedAt": pp.updatedAt, + }) +} + func (pp *ProxySetProvider) Name() string { return pp.name } @@ -85,6 +98,14 @@ func (pp *ProxySetProvider) Reload() error { return nil } +func (pp *ProxySetProvider) HealthCheck() { + pp.mux.Lock() + defer pp.mux.Unlock() + if pp.healthCheck != nil { + pp.healthCheck.check() + } +} + func (pp *ProxySetProvider) Destroy() error { pp.mux.Lock() defer pp.mux.Unlock() @@ -175,6 +196,8 @@ func (pp *ProxySetProvider) pull() error { return err } + now := time.Now() + pp.updatedAt = &now pp.hash = hash pp.setProxies(proxies) @@ -210,9 +233,9 @@ func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) { pp.mux.Lock() if pp.healthCheck != nil { pp.healthCheck.close() - pp.healthCheck = newHealthCheck(proxies, pp.healthCheckOption.URL, pp.healthCheckOption.Interval) - go pp.healthCheck.process() } + pp.healthCheck = newHealthCheck(proxies, pp.healthCheckOption.URL, pp.healthCheckOption.Interval) + go pp.healthCheck.process() pp.mux.Unlock() } } @@ -238,6 +261,15 @@ type CompatibleProvier struct { proxies []C.Proxy } +func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "name": cp.Name(), + "type": cp.Type().String(), + "vehicleType": cp.VehicleType().String(), + "proxies": cp.Proxies(), + }) +} + func (cp *CompatibleProvier) Name() string { return cp.name } @@ -253,6 +285,12 @@ func (cp *CompatibleProvier) Destroy() error { return nil } +func (cp *CompatibleProvier) HealthCheck() { + if cp.healthCheck != nil { + cp.healthCheck.check() + } +} + func (cp *CompatibleProvier) Initial() error { if cp.healthCheck != nil { go cp.healthCheck.process() diff --git a/hub/route/common.go b/hub/route/common.go new file mode 100644 index 0000000000..2240e8915c --- /dev/null +++ b/hub/route/common.go @@ -0,0 +1,17 @@ +package route + +import ( + "net/http" + "net/url" + + "github.com/go-chi/chi" +) + +// When name is composed of a partial escape string, Golang does not unescape it +func getEscapeParam(r *http.Request, paramName string) string { + param := chi.URLParam(r, paramName) + if newParam, err := url.PathUnescape(param); err == nil { + param = newParam + } + return param +} diff --git a/hub/route/ctxkeys.go b/hub/route/ctxkeys.go index 079329718c..563701926d 100644 --- a/hub/route/ctxkeys.go +++ b/hub/route/ctxkeys.go @@ -1,8 +1,10 @@ package route var ( - CtxKeyProxyName = contextKey("proxy name") - CtxKeyProxy = contextKey("proxy") + CtxKeyProxyName = contextKey("proxy name") + CtxKeyProviderName = contextKey("provider name") + CtxKeyProxy = contextKey("proxy") + CtxKeyProvider = contextKey("provider") ) type contextKey string diff --git a/hub/route/provider.go b/hub/route/provider.go new file mode 100644 index 0000000000..ddc2426c59 --- /dev/null +++ b/hub/route/provider.go @@ -0,0 +1,66 @@ +package route + +import ( + "context" + "net/http" + + "github.com/Dreamacro/clash/adapters/provider" + T "github.com/Dreamacro/clash/tunnel" + + "github.com/go-chi/chi" + "github.com/go-chi/render" +) + +func proxyProviderRouter() http.Handler { + r := chi.NewRouter() + r.Get("/", getProviders) + + r.Route("/{name}", func(r chi.Router) { + r.Use(parseProviderName, findProviderByName) + r.Get("/", getProvider) + r.Get("/healthcheck", doProviderHealthCheck) + }) + return r +} + +func getProviders(w http.ResponseWriter, r *http.Request) { + providers := T.Instance().Providers() + render.JSON(w, r, render.M{ + "providers": providers, + }) +} + +func getProvider(w http.ResponseWriter, r *http.Request) { + provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) + render.JSON(w, r, provider) +} + +func doProviderHealthCheck(w http.ResponseWriter, r *http.Request) { + provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) + provider.HealthCheck() + render.NoContent(w, r) +} + +func parseProviderName(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + name := getEscapeParam(r, "name") + ctx := context.WithValue(r.Context(), CtxKeyProviderName, name) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func findProviderByName(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + name := r.Context().Value(CtxKeyProviderName).(string) + providers := T.Instance().Providers() + provider, exist := providers[name] + if !exist { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, ErrNotFound) + return + } + + ctx := context.WithValue(r.Context(), CtxKeyProvider, provider) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/hub/route/proxies.go b/hub/route/proxies.go index bdf4eee0c1..ab769f9cc1 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "net/url" "strconv" "time" @@ -30,13 +29,9 @@ func proxyRouter() http.Handler { return r } -// When name is composed of a partial escape string, Golang does not unescape it func parseProxyName(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - name := chi.URLParam(r, "name") - if newName, err := url.PathUnescape(name); err == nil { - name = newName - } + name := getEscapeParam(r, "name") ctx := context.WithValue(r.Context(), CtxKeyProxyName, name) next.ServeHTTP(w, r.WithContext(ctx)) }) diff --git a/hub/route/server.go b/hub/route/server.go index 560c338fe8..ab4e61c4f9 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -68,6 +68,7 @@ func Start(addr string, secret string) { r.Mount("/proxies", proxyRouter()) r.Mount("/rules", ruleRouter()) r.Mount("/connections", connectionRouter()) + r.Mount("/providers/proxies", proxyProviderRouter()) }) if uiPath != "" { From 0822b526d5d8cc4666fda3ccc52c1c08ae83753d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 13 Dec 2019 00:29:24 +0800 Subject: [PATCH 293/535] Improve: provider api --- adapters/outbound/base.go | 1 + adapters/provider/provider.go | 20 ++++++++++++++++++-- hub/route/provider.go | 15 +++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 0b9c90df77..3dc97825ba 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -142,6 +142,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { mapping := map[string]interface{}{} json.Unmarshal(inner, &mapping) mapping["history"] = p.DelayHistory() + mapping["name"] = p.Name() return json.Marshal(mapping) } diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index 3d718cb6e1..ddecbbf5f8 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -60,6 +60,7 @@ type ProxyProvider interface { Provider Proxies() []C.Proxy HealthCheck() + Update() error } type ProxySchema struct { @@ -106,6 +107,10 @@ func (pp *ProxySetProvider) HealthCheck() { } } +func (pp *ProxySetProvider) Update() error { + return pp.pull() +} + func (pp *ProxySetProvider) Destroy() error { pp.mux.Lock() defer pp.mux.Unlock() @@ -124,8 +129,10 @@ func (pp *ProxySetProvider) Destroy() error { func (pp *ProxySetProvider) Initial() error { var buf []byte var err error - if _, err := os.Stat(pp.vehicle.Path()); err == nil { + if stat, err := os.Stat(pp.vehicle.Path()); err == nil { buf, err = ioutil.ReadFile(pp.vehicle.Path()) + modTime := stat.ModTime() + pp.updatedAt = &modTime } else { buf, err = pp.vehicle.Read() } @@ -180,9 +187,11 @@ func (pp *ProxySetProvider) pull() error { return err } + now := time.Now() hash := md5.Sum(buf) if bytes.Equal(pp.hash[:], hash[:]) { log.Debugln("[Provider] %s's proxies doesn't change", pp.Name()) + pp.updatedAt = &now return nil } @@ -196,7 +205,6 @@ func (pp *ProxySetProvider) pull() error { return err } - now := time.Now() pp.updatedAt = &now pp.hash = hash pp.setProxies(proxies) @@ -224,6 +232,10 @@ func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) { proxies = append(proxies, proxy) } + if len(proxies) == 0 { + return nil, errors.New("File doesn't have any valid proxy") + } + return proxies, nil } @@ -291,6 +303,10 @@ func (cp *CompatibleProvier) HealthCheck() { } } +func (cp *CompatibleProvier) Update() error { + return nil +} + func (cp *CompatibleProvier) Initial() error { if cp.healthCheck != nil { go cp.healthCheck.process() diff --git a/hub/route/provider.go b/hub/route/provider.go index ddc2426c59..f69bec46fb 100644 --- a/hub/route/provider.go +++ b/hub/route/provider.go @@ -18,7 +18,8 @@ func proxyProviderRouter() http.Handler { r.Route("/{name}", func(r chi.Router) { r.Use(parseProviderName, findProviderByName) r.Get("/", getProvider) - r.Get("/healthcheck", doProviderHealthCheck) + r.Put("/", updateProvider) + r.Get("/healthcheck", healthCheckProvider) }) return r } @@ -35,7 +36,17 @@ func getProvider(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, provider) } -func doProviderHealthCheck(w http.ResponseWriter, r *http.Request) { +func updateProvider(w http.ResponseWriter, r *http.Request) { + provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) + if err := provider.Update(); err != nil { + render.Status(r, http.StatusServiceUnavailable) + render.JSON(w, r, newError(err.Error())) + return + } + render.NoContent(w, r) +} + +func healthCheckProvider(w http.ResponseWriter, r *http.Request) { provider := r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) provider.HealthCheck() render.NoContent(w, r) From eae06a4a7d3e7f47b5d442e59e944f7ba5bcc1ff Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 14 Dec 2019 18:13:33 +0800 Subject: [PATCH 294/535] Fix: valid proxy group and remove unused code --- adapters/outboundgroup/parser.go | 6 ++++++ dns/doh.go | 3 --- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index 972305c27e..944976f15d 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -13,6 +13,7 @@ var ( errFormat = errors.New("format error") errType = errors.New("unsupport type") errMissUse = errors.New("`use` field should not be empty") + errMissProxy = errors.New("`use` or `proxies` missing") errMissHealthCheck = errors.New("`url` or `interval` missing") errDuplicateProvider = errors.New("`duplicate provider name") ) @@ -41,6 +42,11 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, groupName := groupOption.Name providers := []provider.ProxyProvider{} + + if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { + return nil, errMissProxy + } + if len(groupOption.Proxies) != 0 { ps, err := getProxies(proxyMap, groupOption.Proxies) if err != nil { diff --git a/dns/doh.go b/dns/doh.go index 51a69df538..d8d6077291 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -13,9 +13,6 @@ import ( const ( // dotMimeType is the DoH mimetype that should be used. dotMimeType = "application/dns-message" - - // dotPath is the URL path that should be used. - dotPath = "/dns-query" ) var dohTransport = &http.Transport{ From dd61e8d19dad69a7fa1e838f718c79eed3483e0d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 20 Dec 2019 17:22:24 +0800 Subject: [PATCH 295/535] Chore: aggregate logger --- config/initial.go | 7 +++---- main.go | 7 +++---- rules/geoip.go | 4 ++-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/config/initial.go b/config/initial.go index cf61d1bc72..63ff427fb0 100644 --- a/config/initial.go +++ b/config/initial.go @@ -10,8 +10,7 @@ import ( "strings" C "github.com/Dreamacro/clash/constant" - - log "github.com/sirupsen/logrus" + "github.com/Dreamacro/clash/log" ) func downloadMMDB(path string) (err error) { @@ -65,13 +64,13 @@ func Init(dir string) error { // initial config.yaml if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { - log.Info("Can't find config, create an empty file") + log.Infoln("Can't find config, create an empty file") os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) } // initial mmdb if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { - log.Info("Can't find MMDB, start download") + log.Infoln("Can't find MMDB, start download") err := downloadMMDB(C.Path.MMDB()) if err != nil { return fmt.Errorf("Can't download MMDB: %s", err.Error()) diff --git a/main.go b/main.go index a08a5eb5d5..9e545e02eb 100644 --- a/main.go +++ b/main.go @@ -12,8 +12,7 @@ import ( "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub" - - log "github.com/sirupsen/logrus" + "github.com/Dreamacro/clash/log" ) var ( @@ -55,11 +54,11 @@ func main() { } if err := config.Init(C.Path.HomeDir()); err != nil { - log.Fatalf("Initial configuration directory error: %s", err.Error()) + log.Fatalln("Initial configuration directory error: %s", err.Error()) } if err := hub.Parse(); err != nil { - log.Fatalf("Parse config error: %s", err.Error()) + log.Fatalln("Parse config error: %s", err.Error()) } sigCh := make(chan os.Signal, 1) diff --git a/rules/geoip.go b/rules/geoip.go index 56b75fea24..4b9029cf2d 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -4,9 +4,9 @@ import ( "sync" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" "github.com/oschwald/geoip2-golang" - log "github.com/sirupsen/logrus" ) var ( @@ -50,7 +50,7 @@ func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { var err error mmdb, err = geoip2.Open(C.Path.MMDB()) if err != nil { - log.Fatalf("Can't load mmdb: %s", err.Error()) + log.Fatalln("Can't load mmdb: %s", err.Error()) } }) From ecb9e4f57db370dde2b0b9bfd28118893dd61800 Mon Sep 17 00:00:00 2001 From: Siji Date: Wed, 25 Dec 2019 12:01:48 +0800 Subject: [PATCH 296/535] Chore: distinguish udp request in log (#449) --- dns/iputil.go | 2 +- tunnel/tunnel.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dns/iputil.go b/dns/iputil.go index 3c34cf1655..f186e5a0da 100644 --- a/dns/iputil.go +++ b/dns/iputil.go @@ -7,7 +7,7 @@ import ( ) var ( - errIPNotFound = errors.New("cannot found ip") + errIPNotFound = errors.New("couldn't find ip") errIPVersion = errors.New("ip version error") ) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index a578a490dd..69765e70cf 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -182,7 +182,7 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) { wg.Add(1) proxy, rule, err := t.resolveMetadata(metadata) if err != nil { - log.Warnln("Parse metadata failed: %s", err.Error()) + log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) t.natTable.Delete(lockKey) wg.Done() return @@ -190,7 +190,7 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) { rawPc, nAddr, err := proxy.DialUDP(metadata) if err != nil { - log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) + log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error()) t.natTable.Delete(lockKey) wg.Done() return @@ -199,9 +199,9 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) { pc = newUDPTracker(rawPc, DefaultManager, metadata, rule) if rule != nil { - log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) + log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) } else { - log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) + log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) } t.natTable.Set(key, pc, addr) From 3435c67e68baa8b648e2c7eecdf8ab0525865f3a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 25 Dec 2019 15:12:11 +0800 Subject: [PATCH 297/535] Fix: ParseWithBytes should recive buffer and parse buffer --- hub/executor/executor.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 7d44d9740a..ac45c1727d 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -65,12 +65,12 @@ func ParseWithPath(path string) (*config.Config, error) { return nil, err } - return config.Parse(buf) + return ParseWithBytes(buf) } -// Parse config with default config path +// ParseWithBytes config with buffer func ParseWithBytes(buf []byte) (*config.Config, error) { - return ParseWithPath(C.Path.Config()) + return config.Parse(buf) } // ApplyConfig dispatch configure to all parts From af40048841994fa1b9776ccd4b5487b5c0f9355f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 26 Dec 2019 18:41:06 +0800 Subject: [PATCH 298/535] Feature: make every provider support health check --- adapters/outboundgroup/parser.go | 13 +++-- adapters/provider/healthcheck.go | 49 +++++++++++++------ adapters/provider/parser.go | 10 ++-- adapters/provider/provider.go | 83 +++++++++++--------------------- 4 files changed, 70 insertions(+), 85 deletions(-) diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index 944976f15d..e54755f833 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -55,7 +55,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, // if Use not empty, drop health check options if len(groupOption.Use) != 0 { - pd, err := provider.NewCompatibleProvier(groupName, ps, nil) + hc := provider.NewHealthCheck(ps, "", 0) + pd, err := provider.NewCompatibleProvier(groupName, ps, hc) if err != nil { return nil, err } @@ -64,7 +65,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, } else { // select don't need health check if groupOption.Type == "select" { - pd, err := provider.NewCompatibleProvier(groupName, ps, nil) + hc := provider.NewHealthCheck(ps, "", 0) + pd, err := provider.NewCompatibleProvier(groupName, ps, hc) if err != nil { return nil, err } @@ -76,11 +78,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, return nil, errMissHealthCheck } - healthOption := &provider.HealthCheckOption{ - URL: groupOption.URL, - Interval: uint(groupOption.Interval), - } - pd, err := provider.NewCompatibleProvier(groupName, ps, healthOption) + hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval)) + pd, err := provider.NewCompatibleProvier(groupName, ps, hc) if err != nil { return nil, err } diff --git a/adapters/provider/healthcheck.go b/adapters/provider/healthcheck.go index 00520666a0..61f343b1b4 100644 --- a/adapters/provider/healthcheck.go +++ b/adapters/provider/healthcheck.go @@ -16,20 +16,37 @@ type HealthCheckOption struct { Interval uint } -type healthCheck struct { - url string - proxies []C.Proxy - ticker *time.Ticker +type HealthCheck struct { + url string + proxies []C.Proxy + interval uint + done chan struct{} } -func (hc *healthCheck) process() { +func (hc *HealthCheck) process() { + ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) + go hc.check() - for range hc.ticker.C { - hc.check() + for { + select { + case <-ticker.C: + hc.check() + case <-hc.done: + ticker.Stop() + return + } } } -func (hc *healthCheck) check() { +func (hc *HealthCheck) setProxy(proxies []C.Proxy) { + hc.proxies = proxies +} + +func (hc *HealthCheck) auto() bool { + return hc.interval != 0 +} + +func (hc *HealthCheck) check() { ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) for _, proxy := range hc.proxies { go proxy.URLTest(ctx, hc.url) @@ -39,15 +56,15 @@ func (hc *healthCheck) check() { cancel() } -func (hc *healthCheck) close() { - hc.ticker.Stop() +func (hc *HealthCheck) close() { + hc.done <- struct{}{} } -func newHealthCheck(proxies []C.Proxy, url string, interval uint) *healthCheck { - ticker := time.NewTicker(time.Duration(interval) * time.Second) - return &healthCheck{ - proxies: proxies, - url: url, - ticker: ticker, +func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck { + return &HealthCheck{ + proxies: proxies, + url: url, + interval: interval, + done: make(chan struct{}, 1), } } diff --git a/adapters/provider/parser.go b/adapters/provider/parser.go index aea22f8d39..7428ad14ba 100644 --- a/adapters/provider/parser.go +++ b/adapters/provider/parser.go @@ -35,13 +35,11 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi return nil, err } - var healthCheckOption *HealthCheckOption + var hcInterval uint = 0 if schema.HealthCheck.Enable { - healthCheckOption = &HealthCheckOption{ - URL: schema.HealthCheck.URL, - Interval: uint(schema.HealthCheck.Interval), - } + hcInterval = uint(schema.HealthCheck.Interval) } + hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval) path := C.Path.Reslove(schema.Path) @@ -56,5 +54,5 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi } interval := time.Duration(uint(schema.Interval)) * time.Second - return NewProxySetProvider(name, interval, vehicle, healthCheckOption), nil + return NewProxySetProvider(name, interval, vehicle, hc), nil } diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index ddecbbf5f8..7f60aeda6e 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -7,9 +7,7 @@ import ( "errors" "fmt" "io/ioutil" - "net/url" "os" - "sync" "time" "github.com/Dreamacro/clash/adapters/outbound" @@ -68,17 +66,13 @@ type ProxySchema struct { } type ProxySetProvider struct { - name string - vehicle Vehicle - hash [16]byte - proxies []C.Proxy - healthCheck *healthCheck - healthCheckOption *HealthCheckOption - ticker *time.Ticker - updatedAt *time.Time - - // mux for avoiding creating new goroutines when pulling - mux sync.Mutex + name string + vehicle Vehicle + hash [16]byte + proxies []C.Proxy + healthCheck *HealthCheck + ticker *time.Ticker + updatedAt *time.Time } func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) { @@ -100,11 +94,7 @@ func (pp *ProxySetProvider) Reload() error { } func (pp *ProxySetProvider) HealthCheck() { - pp.mux.Lock() - defer pp.mux.Unlock() - if pp.healthCheck != nil { - pp.healthCheck.check() - } + pp.healthCheck.check() } func (pp *ProxySetProvider) Update() error { @@ -112,12 +102,7 @@ func (pp *ProxySetProvider) Update() error { } func (pp *ProxySetProvider) Destroy() error { - pp.mux.Lock() - defer pp.mux.Unlock() - if pp.healthCheck != nil { - pp.healthCheck.close() - pp.healthCheck = nil - } + pp.healthCheck.close() if pp.ticker != nil { pp.ticker.Stop() @@ -241,35 +226,32 @@ func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) { func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) { pp.proxies = proxies - if pp.healthCheckOption != nil { - pp.mux.Lock() - if pp.healthCheck != nil { - pp.healthCheck.close() - } - pp.healthCheck = newHealthCheck(proxies, pp.healthCheckOption.URL, pp.healthCheckOption.Interval) - go pp.healthCheck.process() - pp.mux.Unlock() - } + pp.healthCheck.setProxy(proxies) + go pp.healthCheck.check() } -func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, option *HealthCheckOption) *ProxySetProvider { +func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { var ticker *time.Ticker if interval != 0 { ticker = time.NewTicker(interval) } + if hc.auto() { + go hc.process() + } + return &ProxySetProvider{ - name: name, - vehicle: vehicle, - proxies: []C.Proxy{}, - healthCheckOption: option, - ticker: ticker, + name: name, + vehicle: vehicle, + proxies: []C.Proxy{}, + healthCheck: hc, + ticker: ticker, } } type CompatibleProvier struct { name string - healthCheck *healthCheck + healthCheck *HealthCheck proxies []C.Proxy } @@ -291,16 +273,12 @@ func (cp *CompatibleProvier) Reload() error { } func (cp *CompatibleProvier) Destroy() error { - if cp.healthCheck != nil { - cp.healthCheck.close() - } + cp.healthCheck.close() return nil } func (cp *CompatibleProvier) HealthCheck() { - if cp.healthCheck != nil { - cp.healthCheck.check() - } + cp.healthCheck.check() } func (cp *CompatibleProvier) Update() error { @@ -308,9 +286,6 @@ func (cp *CompatibleProvier) Update() error { } func (cp *CompatibleProvier) Initial() error { - if cp.healthCheck != nil { - go cp.healthCheck.process() - } return nil } @@ -326,17 +301,13 @@ func (cp *CompatibleProvier) Proxies() []C.Proxy { return cp.proxies } -func NewCompatibleProvier(name string, proxies []C.Proxy, option *HealthCheckOption) (*CompatibleProvier, error) { +func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) { if len(proxies) == 0 { return nil, errors.New("Provider need one proxy at least") } - var hc *healthCheck - if option != nil { - if _, err := url.Parse(option.URL); err != nil { - return nil, fmt.Errorf("URL format error: %w", err) - } - hc = newHealthCheck(proxies, option.URL, option.Interval) + if hc.auto() { + go hc.process() } return &CompatibleProvier{ From 37603735bd8b8538209bd1bfa2b6d29c0ed72e13 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 26 Dec 2019 18:45:18 +0800 Subject: [PATCH 299/535] Fix: missing health check instance --- config/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index d867282a52..617bdde42a 100644 --- a/config/config.go +++ b/config/config.go @@ -314,7 +314,8 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[ for _, v := range proxyList { ps = append(ps, proxies[v]) } - pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, nil) + hc := provider.NewHealthCheck(ps, "", 0) + pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, hc) providersMap[provider.ReservedName] = pd global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd}) From 96f490f84aac9fd07988b2a3424603ce40e001df Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 28 Dec 2019 00:10:06 +0800 Subject: [PATCH 300/535] Feature: add fake-ip-filter --- README.md | 3 +++ component/fakeip/pool.go | 13 ++++++++++++- config/config.go | 13 ++++++++++++- dns/middleware.go | 4 ++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index afa0c3f151..9277299016 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,9 @@ experimental: # listen: 0.0.0.0:53 # enhanced-mode: redir-host # or fake-ip # # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it + # fake-ip-filter: # fake ip white domain list + # - *.lan + # - localhost.ptlogin2.qq.com # nameserver: # - 114.114.114.114 # - tls://dns.rubyfish.cn:853 # dns over tls diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 86a92ee2ab..ec84d8f8c1 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/Dreamacro/clash/common/cache" + trie "github.com/Dreamacro/clash/component/domain-trie" ) // Pool is a implementation about fake ip generator without storage @@ -15,6 +16,7 @@ type Pool struct { gateway uint32 offset uint32 mux sync.Mutex + host *trie.Trie cache *cache.LruCache } @@ -60,6 +62,14 @@ func (p *Pool) LookBack(ip net.IP) (string, bool) { return "", false } +// LookupHost return if host in host +func (p *Pool) LookupHost(host string) bool { + if p.host == nil { + return false + } + return p.host.Search(host) != nil +} + // Gateway return gateway ip func (p *Pool) Gateway() net.IP { return uintToIP(p.gateway) @@ -96,7 +106,7 @@ func uintToIP(v uint32) net.IP { } // New return Pool instance -func New(ipnet *net.IPNet, size int) (*Pool, error) { +func New(ipnet *net.IPNet, size int, host *trie.Trie) (*Pool, error) { min := ipToUint(ipnet.IP) + 2 ones, bits := ipnet.Mask.Size() @@ -111,6 +121,7 @@ func New(ipnet *net.IPNet, size int) (*Pool, error) { min: min, max: max, gateway: min - 1, + host: host, cache: cache.NewLRUCache(cache.WithSize(size * 2)), }, nil } diff --git a/config/config.go b/config/config.go index 617bdde42a..1b35243ef3 100644 --- a/config/config.go +++ b/config/config.go @@ -81,6 +81,7 @@ type rawDNS struct { Listen string `yaml:"listen"` EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` FakeIPRange string `yaml:"fake-ip-range"` + FakeIPFilter []string `yaml:"fake-ip-filter"` } type rawFallbackFilter struct { @@ -523,7 +524,17 @@ func parseDNS(cfg rawDNS) (*DNS, error) { if err != nil { return nil, err } - pool, err := fakeip.New(ipnet, 1000) + + var host *trie.Trie + // fake ip skip host filter + if len(cfg.FakeIPFilter) != 0 { + host = trie.New() + for _, domain := range cfg.FakeIPFilter { + host.Insert(domain, true) + } + } + + pool, err := fakeip.New(ipnet, 1000, host) if err != nil { return nil, err } diff --git a/dns/middleware.go b/dns/middleware.go index bd0aa7d9fe..2a83c64859 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -26,6 +26,10 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { } host := strings.TrimRight(q.Name, ".") + if fakePool.LookupHost(host) { + next(w, r) + return + } rr := &D.A{} rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} From 3f592988a9b3721342436d2e9ed6534702e51f86 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 28 Dec 2019 00:19:40 +0800 Subject: [PATCH 301/535] Fix: fake pool test --- component/fakeip/pool_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/component/fakeip/pool_test.go b/component/fakeip/pool_test.go index d68c101129..89b161b89f 100644 --- a/component/fakeip/pool_test.go +++ b/component/fakeip/pool_test.go @@ -9,7 +9,7 @@ import ( func TestPool_Basic(t *testing.T) { _, ipnet, _ := net.ParseCIDR("192.168.0.1/29") - pool, _ := New(ipnet, 10) + pool, _ := New(ipnet, 10, nil) first := pool.Lookup("foo.com") last := pool.Lookup("bar.com") @@ -23,7 +23,7 @@ func TestPool_Basic(t *testing.T) { func TestPool_Cycle(t *testing.T) { _, ipnet, _ := net.ParseCIDR("192.168.0.1/30") - pool, _ := New(ipnet, 10) + pool, _ := New(ipnet, 10, nil) first := pool.Lookup("foo.com") same := pool.Lookup("baz.com") @@ -33,7 +33,7 @@ func TestPool_Cycle(t *testing.T) { func TestPool_MaxCacheSize(t *testing.T) { _, ipnet, _ := net.ParseCIDR("192.168.0.1/24") - pool, _ := New(ipnet, 2) + pool, _ := New(ipnet, 2, nil) first := pool.Lookup("foo.com") pool.Lookup("bar.com") @@ -45,7 +45,7 @@ func TestPool_MaxCacheSize(t *testing.T) { func TestPool_DoubleMapping(t *testing.T) { _, ipnet, _ := net.ParseCIDR("192.168.0.1/24") - pool, _ := New(ipnet, 2) + pool, _ := New(ipnet, 2, nil) // fill cache fooIP := pool.Lookup("foo.com") @@ -72,7 +72,7 @@ func TestPool_DoubleMapping(t *testing.T) { func TestPool_Error(t *testing.T) { _, ipnet, _ := net.ParseCIDR("192.168.0.1/31") - _, err := New(ipnet, 10) + _, err := New(ipnet, 10, nil) assert.Error(t, err) } From 93ea037230893576279a098a3429d58db2a70473 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Sat, 28 Dec 2019 18:44:01 +0800 Subject: [PATCH 302/535] Improve: UDP relay refactor (#441) Co-authored-by: Dreamacro --- adapters/inbound/packet.go | 33 +++++++++++++++++++ adapters/outbound/shadowsocks.go | 9 +++-- component/fakeip/pool.go | 20 ++++++++++-- component/socks5/socks5.go | 54 ++++++++++++++++++++++++++++++ constant/adapters.go | 18 ++++++++++ dns/middleware.go | 2 +- dns/resolver.go | 11 ++++++- proxy/socks/udp.go | 7 ++-- proxy/socks/utils.go | 20 ++++++++---- tunnel/connection.go | 24 +++++++------- tunnel/tunnel.go | 56 ++++++++++++++++++++------------ 11 files changed, 202 insertions(+), 52 deletions(-) create mode 100644 adapters/inbound/packet.go diff --git a/adapters/inbound/packet.go b/adapters/inbound/packet.go new file mode 100644 index 0000000000..59ccce85c6 --- /dev/null +++ b/adapters/inbound/packet.go @@ -0,0 +1,33 @@ +package inbound + +import ( + "github.com/Dreamacro/clash/component/socks5" + C "github.com/Dreamacro/clash/constant" +) + +// PacketAdapter is a UDP Packet adapter for socks/redir/tun +type PacketAdapter struct { + C.UDPPacket + metadata *C.Metadata +} + +// Metadata returns destination metadata +func (s *PacketAdapter) Metadata() *C.Metadata { + return s.metadata +} + +// NewPacket is PacketAdapter generator +func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, netType C.NetWork) *PacketAdapter { + metadata := parseSocksAddr(target) + metadata.NetWork = netType + metadata.Type = source + if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil { + metadata.SrcIP = ip + metadata.SrcPort = port + } + + return &PacketAdapter{ + UDPPacket: packet, + metadata: metadata, + } +} diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 80aa6659f0..83e1a8b86b 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -201,8 +201,13 @@ func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { } func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { - n, a, e := uc.PacketConn.ReadFrom(b) + n, _, e := uc.PacketConn.ReadFrom(b) addr := socks5.SplitAddr(b[:n]) + var from net.Addr + if e == nil { + // Get the source IP/Port of packet. + from = addr.UDPAddr() + } copy(b, b[len(addr):]) - return n - len(addr), a, e + return n - len(addr), from, e } diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index ec84d8f8c1..b92b55b395 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -62,12 +62,26 @@ func (p *Pool) LookBack(ip net.IP) (string, bool) { return "", false } -// LookupHost return if host in host -func (p *Pool) LookupHost(host string) bool { +// LookupHost return if domain in host +func (p *Pool) LookupHost(domain string) bool { if p.host == nil { return false } - return p.host.Search(host) != nil + return p.host.Search(domain) != nil +} + +// Exist returns if given ip exists in fake-ip pool +func (p *Pool) Exist(ip net.IP) bool { + p.mux.Lock() + defer p.mux.Unlock() + + if ip = ip.To4(); ip == nil { + return false + } + + n := ipToUint(ip.To4()) + offset := n - p.min + 1 + return p.cache.Exist(offset) } // Gateway return gateway ip diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index 243f34cb4a..b150d1f783 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -2,6 +2,7 @@ package socks5 import ( "bytes" + "encoding/binary" "errors" "io" "net" @@ -62,6 +63,25 @@ func (a Addr) String() string { return net.JoinHostPort(host, port) } +// UDPAddr converts a socks5.Addr to *net.UDPAddr +func (a Addr) UDPAddr() *net.UDPAddr { + if len(a) == 0 { + return nil + } + switch a[0] { + case AtypIPv4: + var ip [net.IPv4len]byte + copy(ip[0:], a[1:1+net.IPv4len]) + return &net.UDPAddr{IP: net.IP(ip[:]), Port: int(binary.BigEndian.Uint16(a[1+net.IPv4len : 1+net.IPv4len+2]))} + case AtypIPv6: + var ip [net.IPv6len]byte + copy(ip[0:], a[1:1+net.IPv6len]) + return &net.UDPAddr{IP: net.IP(ip[:]), Port: int(binary.BigEndian.Uint16(a[1+net.IPv6len : 1+net.IPv6len+2]))} + } + // Other Atyp + return nil +} + // SOCKS errors as defined in RFC 1928 section 6. const ( ErrGeneralFailure = Error(1) @@ -338,6 +358,40 @@ func ParseAddr(s string) Addr { return addr } +// ParseAddrToSocksAddr parse a socks addr from net.addr +// This is a fast path of ParseAddr(addr.String()) +func ParseAddrToSocksAddr(addr net.Addr) Addr { + var hostip net.IP + var port int + if udpaddr, ok := addr.(*net.UDPAddr); ok { + hostip = udpaddr.IP + port = udpaddr.Port + } else if tcpaddr, ok := addr.(*net.TCPAddr); ok { + hostip = tcpaddr.IP + port = tcpaddr.Port + } + + // fallback parse + if hostip == nil { + return ParseAddr(addr.String()) + } + + var parsed Addr + if ip4 := hostip.To4(); ip4.DefaultMask() != nil { + parsed = make([]byte, 1+net.IPv4len+2) + parsed[0] = AtypIPv4 + copy(parsed[1:], ip4) + binary.BigEndian.PutUint16(parsed[1+net.IPv4len:], uint16(port)) + + } else { + parsed = make([]byte, 1+net.IPv6len+2) + parsed[0] = AtypIPv6 + copy(parsed[1:], hostip) + binary.BigEndian.PutUint16(parsed[1+net.IPv6len:], uint16(port)) + } + return parsed +} + // DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet` func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { if len(packet) < 5 { diff --git a/constant/adapters.go b/constant/adapters.go index b51d083be7..f05a23fa13 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -109,3 +109,21 @@ func (at AdapterType) String() string { return "Unknown" } } + +// UDPPacket contains the data of UDP packet, and offers control/info of UDP packet's source +type UDPPacket interface { + // Data get the payload of UDP Packet + Data() []byte + + // WriteBack writes the payload with source IP/Port equals addr + // - variable source IP/Port is important to STUN + // - if addr is not provided, WriteBack will wirte out UDP packet with SourceIP/Prot equals to origional Target, + // this is important when using Fake-IP. + WriteBack(b []byte, addr net.Addr) (n int, err error) + + // Close closes the underlaying connection. + Close() error + + // LocalAddr returns the source IP/Port of packet + LocalAddr() net.Addr +} diff --git a/dns/middleware.go b/dns/middleware.go index 2a83c64859..5aa691ad79 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -75,7 +75,7 @@ func compose(middlewares []middleware, endpoint handler) handler { func newHandler(resolver *Resolver) handler { middlewares := []middleware{} - if resolver.IsFakeIP() { + if resolver.FakeIPEnabled() { middlewares = append(middlewares, withFakeIP(resolver.pool)) } diff --git a/dns/resolver.go b/dns/resolver.go index 1b68d7d6ec..a354081724 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -166,10 +166,19 @@ func (r *Resolver) IsMapping() bool { return r.mapping } -func (r *Resolver) IsFakeIP() bool { +// FakeIPEnabled returns if fake-ip is enabled +func (r *Resolver) FakeIPEnabled() bool { return r.fakeip } +// IsFakeIP determine if given ip is a fake-ip +func (r *Resolver) IsFakeIP(ip net.IP) bool { + if r.FakeIPEnabled() { + return r.pool.Exist(ip) + } + return false +} + func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) { fast, ctx := picker.WithTimeout(context.Background(), time.Second) for _, client := range clients { diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 36228ad563..0e26cc14de 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -1,7 +1,6 @@ package socks import ( - "bytes" "net" adapters "github.com/Dreamacro/clash/adapters/inbound" @@ -57,12 +56,12 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { pool.BufPool.Put(buf[:cap(buf)]) return } - conn := &fakeConn{ + packet := &fakeConn{ PacketConn: pc, remoteAddr: addr, targetAddr: target, - buffer: bytes.NewBuffer(payload), + payload: payload, bufRef: buf, } - tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.UDP)) + tun.AddPacket(adapters.NewPacket(target, packet, C.SOCKS, C.UDP)) } diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index ea05961eb5..8cbe57e9cd 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -1,7 +1,6 @@ package socks import ( - "bytes" "net" "github.com/Dreamacro/clash/common/pool" @@ -12,23 +11,30 @@ type fakeConn struct { net.PacketConn remoteAddr net.Addr targetAddr socks5.Addr - buffer *bytes.Buffer + payload []byte bufRef []byte } -func (c *fakeConn) Read(b []byte) (n int, err error) { - return c.buffer.Read(b) +func (c *fakeConn) Data() []byte { + return c.payload } -func (c *fakeConn) Write(b []byte) (n int, err error) { - packet, err := socks5.EncodeUDPPacket(c.targetAddr, b) +// WriteBack wirtes UDP packet with source(ip, port) = `addr` +func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { + from := c.targetAddr + if addr != nil { + // if addr is provided, use the parsed addr + from = socks5.ParseAddrToSocksAddr(addr) + } + packet, err := socks5.EncodeUDPPacket(from, b) if err != nil { return } return c.PacketConn.WriteTo(packet, c.remoteAddr) } -func (c *fakeConn) RemoteAddr() net.Addr { +// LocalAddr returns the source IP/Port of UDP Packet +func (c *fakeConn) LocalAddr() net.Addr { return c.remoteAddr } diff --git a/tunnel/connection.go b/tunnel/connection.go index 2849bd9f39..825d0b34e5 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -9,6 +9,8 @@ import ( "time" adapters "github.com/Dreamacro/clash/adapters/inbound" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/common/pool" ) @@ -79,21 +81,14 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } } -func (t *Tunnel) handleUDPToRemote(conn net.Conn, pc net.PacketConn, addr net.Addr) { - buf := pool.BufPool.Get().([]byte) - defer pool.BufPool.Put(buf[:cap(buf)]) - - n, err := conn.Read(buf) - if err != nil { +func (t *Tunnel) handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr net.Addr) { + if _, err := pc.WriteTo(packet.Data(), addr); err != nil { return } - if _, err = pc.WriteTo(buf[:n], addr); err != nil { - return - } - DefaultManager.Upload() <- int64(n) + DefaultManager.Upload() <- int64(len(packet.Data())) } -func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn, key string, timeout time.Duration) { +func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, omitSrcAddr bool, timeout time.Duration) { buf := pool.BufPool.Get().([]byte) defer pool.BufPool.Put(buf[:cap(buf)]) defer t.natTable.Delete(key) @@ -101,12 +96,15 @@ func (t *Tunnel) handleUDPToLocal(conn net.Conn, pc net.PacketConn, key string, for { pc.SetReadDeadline(time.Now().Add(timeout)) - n, _, err := pc.ReadFrom(buf) + n, from, err := pc.ReadFrom(buf) if err != nil { return } + if from != nil && omitSrcAddr { + from = nil + } - n, err = conn.Write(buf[:n]) + n, err = packet.WriteBack(buf[:n], from) if err != nil { return } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 69765e70cf..3a8285bc31 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -3,6 +3,7 @@ package tunnel import ( "fmt" "net" + "runtime" "sync" "time" @@ -43,12 +44,12 @@ type Tunnel struct { // Add request to queue func (t *Tunnel) Add(req C.ServerAdapter) { - switch req.Metadata().NetWork { - case C.TCP: - t.tcpQueue.In() <- req - case C.UDP: - t.udpQueue.In() <- req - } + t.tcpQueue.In() <- req +} + +// AddPacket add udp Packet to queue +func (t *Tunnel) AddPacket(packet *inbound.PacketAdapter) { + t.udpQueue.In() <- packet } // Rules return all rules @@ -98,14 +99,23 @@ func (t *Tunnel) SetMode(mode Mode) { t.mode = mode } +// processUDP starts a loop to handle udp packet +func (t *Tunnel) processUDP() { + queue := t.udpQueue.Out() + for elm := range queue { + conn := elm.(*inbound.PacketAdapter) + t.handleUDPConn(conn) + } +} + func (t *Tunnel) process() { - go func() { - queue := t.udpQueue.Out() - for elm := range queue { - conn := elm.(C.ServerAdapter) - t.handleUDPConn(conn) - } - }() + numUDPWorkers := 4 + if runtime.NumCPU() > numUDPWorkers { + numUDPWorkers = runtime.NumCPU() + } + for i := 0; i < numUDPWorkers; i++ { + go t.processUDP() + } queue := t.tcpQueue.Out() for elm := range queue { @@ -119,7 +129,7 @@ func (t *Tunnel) resolveIP(host string) (net.IP, error) { } func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { - return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.IsFakeIP()) && metadata.Host == "" && metadata.DstIP != nil + return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil } func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { @@ -134,7 +144,7 @@ func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName - if dns.DefaultResolver.IsFakeIP() { + if dns.DefaultResolver.FakeIPEnabled() { metadata.DstIP = nil } } @@ -158,25 +168,28 @@ func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) return proxy, rule, nil } -func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) { - metadata := localConn.Metadata() +func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { + metadata := packet.Metadata() if !metadata.Valid() { log.Warnln("[Metadata] not valid: %#v", metadata) return } - src := localConn.RemoteAddr().String() + src := packet.LocalAddr().String() dst := metadata.RemoteAddress() key := src + "-" + dst pc, addr := t.natTable.Get(key) if pc != nil { - t.handleUDPToRemote(localConn, pc, addr) + t.handleUDPToRemote(packet, pc, addr) return } lockKey := key + "-lock" wg, loaded := t.natTable.GetOrCreateLock(lockKey) + + isFakeIP := dns.DefaultResolver.IsFakeIP(metadata.DstIP) + go func() { if !loaded { wg.Add(1) @@ -207,13 +220,14 @@ func (t *Tunnel) handleUDPConn(localConn C.ServerAdapter) { t.natTable.Set(key, pc, addr) t.natTable.Delete(lockKey) wg.Done() - go t.handleUDPToLocal(localConn, pc, key, udpTimeout) + // in fake-ip mode, Full-Cone NAT can never achieve, fallback to omitting src Addr + go t.handleUDPToLocal(packet.UDPPacket, pc, key, isFakeIP, udpTimeout) } wg.Wait() pc, addr := t.natTable.Get(key) if pc != nil { - t.handleUDPToRemote(localConn, pc, addr) + t.handleUDPToRemote(packet, pc, addr) } }() } From 6ce7b6ef835726ff0875f63cbbfeddc3975165ee Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 28 Dec 2019 22:42:30 +0800 Subject: [PATCH 303/535] Chore: update TUN release --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9277299016..e92cc5ad82 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ $ go get -u -v github.com/Dreamacro/clash Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) +Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN) + Check Clash version with: ```sh From 9dda9324947ffce869b10aa6e34a1d51bc030b06 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 30 Dec 2019 10:51:35 +0800 Subject: [PATCH 304/535] Fix: reject should support udp and return dial error --- adapters/outbound/reject.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index b053508228..5ef615400c 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -2,6 +2,7 @@ package outbound import ( "context" + "errors" "io" "net" "time" @@ -17,11 +18,16 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return newConn(&NopConn{}, r), nil } +func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { + return nil, nil, errors.New("match reject rule") +} + func NewReject() *Reject { return &Reject{ Base: &Base{ name: "REJECT", tp: C.Reject, + udp: true, }, } } From b19a49335fdbccc60794903c01e230e074d41169 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 30 Dec 2019 23:01:24 +0800 Subject: [PATCH 305/535] Fix: http vehicle shouldn't save file --- adapters/provider/vehicle.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/adapters/provider/vehicle.go b/adapters/provider/vehicle.go index 8314319456..0b94936887 100644 --- a/adapters/provider/vehicle.go +++ b/adapters/provider/vehicle.go @@ -92,15 +92,12 @@ func (h *HTTPVehicle) Read() ([]byte, error) { if err != nil { return nil, err } + buf, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } - if err := ioutil.WriteFile(h.path, buf, fileMode); err != nil { - return nil, err - } - return buf, nil } From 38458cc4d0de6f074b7585dd76d03b7ef3c4f3f4 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 31 Dec 2019 12:30:42 +0800 Subject: [PATCH 306/535] Migration: change geoip address --- Dockerfile | 4 +--- config/initial.go | 38 ++++++-------------------------------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/Dockerfile b/Dockerfile index bd0d05c904..ba3c66cd1b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,7 @@ FROM golang:alpine as builder RUN apk add --no-cache make git && \ - wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \ - tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \ - mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb + wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb WORKDIR /clash-src COPY . /clash-src RUN go mod download && \ diff --git a/config/initial.go b/config/initial.go index 63ff427fb0..e151e74c89 100644 --- a/config/initial.go +++ b/config/initial.go @@ -1,56 +1,30 @@ package config import ( - "archive/tar" - "compress/gzip" "fmt" "io" "net/http" "os" - "strings" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) func downloadMMDB(path string) (err error) { - resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz") + resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb") if err != nil { return } defer resp.Body.Close() - gr, err := gzip.NewReader(resp.Body) + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return - } - defer gr.Close() - - tr := tar.NewReader(gr) - for { - h, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - return err - } - - if !strings.HasSuffix(h.Name, "GeoLite2-Country.mmdb") { - continue - } - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, tr) - if err != nil { - return err - } + return err } + defer f.Close() + _, err = io.Copy(f, resp.Body) - return nil + return err } // Init prepare necessary files From 50704eaeeb26f1446fb37b4ca6ee6b5acb506714 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 31 Dec 2019 14:47:00 +0800 Subject: [PATCH 307/535] Fix: udp crash --- tunnel/tunnel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 3a8285bc31..61f0897170 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -188,7 +188,7 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { lockKey := key + "-lock" wg, loaded := t.natTable.GetOrCreateLock(lockKey) - isFakeIP := dns.DefaultResolver.IsFakeIP(metadata.DstIP) + isFakeIP := dns.DefaultResolver != nil && dns.DefaultResolver.IsFakeIP(metadata.DstIP) go func() { if !loaded { From 14a3ff32f6647fd0262f6239d43a8ea1762c7ca2 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 1 Jan 2020 18:30:26 +0800 Subject: [PATCH 308/535] Chore: update dependencies --- go.mod | 7 +++---- go.sum | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1dc56b078b..93ad5d67fe 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,12 @@ require ( github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.1 - github.com/miekg/dns v1.1.24 - github.com/oschwald/geoip2-golang v1.3.0 - github.com/oschwald/maxminddb-golang v1.5.0 // indirect + github.com/miekg/dns v1.1.26 + github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 - golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663 + golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.7 diff --git a/go.sum b/go.sum index 6e0954d34d..ccd3efd747 100644 --- a/go.sum +++ b/go.sum @@ -17,12 +17,12 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.24 h1:6G8Eop/HM8hpagajbn0rFQvAKZWiiCa8P6N2I07+wwI= -github.com/miekg/dns v1.1.24/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/oschwald/geoip2-golang v1.3.0 h1:D+Hsdos1NARPbzZ2aInUHZL+dApIzo8E0ErJVsWcku8= -github.com/oschwald/geoip2-golang v1.3.0/go.mod h1:0LTTzix/Ao1uMvOhAV4iLU0Lz7eCrP94qZWBTDKf0iE= -github.com/oschwald/maxminddb-golang v1.5.0 h1:rmyoIV6z2/s9TCJedUuDiKht2RN12LWJ1L7iRGtWY64= -github.com/oschwald/maxminddb-golang v1.5.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= +github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= +github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= +github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= +github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= 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/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -44,8 +44,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.0.0-20191207000613-e7e4b65ae663 h1:Dd5RoEW+yQi+9DMybroBctIdyiwuNT7sJFMC27/6KxI= -golang.org/x/net v0.0.0-20191207000613-e7e4b65ae663/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= @@ -58,6 +58,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w 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/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g= +golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= From 86d3d77a7f15f8dfc81f68add51cba4f84eb591e Mon Sep 17 00:00:00 2001 From: Soff Date: Wed, 1 Jan 2020 19:23:34 +0800 Subject: [PATCH 309/535] Chore: increase DNS timeout (#464) --- dns/resolver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/resolver.go b/dns/resolver.go index a354081724..db4d7d049a 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -180,7 +180,7 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool { } func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) { - fast, ctx := picker.WithTimeout(context.Background(), time.Second) + fast, ctx := picker.WithTimeout(context.Background(), time.Second * 5) for _, client := range clients { r := client fast.Go(func() (interface{}, error) { From 2c0cc374d3e7b07cc31fe1c99a3cb873332d7808 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 9 Jan 2020 18:13:15 +0800 Subject: [PATCH 310/535] Fix: README typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e92cc5ad82..b5328ce7ce 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ experimental: # enhanced-mode: redir-host # or fake-ip # # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it # fake-ip-filter: # fake ip white domain list - # - *.lan + # - '*.lan' # - localhost.ptlogin2.qq.com # nameserver: # - 114.114.114.114 From e68c0d088b412bb4211997f21f964a6fd65b4c70 Mon Sep 17 00:00:00 2001 From: Comzyh Date: Fri, 10 Jan 2020 14:13:44 +0800 Subject: [PATCH 311/535] Fix: upstream dns ExchangeContext workaround (#468) --- common/picker/picker.go | 1 + dns/client.go | 1 + dns/resolver.go | 4 ++-- dns/util.go | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/common/picker/picker.go b/common/picker/picker.go index a30202df4e..49a58f0df5 100644 --- a/common/picker/picker.go +++ b/common/picker/picker.go @@ -59,6 +59,7 @@ func (p *Picker) Wait() interface{} { } // WaitWithoutCancel blocks until the first result return, if timeout will return nil. +// The return of this function will not wait for the cancel of context. func (p *Picker) WaitWithoutCancel() interface{} { select { case <-p.firstDone: diff --git a/dns/client.go b/dns/client.go index 881fcded4e..b79a997d43 100644 --- a/dns/client.go +++ b/dns/client.go @@ -16,6 +16,7 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { } func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + // Please note that miekg/dns ExchangeContext doesn't respond to context cancel. msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address) return } diff --git a/dns/resolver.go b/dns/resolver.go index db4d7d049a..6a9eeeba4d 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -180,7 +180,7 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool { } func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) { - fast, ctx := picker.WithTimeout(context.Background(), time.Second * 5) + fast, ctx := picker.WithTimeout(context.Background(), time.Second*5) for _, client := range clients { r := client fast.Go(func() (interface{}, error) { @@ -192,7 +192,7 @@ func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err }) } - elm := fast.Wait() + elm := fast.WaitWithoutCancel() if elm == nil { return nil, errors.New("All DNS requests failed") } diff --git a/dns/util.go b/dns/util.go index cb6a1c4e6e..d5cb216db5 100644 --- a/dns/util.go +++ b/dns/util.go @@ -134,6 +134,7 @@ func transform(servers []NameServer) []resolver { NextProtos: []string{"dns"}, }, UDPSize: 4096, + Timeout: 5 * time.Second, }, Address: s.Addr, }) From 6b7144acceb96833697f77fcf88d7273e7266c86 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sat, 11 Jan 2020 00:20:10 +0800 Subject: [PATCH 312/535] Chore: export reset manager statistic api (#476) --- tunnel/manager.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tunnel/manager.go b/tunnel/manager.go index d4d627e025..50c2e11683 100644 --- a/tunnel/manager.go +++ b/tunnel/manager.go @@ -61,6 +61,15 @@ func (m *Manager) Snapshot() *Snapshot { } } +func (m *Manager) ResetStatistic() { + m.uploadTemp = 0 + m.uploadBlip = 0 + m.uploadTotal = 0 + m.downloadTemp = 0 + m.downloadBlip = 0 + m.downloadTotal = 0 +} + func (m *Manager) handle() { go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal) go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal) From 2810533df45161f12e16a54599dcb7401fc39271 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sat, 11 Jan 2020 00:22:34 +0800 Subject: [PATCH 313/535] Chore: export raw config struct (#475) --- config/config.go | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/config/config.go b/config/config.go index 1b35243ef3..e9068f5623 100644 --- a/config/config.go +++ b/config/config.go @@ -72,24 +72,24 @@ type Config struct { Providers map[string]provider.ProxyProvider } -type rawDNS struct { +type RawDNS struct { Enable bool `yaml:"enable"` IPv6 bool `yaml:"ipv6"` NameServer []string `yaml:"nameserver"` Fallback []string `yaml:"fallback"` - FallbackFilter rawFallbackFilter `yaml:"fallback-filter"` + FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` Listen string `yaml:"listen"` EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` FakeIPRange string `yaml:"fake-ip-range"` FakeIPFilter []string `yaml:"fake-ip-filter"` } -type rawFallbackFilter struct { +type RawFallbackFilter struct { GeoIP bool `yaml:"geoip"` IPCIDR []string `yaml:"ipcidr"` } -type rawConfig struct { +type RawConfig struct { Port int `yaml:"port"` SocksPort int `yaml:"socks-port"` RedirPort int `yaml:"redir-port"` @@ -104,7 +104,7 @@ type rawConfig struct { ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"` Hosts map[string]string `yaml:"hosts"` - DNS rawDNS `yaml:"dns"` + DNS RawDNS `yaml:"dns"` Experimental Experimental `yaml:"experimental"` Proxy []map[string]interface{} `yaml:"Proxy"` ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` @@ -113,10 +113,17 @@ type rawConfig struct { // Parse config func Parse(buf []byte) (*Config, error) { - config := &Config{} + rawCfg, err := UnmarshalRawConfig(buf) + if err != nil { + return nil, err + } + + return ParseRawConfig(rawCfg) +} +func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with some default value - rawCfg := &rawConfig{ + rawCfg := &RawConfig{ AllowLan: false, BindAddress: "*", Mode: T.Rule, @@ -129,19 +136,26 @@ func Parse(buf []byte) (*Config, error) { Experimental: Experimental{ IgnoreResolveFail: true, }, - DNS: rawDNS{ + DNS: RawDNS{ Enable: false, FakeIPRange: "198.18.0.1/16", - FallbackFilter: rawFallbackFilter{ + FallbackFilter: RawFallbackFilter{ GeoIP: true, IPCIDR: []string{}, }, }, } + if err := yaml.Unmarshal(buf, &rawCfg); err != nil { return nil, err } + return rawCfg, nil +} + +func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { + config := &Config{} + config.Experimental = &rawCfg.Experimental general, err := parseGeneral(rawCfg) @@ -176,10 +190,11 @@ func Parse(buf []byte) (*Config, error) { config.Hosts = hosts config.Users = parseAuthentication(rawCfg.Authentication) + return config, nil } -func parseGeneral(cfg *rawConfig) (*General, error) { +func parseGeneral(cfg *RawConfig) (*General, error) { port := cfg.Port socksPort := cfg.SocksPort redirPort := cfg.RedirPort @@ -214,7 +229,7 @@ func parseGeneral(cfg *rawConfig) (*General, error) { return general, nil } -func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) { +func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) { proxies = make(map[string]C.Proxy) providersMap = make(map[string]provider.ProxyProvider) proxyList := []string{} @@ -324,7 +339,7 @@ func parseProxies(cfg *rawConfig) (proxies map[string]C.Proxy, providersMap map[ return proxies, providersMap, nil } -func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { +func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { rules := []C.Rule{} rulesConfig := cfg.Rule @@ -403,7 +418,7 @@ func parseRules(cfg *rawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { return rules, nil } -func parseHosts(cfg *rawConfig) (*trie.Trie, error) { +func parseHosts(cfg *RawConfig) (*trie.Trie, error) { tree := trie.New() if len(cfg.Hosts) != 0 { for domain, ipStr := range cfg.Hosts { @@ -496,7 +511,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { return ipNets, nil } -func parseDNS(cfg rawDNS) (*DNS, error) { +func parseDNS(cfg RawDNS) (*DNS, error) { if cfg.Enable && len(cfg.NameServer) == 0 { return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") } From f688eda2c22724c203e73020e1be8adfa1e4351c Mon Sep 17 00:00:00 2001 From: comwrg Date: Sat, 11 Jan 2020 21:02:55 +0800 Subject: [PATCH 314/535] Chore: fix typo (#479) --- adapters/outboundgroup/parser.go | 6 +++--- adapters/provider/provider.go | 26 +++++++++++++------------- config/config.go | 4 ++-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index e54755f833..f0c012581d 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -56,7 +56,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, // if Use not empty, drop health check options if len(groupOption.Use) != 0 { hc := provider.NewHealthCheck(ps, "", 0) - pd, err := provider.NewCompatibleProvier(groupName, ps, hc) + pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, err } @@ -66,7 +66,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, // select don't need health check if groupOption.Type == "select" { hc := provider.NewHealthCheck(ps, "", 0) - pd, err := provider.NewCompatibleProvier(groupName, ps, hc) + pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, err } @@ -79,7 +79,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, } hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval)) - pd, err := provider.NewCompatibleProvier(groupName, ps, hc) + pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, err } diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index 7f60aeda6e..139ce70388 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -249,13 +249,13 @@ func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, h } } -type CompatibleProvier struct { +type CompatibleProvider struct { name string healthCheck *HealthCheck proxies []C.Proxy } -func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) { +func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ "name": cp.Name(), "type": cp.Type().String(), @@ -264,44 +264,44 @@ func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) { }) } -func (cp *CompatibleProvier) Name() string { +func (cp *CompatibleProvider) Name() string { return cp.name } -func (cp *CompatibleProvier) Reload() error { +func (cp *CompatibleProvider) Reload() error { return nil } -func (cp *CompatibleProvier) Destroy() error { +func (cp *CompatibleProvider) Destroy() error { cp.healthCheck.close() return nil } -func (cp *CompatibleProvier) HealthCheck() { +func (cp *CompatibleProvider) HealthCheck() { cp.healthCheck.check() } -func (cp *CompatibleProvier) Update() error { +func (cp *CompatibleProvider) Update() error { return nil } -func (cp *CompatibleProvier) Initial() error { +func (cp *CompatibleProvider) Initial() error { return nil } -func (cp *CompatibleProvier) VehicleType() VehicleType { +func (cp *CompatibleProvider) VehicleType() VehicleType { return Compatible } -func (cp *CompatibleProvier) Type() ProviderType { +func (cp *CompatibleProvider) Type() ProviderType { return Proxy } -func (cp *CompatibleProvier) Proxies() []C.Proxy { +func (cp *CompatibleProvider) Proxies() []C.Proxy { return cp.proxies } -func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) { +func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { if len(proxies) == 0 { return nil, errors.New("Provider need one proxy at least") } @@ -310,7 +310,7 @@ func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*Com go hc.process() } - return &CompatibleProvier{ + return &CompatibleProvider{ name: name, proxies: proxies, healthCheck: hc, diff --git a/config/config.go b/config/config.go index e9068f5623..24cd330bce 100644 --- a/config/config.go +++ b/config/config.go @@ -314,7 +314,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ proxies[groupName] = outbound.NewProxy(group) } - // initial compatible provier + // initial compatible provider for _, pd := range providersMap { if pd.VehicleType() != provider.Compatible { continue @@ -331,7 +331,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ ps = append(ps, proxies[v]) } hc := provider.NewHealthCheck(ps, "", 0) - pd, _ := provider.NewCompatibleProvier(provider.ReservedName, ps, hc) + pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) providersMap[provider.ReservedName] = pd global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd}) From 90713510221422d117caf7ed1bf64fb521a3b62d Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sat, 11 Jan 2020 21:07:01 +0800 Subject: [PATCH 315/535] Chore: aggregate mmdb (#474) --- component/mmdb/mmdb.go | 35 +++++++++++++++++++++++++++++++++++ dns/filters.go | 12 ++++++------ dns/resolver.go | 10 ---------- rules/geoip.go | 21 ++------------------- 4 files changed, 43 insertions(+), 35 deletions(-) create mode 100644 component/mmdb/mmdb.go diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go new file mode 100644 index 0000000000..6bc2ad7388 --- /dev/null +++ b/component/mmdb/mmdb.go @@ -0,0 +1,35 @@ +package mmdb + +import ( + "sync" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + + "github.com/oschwald/geoip2-golang" +) + +var mmdb *geoip2.Reader +var once sync.Once + +func LoadFromBytes(buffer []byte) { + once.Do(func() { + var err error + mmdb, err = geoip2.FromBytes(buffer) + if err != nil { + log.Fatalln("Can't load mmdb: %s", err.Error()) + } + }) +} + +func Instance() *geoip2.Reader { + once.Do(func() { + var err error + mmdb, err = geoip2.Open(C.Path.MMDB()) + if err != nil { + log.Fatalln("Can't load mmdb: %s", err.Error()) + } + }) + + return mmdb +} diff --git a/dns/filters.go b/dns/filters.go index abb99f4771..456280fa06 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -1,6 +1,10 @@ package dns -import "net" +import ( + "net" + + "github.com/Dreamacro/clash/component/mmdb" +) type fallbackFilter interface { Match(net.IP) bool @@ -9,11 +13,7 @@ type fallbackFilter interface { type geoipFilter struct{} func (gf *geoipFilter) Match(ip net.IP) bool { - if mmdb == nil { - return false - } - - record, _ := mmdb.Country(ip) + record, _ := mmdb.Instance().Country(ip) return record.Country.IsoCode == "CN" || record.Country.IsoCode == "" } diff --git a/dns/resolver.go b/dns/resolver.go index 6a9eeeba4d..7e76e8191f 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -6,17 +6,14 @@ import ( "errors" "net" "strings" - "sync" "time" "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/picker" trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/fakeip" - C "github.com/Dreamacro/clash/constant" D "github.com/miekg/dns" - geoip2 "github.com/oschwald/geoip2-golang" "golang.org/x/sync/singleflight" ) @@ -30,9 +27,6 @@ var ( var ( globalSessionCache = tls.NewLRUClientSessionCache(64) - - mmdb *geoip2.Reader - once sync.Once ) type resolver interface { @@ -311,10 +305,6 @@ func New(config Config) *Resolver { fallbackFilters := []fallbackFilter{} if config.FallbackFilter.GeoIP { - once.Do(func() { - mmdb, _ = geoip2.Open(C.Path.MMDB()) - }) - fallbackFilters = append(fallbackFilters, &geoipFilter{}) } for _, ipnet := range config.FallbackFilter.IPCIDR { diff --git a/rules/geoip.go b/rules/geoip.go index 4b9029cf2d..cfe8b52b5e 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -1,17 +1,8 @@ package rules import ( - "sync" - + "github.com/Dreamacro/clash/component/mmdb" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" - - "github.com/oschwald/geoip2-golang" -) - -var ( - mmdb *geoip2.Reader - once sync.Once ) type GEOIP struct { @@ -29,7 +20,7 @@ func (g *GEOIP) Match(metadata *C.Metadata) bool { if ip == nil { return false } - record, _ := mmdb.Country(ip) + record, _ := mmdb.Instance().Country(ip) return record.Country.IsoCode == g.country } @@ -46,14 +37,6 @@ func (g *GEOIP) NoResolveIP() bool { } func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { - once.Do(func() { - var err error - mmdb, err = geoip2.Open(C.Path.MMDB()) - if err != nil { - log.Fatalln("Can't load mmdb: %s", err.Error()) - } - }) - geoip := &GEOIP{ country: country, adapter: adapter, From 14fb78900239eff5d6c68612d548cb4801a6caca Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 28 Jan 2020 16:39:52 +0800 Subject: [PATCH 316/535] Feature: add arm32 and arm64 docker image --- Dockerfile.arm32v7 | 19 +++++++++++++++++++ Dockerfile.arm64v8 | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 Dockerfile.arm32v7 create mode 100644 Dockerfile.arm64v8 diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 new file mode 100644 index 0000000000..2d43fa468b --- /dev/null +++ b/Dockerfile.arm32v7 @@ -0,0 +1,19 @@ +FROM golang:alpine as builder + +RUN apk add --no-cache make git && \ + wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \ + wget -O /qemu-arm-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static + +WORKDIR /clash-src +COPY . /clash-src +RUN go mod download && \ + make linux-armv7 && \ + mv ./bin/clash-linux-armv7 /clash + +FROM arm32v7/alpine:latest + +COPY --from=builder /qemu-arm-static /usr/bin/ +COPY --from=builder /Country.mmdb /root/.config/clash/ +COPY --from=builder /clash / +RUN apk add --no-cache ca-certificates +ENTRYPOINT ["/clash"] diff --git a/Dockerfile.arm64v8 b/Dockerfile.arm64v8 new file mode 100644 index 0000000000..0debda0463 --- /dev/null +++ b/Dockerfile.arm64v8 @@ -0,0 +1,19 @@ +FROM golang:alpine as builder + +RUN apk add --no-cache make git && \ + wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \ + wget -O /qemu-aarch64-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static + +WORKDIR /clash-src +COPY . /clash-src +RUN go mod download && \ + make linux-armv8 && \ + mv ./bin/clash-linux-armv8 /clash + +FROM arm64v8/alpine:latest + +COPY --from=builder /qemu-aarch64-static /usr/bin/ +COPY --from=builder /Country.mmdb /root/.config/clash/ +COPY --from=builder /clash / +RUN apk add --no-cache ca-certificates +ENTRYPOINT ["/clash"] From 82c387e92b3a3ee546fa09ed4985c0c284d29727 Mon Sep 17 00:00:00 2001 From: Jason Chen Date: Thu, 30 Jan 2020 17:03:11 +0800 Subject: [PATCH 317/535] Chore: fix typo (#490) --- adapters/provider/parser.go | 2 +- config/config.go | 2 +- constant/path.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adapters/provider/parser.go b/adapters/provider/parser.go index 7428ad14ba..e662b67964 100644 --- a/adapters/provider/parser.go +++ b/adapters/provider/parser.go @@ -41,7 +41,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi } hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval) - path := C.Path.Reslove(schema.Path) + path := C.Path.Resolve(schema.Path) var vehicle Vehicle switch schema.Type { diff --git a/config/config.go b/config/config.go index 24cd330bce..be6ae5426d 100644 --- a/config/config.go +++ b/config/config.go @@ -207,7 +207,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { logLevel := cfg.LogLevel if externalUI != "" { - externalUI = C.Path.Reslove(externalUI) + externalUI = C.Path.Resolve(externalUI) if _, err := os.Stat(externalUI); os.IsNotExist(err) { return nil, fmt.Errorf("external-ui: %s not exist", externalUI) diff --git a/constant/path.go b/constant/path.go index 9af355b259..6f4af046aa 100644 --- a/constant/path.go +++ b/constant/path.go @@ -44,8 +44,8 @@ func (p *path) Config() string { return p.configFile } -// Reslove return a absolute path or a relative path with homedir -func (p *path) Reslove(path string) string { +// Resolve return a absolute path or a relative path with homedir +func (p *path) Resolve(path string) string { if !filepath.IsAbs(path) { return filepath.Join(p.HomeDir(), path) } From c626b988a690238199198a77fef529bac405d7b3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 30 Jan 2020 17:25:55 +0800 Subject: [PATCH 318/535] Fix: add docker hub pre build --- hooks/pre_build | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 hooks/pre_build diff --git a/hooks/pre_build b/hooks/pre_build new file mode 100644 index 0000000000..b7cf923d69 --- /dev/null +++ b/hooks/pre_build @@ -0,0 +1,4 @@ +#!/bin/bash +# Register qemu-*-static for all supported processors except the +# current one, but also remove all registered binfmt_misc before +docker run --rm --privileged multiarch/qemu-user-static:register --reset --credential yes From aa207ec66411248bea9d872443b4751459e48cce Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 30 Jan 2020 21:19:51 +0800 Subject: [PATCH 319/535] Fix: qemu permission --- Dockerfile.arm32v7 | 3 ++- Dockerfile.arm64v8 | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 index 2d43fa468b..e2f2d299f3 100644 --- a/Dockerfile.arm32v7 +++ b/Dockerfile.arm32v7 @@ -2,7 +2,8 @@ FROM golang:alpine as builder RUN apk add --no-cache make git && \ wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \ - wget -O /qemu-arm-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static + wget -O /qemu-arm-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static && \ + chmod +x /qemu-arm-static WORKDIR /clash-src COPY . /clash-src diff --git a/Dockerfile.arm64v8 b/Dockerfile.arm64v8 index 0debda0463..b532e4198f 100644 --- a/Dockerfile.arm64v8 +++ b/Dockerfile.arm64v8 @@ -2,7 +2,8 @@ FROM golang:alpine as builder RUN apk add --no-cache make git && \ wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \ - wget -O /qemu-aarch64-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static + wget -O /qemu-aarch64-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static && \ + chmod +x /qemu-aarch64-static WORKDIR /clash-src COPY . /clash-src From 26ce3e8814db8249a17f8bfda6ac50572d7ca6ac Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 31 Jan 2020 14:43:54 +0800 Subject: [PATCH 320/535] Improve: udp NAT type --- adapters/inbound/packet.go | 4 ++-- adapters/outbound/base.go | 4 ++-- adapters/outbound/direct.go | 11 +++-------- adapters/outbound/reject.go | 4 ++-- adapters/outbound/shadowsocks.go | 19 +++++++------------ adapters/outbound/socks5.go | 20 +++++--------------- adapters/outbound/vmess.go | 8 ++++---- adapters/outboundgroup/fallback.go | 7 +++---- adapters/outboundgroup/loadbalance.go | 2 +- adapters/outboundgroup/selector.go | 7 +++---- adapters/outboundgroup/urltest.go | 7 +++---- component/nat/table.go | 20 +++++--------------- constant/adapters.go | 2 +- constant/metadata.go | 12 ++++++++++++ proxy/socks/udp.go | 5 ++--- proxy/socks/utils.go | 21 +++++++-------------- tunnel/connection.go | 7 ++----- tunnel/tunnel.go | 19 +++++++------------ 18 files changed, 71 insertions(+), 108 deletions(-) diff --git a/adapters/inbound/packet.go b/adapters/inbound/packet.go index 59ccce85c6..001a579b80 100644 --- a/adapters/inbound/packet.go +++ b/adapters/inbound/packet.go @@ -17,9 +17,9 @@ func (s *PacketAdapter) Metadata() *C.Metadata { } // NewPacket is PacketAdapter generator -func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, netType C.NetWork) *PacketAdapter { +func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAdapter { metadata := parseSocksAddr(target) - metadata.NetWork = netType + metadata.NetWork = C.UDP metadata.Type = source if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil { metadata.SrcIP = ip diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 3dc97825ba..df1c61bc54 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -30,8 +30,8 @@ func (b *Base) Type() C.AdapterType { return b.tp } -func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - return nil, nil, errors.New("no support") +func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + return nil, errors.New("no support") } func (b *Base) SupportUDP() bool { diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 061d03618d..d2b7a9abc7 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -25,17 +25,12 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return newConn(c, d), nil } -func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { +func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { pc, err := net.ListenPacket("udp", "") if err != nil { - return nil, nil, err - } - - addr, err := resolveUDPAddr("udp", metadata.RemoteAddress()) - if err != nil { - return nil, nil, err + return nil, err } - return newPacketConn(pc, d), addr, nil + return newPacketConn(pc, d), nil } func NewDirect() *Direct { diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index 5ef615400c..7daba1a22f 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -18,8 +18,8 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return newConn(&NopConn{}, r), nil } -func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - return nil, nil, errors.New("match reject rule") +func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + return nil, errors.New("match reject rule") } func NewReject() *Reject { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 83e1a8b86b..217c8e2e5e 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -82,24 +82,19 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C return newConn(c, ss), err } -func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { +func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { pc, err := net.ListenPacket("udp", "") if err != nil { - return nil, nil, err + return nil, err } addr, err := resolveUDPAddr("udp", ss.server) if err != nil { - return nil, nil, err - } - - targetAddr := socks5.ParseAddr(metadata.RemoteAddress()) - if targetAddr == nil { - return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort) + return nil, err } pc = ss.cipher.PacketConn(pc) - return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil + return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: addr}, ss), nil } func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { @@ -189,15 +184,15 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { type ssUDPConn struct { net.PacketConn - rAddr socks5.Addr + rAddr net.Addr } func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - packet, err := socks5.EncodeUDPPacket(uc.rAddr, b) + packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { return } - return uc.PacketConn.WriteTo(packet[3:], addr) + return uc.PacketConn.WriteTo(packet[3:], uc.rAddr) } func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 7632be3045..79cf05b77b 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -60,7 +60,7 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn return newConn(c, ss), nil } -func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) { +func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) defer cancel() c, err := dialContext(ctx, "tcp", ss.addr) @@ -96,16 +96,6 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err return } - addr, err := net.ResolveUDPAddr("udp", bindAddr.String()) - if err != nil { - return - } - - targetAddr := socks5.ParseAddr(metadata.RemoteAddress()) - if targetAddr == nil { - return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort) - } - pc, err := net.ListenPacket("udp", "") if err != nil { return @@ -119,7 +109,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err pc.Close() }() - return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr, tcpConn: c}, ss), addr, nil + return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil } func NewSocks5(option Socks5Option) *Socks5 { @@ -149,16 +139,16 @@ func NewSocks5(option Socks5Option) *Socks5 { type socksUDPConn struct { net.PacketConn - rAddr socks5.Addr + rAddr net.Addr tcpConn net.Conn } func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - packet, err := socks5.EncodeUDPPacket(uc.rAddr, b) + packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { return } - return uc.PacketConn.WriteTo(packet, addr) + return uc.PacketConn.WriteTo(packet, uc.rAddr) } func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index c653b106fb..a7a5016d2e 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -42,19 +42,19 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return newConn(c, v), err } -func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { +func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) defer cancel() c, err := dialContext(ctx, "tcp", v.server) if err != nil { - return nil, nil, fmt.Errorf("%s connect error", v.server) + return nil, fmt.Errorf("%s connect error", v.server) } tcpKeepAlive(c) c, err = v.client.New(c, parseVmessAddr(metadata)) if err != nil { - return nil, nil, fmt.Errorf("new vmess client error: %v", err) + return nil, fmt.Errorf("new vmess client error: %v", err) } - return newPacketConn(&vmessUDPConn{Conn: c}, v), c.RemoteAddr(), nil + return newPacketConn(&vmessUDPConn{Conn: c}, v), nil } func NewVmess(option VmessOption) (*Vmess, error) { diff --git a/adapters/outboundgroup/fallback.go b/adapters/outboundgroup/fallback.go index 2104c39e1c..7a344d090b 100644 --- a/adapters/outboundgroup/fallback.go +++ b/adapters/outboundgroup/fallback.go @@ -3,7 +3,6 @@ package outboundgroup import ( "context" "encoding/json" - "net" "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/provider" @@ -31,13 +30,13 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con return c, err } -func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { +func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { proxy := f.findAliveProxy() - pc, addr, err := proxy.DialUDP(metadata) + pc, err := proxy.DialUDP(metadata) if err == nil { pc.AppendToChains(f) } - return pc, addr, err + return pc, err } func (f *Fallback) SupportUDP() bool { diff --git a/adapters/outboundgroup/loadbalance.go b/adapters/outboundgroup/loadbalance.go index 78a942e0a6..1495cda234 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -74,7 +74,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c return } -func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) { +func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error) { defer func() { if err == nil { pc.AppendToChains(lb) diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index fd9ef041fc..533a6126dd 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "net" "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/provider" @@ -27,12 +26,12 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con return c, err } -func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - pc, addr, err := s.selected.DialUDP(metadata) +func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + pc, err := s.selected.DialUDP(metadata) if err == nil { pc.AppendToChains(s) } - return pc, addr, err + return pc, err } func (s *Selector) SupportUDP() bool { diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index cf1ad13814..c985eea8ad 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -3,7 +3,6 @@ package outboundgroup import ( "context" "encoding/json" - "net" "time" "github.com/Dreamacro/clash/adapters/outbound" @@ -31,12 +30,12 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co return c, err } -func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) { - pc, addr, err := u.fast().DialUDP(metadata) +func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + pc, err := u.fast().DialUDP(metadata) if err == nil { pc.AppendToChains(u) } - return pc, addr, err + return pc, err } func (u *URLTest) proxies() []C.Proxy { diff --git a/component/nat/table.go b/component/nat/table.go index eac9846771..a88a00f0c6 100644 --- a/component/nat/table.go +++ b/component/nat/table.go @@ -9,26 +9,16 @@ type Table struct { mapping sync.Map } -type element struct { - RemoteAddr net.Addr - RemoteConn net.PacketConn +func (t *Table) Set(key string, pc net.PacketConn) { + t.mapping.Store(key, pc) } -func (t *Table) Set(key string, pc net.PacketConn, addr net.Addr) { - // set conn read timeout - t.mapping.Store(key, &element{ - RemoteConn: pc, - RemoteAddr: addr, - }) -} - -func (t *Table) Get(key string) (net.PacketConn, net.Addr) { +func (t *Table) Get(key string) net.PacketConn { item, exist := t.mapping.Load(key) if !exist { - return nil, nil + return nil } - elm := item.(*element) - return elm.RemoteConn, elm.RemoteAddr + return item.(net.PacketConn) } func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) { diff --git a/constant/adapters.go b/constant/adapters.go index f05a23fa13..f7614c3a62 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -59,7 +59,7 @@ type ProxyAdapter interface { Name() string Type() AdapterType DialContext(ctx context.Context, metadata *Metadata) (Conn, error) - DialUDP(metadata *Metadata) (PacketConn, net.Addr, error) + DialUDP(metadata *Metadata) (PacketConn, error) SupportUDP() bool MarshalJSON() ([]byte, error) } diff --git a/constant/metadata.go b/constant/metadata.go index afcd344317..8bdbd7bc9b 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -3,6 +3,7 @@ package constant import ( "encoding/json" "net" + "strconv" ) // Socks addr type @@ -70,6 +71,17 @@ func (m *Metadata) RemoteAddress() string { return net.JoinHostPort(m.String(), m.DstPort) } +func (m *Metadata) UDPAddr() *net.UDPAddr { + if m.NetWork != UDP || m.DstIP == nil { + return nil + } + port, _ := strconv.Atoi(m.DstPort) + return &net.UDPAddr{ + IP: m.DstIP, + Port: port, + } +} + func (m *Metadata) String() string { if m.Host != "" { return m.Host diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 0e26cc14de..2239af3c21 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -58,10 +58,9 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { } packet := &fakeConn{ PacketConn: pc, - remoteAddr: addr, - targetAddr: target, + rAddr: addr, payload: payload, bufRef: buf, } - tun.AddPacket(adapters.NewPacket(target, packet, C.SOCKS, C.UDP)) + tun.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) } diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index 8cbe57e9cd..51a1911eb2 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -9,10 +9,9 @@ import ( type fakeConn struct { net.PacketConn - remoteAddr net.Addr - targetAddr socks5.Addr - payload []byte - bufRef []byte + rAddr net.Addr + payload []byte + bufRef []byte } func (c *fakeConn) Data() []byte { @@ -21,25 +20,19 @@ func (c *fakeConn) Data() []byte { // WriteBack wirtes UDP packet with source(ip, port) = `addr` func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { - from := c.targetAddr - if addr != nil { - // if addr is provided, use the parsed addr - from = socks5.ParseAddrToSocksAddr(addr) - } - packet, err := socks5.EncodeUDPPacket(from, b) + packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { return } - return c.PacketConn.WriteTo(packet, c.remoteAddr) + return c.PacketConn.WriteTo(packet, c.rAddr) } // LocalAddr returns the source IP/Port of UDP Packet func (c *fakeConn) LocalAddr() net.Addr { - return c.remoteAddr + return c.PacketConn.LocalAddr() } func (c *fakeConn) Close() error { - err := c.PacketConn.Close() pool.BufPool.Put(c.bufRef[:cap(c.bufRef)]) - return err + return nil } diff --git a/tunnel/connection.go b/tunnel/connection.go index 825d0b34e5..6771aa4074 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -88,21 +88,18 @@ func (t *Tunnel) handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr n DefaultManager.Upload() <- int64(len(packet.Data())) } -func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, omitSrcAddr bool, timeout time.Duration) { +func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) { buf := pool.BufPool.Get().([]byte) defer pool.BufPool.Put(buf[:cap(buf)]) defer t.natTable.Delete(key) defer pc.Close() for { - pc.SetReadDeadline(time.Now().Add(timeout)) + pc.SetReadDeadline(time.Now().Add(udpTimeout)) n, from, err := pc.ReadFrom(buf) if err != nil { return } - if from != nil && omitSrcAddr { - from = nil - } n, err = packet.WriteBack(buf[:n], from) if err != nil { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 61f0897170..50ba1cd694 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -175,11 +175,10 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { return } - src := packet.LocalAddr().String() - dst := metadata.RemoteAddress() - key := src + "-" + dst + key := packet.LocalAddr().String() - pc, addr := t.natTable.Get(key) + pc := t.natTable.Get(key) + addr := metadata.UDPAddr() if pc != nil { t.handleUDPToRemote(packet, pc, addr) return @@ -188,8 +187,6 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { lockKey := key + "-lock" wg, loaded := t.natTable.GetOrCreateLock(lockKey) - isFakeIP := dns.DefaultResolver != nil && dns.DefaultResolver.IsFakeIP(metadata.DstIP) - go func() { if !loaded { wg.Add(1) @@ -201,14 +198,13 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { return } - rawPc, nAddr, err := proxy.DialUDP(metadata) + rawPc, err := proxy.DialUDP(metadata) if err != nil { log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error()) t.natTable.Delete(lockKey) wg.Done() return } - addr = nAddr pc = newUDPTracker(rawPc, DefaultManager, metadata, rule) if rule != nil { @@ -217,15 +213,14 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) } - t.natTable.Set(key, pc, addr) + t.natTable.Set(key, pc) t.natTable.Delete(lockKey) wg.Done() - // in fake-ip mode, Full-Cone NAT can never achieve, fallback to omitting src Addr - go t.handleUDPToLocal(packet.UDPPacket, pc, key, isFakeIP, udpTimeout) + go t.handleUDPToLocal(packet.UDPPacket, pc, key) } wg.Wait() - pc, addr := t.natTable.Get(key) + pc := t.natTable.Get(key) if pc != nil { t.handleUDPToRemote(packet, pc, addr) } From 19bb0b655c93fc5d3e6d0648d046c0bc1a15b94c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 31 Jan 2020 14:58:54 +0800 Subject: [PATCH 321/535] Fix: match log display --- constant/metadata.go | 4 ++++ tunnel/tunnel.go | 26 ++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/constant/metadata.go b/constant/metadata.go index 8bdbd7bc9b..78ac95806b 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -71,6 +71,10 @@ func (m *Metadata) RemoteAddress() string { return net.JoinHostPort(m.String(), m.DstPort) } +func (m *Metadata) SourceAddress() string { + return net.JoinHostPort(m.SrcIP.String(), m.SrcPort) +} + func (m *Metadata) UDPAddr() *net.UDPAddr { if m.NetWork != UDP || m.DstIP == nil { return nil diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 50ba1cd694..5b388353fa 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -207,10 +207,15 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { } pc = newUDPTracker(rawPc, DefaultManager, metadata, rule) - if rule != nil { - log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) - } else { - log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) + switch true { + case rule != nil: + log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) + case t.mode == Global: + log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) + case t.mode == Direct: + log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) + default: + log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) } t.natTable.Set(key, pc) @@ -250,10 +255,15 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule) defer remoteConn.Close() - if rule != nil { - log.Infoln("%s --> %v match %s using %s", metadata.SrcIP.String(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String()) - } else { - log.Infoln("%s --> %v doesn't match any rule using DIRECT", metadata.SrcIP.String(), metadata.String()) + switch true { + case rule != nil: + log.Infoln("[TCP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String()) + case t.mode == Global: + log.Infoln("[TCP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) + case t.mode == Direct: + log.Infoln("[TCP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) + default: + log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) } switch adapter := localConn.(type) { From b0f9c6afa8f89c3b8dcfde358f6c7e030969a733 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 31 Jan 2020 15:03:59 +0800 Subject: [PATCH 322/535] Fix: should close socks udp PacketConn --- proxy/socks/utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index 51a1911eb2..af3d8a6b84 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -33,6 +33,7 @@ func (c *fakeConn) LocalAddr() net.Addr { } func (c *fakeConn) Close() error { + err := c.PacketConn.Close() pool.BufPool.Put(c.bufRef[:cap(c.bufRef)]) - return nil + return err } From 72c0af9739898c3ec1797c96b15adc9c7ba9aed4 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 31 Jan 2020 19:26:33 +0800 Subject: [PATCH 323/535] Chore: udp resolve ip on local --- tunnel/tunnel.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 5b388353fa..061a82e5d2 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -175,6 +175,15 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { return } + if metadata.DstIP == nil { + ip, err := t.resolveIP(metadata.Host) + if err != nil { + log.Warnln("[UDP] Resolve %s failed: %s, %#v", metadata.Host, err.Error(), metadata) + return + } + metadata.DstIP = ip + } + key := packet.LocalAddr().String() pc := t.natTable.Get(key) From dcf97ff5b4ccd088ed83889ae2bc0b016b8cda78 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 7 Feb 2020 20:53:43 +0800 Subject: [PATCH 324/535] Fix: should prehandle metadata before resolve --- constant/metadata.go | 4 ++++ tunnel/tunnel.go | 47 ++++++++++++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/constant/metadata.go b/constant/metadata.go index 78ac95806b..61ce434361 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -75,6 +75,10 @@ func (m *Metadata) SourceAddress() string { return net.JoinHostPort(m.SrcIP.String(), m.SrcPort) } +func (m *Metadata) Resolved() bool { + return m.DstIP != nil +} + func (m *Metadata) UDPAddr() *net.UDPAddr { if m.NetWork != UDP || m.DstIP == nil { return nil diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 061a82e5d2..f572399a78 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -132,8 +132,8 @@ func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil } -func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { - // handle host equal IP string +func (t *Tunnel) preHandleMetadata(metadata *C.Metadata) error { + // handle IP string on host if ip := net.ParseIP(metadata.Host); ip != nil { metadata.DstIP = ip } @@ -147,9 +147,15 @@ func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) if dns.DefaultResolver.FakeIPEnabled() { metadata.DstIP = nil } + } else if dns.DefaultResolver.IsFakeIP(metadata.DstIP) { + return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) } } + return nil +} + +func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { var proxy C.Proxy var rule C.Rule switch t.mode { @@ -175,21 +181,24 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { return } - if metadata.DstIP == nil { - ip, err := t.resolveIP(metadata.Host) - if err != nil { - log.Warnln("[UDP] Resolve %s failed: %s, %#v", metadata.Host, err.Error(), metadata) - return - } - metadata.DstIP = ip + if err := t.preHandleMetadata(metadata); err != nil { + log.Debugln("[Metadata PreHandle] error: %s", err) + return } key := packet.LocalAddr().String() - pc := t.natTable.Get(key) - addr := metadata.UDPAddr() if pc != nil { - t.handleUDPToRemote(packet, pc, addr) + if !metadata.Resolved() { + ip, err := t.resolveIP(metadata.Host) + if err != nil { + log.Warnln("[UDP] Resolve %s failed: %s, %#v", metadata.Host, err.Error(), metadata) + return + } + metadata.DstIP = ip + } + + t.handleUDPToRemote(packet, pc, metadata.UDPAddr()) return } @@ -236,7 +245,14 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { wg.Wait() pc := t.natTable.Get(key) if pc != nil { - t.handleUDPToRemote(packet, pc, addr) + if !metadata.Resolved() { + ip, err := dns.ResolveIP(metadata.Host) + if err != nil { + return + } + metadata.DstIP = ip + } + t.handleUDPToRemote(packet, pc, metadata.UDPAddr()) } }() } @@ -250,6 +266,11 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { return } + if err := t.preHandleMetadata(metadata); err != nil { + log.Debugln("[Metadata PreHandle] error: %s", err) + return + } + proxy, rule, err := t.resolveMetadata(metadata) if err != nil { log.Warnln("Parse metadata failed: %v", err) From a55be58c012ee3c4b85a5aa6f0456944e12f661c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 8 Feb 2020 16:21:52 +0800 Subject: [PATCH 325/535] Fix: provider should fallback to read remote when local file invalid --- adapters/provider/provider.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index 139ce70388..fef148a42d 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -128,7 +128,11 @@ func (pp *ProxySetProvider) Initial() error { proxies, err := pp.parse(buf) if err != nil { - return err + // parse local file error, fallback to remote + buf, err = pp.vehicle.Read() + if err != nil { + return err + } } if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil { From afc9f3f59ab285b134e5104be2e5a89f1d229ffe Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 9 Feb 2020 17:02:48 +0800 Subject: [PATCH 326/535] Chore: use custom dialer --- adapters/outbound/direct.go | 3 ++- adapters/outbound/shadowsocks.go | 3 ++- adapters/outbound/socks5.go | 3 ++- adapters/outbound/util.go | 3 ++- adapters/provider/vehicle.go | 3 +++ common/cache/lrucache.go | 2 +- component/dialer/dialer.go | 38 ++++++++++++++++++++++++++++++++ component/dialer/hook.go | 11 +++++++++ dns/client.go | 4 ++++ dns/doh.go | 3 +++ 10 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 component/dialer/dialer.go create mode 100644 component/dialer/hook.go diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index d2b7a9abc7..1da750bc23 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -4,6 +4,7 @@ import ( "context" "net" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" ) @@ -26,7 +27,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, } func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := net.ListenPacket("udp", "") + pc, err := dialer.ListenPacket("udp", "") if err != nil { return nil, err } diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 217c8e2e5e..f0313e999e 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -9,6 +9,7 @@ import ( "strconv" "github.com/Dreamacro/clash/common/structure" + "github.com/Dreamacro/clash/component/dialer" obfs "github.com/Dreamacro/clash/component/simple-obfs" "github.com/Dreamacro/clash/component/socks5" v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin" @@ -83,7 +84,7 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C } func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := net.ListenPacket("udp", "") + pc, err := dialer.ListenPacket("udp", "") if err != nil { return nil, err } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 79cf05b77b..8c5b61f622 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -9,6 +9,7 @@ import ( "net" "strconv" + "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" ) @@ -96,7 +97,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { return } - pc, err := net.ListenPacket("udp", "") + pc, err := dialer.ListenPacket("udp", "") if err != nil { return } diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index b3deb0a7c6..e082954b72 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -11,6 +11,7 @@ import ( "sync" "time" + "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -107,7 +108,7 @@ func dialContext(ctx context.Context, network, address string) (net.Conn, error) var primary, fallback dialResult startRacer := func(ctx context.Context, host string, ipv6 bool) { - dialer := net.Dialer{} + dialer := dialer.Dialer() result := dialResult{ipv6: ipv6, done: true} defer func() { select { diff --git a/adapters/provider/vehicle.go b/adapters/provider/vehicle.go index 0b94936887..cc873a9093 100644 --- a/adapters/provider/vehicle.go +++ b/adapters/provider/vehicle.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "net/http" "time" + + "github.com/Dreamacro/clash/component/dialer" ) // Vehicle Type @@ -85,6 +87,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, + DialContext: dialer.DialContext, } client := http.Client{Transport: transport} diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index a73b0b9eed..6fe1d74cb4 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -12,7 +12,7 @@ import ( type Option func(*LruCache) // EvictCallback is used to get a callback when a cache entry is evicted -type EvictCallback func(key interface{}, value interface{}) +type EvictCallback = func(key interface{}, value interface{}) // WithEvict set the evict callback func WithEvict(cb EvictCallback) Option { diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go new file mode 100644 index 0000000000..bb8f122ed5 --- /dev/null +++ b/component/dialer/dialer.go @@ -0,0 +1,38 @@ +package dialer + +import ( + "context" + "net" +) + +func Dialer() *net.Dialer { + dialer := &net.Dialer{} + if DialerHook != nil { + DialerHook(dialer) + } + + return dialer +} + +func ListenConfig() *net.ListenConfig { + cfg := &net.ListenConfig{} + if ListenConfigHook != nil { + ListenConfigHook(cfg) + } + + return cfg +} + +func Dial(network, address string) (net.Conn, error) { + return DialContext(context.Background(), network, address) +} + +func DialContext(ctx context.Context, network, address string) (net.Conn, error) { + dailer := Dialer() + return dailer.DialContext(ctx, network, address) +} + +func ListenPacket(network, address string) (net.PacketConn, error) { + lc := ListenConfig() + return lc.ListenPacket(context.Background(), network, address) +} diff --git a/component/dialer/hook.go b/component/dialer/hook.go new file mode 100644 index 0000000000..4ef7114356 --- /dev/null +++ b/component/dialer/hook.go @@ -0,0 +1,11 @@ +package dialer + +import "net" + +type DialerHookFunc = func(*net.Dialer) +type ListenConfigHookFunc = func(*net.ListenConfig) + +var ( + DialerHook DialerHookFunc = nil + ListenConfigHook ListenConfigHookFunc = nil +) diff --git a/dns/client.go b/dns/client.go index b79a997d43..91ba7ec5a6 100644 --- a/dns/client.go +++ b/dns/client.go @@ -3,6 +3,8 @@ package dns import ( "context" + "github.com/Dreamacro/clash/component/dialer" + D "github.com/miekg/dns" ) @@ -16,6 +18,8 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { } func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { + c.Client.Dialer = dialer.Dialer() + // Please note that miekg/dns ExchangeContext doesn't respond to context cancel. msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address) return diff --git a/dns/doh.go b/dns/doh.go index d8d6077291..4aecd355a9 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "net/http" + "github.com/Dreamacro/clash/component/dialer" + D "github.com/miekg/dns" ) @@ -17,6 +19,7 @@ const ( var dohTransport = &http.Transport{ TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, + DialContext: dialer.DialContext, } type dohClient struct { From 6641bf7c073d118821312f3b3a25699716b59525 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 12 Feb 2020 13:12:07 +0800 Subject: [PATCH 327/535] Fix: vmessUDPConn should return a correctly address --- adapters/outbound/vmess.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index a7a5016d2e..401f3b87fa 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -54,7 +54,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { if err != nil { return nil, fmt.Errorf("new vmess client error: %v", err) } - return newPacketConn(&vmessUDPConn{Conn: c}, v), nil + return newPacketConn(&vmessUDPConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil } func NewVmess(option VmessOption) (*Vmess, error) { @@ -117,6 +117,7 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { type vmessUDPConn struct { net.Conn + rAddr net.Addr } func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { @@ -125,5 +126,5 @@ func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { n, err := uc.Conn.Read(b) - return n, uc.RemoteAddr(), err + return n, uc.rAddr, err } From 8b5e5114266bfc1509ddc9db267af67ec1b69fe1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 14 Feb 2020 16:36:20 +0800 Subject: [PATCH 328/535] Fix: use the fastest whether the result is successful --- common/picker/picker.go | 25 ++----------------------- common/picker/picker_test.go | 27 --------------------------- dns/client.go | 21 ++++++++++++++++++--- dns/middleware.go | 6 ++++-- dns/resolver.go | 14 ++++++-------- 5 files changed, 30 insertions(+), 63 deletions(-) diff --git a/common/picker/picker.go b/common/picker/picker.go index 49a58f0df5..0c846cb9cc 100644 --- a/common/picker/picker.go +++ b/common/picker/picker.go @@ -17,15 +17,12 @@ type Picker struct { once sync.Once result interface{} - - firstDone chan struct{} } func newPicker(ctx context.Context, cancel func()) *Picker { return &Picker{ - ctx: ctx, - cancel: cancel, - firstDone: make(chan struct{}, 1), + ctx: ctx, + cancel: cancel, } } @@ -42,12 +39,6 @@ func WithTimeout(ctx context.Context, timeout time.Duration) (*Picker, context.C return newPicker(ctx, cancel), ctx } -// WithoutAutoCancel returns a new Picker and an associated Context derived from ctx, -// but it wouldn't cancel context when the first element return. -func WithoutAutoCancel(ctx context.Context) *Picker { - return newPicker(ctx, nil) -} - // Wait blocks until all function calls from the Go method have returned, // then returns the first nil error result (if any) from them. func (p *Picker) Wait() interface{} { @@ -58,17 +49,6 @@ func (p *Picker) Wait() interface{} { return p.result } -// WaitWithoutCancel blocks until the first result return, if timeout will return nil. -// The return of this function will not wait for the cancel of context. -func (p *Picker) WaitWithoutCancel() interface{} { - select { - case <-p.firstDone: - return p.result - case <-p.ctx.Done(): - return p.result - } -} - // Go calls the given function in a new goroutine. // The first call to return a nil error cancels the group; its result will be returned by Wait. func (p *Picker) Go(f func() (interface{}, error)) { @@ -80,7 +60,6 @@ func (p *Picker) Go(f func() (interface{}, error)) { if ret, err := f(); err == nil { p.once.Do(func() { p.result = ret - p.firstDone <- struct{}{} if p.cancel != nil { p.cancel() } diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go index 9e1650096d..8f0ba9586f 100644 --- a/common/picker/picker_test.go +++ b/common/picker/picker_test.go @@ -37,30 +37,3 @@ func TestPicker_Timeout(t *testing.T) { number := picker.Wait() assert.Nil(t, number) } - -func TestPicker_WaitWithoutAutoCancel(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*60) - defer cancel() - picker := WithoutAutoCancel(ctx) - - trigger := false - picker.Go(sleepAndSend(ctx, 10, 1)) - picker.Go(func() (interface{}, error) { - timer := time.NewTimer(time.Millisecond * time.Duration(30)) - select { - case <-timer.C: - trigger = true - return 2, nil - case <-ctx.Done(): - return nil, ctx.Err() - } - }) - elm := picker.WaitWithoutCancel() - - assert.NotNil(t, elm) - assert.Equal(t, elm.(int), 1) - - elm = picker.Wait() - assert.True(t, trigger) - assert.Equal(t, elm.(int), 1) -} diff --git a/dns/client.go b/dns/client.go index 91ba7ec5a6..a3d566695e 100644 --- a/dns/client.go +++ b/dns/client.go @@ -20,7 +20,22 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { c.Client.Dialer = dialer.Dialer() - // Please note that miekg/dns ExchangeContext doesn't respond to context cancel. - msg, _, err = c.Client.ExchangeContext(ctx, m, c.Address) - return + // miekg/dns ExchangeContext doesn't respond to context cancel. + // this is a workaround + type result struct { + msg *D.Msg + err error + } + ch := make(chan result, 1) + go func() { + msg, _, err := c.Client.ExchangeContext(ctx, m, c.Address) + ch <- result{msg, err} + }() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case ret := <-ch: + return ret.msg, ret.err + } } diff --git a/dns/middleware.go b/dns/middleware.go index 5aa691ad79..90d5a7fea4 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -39,7 +39,8 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { msg.Answer = []D.RR{rr} setMsgTTL(msg, 1) - msg.SetReply(r) + msg.SetRcode(r, msg.Rcode) + msg.Authoritative = true w.WriteMsg(msg) return } @@ -55,7 +56,8 @@ func withResolver(resolver *Resolver) handler { D.HandleFailed(w, r) return } - msg.SetReply(r) + msg.SetRcode(r, msg.Rcode) + msg.Authoritative = true w.WriteMsg(msg) return } diff --git a/dns/resolver.go b/dns/resolver.go index 7e76e8191f..58c2a3e92a 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "math/rand" "net" "strings" "time" @@ -178,15 +179,11 @@ func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err for _, client := range clients { r := client fast.Go(func() (interface{}, error) { - msg, err := r.ExchangeContext(ctx, m) - if err != nil || msg.Rcode != D.RcodeSuccess { - return nil, errors.New("resolve error") - } - return msg, nil + return r.ExchangeContext(ctx, m) }) } - elm := fast.WaitWithoutCancel() + elm := fast.Wait() if elm == nil { return nil, errors.New("All DNS requests failed") } @@ -239,11 +236,12 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) } ips := r.msgToIP(msg) - if len(ips) == 0 { + ipLength := len(ips) + if ipLength == 0 { return nil, errIPNotFound } - ip = ips[0] + ip = ips[rand.Intn(ipLength)] return } From f69f635e0b0ada91f68be3a8566cf36e51f81818 Mon Sep 17 00:00:00 2001 From: Dreamacro Date: Fri, 14 Feb 2020 19:15:40 +0800 Subject: [PATCH 329/535] Chore: add issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 96 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 78 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..932ada0147 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,96 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[Bug]" +labels: '' +assignees: '' + +--- + + +感谢你向 Clash Core 提交 issue! +在提交之前,请确认: + +- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的问题 +- [ ] 这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 Openclash、Koolclash 等)的特定问题 +- [ ] 我已经使用 Clash core 的 dev 分支版本测试过,问题依旧存在 +- [ ] 如果你可以自己 debug 并解决的话,提交 PR 吧! + +请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。 + + + +我都确认过了,我要继续提交。 + +------------------------------------------------------------------ + +请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。 + + +### clash core config + +``` +…… +``` + +### Clash log + +``` +…… +``` + +### 环境 Environment + +* Clash Core 的操作系统 (the OS that the Clash core is running on) +…… +* 使用者的操作系统 (the OS running on the client) +…… +* 网路环境或拓扑 (network conditions/topology) +…… +* iptables,如果适用 (if applicable) +…… +* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) +…… +* 其他 +…… + +### 说明 Description + + + +### 重现问题的具体布骤 Steps to Reproduce + +1. [First Step] +2. [Second Step] +3. …… + +**我预期会发生……?** + + +**实际上发生了什麽?** + + +### 可能的解决方案 Possible Solution + + + + +### 更多信息 More Information diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..d5ddc956a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,78 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature]" +labels: '' +assignees: '' + +--- + + +感谢你向 Clash Core 提交 Feature Request! +在提交之前,请确认: + +- [ ] 我已经在 [Issue Tracker](……/) 中找过我要提出的请求 + +请注意,如果你并没有遵照这个 issue template 填写内容,我们将直接关闭这个 issue。 + + + +我都确认过了,我要继续提交。 + +------------------------------------------------------------------ + +请附上任何可以帮助我们解决这个问题的信息,如果我们收到的信息不足,我们将对这个 issue 加上 *Needs more information* 标记并在收到更多资讯之前关闭 issue。 + + +### Clash core config + +``` +…… +``` + +### Clash log + +``` +…… +``` + +### 环境 Environment + +* Clash Core 的操作系统 (the OS that the Clash core is running on) +…… +* 使用者的操作系统 (the OS running on the client) +…… +* 网路环境或拓扑 (network conditions/topology) +…… +* iptables,如果适用 (if applicable) +…… +* ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) +…… +* 其他 +…… + +### 说明 Description + + + +### 可能的解决方案 Possible Solution + + + + +### 更多信息 More Information From d75cb069d93e7051e56730859a8458aa90186f6f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 15 Feb 2020 21:42:46 +0800 Subject: [PATCH 330/535] Feature: add default-nameserver and outbound interface --- README.md | 4 + adapters/outbound/direct.go | 2 +- adapters/outbound/http.go | 3 +- adapters/outbound/shadowsocks.go | 2 +- adapters/outbound/snell.go | 3 +- adapters/outbound/socks5.go | 4 +- adapters/outbound/util.go | 85 +------ adapters/outbound/vmess.go | 5 +- component/dialer/dialer.go | 120 +++++++++- component/dialer/hook.go | 139 ++++++++++- .../resolver/resolver.go | 35 ++- config/config.go | 81 ++++--- dns/client.go | 24 +- dns/doh.go | 33 ++- dns/resolver.go | 35 ++- dns/util.go | 11 +- hub/executor/executor.go | 35 ++- hub/route/configs.go | 18 +- hub/route/provider.go | 6 +- hub/route/proxies.go | 6 +- hub/route/rules.go | 4 +- proxy/http/server.go | 8 +- proxy/redir/tcp.go | 6 +- proxy/socks/tcp.go | 6 +- proxy/socks/udp.go | 3 +- tunnel/connection.go | 10 +- tunnel/mode.go | 14 +- tunnel/tunnel.go | 223 ++++++++---------- 28 files changed, 578 insertions(+), 347 deletions(-) rename dns/iputil.go => component/resolver/resolver.go (70%) diff --git a/README.md b/README.md index b5328ce7ce..3be09c9d70 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ external-controller: 127.0.0.1:9090 # experimental feature experimental: ignore-resolve-fail: true # ignore dns resolve fail, default value is true + # interface-name: en0 # outbound interface name # authentication of local SOCKS5/HTTP(S) server # authentication: @@ -130,6 +131,9 @@ experimental: # enable: true # set true to enable dns (default is false) # ipv6: false # default is false # listen: 0.0.0.0:53 + # # default-nameserver: # resolve dns nameserver host, should fill pure IP + # # - 114.114.114.114 + # # - 8.8.8.8 # enhanced-mode: redir-host # or fake-ip # # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it # fake-ip-filter: # fake ip white domain list diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 1da750bc23..c118425dbe 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -18,7 +18,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) } - c, err := dialContext(ctx, "tcp", address) + c, err := dialer.DialContext(ctx, "tcp", address) if err != nil { return nil, err } diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index 5223f27db2..e0ae0c41f9 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -13,6 +13,7 @@ import ( "net/url" "strconv" + "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" ) @@ -35,7 +36,7 @@ type HttpOption struct { } func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialContext(ctx, "tcp", h.addr) + c, err := dialer.DialContext(ctx, "tcp", h.addr) if err == nil && h.tlsConfig != nil { cc := tls.Client(c, h.tlsConfig) err = cc.Handshake() diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index f0313e999e..245d00cef9 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -60,7 +60,7 @@ type v2rayObfsOption struct { } func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialContext(ctx, "tcp", ss.server) + c, err := dialer.DialContext(ctx, "tcp", ss.server) if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.server, err) } diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go index 4626bbef35..f96d8fff02 100644 --- a/adapters/outbound/snell.go +++ b/adapters/outbound/snell.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/Dreamacro/clash/common/structure" + "github.com/Dreamacro/clash/component/dialer" obfs "github.com/Dreamacro/clash/component/simple-obfs" "github.com/Dreamacro/clash/component/snell" C "github.com/Dreamacro/clash/constant" @@ -28,7 +29,7 @@ type SnellOption struct { } func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialContext(ctx, "tcp", s.server) + c, err := dialer.DialContext(ctx, "tcp", s.server) if err != nil { return nil, fmt.Errorf("%s connect error: %w", s.server, err) } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 8c5b61f622..2c47bb446a 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -36,7 +36,7 @@ type Socks5Option struct { } func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialContext(ctx, "tcp", ss.addr) + c, err := dialer.DialContext(ctx, "tcp", ss.addr) if err == nil && ss.tls { cc := tls.Client(c, ss.tlsConfig) @@ -64,7 +64,7 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) defer cancel() - c, err := dialContext(ctx, "tcp", ss.addr) + c, err := dialer.DialContext(ctx, "tcp", ss.addr) if err != nil { err = fmt.Errorf("%s connect error: %w", ss.addr, err) return diff --git a/adapters/outbound/util.go b/adapters/outbound/util.go index e082954b72..4a92c24c27 100644 --- a/adapters/outbound/util.go +++ b/adapters/outbound/util.go @@ -2,7 +2,6 @@ package outbound import ( "bytes" - "context" "crypto/tls" "fmt" "net" @@ -11,10 +10,9 @@ import ( "sync" "time" - "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/dns" ) const ( @@ -88,92 +86,13 @@ func serializesSocksAddr(metadata *C.Metadata) []byte { return bytes.Join(buf, nil) } -func dialContext(ctx context.Context, network, address string) (net.Conn, error) { - host, port, err := net.SplitHostPort(address) - if err != nil { - return nil, err - } - - returned := make(chan struct{}) - defer close(returned) - - type dialResult struct { - net.Conn - error - resolved bool - ipv6 bool - done bool - } - results := make(chan dialResult) - var primary, fallback dialResult - - startRacer := func(ctx context.Context, host string, ipv6 bool) { - dialer := dialer.Dialer() - result := dialResult{ipv6: ipv6, done: true} - defer func() { - select { - case results <- result: - case <-returned: - if result.Conn != nil { - result.Conn.Close() - } - } - }() - - var ip net.IP - if ipv6 { - ip, result.error = dns.ResolveIPv6(host) - } else { - ip, result.error = dns.ResolveIPv4(host) - } - if result.error != nil { - return - } - result.resolved = true - - if ipv6 { - result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port)) - } else { - result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) - } - } - - go startRacer(ctx, host, false) - go startRacer(ctx, host, true) - - for { - select { - case res := <-results: - if res.error == nil { - return res.Conn, nil - } - - if !res.ipv6 { - primary = res - } else { - fallback = res - } - - if primary.done && fallback.done { - if primary.resolved { - return nil, primary.error - } else if fallback.resolved { - return nil, fallback.error - } else { - return nil, primary.error - } - } - } - } -} - func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err } - ip, err := dns.ResolveIP(host) + ip, err := resolver.ResolveIP(host) if err != nil { return nil, err } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 401f3b87fa..197ba9a584 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/vmess" C "github.com/Dreamacro/clash/constant" ) @@ -33,7 +34,7 @@ type VmessOption struct { } func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialContext(ctx, "tcp", v.server) + c, err := dialer.DialContext(ctx, "tcp", v.server) if err != nil { return nil, fmt.Errorf("%s connect error", v.server) } @@ -45,7 +46,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) defer cancel() - c, err := dialContext(ctx, "tcp", v.server) + c, err := dialer.DialContext(ctx, "tcp", v.server) if err != nil { return nil, fmt.Errorf("%s connect error", v.server) } diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index bb8f122ed5..6dcecdc165 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -2,7 +2,10 @@ package dialer import ( "context" + "errors" "net" + + "github.com/Dreamacro/clash/component/resolver" ) func Dialer() *net.Dialer { @@ -28,11 +31,124 @@ func Dial(network, address string) (net.Conn, error) { } func DialContext(ctx context.Context, network, address string) (net.Conn, error) { - dailer := Dialer() - return dailer.DialContext(ctx, network, address) + switch network { + case "tcp4", "tcp6", "udp4", "udp6": + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + dialer := Dialer() + + var ip net.IP + switch network { + case "tcp4", "udp4": + ip, err = resolver.ResolveIPv4(host) + default: + ip, err = resolver.ResolveIPv6(host) + } + + if err != nil { + return nil, err + } + + if DialHook != nil { + DialHook(dialer, network, ip) + } + return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) + case "tcp", "udp": + return dualStackDailContext(ctx, network, address) + default: + return nil, errors.New("network invalid") + } } func ListenPacket(network, address string) (net.PacketConn, error) { lc := ListenConfig() + + if ListenPacketHook != nil && address == "" { + ip := ListenPacketHook() + if ip != nil { + address = net.JoinHostPort(ip.String(), "0") + } + } return lc.ListenPacket(context.Background(), network, address) } + +func dualStackDailContext(ctx context.Context, network, address string) (net.Conn, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + returned := make(chan struct{}) + defer close(returned) + + type dialResult struct { + net.Conn + error + resolved bool + ipv6 bool + done bool + } + results := make(chan dialResult) + var primary, fallback dialResult + + startRacer := func(ctx context.Context, network, host string, ipv6 bool) { + dialer := Dialer() + result := dialResult{ipv6: ipv6, done: true} + defer func() { + select { + case results <- result: + case <-returned: + if result.Conn != nil { + result.Conn.Close() + } + } + }() + + var ip net.IP + if ipv6 { + ip, result.error = resolver.ResolveIPv6(host) + } else { + ip, result.error = resolver.ResolveIPv4(host) + } + if result.error != nil { + return + } + result.resolved = true + + if DialHook != nil { + DialHook(dialer, network, ip) + } + result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) + } + + go startRacer(ctx, network+"4", host, false) + go startRacer(ctx, network+"6", host, true) + + for { + select { + case res := <-results: + if res.error == nil { + return res.Conn, nil + } + + if !res.ipv6 { + primary = res + } else { + fallback = res + } + + if primary.done && fallback.done { + if primary.resolved { + return nil, primary.error + } else if fallback.resolved { + return nil, fallback.error + } else { + return nil, primary.error + } + } + } + } +} diff --git a/component/dialer/hook.go b/component/dialer/hook.go index 4ef7114356..3546a393ee 100644 --- a/component/dialer/hook.go +++ b/component/dialer/hook.go @@ -1,11 +1,142 @@ package dialer -import "net" +import ( + "errors" + "net" + "time" -type DialerHookFunc = func(*net.Dialer) + "github.com/Dreamacro/clash/common/singledo" +) + +type DialerHookFunc = func(dialer *net.Dialer) +type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) type ListenConfigHookFunc = func(*net.ListenConfig) +type ListenPacketHookFunc = func() net.IP + +var ( + DialerHook DialerHookFunc + DialHook DialHookFunc + ListenConfigHook ListenConfigHookFunc + ListenPacketHook ListenPacketHookFunc +) var ( - DialerHook DialerHookFunc = nil - ListenConfigHook ListenConfigHookFunc = nil + ErrAddrNotFound = errors.New("addr not found") + ErrNetworkNotSupport = errors.New("network not support") ) + +func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) { + ipv4 := ip.To4() != nil + + for _, elm := range addrs { + addr, ok := elm.(*net.IPNet) + if !ok { + continue + } + + addrV4 := addr.IP.To4() != nil + + if addrV4 && ipv4 { + return &net.TCPAddr{IP: addr.IP, Port: 0}, nil + } else if !addrV4 && !ipv4 { + return &net.TCPAddr{IP: addr.IP, Port: 0}, nil + } + } + + return nil, ErrAddrNotFound +} + +func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { + ipv4 := ip.To4() != nil + + for _, elm := range addrs { + addr, ok := elm.(*net.IPNet) + if !ok { + continue + } + + addrV4 := addr.IP.To4() != nil + + if addrV4 && ipv4 { + return &net.UDPAddr{IP: addr.IP, Port: 0}, nil + } else if !addrV4 && !ipv4 { + return &net.UDPAddr{IP: addr.IP, Port: 0}, nil + } + } + + return nil, ErrAddrNotFound +} + +func ListenPacketWithInterface(name string) ListenPacketHookFunc { + single := singledo.NewSingle(5 * time.Second) + + return func() net.IP { + elm, err, _ := single.Do(func() (interface{}, error) { + iface, err := net.InterfaceByName(name) + if err != nil { + return nil, err + } + + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + + return addrs, nil + }) + + if err != nil { + return nil + } + + addrs := elm.([]net.Addr) + + for _, elm := range addrs { + addr, ok := elm.(*net.IPNet) + if !ok || addr.IP.To4() == nil { + continue + } + + return addr.IP + } + + return nil + } +} + +func DialerWithInterface(name string) DialHookFunc { + single := singledo.NewSingle(5 * time.Second) + + return func(dialer *net.Dialer, network string, ip net.IP) { + elm, err, _ := single.Do(func() (interface{}, error) { + iface, err := net.InterfaceByName(name) + if err != nil { + return nil, err + } + + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + + return addrs, nil + }) + + if err != nil { + return + } + + addrs := elm.([]net.Addr) + + switch network { + case "tcp", "tcp4", "tcp6": + if addr, err := lookupTCPAddr(ip, addrs); err == nil { + dialer.LocalAddr = addr + } + case "udp", "udp4", "udp6": + if addr, err := lookupUDPAddr(ip, addrs); err == nil { + dialer.LocalAddr = addr + } + } + } +} diff --git a/dns/iputil.go b/component/resolver/resolver.go similarity index 70% rename from dns/iputil.go rename to component/resolver/resolver.go index f186e5a0da..f7fc1e8981 100644 --- a/dns/iputil.go +++ b/component/resolver/resolver.go @@ -1,16 +1,32 @@ -package dns +package resolver import ( "errors" "net" "strings" + + trie "github.com/Dreamacro/clash/component/domain-trie" +) + +var ( + // DefaultResolver aim to resolve ip + DefaultResolver Resolver + + // DefaultHosts aim to resolve hosts + DefaultHosts = trie.New() ) var ( - errIPNotFound = errors.New("couldn't find ip") - errIPVersion = errors.New("ip version error") + ErrIPNotFound = errors.New("couldn't find ip") + ErrIPVersion = errors.New("ip version error") ) +type Resolver interface { + ResolveIP(host string) (ip net.IP, err error) + ResolveIPv4(host string) (ip net.IP, err error) + ResolveIPv6(host string) (ip net.IP, err error) +} + // ResolveIPv4 with a host, return ipv4 func ResolveIPv4(host string) (net.IP, error) { if node := DefaultHosts.Search(host); node != nil { @@ -24,7 +40,7 @@ func ResolveIPv4(host string) (net.IP, error) { if !strings.Contains(host, ":") { return ip, nil } - return nil, errIPVersion + return nil, ErrIPVersion } if DefaultResolver != nil { @@ -42,7 +58,7 @@ func ResolveIPv4(host string) (net.IP, error) { } } - return nil, errIPNotFound + return nil, ErrIPNotFound } // ResolveIPv6 with a host, return ipv6 @@ -58,7 +74,7 @@ func ResolveIPv6(host string) (net.IP, error) { if strings.Contains(host, ":") { return ip, nil } - return nil, errIPVersion + return nil, ErrIPVersion } if DefaultResolver != nil { @@ -76,7 +92,7 @@ func ResolveIPv6(host string) (net.IP, error) { } } - return nil, errIPNotFound + return nil, ErrIPNotFound } // ResolveIP with a host, return ip @@ -86,10 +102,7 @@ func ResolveIP(host string) (net.IP, error) { } if DefaultResolver != nil { - if DefaultResolver.ipv6 { - return DefaultResolver.ResolveIP(host) - } - return DefaultResolver.ResolveIPv4(host) + return DefaultResolver.ResolveIP(host) } ip := net.ParseIP(host) diff --git a/config/config.go b/config/config.go index be6ae5426d..e43006a759 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config import ( + "errors" "fmt" "net" "net/url" @@ -30,7 +31,7 @@ type General struct { Authentication []string `json:"authentication"` AllowLan bool `json:"allow-lan"` BindAddress string `json:"bind-address"` - Mode T.Mode `json:"mode"` + Mode T.TunnelMode `json:"mode"` LogLevel log.LogLevel `json:"log-level"` ExternalController string `json:"-"` ExternalUI string `json:"-"` @@ -39,14 +40,15 @@ type General struct { // DNS config type DNS struct { - Enable bool `yaml:"enable"` - IPv6 bool `yaml:"ipv6"` - NameServer []dns.NameServer `yaml:"nameserver"` - Fallback []dns.NameServer `yaml:"fallback"` - FallbackFilter FallbackFilter `yaml:"fallback-filter"` - Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` - FakeIPRange *fakeip.Pool + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []dns.NameServer `yaml:"nameserver"` + Fallback []dns.NameServer `yaml:"fallback"` + FallbackFilter FallbackFilter `yaml:"fallback-filter"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` + FakeIPRange *fakeip.Pool } // FallbackFilter config @@ -57,7 +59,8 @@ type FallbackFilter struct { // Experimental config type Experimental struct { - IgnoreResolveFail bool `yaml:"ignore-resolve-fail"` + IgnoreResolveFail bool `yaml:"ignore-resolve-fail"` + Interface string `yaml:"interface-name"` } // Config is clash config manager @@ -73,15 +76,16 @@ type Config struct { } type RawDNS struct { - Enable bool `yaml:"enable"` - IPv6 bool `yaml:"ipv6"` - NameServer []string `yaml:"nameserver"` - Fallback []string `yaml:"fallback"` - FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` - Listen string `yaml:"listen"` - EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` - FakeIPRange string `yaml:"fake-ip-range"` - FakeIPFilter []string `yaml:"fake-ip-filter"` + Enable bool `yaml:"enable"` + IPv6 bool `yaml:"ipv6"` + NameServer []string `yaml:"nameserver"` + Fallback []string `yaml:"fallback"` + FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` + Listen string `yaml:"listen"` + EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` + FakeIPRange string `yaml:"fake-ip-range"` + FakeIPFilter []string `yaml:"fake-ip-filter"` + DefaultNameserver []string `yaml:"default-nameserver"` } type RawFallbackFilter struct { @@ -96,7 +100,7 @@ type RawConfig struct { Authentication []string `yaml:"authentication"` AllowLan bool `yaml:"allow-lan"` BindAddress string `yaml:"bind-address"` - Mode T.Mode `yaml:"mode"` + Mode T.TunnelMode `yaml:"mode"` LogLevel log.LogLevel `yaml:"log-level"` ExternalController string `yaml:"external-controller"` ExternalUI string `yaml:"external-ui"` @@ -143,6 +147,10 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { GeoIP: true, IPCIDR: []string{}, }, + DefaultNameserver: []string{ + "114.114.114.114", + "8.8.8.8", + }, }, } @@ -433,21 +441,21 @@ func parseHosts(cfg *RawConfig) (*trie.Trie, error) { return tree, nil } -func hostWithDefaultPort(host string, defPort string) (string, error) { +func hostWithDefaultPort(host string, defPort string) (string, string, error) { if !strings.Contains(host, ":") { host += ":" } hostname, port, err := net.SplitHostPort(host) if err != nil { - return "", err + return "", "", err } if port == "" { port = defPort } - return net.JoinHostPort(hostname, port), nil + return net.JoinHostPort(hostname, port), hostname, nil } func parseNameServer(servers []string) ([]dns.NameServer, error) { @@ -463,20 +471,21 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } - var host, dnsNetType string + var addr, dnsNetType, host string switch u.Scheme { case "udp": - host, err = hostWithDefaultPort(u.Host, "53") + addr, host, err = hostWithDefaultPort(u.Host, "53") dnsNetType = "" // UDP case "tcp": - host, err = hostWithDefaultPort(u.Host, "53") + addr, host, err = hostWithDefaultPort(u.Host, "53") dnsNetType = "tcp" // TCP case "tls": - host, err = hostWithDefaultPort(u.Host, "853") + addr, host, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "tcp-tls" // DNS over TLS case "https": clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} - host = clearURL.String() + addr = clearURL.String() + _, host, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "https" // DNS over HTTPS default: return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) @@ -490,7 +499,8 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { nameservers, dns.NameServer{ Net: dnsNetType, - Addr: host, + Addr: addr, + Host: host, }, ) } @@ -534,6 +544,19 @@ func parseDNS(cfg RawDNS) (*DNS, error) { return nil, err } + if len(cfg.DefaultNameserver) == 0 { + return nil, errors.New("default nameserver should have at least one nameserver") + } + if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver); err != nil { + return nil, err + } + // check default nameserver is pure ip addr + for _, ns := range dnsCfg.DefaultNameserver { + if net.ParseIP(ns.Host) == nil { + return nil, errors.New("default nameserver should be pure IP") + } + } + if cfg.EnhancedMode == dns.FAKEIP { _, ipnet, err := net.ParseCIDR(cfg.FakeIPRange) if err != nil { diff --git a/dns/client.go b/dns/client.go index a3d566695e..1f1e6c6f8d 100644 --- a/dns/client.go +++ b/dns/client.go @@ -2,6 +2,7 @@ package dns import ( "context" + "strings" "github.com/Dreamacro/clash/component/dialer" @@ -10,7 +11,9 @@ import ( type client struct { *D.Client - Address string + r *Resolver + addr string + host string } func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { @@ -18,7 +21,22 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { } func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { - c.Client.Dialer = dialer.Dialer() + network := "udp" + if strings.HasPrefix(c.Client.Net, "tcp") { + network = "tcp" + } + + ip, err := c.r.ResolveIPv4(c.host) + if err != nil { + return nil, err + } + + d := dialer.Dialer() + if dialer.DialHook != nil { + dialer.DialHook(d, network, ip) + } + + c.Client.Dialer = d // miekg/dns ExchangeContext doesn't respond to context cancel. // this is a workaround @@ -28,7 +46,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err } ch := make(chan result, 1) go func() { - msg, _, err := c.Client.ExchangeContext(ctx, m, c.Address) + msg, _, err := c.Client.Exchange(m, c.addr) ch <- result{msg, err} }() diff --git a/dns/doh.go b/dns/doh.go index 4aecd355a9..8715393edc 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -5,6 +5,7 @@ import ( "context" "crypto/tls" "io/ioutil" + "net" "net/http" "github.com/Dreamacro/clash/component/dialer" @@ -17,13 +18,9 @@ const ( dotMimeType = "application/dns-message" ) -var dohTransport = &http.Transport{ - TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, - DialContext: dialer.DialContext, -} - type dohClient struct { - url string + url string + transport *http.Transport } func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { @@ -58,7 +55,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) { } func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { - client := &http.Client{Transport: dohTransport} + client := &http.Client{Transport: dc.transport} resp, err := client.Do(req) if err != nil { return nil, err @@ -73,3 +70,25 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { err = msg.Unpack(buf) return msg, err } + +func newDoHClient(url string, r *Resolver) *dohClient { + return &dohClient{ + url: url, + transport: &http.Transport{ + TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + + ip, err := r.ResolveIPv4(host) + if err != nil { + return nil, err + } + + return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port)) + }, + }, + } +} diff --git a/dns/resolver.go b/dns/resolver.go index 58c2a3e92a..480818c910 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -11,26 +11,18 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/picker" - trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/fakeip" + "github.com/Dreamacro/clash/component/resolver" D "github.com/miekg/dns" "golang.org/x/sync/singleflight" ) -var ( - // DefaultResolver aim to resolve ip - DefaultResolver *Resolver - - // DefaultHosts aim to resolve hosts - DefaultHosts = trie.New() -) - var ( globalSessionCache = tls.NewLRUClientSessionCache(64) ) -type resolver interface { +type dnsClient interface { Exchange(m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) } @@ -45,8 +37,8 @@ type Resolver struct { mapping bool fakeip bool pool *fakeip.Pool - main []resolver - fallback []resolver + main []dnsClient + fallback []dnsClient fallbackFilters []fallbackFilter group singleflight.Group cache *cache.Cache @@ -74,7 +66,7 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { ip, open := <-ch if !open { - return nil, errIPNotFound + return nil, resolver.ErrIPNotFound } return ip, nil @@ -174,7 +166,7 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool { return false } -func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) { +func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { fast, ctx := picker.WithTimeout(context.Background(), time.Second*5) for _, client := range clients { r := client @@ -238,7 +230,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) ips := r.msgToIP(msg) ipLength := len(ips) if ipLength == 0 { - return nil, errIPNotFound + return nil, resolver.ErrIPNotFound } ip = ips[rand.Intn(ipLength)] @@ -260,7 +252,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { return ips } -func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result { +func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result { ch := make(chan *result) go func() { res, err := r.batchExchange(client, msg) @@ -272,6 +264,7 @@ func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result { type NameServer struct { Net string Addr string + Host string } type FallbackFilter struct { @@ -281,6 +274,7 @@ type FallbackFilter struct { type Config struct { Main, Fallback []NameServer + Default []NameServer IPv6 bool EnhancedMode EnhancedMode FallbackFilter FallbackFilter @@ -288,9 +282,14 @@ type Config struct { } func New(config Config) *Resolver { + defaultResolver := &Resolver{ + main: transform(config.Default, nil), + cache: cache.New(time.Second * 60), + } + r := &Resolver{ ipv6: config.IPv6, - main: transform(config.Main), + main: transform(config.Main, defaultResolver), cache: cache.New(time.Second * 60), mapping: config.EnhancedMode == MAPPING, fakeip: config.EnhancedMode == FAKEIP, @@ -298,7 +297,7 @@ func New(config Config) *Resolver { } if len(config.Fallback) != 0 { - r.fallback = transform(config.Fallback) + r.fallback = transform(config.Fallback, defaultResolver) } fallbackFilters := []fallbackFilter{} diff --git a/dns/util.go b/dns/util.go index d5cb216db5..2cf0cd9b6a 100644 --- a/dns/util.go +++ b/dns/util.go @@ -8,9 +8,9 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/log" - yaml "gopkg.in/yaml.v2" D "github.com/miekg/dns" + yaml "gopkg.in/yaml.v2" ) var ( @@ -117,11 +117,11 @@ func isIPRequest(q D.Question) bool { return false } -func transform(servers []NameServer) []resolver { - ret := []resolver{} +func transform(servers []NameServer, resolver *Resolver) []dnsClient { + ret := []dnsClient{} for _, s := range servers { if s.Net == "https" { - ret = append(ret, &dohClient{url: s.Addr}) + ret = append(ret, newDoHClient(s.Addr, resolver)) continue } @@ -136,7 +136,8 @@ func transform(servers []NameServer) []resolver { UDPSize: 4096, Timeout: 5 * time.Second, }, - Address: s.Addr, + addr: s.Addr, + host: s.Host, }) } return ret diff --git a/hub/executor/executor.go b/hub/executor/executor.go index ac45c1727d..5916bfbb26 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -8,14 +8,16 @@ import ( "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" + "github.com/Dreamacro/clash/component/dialer" trie "github.com/Dreamacro/clash/component/domain-trie" + "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" P "github.com/Dreamacro/clash/proxy" authStore "github.com/Dreamacro/clash/proxy/auth" - T "github.com/Dreamacro/clash/tunnel" + "github.com/Dreamacro/clash/tunnel" ) // forward compatibility before 1.0 @@ -83,7 +85,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateRules(cfg.Rules) updateDNS(cfg.DNS) updateHosts(cfg.Hosts) - updateExperimental(cfg.Experimental) + updateExperimental(cfg) } func GetGeneral() *config.General { @@ -100,20 +102,30 @@ func GetGeneral() *config.General { Authentication: authenticator, AllowLan: P.AllowLan(), BindAddress: P.BindAddress(), - Mode: T.Instance().Mode(), + Mode: tunnel.Mode(), LogLevel: log.Level(), } return general } -func updateExperimental(c *config.Experimental) { - T.Instance().UpdateExperimental(c.IgnoreResolveFail) +func updateExperimental(c *config.Config) { + cfg := c.Experimental + + tunnel.UpdateExperimental(cfg.IgnoreResolveFail) + if cfg.Interface != "" && c.DNS.Enable { + dialer.DialHook = dialer.DialerWithInterface(cfg.Interface) + dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface) + } else { + dialer.DialHook = nil + dialer.ListenPacketHook = nil + } } func updateDNS(c *config.DNS) { if c.Enable == false { - dns.DefaultResolver = nil + resolver.DefaultResolver = nil + tunnel.SetResolver(nil) dns.ReCreateServer("", nil) return } @@ -127,8 +139,10 @@ func updateDNS(c *config.DNS) { GeoIP: c.FallbackFilter.GeoIP, IPCIDR: c.FallbackFilter.IPCIDR, }, + Default: c.DefaultNameserver, }) - dns.DefaultResolver = r + resolver.DefaultResolver = r + tunnel.SetResolver(r) if err := dns.ReCreateServer(c.Listen, r); err != nil { log.Errorln("Start DNS server error: %s", err.Error()) return @@ -140,11 +154,10 @@ func updateDNS(c *config.DNS) { } func updateHosts(tree *trie.Trie) { - dns.DefaultHosts = tree + resolver.DefaultHosts = tree } func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { - tunnel := T.Instance() oldProviders := tunnel.Providers() // close providers goroutine @@ -156,12 +169,12 @@ func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.Pro } func updateRules(rules []C.Rule) { - T.Instance().UpdateRules(rules) + tunnel.UpdateRules(rules) } func updateGeneral(general *config.General) { log.SetLevel(general.LogLevel) - T.Instance().SetMode(general.Mode) + tunnel.SetMode(general.Mode) allowLan := general.AllowLan P.SetAllowLan(allowLan) diff --git a/hub/route/configs.go b/hub/route/configs.go index ea720a7f79..dd97a840b4 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -8,7 +8,7 @@ import ( "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" P "github.com/Dreamacro/clash/proxy" - T "github.com/Dreamacro/clash/tunnel" + "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -23,13 +23,13 @@ func configRouter() http.Handler { } type configSchema struct { - Port *int `json:"port"` - SocksPort *int `json:"socks-port"` - RedirPort *int `json:"redir-port"` - AllowLan *bool `json:"allow-lan"` - BindAddress *string `json:"bind-address"` - Mode *T.Mode `json:"mode"` - LogLevel *log.LogLevel `json:"log-level"` + Port *int `json:"port"` + SocksPort *int `json:"socks-port"` + RedirPort *int `json:"redir-port"` + AllowLan *bool `json:"allow-lan"` + BindAddress *string `json:"bind-address"` + Mode *tunnel.TunnelMode `json:"mode"` + LogLevel *log.LogLevel `json:"log-level"` } func getConfigs(w http.ResponseWriter, r *http.Request) { @@ -67,7 +67,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) if general.Mode != nil { - T.Instance().SetMode(*general.Mode) + tunnel.SetMode(*general.Mode) } if general.LogLevel != nil { diff --git a/hub/route/provider.go b/hub/route/provider.go index f69bec46fb..eb7f832016 100644 --- a/hub/route/provider.go +++ b/hub/route/provider.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/Dreamacro/clash/adapters/provider" - T "github.com/Dreamacro/clash/tunnel" + "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -25,7 +25,7 @@ func proxyProviderRouter() http.Handler { } func getProviders(w http.ResponseWriter, r *http.Request) { - providers := T.Instance().Providers() + providers := tunnel.Providers() render.JSON(w, r, render.M{ "providers": providers, }) @@ -63,7 +63,7 @@ func parseProviderName(next http.Handler) http.Handler { func findProviderByName(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Context().Value(CtxKeyProviderName).(string) - providers := T.Instance().Providers() + providers := tunnel.Providers() provider, exist := providers[name] if !exist { render.Status(r, http.StatusNotFound) diff --git a/hub/route/proxies.go b/hub/route/proxies.go index ab769f9cc1..e3cb066cc6 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -10,7 +10,7 @@ import ( "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outboundgroup" C "github.com/Dreamacro/clash/constant" - T "github.com/Dreamacro/clash/tunnel" + "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -40,7 +40,7 @@ func parseProxyName(next http.Handler) http.Handler { func findProxyByName(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Context().Value(CtxKeyProxyName).(string) - proxies := T.Instance().Proxies() + proxies := tunnel.Proxies() proxy, exist := proxies[name] if !exist { render.Status(r, http.StatusNotFound) @@ -54,7 +54,7 @@ func findProxyByName(next http.Handler) http.Handler { } func getProxies(w http.ResponseWriter, r *http.Request) { - proxies := T.Instance().Proxies() + proxies := tunnel.Proxies() render.JSON(w, r, render.M{ "proxies": proxies, }) diff --git a/hub/route/rules.go b/hub/route/rules.go index 6a223ab5a4..d03ca63ff6 100644 --- a/hub/route/rules.go +++ b/hub/route/rules.go @@ -3,7 +3,7 @@ package route import ( "net/http" - T "github.com/Dreamacro/clash/tunnel" + "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi" "github.com/go-chi/render" @@ -22,7 +22,7 @@ type Rule struct { } func getRules(w http.ResponseWriter, r *http.Request) { - rawRules := T.Instance().Rules() + rawRules := tunnel.Rules() rules := []Rule{} for _, rule := range rawRules { diff --git a/proxy/http/server.go b/proxy/http/server.go index 51941ef6e0..de7e0fca52 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -16,10 +16,6 @@ import ( "github.com/Dreamacro/clash/tunnel" ) -var ( - tun = tunnel.Instance() -) - type HttpListener struct { net.Listener address string @@ -100,9 +96,9 @@ func handleConn(conn net.Conn, cache *cache.Cache) { if err != nil { return } - tun.Add(adapters.NewHTTPS(request, conn)) + tunnel.Add(adapters.NewHTTPS(request, conn)) return } - tun.Add(adapters.NewHTTP(request, conn)) + tunnel.Add(adapters.NewHTTP(request, conn)) } diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index fd4556037d..583521986d 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -9,10 +9,6 @@ import ( "github.com/Dreamacro/clash/tunnel" ) -var ( - tun = tunnel.Instance() -) - type RedirListener struct { net.Listener address string @@ -59,5 +55,5 @@ func handleRedir(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tun.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP)) + tunnel.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP)) } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 1d080dd6b7..eeff4bc454 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -13,10 +13,6 @@ import ( "github.com/Dreamacro/clash/tunnel" ) -var ( - tun = tunnel.Instance() -) - type SockListener struct { net.Listener address string @@ -68,5 +64,5 @@ func handleSocks(conn net.Conn) { io.Copy(ioutil.Discard, conn) return } - tun.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) + tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) } diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 2239af3c21..46e681ff64 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -7,6 +7,7 @@ import ( "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" ) type SockUDPListener struct { @@ -62,5 +63,5 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { payload: payload, bufRef: buf, } - tun.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) + tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) } diff --git a/tunnel/connection.go b/tunnel/connection.go index 6771aa4074..98f3bbce54 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -14,7 +14,7 @@ import ( "github.com/Dreamacro/clash/common/pool" ) -func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { +func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { req := request.R host := req.Host @@ -81,17 +81,17 @@ func (t *Tunnel) handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } } -func (t *Tunnel) handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr net.Addr) { +func handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr net.Addr) { if _, err := pc.WriteTo(packet.Data(), addr); err != nil { return } DefaultManager.Upload() <- int64(len(packet.Data())) } -func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) { +func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) { buf := pool.BufPool.Get().([]byte) defer pool.BufPool.Put(buf[:cap(buf)]) - defer t.natTable.Delete(key) + defer natTable.Delete(key) defer pc.Close() for { @@ -109,7 +109,7 @@ func (t *Tunnel) handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key str } } -func (t *Tunnel) handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { +func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { relay(request, outbound) } diff --git a/tunnel/mode.go b/tunnel/mode.go index 5f7d963909..b00b73ab60 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -5,11 +5,11 @@ import ( "errors" ) -type Mode int +type TunnelMode int var ( // ModeMapping is a mapping for Mode enum - ModeMapping = map[string]Mode{ + ModeMapping = map[string]TunnelMode{ Global.String(): Global, Rule.String(): Rule, Direct.String(): Direct, @@ -17,13 +17,13 @@ var ( ) const ( - Global Mode = iota + Global TunnelMode = iota Rule Direct ) // UnmarshalJSON unserialize Mode -func (m *Mode) UnmarshalJSON(data []byte) error { +func (m *TunnelMode) UnmarshalJSON(data []byte) error { var tp string json.Unmarshal(data, &tp) mode, exist := ModeMapping[tp] @@ -35,7 +35,7 @@ func (m *Mode) UnmarshalJSON(data []byte) error { } // UnmarshalYAML unserialize Mode with yaml -func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error { var tp string unmarshal(&tp) mode, exist := ModeMapping[tp] @@ -47,11 +47,11 @@ func (m *Mode) UnmarshalYAML(unmarshal func(interface{}) error) error { } // MarshalJSON serialize Mode -func (m Mode) MarshalJSON() ([]byte, error) { +func (m TunnelMode) MarshalJSON() ([]byte, error) { return json.Marshal(m.String()) } -func (m Mode) String() string { +func (m TunnelMode) String() string { switch m { case Global: return "Global" diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index f572399a78..0a37bb1feb 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -10,6 +10,7 @@ import ( "github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/nat" + "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" @@ -18,136 +19,136 @@ import ( ) var ( - tunnel *Tunnel - once sync.Once - - // default timeout for UDP session - udpTimeout = 60 * time.Second -) - -// Tunnel handle relay inbound proxy and outbound proxy -type Tunnel struct { - tcpQueue *channels.InfiniteChannel - udpQueue *channels.InfiniteChannel - natTable *nat.Table - rules []C.Rule - proxies map[string]C.Proxy - providers map[string]provider.ProxyProvider - configMux sync.RWMutex + tcpQueue = channels.NewInfiniteChannel() + udpQueue = channels.NewInfiniteChannel() + natTable = nat.New() + rules []C.Rule + proxies = make(map[string]C.Proxy) + providers map[string]provider.ProxyProvider + configMux sync.RWMutex + enhancedMode *dns.Resolver // experimental features ignoreResolveFail bool // Outbound Rule - mode Mode + mode = Rule + + // default timeout for UDP session + udpTimeout = 60 * time.Second +) + +func init() { + go process() } // Add request to queue -func (t *Tunnel) Add(req C.ServerAdapter) { - t.tcpQueue.In() <- req +func Add(req C.ServerAdapter) { + tcpQueue.In() <- req } // AddPacket add udp Packet to queue -func (t *Tunnel) AddPacket(packet *inbound.PacketAdapter) { - t.udpQueue.In() <- packet +func AddPacket(packet *inbound.PacketAdapter) { + udpQueue.In() <- packet } // Rules return all rules -func (t *Tunnel) Rules() []C.Rule { - return t.rules +func Rules() []C.Rule { + return rules } // UpdateRules handle update rules -func (t *Tunnel) UpdateRules(rules []C.Rule) { - t.configMux.Lock() - t.rules = rules - t.configMux.Unlock() +func UpdateRules(newRules []C.Rule) { + configMux.Lock() + rules = newRules + configMux.Unlock() } // Proxies return all proxies -func (t *Tunnel) Proxies() map[string]C.Proxy { - return t.proxies +func Proxies() map[string]C.Proxy { + return proxies } // Providers return all compatible providers -func (t *Tunnel) Providers() map[string]provider.ProxyProvider { - return t.providers +func Providers() map[string]provider.ProxyProvider { + return providers } // UpdateProxies handle update proxies -func (t *Tunnel) UpdateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { - t.configMux.Lock() - t.proxies = proxies - t.providers = providers - t.configMux.Unlock() +func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provider.ProxyProvider) { + configMux.Lock() + proxies = newProxies + providers = newProviders + configMux.Unlock() } // UpdateExperimental handle update experimental config -func (t *Tunnel) UpdateExperimental(ignoreResolveFail bool) { - t.configMux.Lock() - t.ignoreResolveFail = ignoreResolveFail - t.configMux.Unlock() +func UpdateExperimental(value bool) { + configMux.Lock() + ignoreResolveFail = value + configMux.Unlock() } // Mode return current mode -func (t *Tunnel) Mode() Mode { - return t.mode +func Mode() TunnelMode { + return mode } // SetMode change the mode of tunnel -func (t *Tunnel) SetMode(mode Mode) { - t.mode = mode +func SetMode(m TunnelMode) { + mode = m +} + +// SetResolver set custom dns resolver for enhanced mode +func SetResolver(r *dns.Resolver) { + enhancedMode = r } // processUDP starts a loop to handle udp packet -func (t *Tunnel) processUDP() { - queue := t.udpQueue.Out() +func processUDP() { + queue := udpQueue.Out() for elm := range queue { conn := elm.(*inbound.PacketAdapter) - t.handleUDPConn(conn) + handleUDPConn(conn) } } -func (t *Tunnel) process() { +func process() { numUDPWorkers := 4 if runtime.NumCPU() > numUDPWorkers { numUDPWorkers = runtime.NumCPU() } for i := 0; i < numUDPWorkers; i++ { - go t.processUDP() + go processUDP() } - queue := t.tcpQueue.Out() + queue := tcpQueue.Out() for elm := range queue { conn := elm.(C.ServerAdapter) - go t.handleTCPConn(conn) + go handleTCPConn(conn) } } -func (t *Tunnel) resolveIP(host string) (net.IP, error) { - return dns.ResolveIP(host) -} - -func (t *Tunnel) needLookupIP(metadata *C.Metadata) bool { - return dns.DefaultResolver != nil && (dns.DefaultResolver.IsMapping() || dns.DefaultResolver.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil +func needLookupIP(metadata *C.Metadata) bool { + return enhancedMode != nil && (enhancedMode.IsMapping() || enhancedMode.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil } -func (t *Tunnel) preHandleMetadata(metadata *C.Metadata) error { +func preHandleMetadata(metadata *C.Metadata) error { // handle IP string on host if ip := net.ParseIP(metadata.Host); ip != nil { metadata.DstIP = ip } // preprocess enhanced-mode metadata - if t.needLookupIP(metadata) { - host, exist := dns.DefaultResolver.IPToHost(metadata.DstIP) + if needLookupIP(metadata) { + host, exist := enhancedMode.IPToHost(metadata.DstIP) if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName - if dns.DefaultResolver.FakeIPEnabled() { + if enhancedMode.FakeIPEnabled() { metadata.DstIP = nil } - } else if dns.DefaultResolver.IsFakeIP(metadata.DstIP) { + } else if enhancedMode.IsFakeIP(metadata.DstIP) { return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) } } @@ -155,18 +156,18 @@ func (t *Tunnel) preHandleMetadata(metadata *C.Metadata) error { return nil } -func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { +func resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { var proxy C.Proxy var rule C.Rule - switch t.mode { + switch mode { case Direct: - proxy = t.proxies["DIRECT"] + proxy = proxies["DIRECT"] case Global: - proxy = t.proxies["GLOBAL"] + proxy = proxies["GLOBAL"] // Rule default: var err error - proxy, rule, err = t.match(metadata) + proxy, rule, err = match(metadata) if err != nil { return nil, nil, err } @@ -174,23 +175,23 @@ func (t *Tunnel) resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) return proxy, rule, nil } -func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { +func handleUDPConn(packet *inbound.PacketAdapter) { metadata := packet.Metadata() if !metadata.Valid() { log.Warnln("[Metadata] not valid: %#v", metadata) return } - if err := t.preHandleMetadata(metadata); err != nil { + if err := preHandleMetadata(metadata); err != nil { log.Debugln("[Metadata PreHandle] error: %s", err) return } key := packet.LocalAddr().String() - pc := t.natTable.Get(key) + pc := natTable.Get(key) if pc != nil { if !metadata.Resolved() { - ip, err := t.resolveIP(metadata.Host) + ip, err := resolver.ResolveIP(metadata.Host) if err != nil { log.Warnln("[UDP] Resolve %s failed: %s, %#v", metadata.Host, err.Error(), metadata) return @@ -198,20 +199,20 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { metadata.DstIP = ip } - t.handleUDPToRemote(packet, pc, metadata.UDPAddr()) + handleUDPToRemote(packet, pc, metadata.UDPAddr()) return } lockKey := key + "-lock" - wg, loaded := t.natTable.GetOrCreateLock(lockKey) + wg, loaded := natTable.GetOrCreateLock(lockKey) go func() { if !loaded { wg.Add(1) - proxy, rule, err := t.resolveMetadata(metadata) + proxy, rule, err := resolveMetadata(metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) - t.natTable.Delete(lockKey) + natTable.Delete(lockKey) wg.Done() return } @@ -219,7 +220,7 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { rawPc, err := proxy.DialUDP(metadata) if err != nil { log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error()) - t.natTable.Delete(lockKey) + natTable.Delete(lockKey) wg.Done() return } @@ -228,36 +229,36 @@ func (t *Tunnel) handleUDPConn(packet *inbound.PacketAdapter) { switch true { case rule != nil: log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) - case t.mode == Global: + case mode == Global: log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) - case t.mode == Direct: + case mode == Direct: log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) default: log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) } - t.natTable.Set(key, pc) - t.natTable.Delete(lockKey) + natTable.Set(key, pc) + natTable.Delete(lockKey) wg.Done() - go t.handleUDPToLocal(packet.UDPPacket, pc, key) + go handleUDPToLocal(packet.UDPPacket, pc, key) } wg.Wait() - pc := t.natTable.Get(key) + pc := natTable.Get(key) if pc != nil { if !metadata.Resolved() { - ip, err := dns.ResolveIP(metadata.Host) + ip, err := resolver.ResolveIP(metadata.Host) if err != nil { return } metadata.DstIP = ip } - t.handleUDPToRemote(packet, pc, metadata.UDPAddr()) + handleUDPToRemote(packet, pc, metadata.UDPAddr()) } }() } -func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { +func handleTCPConn(localConn C.ServerAdapter) { defer localConn.Close() metadata := localConn.Metadata() @@ -266,12 +267,12 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { return } - if err := t.preHandleMetadata(metadata); err != nil { + if err := preHandleMetadata(metadata); err != nil { log.Debugln("[Metadata PreHandle] error: %s", err) return } - proxy, rule, err := t.resolveMetadata(metadata) + proxy, rule, err := resolveMetadata(metadata) if err != nil { log.Warnln("Parse metadata failed: %v", err) return @@ -288,9 +289,9 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { switch true { case rule != nil: log.Infoln("[TCP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String()) - case t.mode == Global: + case mode == Global: log.Infoln("[TCP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) - case t.mode == Direct: + case mode == Direct: log.Infoln("[TCP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) default: log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) @@ -298,33 +299,33 @@ func (t *Tunnel) handleTCPConn(localConn C.ServerAdapter) { switch adapter := localConn.(type) { case *inbound.HTTPAdapter: - t.handleHTTP(adapter, remoteConn) + handleHTTP(adapter, remoteConn) case *inbound.SocketAdapter: - t.handleSocket(adapter, remoteConn) + handleSocket(adapter, remoteConn) } } -func (t *Tunnel) shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { +func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { return !rule.NoResolveIP() && metadata.Host != "" && metadata.DstIP == nil } -func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { - t.configMux.RLock() - defer t.configMux.RUnlock() +func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { + configMux.RLock() + defer configMux.RUnlock() var resolved bool - if node := dns.DefaultHosts.Search(metadata.Host); node != nil { + if node := resolver.DefaultHosts.Search(metadata.Host); node != nil { ip := node.Data.(net.IP) metadata.DstIP = ip resolved = true } - for _, rule := range t.rules { - if !resolved && t.shouldResolveIP(rule, metadata) { - ip, err := t.resolveIP(metadata.Host) + for _, rule := range rules { + if !resolved && shouldResolveIP(rule, metadata) { + ip, err := resolver.ResolveIP(metadata.Host) if err != nil { - if !t.ignoreResolveFail { + if !ignoreResolveFail { return nil, nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error()) } log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) @@ -336,7 +337,7 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { } if rule.Match(metadata) { - adapter, ok := t.proxies[rule.Adapter()] + adapter, ok := proxies[rule.Adapter()] if !ok { continue } @@ -348,24 +349,6 @@ func (t *Tunnel) match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { return adapter, rule, nil } } - return t.proxies["DIRECT"], nil, nil -} - -func newTunnel() *Tunnel { - return &Tunnel{ - tcpQueue: channels.NewInfiniteChannel(), - udpQueue: channels.NewInfiniteChannel(), - natTable: nat.New(), - proxies: make(map[string]C.Proxy), - mode: Rule, - } -} -// Instance return singleton instance of Tunnel -func Instance() *Tunnel { - once.Do(func() { - tunnel = newTunnel() - go tunnel.process() - }) - return tunnel + return proxies["DIRECT"], nil, nil } From e9032c55fa089423652a466c7297ccf132e9828d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 17 Feb 2020 12:25:55 +0800 Subject: [PATCH 331/535] Chore: update dependencies --- go.mod | 10 +++++----- go.sum | 29 ++++++++++++++--------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 93ad5d67fe..48d0450730 100644 --- a/go.mod +++ b/go.mod @@ -5,18 +5,18 @@ go 1.13 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.5 github.com/eapache/queue v1.1.0 // indirect - github.com/go-chi/chi v4.0.2+incompatible + github.com/go-chi/chi v4.0.3+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.1 - github.com/miekg/dns v1.1.26 + github.com/miekg/dns v1.1.27 github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 - golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 - golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 + golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 + golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/eapache/channels.v1 v1.1.0 - gopkg.in/yaml.v2 v2.2.7 + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index ccd3efd747..9835b57c2e 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ 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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= -github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= +github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= @@ -17,8 +17,8 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +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/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= @@ -35,17 +35,19 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -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/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg= +golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= @@ -55,21 +57,18 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -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/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 7b48138ad0afc11d1ebbaa86095127e8384d6d42 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 17 Feb 2020 17:34:19 +0800 Subject: [PATCH 332/535] Fix: vmess udp crash --- adapters/outbound/base.go | 11 ++++++++--- adapters/outbound/direct.go | 18 +++++++++++++++++- adapters/outbound/shadowsocks.go | 20 ++++++++++++++------ adapters/outbound/socks5.go | 18 +++++++++++++----- adapters/outbound/vmess.go | 23 +++++++++++++++++++---- component/nat/table.go | 9 +++++---- constant/adapters.go | 1 + tunnel/connection.go | 4 ++-- tunnel/tunnel.go | 20 ++------------------ 9 files changed, 81 insertions(+), 43 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index df1c61bc54..a58976f45e 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -65,8 +65,13 @@ func newConn(c net.Conn, a C.ProxyAdapter) C.Conn { return &conn{c, []string{a.Name()}} } -type packetConn struct { +type PacketConn interface { net.PacketConn + WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) +} + +type packetConn struct { + PacketConn chain C.Chain } @@ -78,8 +83,8 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) } -func newPacketConn(c net.PacketConn, a C.ProxyAdapter) C.PacketConn { - return &packetConn{c, []string{a.Name()}} +func newPacketConn(pc PacketConn, a C.ProxyAdapter) C.PacketConn { + return &packetConn{pc, []string{a.Name()}} } type Proxy struct { diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index c118425dbe..1d6d381e35 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -5,6 +5,7 @@ import ( "net" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" ) @@ -31,7 +32,22 @@ func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { if err != nil { return nil, err } - return newPacketConn(pc, d), nil + return newPacketConn(&directPacketConn{pc}, d), nil +} + +type directPacketConn struct { + net.PacketConn +} + +func (dp *directPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return 0, err + } + metadata.DstIP = ip + } + return dp.WriteTo(p, metadata.UDPAddr()) } func NewDirect() *Direct { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 245d00cef9..fbb6bc3cec 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -95,7 +95,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } pc = ss.cipher.PacketConn(pc) - return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: addr}, ss), nil + return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil } func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { @@ -183,21 +183,29 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { }, nil } -type ssUDPConn struct { +type ssPacketConn struct { net.PacketConn rAddr net.Addr } -func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { +func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { return } - return uc.PacketConn.WriteTo(packet[3:], uc.rAddr) + return spc.PacketConn.WriteTo(packet[3:], spc.rAddr) } -func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { - n, _, e := uc.PacketConn.ReadFrom(b) +func (spc *ssPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { + packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p) + if err != nil { + return + } + return spc.PacketConn.WriteTo(packet[3:], spc.rAddr) +} + +func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, _, e := spc.PacketConn.ReadFrom(b) addr := socks5.SplitAddr(b[:n]) var from net.Addr if e == nil { diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 2c47bb446a..41294f7cc9 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -110,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { pc.Close() }() - return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil + return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil } func NewSocks5(option Socks5Option) *Socks5 { @@ -138,13 +138,13 @@ func NewSocks5(option Socks5Option) *Socks5 { } } -type socksUDPConn struct { +type socksPacketConn struct { net.PacketConn rAddr net.Addr tcpConn net.Conn } -func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { +func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { return @@ -152,7 +152,15 @@ func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return uc.PacketConn.WriteTo(packet, uc.rAddr) } -func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { +func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { + packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p) + if err != nil { + return + } + return uc.PacketConn.WriteTo(packet, uc.rAddr) +} + +func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, a, e := uc.PacketConn.ReadFrom(b) if e != nil { return 0, nil, e @@ -167,7 +175,7 @@ func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { return n - addrLength - 3, a, nil } -func (uc *socksUDPConn) Close() error { +func (uc *socksPacketConn) Close() error { uc.tcpConn.Close() return uc.PacketConn.Close() } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 197ba9a584..73a8f4a9b3 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -2,12 +2,14 @@ package outbound import ( "context" + "errors" "fmt" "net" "strconv" "strings" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/vmess" C "github.com/Dreamacro/clash/constant" ) @@ -44,6 +46,15 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, } func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + // vmess use stream-oriented udp, so clash needs a net.UDPAddr + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return nil, errors.New("can't resolve ip") + } + metadata.DstIP = ip + } + ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) defer cancel() c, err := dialer.DialContext(ctx, "tcp", v.server) @@ -55,7 +66,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { if err != nil { return nil, fmt.Errorf("new vmess client error: %v", err) } - return newPacketConn(&vmessUDPConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil + return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil } func NewVmess(option VmessOption) (*Vmess, error) { @@ -116,16 +127,20 @@ func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr { } } -type vmessUDPConn struct { +type vmessPacketConn struct { net.Conn rAddr net.Addr } -func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { +func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { return uc.Conn.Write(b) } -func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { +func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { + return uc.Conn.Write(p) +} + +func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, err := uc.Conn.Read(b) return n, uc.rAddr, err } diff --git a/component/nat/table.go b/component/nat/table.go index a88a00f0c6..9a8696b737 100644 --- a/component/nat/table.go +++ b/component/nat/table.go @@ -1,24 +1,25 @@ package nat import ( - "net" "sync" + + C "github.com/Dreamacro/clash/constant" ) type Table struct { mapping sync.Map } -func (t *Table) Set(key string, pc net.PacketConn) { +func (t *Table) Set(key string, pc C.PacketConn) { t.mapping.Store(key, pc) } -func (t *Table) Get(key string) net.PacketConn { +func (t *Table) Get(key string) C.PacketConn { item, exist := t.mapping.Load(key) if !exist { return nil } - return item.(net.PacketConn) + return item.(C.PacketConn) } func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) { diff --git a/constant/adapters.go b/constant/adapters.go index f7614c3a62..31db23b59c 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -53,6 +53,7 @@ type Conn interface { type PacketConn interface { net.PacketConn Connection + WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error) } type ProxyAdapter interface { diff --git a/tunnel/connection.go b/tunnel/connection.go index 98f3bbce54..b1d9978c88 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -81,8 +81,8 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } } -func handleUDPToRemote(packet C.UDPPacket, pc net.PacketConn, addr net.Addr) { - if _, err := pc.WriteTo(packet.Data(), addr); err != nil { +func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) { + if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil { return } DefaultManager.Upload() <- int64(len(packet.Data())) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 0a37bb1feb..b35688aa79 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -190,16 +190,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { key := packet.LocalAddr().String() pc := natTable.Get(key) if pc != nil { - if !metadata.Resolved() { - ip, err := resolver.ResolveIP(metadata.Host) - if err != nil { - log.Warnln("[UDP] Resolve %s failed: %s, %#v", metadata.Host, err.Error(), metadata) - return - } - metadata.DstIP = ip - } - - handleUDPToRemote(packet, pc, metadata.UDPAddr()) + handleUDPToRemote(packet, pc, metadata) return } @@ -246,14 +237,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { wg.Wait() pc := natTable.Get(key) if pc != nil { - if !metadata.Resolved() { - ip, err := resolver.ResolveIP(metadata.Host) - if err != nil { - return - } - metadata.DstIP = ip - } - handleUDPToRemote(packet, pc, metadata.UDPAddr()) + handleUDPToRemote(packet, pc, metadata) } }() } From df0ab6aa8ede696d9bd2bae17f4e944b70db528a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 17 Feb 2020 20:11:46 +0800 Subject: [PATCH 333/535] Fix: ipv6 dns crash --- dns/client.go | 2 +- dns/resolver.go | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/dns/client.go b/dns/client.go index 1f1e6c6f8d..6341b6577b 100644 --- a/dns/client.go +++ b/dns/client.go @@ -26,7 +26,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err network = "tcp" } - ip, err := c.r.ResolveIPv4(c.host) + ip, err := c.r.ResolveIP(c.host) if err != nil { return nil, err } diff --git a/dns/resolver.go b/dns/resolver.go index 480818c910..7d5fbc9c27 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -44,23 +44,20 @@ type Resolver struct { cache *cache.Cache } -// ResolveIP request with TypeA and TypeAAAA, priority return TypeAAAA +// ResolveIP request with TypeA and TypeAAAA, priority return TypeA func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) { - ch := make(chan net.IP) + ch := make(chan net.IP, 1) go func() { defer close(ch) - ip, err := r.resolveIP(host, D.TypeA) + ip, err := r.resolveIP(host, D.TypeAAAA) if err != nil { return } ch <- ip }() - ip, err = r.resolveIP(host, D.TypeAAAA) + ip, err = r.resolveIP(host, D.TypeA) if err == nil { - go func() { - <-ch - }() return } @@ -216,6 +213,8 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) return ip, nil } else if dnsType == D.TypeA && isIPv4 { return ip, nil + } else { + return nil, resolver.ErrIPVersion } } From 46edae98960f31faf52f50142b7612b14e66336e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 17 Feb 2020 22:13:15 +0800 Subject: [PATCH 334/535] Fix: domain dns crash --- config/config.go | 19 +++++++++---------- dns/client.go | 28 ++++++++++++++++++---------- dns/resolver.go | 1 - dns/util.go | 7 +++++-- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/config/config.go b/config/config.go index e43006a759..d0260b868a 100644 --- a/config/config.go +++ b/config/config.go @@ -441,21 +441,21 @@ func parseHosts(cfg *RawConfig) (*trie.Trie, error) { return tree, nil } -func hostWithDefaultPort(host string, defPort string) (string, string, error) { +func hostWithDefaultPort(host string, defPort string) (string, error) { if !strings.Contains(host, ":") { host += ":" } hostname, port, err := net.SplitHostPort(host) if err != nil { - return "", "", err + return "", err } if port == "" { port = defPort } - return net.JoinHostPort(hostname, port), hostname, nil + return net.JoinHostPort(hostname, port), nil } func parseNameServer(servers []string) ([]dns.NameServer, error) { @@ -471,21 +471,20 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } - var addr, dnsNetType, host string + var addr, dnsNetType string switch u.Scheme { case "udp": - addr, host, err = hostWithDefaultPort(u.Host, "53") + addr, err = hostWithDefaultPort(u.Host, "53") dnsNetType = "" // UDP case "tcp": - addr, host, err = hostWithDefaultPort(u.Host, "53") + addr, err = hostWithDefaultPort(u.Host, "53") dnsNetType = "tcp" // TCP case "tls": - addr, host, err = hostWithDefaultPort(u.Host, "853") + addr, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "tcp-tls" // DNS over TLS case "https": clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} addr = clearURL.String() - _, host, err = hostWithDefaultPort(u.Host, "853") dnsNetType = "https" // DNS over HTTPS default: return nil, fmt.Errorf("DNS NameServer[%d] unsupport scheme: %s", idx, u.Scheme) @@ -500,7 +499,6 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { dns.NameServer{ Net: dnsNetType, Addr: addr, - Host: host, }, ) } @@ -552,7 +550,8 @@ func parseDNS(cfg RawDNS) (*DNS, error) { } // check default nameserver is pure ip addr for _, ns := range dnsCfg.DefaultNameserver { - if net.ParseIP(ns.Host) == nil { + host, _, err := net.SplitHostPort(ns.Addr) + if err != nil || net.ParseIP(host) == nil { return nil, errors.New("default nameserver should be pure IP") } } diff --git a/dns/client.go b/dns/client.go index 6341b6577b..3546adbfdc 100644 --- a/dns/client.go +++ b/dns/client.go @@ -2,6 +2,8 @@ package dns import ( "context" + "fmt" + "net" "strings" "github.com/Dreamacro/clash/component/dialer" @@ -12,7 +14,7 @@ import ( type client struct { *D.Client r *Resolver - addr string + port string host string } @@ -21,18 +23,24 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) { } func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { - network := "udp" - if strings.HasPrefix(c.Client.Net, "tcp") { - network = "tcp" - } - - ip, err := c.r.ResolveIP(c.host) - if err != nil { - return nil, err + var ip net.IP + if c.r == nil { + // a default ip dns + ip = net.ParseIP(c.host) + } else { + var err error + if ip, err = c.r.ResolveIP(c.host); err != nil { + println("?") + return nil, fmt.Errorf("use default dns resolve failed: %w", err) + } } d := dialer.Dialer() if dialer.DialHook != nil { + network := "udp" + if strings.HasPrefix(c.Client.Net, "tcp") { + network = "tcp" + } dialer.DialHook(d, network, ip) } @@ -46,7 +54,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err } ch := make(chan result, 1) go func() { - msg, _, err := c.Client.Exchange(m, c.addr) + msg, _, err := c.Client.Exchange(m, net.JoinHostPort(ip.String(), c.port)) ch <- result{msg, err} }() diff --git a/dns/resolver.go b/dns/resolver.go index 7d5fbc9c27..fefdafdc6d 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -263,7 +263,6 @@ func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result type NameServer struct { Net string Addr string - Host string } type FallbackFilter struct { diff --git a/dns/util.go b/dns/util.go index 2cf0cd9b6a..00ac4566e9 100644 --- a/dns/util.go +++ b/dns/util.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "encoding/json" "errors" + "net" "time" "github.com/Dreamacro/clash/common/cache" @@ -125,6 +126,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { continue } + host, port, _ := net.SplitHostPort(s.Addr) ret = append(ret, &client{ Client: &D.Client{ Net: s.Net, @@ -136,8 +138,9 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { UDPSize: 4096, Timeout: 5 * time.Second, }, - addr: s.Addr, - host: s.Host, + port: port, + host: host, + r: resolver, }) } return ret From 8d07c1eb3ecd9b125b222fedbe644a77c53f43e5 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 18 Feb 2020 13:48:15 +0800 Subject: [PATCH 335/535] Chore: initial config with `port` --- config/initial.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/initial.go b/config/initial.go index e151e74c89..09b2e4f370 100644 --- a/config/initial.go +++ b/config/initial.go @@ -38,15 +38,19 @@ func Init(dir string) error { // initial config.yaml if _, err := os.Stat(C.Path.Config()); os.IsNotExist(err) { - log.Infoln("Can't find config, create an empty file") - os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) + log.Infoln("Can't find config, create a initial config file") + f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error()) + } + f.Write([]byte(`port: 7890`)) + f.Close() } // initial mmdb if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { log.Infoln("Can't find MMDB, start download") - err := downloadMMDB(C.Path.MMDB()) - if err != nil { + if err := downloadMMDB(C.Path.MMDB()); err != nil { return fmt.Errorf("Can't download MMDB: %s", err.Error()) } } From f3f8e7e52fb6ebffe57dc12bc17d58522c6c0261 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 18 Feb 2020 14:26:42 +0800 Subject: [PATCH 336/535] Chore: remove println --- dns/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/dns/client.go b/dns/client.go index 3546adbfdc..f12b0e013c 100644 --- a/dns/client.go +++ b/dns/client.go @@ -30,7 +30,6 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err } else { var err error if ip, err = c.r.ResolveIP(c.host); err != nil { - println("?") return nil, fmt.Errorf("use default dns resolve failed: %w", err) } } From 0f4cdbf18778e0d93c648dd192dc1198b874efcc Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 18 Feb 2020 16:05:12 +0800 Subject: [PATCH 337/535] Chore: remove unused code --- adapters/outbound/shadowsocks.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index fbb6bc3cec..16c2dc7e57 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -206,12 +206,10 @@ func (spc *ssPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n in func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, _, e := spc.PacketConn.ReadFrom(b) - addr := socks5.SplitAddr(b[:n]) - var from net.Addr - if e == nil { - // Get the source IP/Port of packet. - from = addr.UDPAddr() + if e != nil { + return 0, nil, e } + addr := socks5.SplitAddr(b[:n]) copy(b, b[len(addr):]) - return n - len(addr), from, e + return n - len(addr), addr.UDPAddr(), e } From d68339cea7c986c32b5cb31e32b1b5b02707fbe0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 20 Feb 2020 11:29:16 +0800 Subject: [PATCH 338/535] Fix: socks5 inbound return remote udp addr for identity --- proxy/socks/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index af3d8a6b84..b4ef429b5e 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -29,7 +29,7 @@ func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { // LocalAddr returns the source IP/Port of UDP Packet func (c *fakeConn) LocalAddr() net.Addr { - return c.PacketConn.LocalAddr() + return c.rAddr } func (c *fakeConn) Close() error { From 609869bf5a1d779b0ac6a0caca9ff9ea67b0c40f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 21 Feb 2020 18:00:19 +0800 Subject: [PATCH 339/535] Change: make ping under authentication --- hub/route/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/route/server.go b/hub/route/server.go index ab4e61c4f9..7c892657ef 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -57,10 +57,10 @@ func Start(addr string, secret string) { }) r.Use(cors.Handler) - r.Get("/", hello) r.Group(func(r chi.Router) { r.Use(authentication) + r.Get("/", hello) r.Get("/logs", getLogs) r.Get("/traffic", traffic) r.Get("/version", version) From 9eaca6e4ab4ccf584d6ced0da56dc5a9c82a2b52 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 22 Feb 2020 15:17:43 +0800 Subject: [PATCH 340/535] Fix: provider fallback should reparse proxies --- adapters/provider/provider.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index fef148a42d..8ec4a1089b 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -114,10 +114,12 @@ func (pp *ProxySetProvider) Destroy() error { func (pp *ProxySetProvider) Initial() error { var buf []byte var err error + var isLocal bool if stat, err := os.Stat(pp.vehicle.Path()); err == nil { buf, err = ioutil.ReadFile(pp.vehicle.Path()) modTime := stat.ModTime() pp.updatedAt = &modTime + isLocal = true } else { buf, err = pp.vehicle.Read() } @@ -128,11 +130,20 @@ func (pp *ProxySetProvider) Initial() error { proxies, err := pp.parse(buf) if err != nil { + if !isLocal { + return err + } + // parse local file error, fallback to remote buf, err = pp.vehicle.Read() if err != nil { return err } + + proxies, err = pp.parse(buf) + if err != nil { + return err + } } if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil { From 0740d20ba03593c9ec7e9a5154b2daaef6b0d2ab Mon Sep 17 00:00:00 2001 From: Mac_Zhou Date: Tue, 25 Feb 2020 16:08:13 +0800 Subject: [PATCH 341/535] Chore: disable url-test http redirect (#536) --- adapters/outbound/base.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index a58976f45e..8ef506703e 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -194,7 +194,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { ExpectContinueTimeout: 1 * time.Second, } - client := http.Client{Transport: transport} + client := http.Client{ + Transport: transport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } resp, err := client.Do(req) if err != nil { return From c4994d642963c825b1070dc19d63511bbe536fc8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 25 Feb 2020 21:51:48 +0800 Subject: [PATCH 342/535] Fix: dns not cache RcodeServerFailure --- dns/resolver.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dns/resolver.go b/dns/resolver.go index fefdafdc6d..b6dfc733de 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -168,7 +168,13 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err for _, client := range clients { r := client fast.Go(func() (interface{}, error) { - return r.ExchangeContext(ctx, m) + m, err := r.ExchangeContext(ctx, m) + if err != nil { + return nil, err + } else if m.Rcode == D.RcodeServerFailure { + return nil, errors.New("server failure") + } + return m, nil }) } From e81b88fb94b7de7d90aeb9f356ce1af8e02193c7 Mon Sep 17 00:00:00 2001 From: Wu Haotian Date: Sat, 29 Feb 2020 17:48:26 +0800 Subject: [PATCH 343/535] Feature: add configuration test command (#524) --- main.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/main.go b/main.go index 9e545e02eb..ec705b6ef7 100644 --- a/main.go +++ b/main.go @@ -10,13 +10,16 @@ import ( "syscall" "github.com/Dreamacro/clash/config" + "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub" + "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" ) var ( version bool + testConfig bool homeDir string configFile string ) @@ -25,6 +28,7 @@ func init() { flag.StringVar(&homeDir, "d", "", "set configuration directory") flag.StringVar(&configFile, "f", "", "specify configuration file") flag.BoolVar(&version, "v", false, "show current version of clash") + flag.BoolVar(&testConfig, "t", false, "test configuration and exit") flag.Parse() } @@ -57,6 +61,16 @@ func main() { log.Fatalln("Initial configuration directory error: %s", err.Error()) } + if testConfig { + if _, err := executor.Parse(); err != nil { + log.Errorln(err.Error()) + fmt.Printf("configuration file %s test failed\n", constant.Path.Config()) + os.Exit(1) + } + fmt.Printf("configuration file %s test is successful\n", constant.Path.Config()) + return + } + if err := hub.Parse(); err != nil { log.Fatalln("Parse config error: %s", err.Error()) } From 814bd053150a703d9bd015e1c3808839848641e3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 1 Mar 2020 01:46:02 +0800 Subject: [PATCH 344/535] Fix: ss udp return error when addr parse failed --- adapters/outbound/shadowsocks.go | 6 ++++++ adapters/outbound/socks5.go | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 16c2dc7e57..5e748f1cce 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "encoding/json" + "errors" "fmt" "net" "strconv" @@ -209,7 +210,12 @@ func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { if e != nil { return 0, nil, e } + addr := socks5.SplitAddr(b[:n]) + if addr == nil { + return 0, nil, errors.New("parse addr error") + } + copy(b, b[len(addr):]) return n - len(addr), addr.UDPAddr(), e } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 41294f7cc9..ed976a6a0b 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -161,7 +161,7 @@ func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n } func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { - n, a, e := uc.PacketConn.ReadFrom(b) + n, _, e := uc.PacketConn.ReadFrom(b) if e != nil { return 0, nil, e } @@ -170,9 +170,8 @@ func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, err } // due to DecodeUDPPacket is mutable, record addr length - addrLength := len(addr) copy(b, payload) - return n - addrLength - 3, a, nil + return n - len(addr) - 3, addr.UDPAddr(), nil } func (uc *socksPacketConn) Close() error { From 23525ecc1521555190505640915ddf123b883d75 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 1 Mar 2020 01:48:08 +0800 Subject: [PATCH 345/535] Migration: go 1.14 --- .github/workflows/go.yml | 2 +- go.mod | 8 ++++---- go.sum | 10 ++++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 7eaaca8d16..1fd7f402c3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,7 +9,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v1 with: - go-version: 1.13.x + go-version: 1.14.x - name: Check out code into the Go module directory uses: actions/checkout@v1 diff --git a/go.mod b/go.mod index 48d0450730..a7b59f5946 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Dreamacro/clash -go 1.13 +go 1.14 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.5 @@ -13,9 +13,9 @@ require ( github.com/miekg/dns v1.1.27 github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.4.2 - github.com/stretchr/testify v1.4.0 - golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 - golang.org/x/net v0.0.0-20200202094626-16171245cfb2 + github.com/stretchr/testify v1.5.1 + golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum index 9835b57c2e..d07822ed9b 100644 --- a/go.sum +++ b/go.sum @@ -34,20 +34,22 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg= -golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= From e57a13ed7aed3931bea6473d813010fe2b5df128 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 2 Mar 2020 23:47:23 +0800 Subject: [PATCH 346/535] Fix: mutable SplitAddr cause panic --- adapters/outbound/shadowsocks.go | 7 ++++++- adapters/outbound/socks5.go | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 5e748f1cce..5ad18df99d 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -216,6 +216,11 @@ func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, errors.New("parse addr error") } + udpAddr := addr.UDPAddr() + if udpAddr == nil { + return 0, nil, errors.New("parse addr error") + } + copy(b, b[len(addr):]) - return n - len(addr), addr.UDPAddr(), e + return n - len(addr), udpAddr, e } diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index ed976a6a0b..c8bd1bf585 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -3,6 +3,7 @@ package outbound import ( "context" "crypto/tls" + "errors" "fmt" "io" "io/ioutil" @@ -169,9 +170,15 @@ func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { if err != nil { return 0, nil, err } + + udpAddr := addr.UDPAddr() + if udpAddr == nil { + return 0, nil, errors.New("parse udp addr error") + } + // due to DecodeUDPPacket is mutable, record addr length copy(b, payload) - return n - len(addr) - 3, addr.UDPAddr(), nil + return n - len(addr) - 3, udpAddr, nil } func (uc *socksPacketConn) Close() error { From 88d8f937938d7aba774b1af638df6910b1a1a14d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 7 Mar 2020 20:01:24 +0800 Subject: [PATCH 347/535] Change: rename some field --- README.md | 32 +++++++++++++++++++++++++++++--- config/config.go | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3be09c9d70..2f7aba250d 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ experimental: # ipcidr: # ips in these subnets will be considered polluted # - 240.0.0.0/4 -Proxy: +proxies: # shadowsocks # The supported ciphers(encrypt methods): # aes-128-gcm aes-192-gcm aes-256-gcm @@ -243,7 +243,7 @@ Proxy: # mode: http # or tls # host: bing.com -Proxy Group: +proxy-groups: # url-test select which proxy will be used by benchmarking speed to a URL. - name: "auto" type: url-test @@ -283,8 +283,34 @@ Proxy Group: - ss2 - vmess1 - auto + + - name: UseProvider + type: select + use: + - provider1 + proxies: + - Proxy + - DIRECT -Rule: +proxy-providers: + provider1: + type: http + url: "url" + interval: 3600 + path: ./hk.yaml + health-check: + enable: true + interval: 600 + url: http://www.gstatic.com/generate_204 + test: + type: file + path: /test.yaml + health-check: + enable: true + interval: 36000 + url: http://www.gstatic.com/generate_204 + +rules: - DOMAIN-SUFFIX,google.com,auto - DOMAIN-KEYWORD,google,auto - DOMAIN,google.com,auto diff --git a/config/config.go b/config/config.go index d0260b868a..0a74d9bc58 100644 --- a/config/config.go +++ b/config/config.go @@ -106,13 +106,19 @@ type RawConfig struct { ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` - ProxyProvider map[string]map[string]interface{} `yaml:"proxy-provider"` + ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"` Hosts map[string]string `yaml:"hosts"` DNS RawDNS `yaml:"dns"` Experimental Experimental `yaml:"experimental"` - Proxy []map[string]interface{} `yaml:"Proxy"` - ProxyGroup []map[string]interface{} `yaml:"Proxy Group"` - Rule []string `yaml:"Rule"` + Proxy []map[string]interface{} `yaml:"proxies"` + ProxyGroup []map[string]interface{} `yaml:"proxy-groups"` + Rule []string `yaml:"rules"` + + // remove after 1.0 + ProxyProviderOld map[string]map[string]interface{} `yaml:"proxy-provider"` + ProxyOld []map[string]interface{} `yaml:"Proxy"` + ProxyGroupOld []map[string]interface{} `yaml:"Proxy Group"` + RuleOld []string `yaml:"Rule"` } // Parse config @@ -152,6 +158,11 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { "8.8.8.8", }, }, + + // remove after 1.0 + RuleOld: []string{}, + ProxyOld: []map[string]interface{}{}, + ProxyGroupOld: []map[string]interface{}{}, } if err := yaml.Unmarshal(buf, &rawCfg); err != nil { @@ -245,6 +256,18 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ groupsConfig := cfg.ProxyGroup providersConfig := cfg.ProxyProvider + if len(proxiesConfig) == 0 { + proxiesConfig = cfg.ProxyOld + } + + if len(groupsConfig) == 0 { + groupsConfig = cfg.ProxyGroupOld + } + + if len(providersConfig) == 0 { + providersConfig = cfg.ProxyProvider + } + defer func() { // Destroy already created provider when err != nil if err != nil { @@ -351,6 +374,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { rules := []C.Rule{} rulesConfig := cfg.Rule + + // remove after 1.0 + if len(rules) == 0 { + rulesConfig = cfg.RuleOld + } + // parse rules for idx, line := range rulesConfig { rule := trimArr(strings.Split(line, ",")) From c733f807939bd2cc2a5186836c93cd06a4dac220 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 8 Mar 2020 13:00:42 +0800 Subject: [PATCH 348/535] Fix: #563 and fallback error return --- dns/resolver.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dns/resolver.go b/dns/resolver.go index b6dfc733de..986aec79b5 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -118,8 +118,7 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { ret, err, _ := r.group.Do(q.String(), func() (interface{}, error) { isIPReq := isIPRequest(q) if isIPReq { - msg, err := r.fallbackExchange(m) - return msg, err + return r.fallbackExchange(m) } return r.batchExchange(r.main, m) @@ -171,7 +170,7 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err m, err := r.ExchangeContext(ctx, m) if err != nil { return nil, err - } else if m.Rcode == D.RcodeServerFailure { + } else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused { return nil, errors.New("server failure") } return m, nil @@ -201,6 +200,7 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { if r.shouldFallback(ips[0]) { go func() { <-fallbackMsg }() msg = res.Msg + err = res.Error return msg, err } } From b2c9cbb43ebdc74191f9109e644ce24ddc844d3c Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 8 Mar 2020 13:01:06 +0800 Subject: [PATCH 349/535] Chore: update dependencies --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a7b59f5946..ebe586cdf2 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,8 @@ require ( github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.5.1 - golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b + golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 + golang.org/x/net v0.0.0-20200301022130-244492dfa37a golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum index d07822ed9b..e6679861ee 100644 --- a/go.sum +++ b/go.sum @@ -40,16 +40,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= -golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= From f7f30d34062f31aff622516dac11b656c07e8194 Mon Sep 17 00:00:00 2001 From: duama <30264485+duament@users.noreply.github.com> Date: Sun, 8 Mar 2020 21:58:49 +0800 Subject: [PATCH 350/535] Feature: add UDP TPROXY support on Linux (#562) --- proxy/listener.go | 15 ++++++ proxy/redir/udp.go | 78 +++++++++++++++++++++++++++++++ proxy/redir/udp_linux.go | 69 +++++++++++++++++++++++++++ proxy/redir/udp_other.go | 16 +++++++ proxy/redir/utils.go | 41 ++++++++++++++++ proxy/redir/utils_linux.go | 96 ++++++++++++++++++++++++++++++++++++++ proxy/redir/utils_other.go | 12 +++++ 7 files changed, 327 insertions(+) create mode 100644 proxy/redir/udp.go create mode 100644 proxy/redir/udp_linux.go create mode 100644 proxy/redir/udp_other.go create mode 100644 proxy/redir/utils.go create mode 100644 proxy/redir/utils_linux.go create mode 100644 proxy/redir/utils_other.go diff --git a/proxy/listener.go b/proxy/listener.go index 79741581a4..1089e39dbb 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -5,6 +5,7 @@ import ( "net" "strconv" + "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/socks" @@ -18,6 +19,7 @@ var ( socksUDPListener *socks.SockUDPListener httpListener *http.HttpListener redirListener *redir.RedirListener + redirUDPListener *redir.RedirUDPListener ) type listener interface { @@ -131,6 +133,14 @@ func ReCreateRedir(port int) error { redirListener = nil } + if redirUDPListener != nil { + if redirUDPListener.Address() == addr { + return nil + } + redirUDPListener.Close() + redirUDPListener = nil + } + if portIsZero(addr) { return nil } @@ -141,6 +151,11 @@ func ReCreateRedir(port int) error { return err } + redirUDPListener, err = redir.NewRedirUDPProxy(addr) + if err != nil { + log.Warnln("Failed to start Redir UDP Listener: %s", err) + } + return nil } diff --git a/proxy/redir/udp.go b/proxy/redir/udp.go new file mode 100644 index 0000000000..83506d8698 --- /dev/null +++ b/proxy/redir/udp.go @@ -0,0 +1,78 @@ +package redir + +import ( + "net" + + adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/socks5" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/tunnel" +) + +type RedirUDPListener struct { + net.PacketConn + address string + closed bool +} + +func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { + l, err := net.ListenPacket("udp", addr) + if err != nil { + return nil, err + } + + rl := &RedirUDPListener{l, addr, false} + + c := l.(*net.UDPConn) + + err = setsockopt(c, addr) + if err != nil { + return nil, err + } + + go func() { + oob := make([]byte, 1024) + for { + buf := pool.BufPool.Get().([]byte) + + n, oobn, _, remoteAddr, err := c.ReadMsgUDP(buf, oob) + if err != nil { + pool.BufPool.Put(buf[:cap(buf)]) + if rl.closed { + break + } + continue + } + + origDst, err := getOrigDst(oob, oobn) + if err != nil { + continue + } + handleRedirUDP(l, buf[:n], remoteAddr, origDst) + } + }() + + return rl, nil +} + +func (l *RedirUDPListener) Close() error { + l.closed = true + return l.PacketConn.Close() +} + +func (l *RedirUDPListener) Address() string { + return l.address +} + +func handleRedirUDP(pc net.PacketConn, buf []byte, addr *net.UDPAddr, origDst *net.UDPAddr) { + target := socks5.ParseAddrToSocksAddr(origDst) + + packet := &fakeConn{ + PacketConn: pc, + origDst: origDst, + rAddr: addr, + buf: buf, + } + tunnel.AddPacket(adapters.NewPacket(target, packet, C.REDIR)) +} diff --git a/proxy/redir/udp_linux.go b/proxy/redir/udp_linux.go new file mode 100644 index 0000000000..83a82a7237 --- /dev/null +++ b/proxy/redir/udp_linux.go @@ -0,0 +1,69 @@ +// +build linux + +package redir + +import ( + "encoding/binary" + "errors" + "net" + "syscall" +) + +const ( + IPV6_TRANSPARENT = 0x4b + IPV6_RECVORIGDSTADDR = 0x4a +) + +func setsockopt(c *net.UDPConn, addr string) error { + isIPv6 := true + host, _, err := net.SplitHostPort(addr) + if err != nil { + return err + } + ip := net.ParseIP(host) + if ip != nil && ip.To4() != nil { + isIPv6 = false + } + + rc, err := c.SyscallConn() + if err != nil { + return err + } + + rc.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) + if err == nil && isIPv6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) + } + + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) + } + if err == nil && isIPv6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) + } + }) + + return err +} + +func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { + msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) + if err != nil { + return nil, err + } + + for _, msg := range msgs { + if msg.Header.Level == syscall.SOL_IP && msg.Header.Type == syscall.IP_RECVORIGDSTADDR { + ip := net.IP(msg.Data[4:8]) + port := binary.BigEndian.Uint16(msg.Data[2:4]) + return &net.UDPAddr{IP: ip, Port: int(port)}, nil + } else if msg.Header.Level == syscall.SOL_IPV6 && msg.Header.Type == IPV6_RECVORIGDSTADDR { + ip := net.IP(msg.Data[8:24]) + port := binary.BigEndian.Uint16(msg.Data[2:4]) + return &net.UDPAddr{IP: ip, Port: int(port)}, nil + } + } + + return nil, errors.New("cannot find origDst") +} diff --git a/proxy/redir/udp_other.go b/proxy/redir/udp_other.go new file mode 100644 index 0000000000..d9afdb6829 --- /dev/null +++ b/proxy/redir/udp_other.go @@ -0,0 +1,16 @@ +// +build !linux + +package redir + +import ( + "errors" + "net" +) + +func setsockopt(c *net.UDPConn, addr string) error { + return errors.New("UDP redir not supported on current platform") +} + +func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { + return nil, errors.New("UDP redir not supported on current platform") +} diff --git a/proxy/redir/utils.go b/proxy/redir/utils.go new file mode 100644 index 0000000000..71756d4d77 --- /dev/null +++ b/proxy/redir/utils.go @@ -0,0 +1,41 @@ +package redir + +import ( + "net" + + "github.com/Dreamacro/clash/common/pool" +) + +type fakeConn struct { + net.PacketConn + origDst net.Addr + rAddr net.Addr + buf []byte +} + +func (c *fakeConn) Data() []byte { + return c.buf +} + +// WriteBack opens a new socket binding `origDst` to wirte UDP packet back +func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { + tc, err := dialUDP("udp", c.origDst.(*net.UDPAddr), c.rAddr.(*net.UDPAddr)) + if err != nil { + n = 0 + return + } + n, err = tc.Write(b) + tc.Close() + return +} + +// LocalAddr returns the source IP/Port of UDP Packet +func (c *fakeConn) LocalAddr() net.Addr { + return c.rAddr +} + +func (c *fakeConn) Close() error { + err := c.PacketConn.Close() + pool.BufPool.Put(c.buf[:cap(c.buf)]) + return err +} diff --git a/proxy/redir/utils_linux.go b/proxy/redir/utils_linux.go new file mode 100644 index 0000000000..888601a482 --- /dev/null +++ b/proxy/redir/utils_linux.go @@ -0,0 +1,96 @@ +// +build linux + +package redir + +import ( + "fmt" + "net" + "os" + "strconv" + "syscall" +) + +// dialUDP acts like net.DialUDP for transparent proxy. +// It binds to a non-local address(`lAddr`). +func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { + rSockAddr, err := udpAddrToSockAddr(rAddr) + if err != nil { + return nil, err + } + + lSockAddr, err := udpAddrToSockAddr(lAddr) + if err != nil { + return nil, err + } + + fd, err := syscall.Socket(udpAddrFamily(network, lAddr, rAddr), syscall.SOCK_DGRAM, 0) + if err != nil { + return nil, err + } + + if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { + syscall.Close(fd) + return nil, err + } + + if err = syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil { + syscall.Close(fd) + return nil, err + } + + if err = syscall.Bind(fd, lSockAddr); err != nil { + syscall.Close(fd) + return nil, err + } + + if err = syscall.Connect(fd, rSockAddr); err != nil { + syscall.Close(fd) + return nil, err + } + + fdFile := os.NewFile(uintptr(fd), fmt.Sprintf("net-udp-dial-%s", rAddr.String())) + defer fdFile.Close() + + c, err := net.FileConn(fdFile) + if err != nil { + syscall.Close(fd) + return nil, err + } + + return c.(*net.UDPConn), nil +} + +func udpAddrToSockAddr(addr *net.UDPAddr) (syscall.Sockaddr, error) { + switch { + case addr.IP.To4() != nil: + ip := [4]byte{} + copy(ip[:], addr.IP.To4()) + + return &syscall.SockaddrInet4{Addr: ip, Port: addr.Port}, nil + + default: + ip := [16]byte{} + copy(ip[:], addr.IP.To16()) + + zoneID, err := strconv.ParseUint(addr.Zone, 10, 32) + if err != nil { + zoneID = 0 + } + + return &syscall.SockaddrInet6{Addr: ip, Port: addr.Port, ZoneId: uint32(zoneID)}, nil + } +} + +func udpAddrFamily(net string, lAddr, rAddr *net.UDPAddr) int { + switch net[len(net)-1] { + case '4': + return syscall.AF_INET + case '6': + return syscall.AF_INET6 + } + + if (lAddr == nil || lAddr.IP.To4() != nil) && (rAddr == nil || lAddr.IP.To4() != nil) { + return syscall.AF_INET + } + return syscall.AF_INET6 +} diff --git a/proxy/redir/utils_other.go b/proxy/redir/utils_other.go new file mode 100644 index 0000000000..faec71e273 --- /dev/null +++ b/proxy/redir/utils_other.go @@ -0,0 +1,12 @@ +// +build !linux + +package redir + +import ( + "errors" + "net" +) + +func dialUDP(network string, lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, error) { + return nil, errors.New("UDP redir not supported on current platform") +} From d8a771916a3372b3f547820b4924b4088ff32f73 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 8 Mar 2020 23:34:46 +0800 Subject: [PATCH 351/535] Fix: provider parse --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 0a74d9bc58..e828f03d78 100644 --- a/config/config.go +++ b/config/config.go @@ -265,7 +265,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ } if len(providersConfig) == 0 { - providersConfig = cfg.ProxyProvider + providersConfig = cfg.ProxyProviderOld } defer func() { From 14d51377039c7fd3640adaec9b26996e10eb9b94 Mon Sep 17 00:00:00 2001 From: Ti Date: Mon, 9 Mar 2020 10:40:21 +0800 Subject: [PATCH 352/535] Fix: rules parse (#568) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index e828f03d78..cc7254bb4b 100644 --- a/config/config.go +++ b/config/config.go @@ -376,7 +376,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { rulesConfig := cfg.Rule // remove after 1.0 - if len(rules) == 0 { + if len(rulesConfig) == 0 { rulesConfig = cfg.RuleOld } From b2630955339a7d25437a59b6f5ce37b99f65d2ab Mon Sep 17 00:00:00 2001 From: duama <30264485+duament@users.noreply.github.com> Date: Tue, 10 Mar 2020 20:36:24 +0800 Subject: [PATCH 353/535] Fix: TPROXY fakeip (#572) --- proxy/redir/udp.go | 15 ++++++--------- proxy/redir/utils.go | 11 +++++------ tunnel/connection.go | 6 +++++- tunnel/tunnel.go | 8 +++++++- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/proxy/redir/udp.go b/proxy/redir/udp.go index 83506d8698..89209aa963 100644 --- a/proxy/redir/udp.go +++ b/proxy/redir/udp.go @@ -35,8 +35,7 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { oob := make([]byte, 1024) for { buf := pool.BufPool.Get().([]byte) - - n, oobn, _, remoteAddr, err := c.ReadMsgUDP(buf, oob) + n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) if err != nil { pool.BufPool.Put(buf[:cap(buf)]) if rl.closed { @@ -45,11 +44,11 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { continue } - origDst, err := getOrigDst(oob, oobn) + rAddr, err := getOrigDst(oob, oobn) if err != nil { continue } - handleRedirUDP(l, buf[:n], remoteAddr, origDst) + handleRedirUDP(l, buf[:n], lAddr, rAddr) } }() @@ -65,13 +64,11 @@ func (l *RedirUDPListener) Address() string { return l.address } -func handleRedirUDP(pc net.PacketConn, buf []byte, addr *net.UDPAddr, origDst *net.UDPAddr) { - target := socks5.ParseAddrToSocksAddr(origDst) - +func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { + target := socks5.ParseAddrToSocksAddr(rAddr) packet := &fakeConn{ PacketConn: pc, - origDst: origDst, - rAddr: addr, + lAddr: lAddr, buf: buf, } tunnel.AddPacket(adapters.NewPacket(target, packet, C.REDIR)) diff --git a/proxy/redir/utils.go b/proxy/redir/utils.go index 71756d4d77..f6a60d460d 100644 --- a/proxy/redir/utils.go +++ b/proxy/redir/utils.go @@ -8,18 +8,17 @@ import ( type fakeConn struct { net.PacketConn - origDst net.Addr - rAddr net.Addr - buf []byte + lAddr *net.UDPAddr + buf []byte } func (c *fakeConn) Data() []byte { return c.buf } -// WriteBack opens a new socket binding `origDst` to wirte UDP packet back +// WriteBack opens a new socket binding `addr` to wirte UDP packet back func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { - tc, err := dialUDP("udp", c.origDst.(*net.UDPAddr), c.rAddr.(*net.UDPAddr)) + tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) if err != nil { n = 0 return @@ -31,7 +30,7 @@ func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { // LocalAddr returns the source IP/Port of UDP Packet func (c *fakeConn) LocalAddr() net.Addr { - return c.rAddr + return c.lAddr } func (c *fakeConn) Close() error { diff --git a/tunnel/connection.go b/tunnel/connection.go index b1d9978c88..9553ed73f6 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -88,7 +88,7 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata DefaultManager.Upload() <- int64(len(packet.Data())) } -func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) { +func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { buf := pool.BufPool.Get().([]byte) defer pool.BufPool.Put(buf[:cap(buf)]) defer natTable.Delete(key) @@ -101,6 +101,10 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string) { return } + if fAddr != nil { + from = fAddr + } + n, err = packet.WriteBack(buf[:n], from) if err != nil { return diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index b35688aa79..7dbb61e0e9 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -182,6 +182,12 @@ func handleUDPConn(packet *inbound.PacketAdapter) { return } + // make a fAddr if requset ip is fakeip + var fAddr net.Addr + if enhancedMode != nil && enhancedMode.IsFakeIP(metadata.DstIP) { + fAddr = metadata.UDPAddr() + } + if err := preHandleMetadata(metadata); err != nil { log.Debugln("[Metadata PreHandle] error: %s", err) return @@ -231,7 +237,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { natTable.Set(key, pc) natTable.Delete(lockKey) wg.Done() - go handleUDPToLocal(packet.UDPPacket, pc, key) + go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) } wg.Wait() From 9471d80785690e7acd08837f12eb29b455e386b9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 13 Mar 2020 00:11:54 +0800 Subject: [PATCH 354/535] Fix: dns fallback logic --- dns/filters.go | 2 +- dns/resolver.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dns/filters.go b/dns/filters.go index 456280fa06..03089d4c8d 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -14,7 +14,7 @@ type geoipFilter struct{} func (gf *geoipFilter) Match(ip net.IP) bool { record, _ := mmdb.Instance().Country(ip) - return record.Country.IsoCode == "CN" || record.Country.IsoCode == "" + return record.Country.IsoCode != "CN" && record.Country.IsoCode != "" } type ipnetFilter struct { diff --git a/dns/resolver.go b/dns/resolver.go index 986aec79b5..dc526a7b48 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -197,8 +197,7 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { res := <-msgCh if res.Error == nil { if ips := r.msgToIP(res.Msg); len(ips) != 0 { - if r.shouldFallback(ips[0]) { - go func() { <-fallbackMsg }() + if !r.shouldFallback(ips[0]) { msg = res.Msg err = res.Error return msg, err @@ -258,7 +257,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { } func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result { - ch := make(chan *result) + ch := make(chan *result, 1) go func() { res, err := r.batchExchange(client, msg) ch <- &result{Msg: res, Error: err} From 082847b40384c037842fb2ea95b24107dda58903 Mon Sep 17 00:00:00 2001 From: Kaming Chan Date: Sun, 15 Mar 2020 19:40:39 +0800 Subject: [PATCH 355/535] Chore: support MarshalYAML to some config filed (#581) --- dns/util.go | 5 ++--- log/level.go | 5 +++++ tunnel/mode.go | 5 +++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/dns/util.go b/dns/util.go index 00ac4566e9..ad52f051f8 100644 --- a/dns/util.go +++ b/dns/util.go @@ -11,7 +11,6 @@ import ( "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" - yaml "gopkg.in/yaml.v2" ) var ( @@ -46,8 +45,8 @@ func (e *EnhancedMode) UnmarshalYAML(unmarshal func(interface{}) error) error { } // MarshalYAML serialize EnhancedMode with yaml -func (e EnhancedMode) MarshalYAML() ([]byte, error) { - return yaml.Marshal(e.String()) +func (e EnhancedMode) MarshalYAML() (interface{}, error) { + return e.String(), nil } // UnmarshalJSON unserialize EnhancedMode with json diff --git a/log/level.go b/log/level.go index e95e36d2e1..ea223c127c 100644 --- a/log/level.go +++ b/log/level.go @@ -55,6 +55,11 @@ func (l LogLevel) MarshalJSON() ([]byte, error) { return json.Marshal(l.String()) } +// MarshalYAML serialize LogLevel with yaml +func (l LogLevel) MarshalYAML() (interface{}, error) { + return l.String(), nil +} + func (l LogLevel) String() string { switch l { case INFO: diff --git a/tunnel/mode.go b/tunnel/mode.go index b00b73ab60..655c9f5762 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -51,6 +51,11 @@ func (m TunnelMode) MarshalJSON() ([]byte, error) { return json.Marshal(m.String()) } +// MarshalYAML serialize TunnelMode with yaml +func (m TunnelMode) MarshalYAML() (interface{}, error) { + return m.String(), nil +} + func (m TunnelMode) String() string { switch m { case Global: From 230e01f07884347fa8c76ea03b7b01912b37f881 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 19 Mar 2020 11:04:56 +0800 Subject: [PATCH 356/535] Fix: config parse panic --- common/structure/structure.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/structure/structure.go b/common/structure/structure.go index 0a047a410b..75579ffcb5 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -216,6 +216,11 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle } v := dataVal.MapIndex(k).Interface() + if v == nil { + errors = append(errors, fmt.Sprintf("filed %s invalid", fieldName)) + continue + } + currentVal := reflect.Indirect(reflect.New(valElemType)) if err := d.decode(fieldName, v, currentVal); err != nil { errors = append(errors, err.Error()) From b562f28c1be869337224a88d4342abbd169882a6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 19 Mar 2020 20:26:53 +0800 Subject: [PATCH 357/535] Feature: support trojan --- README.md | 13 ++++ adapters/outbound/parser.go | 7 ++ adapters/outbound/trojan.go | 107 +++++++++++++++++++++++++++ component/trojan/trojan.go | 144 ++++++++++++++++++++++++++++++++++++ constant/adapters.go | 26 ++++--- 5 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 adapters/outbound/trojan.go create mode 100644 component/trojan/trojan.go diff --git a/README.md b/README.md index 2f7aba250d..a67113f7a4 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,19 @@ proxies: # mode: http # or tls # host: bing.com + # trojan + - name: "trojan" + type: trojan + server: server + port: 443 + password: yourpsk + # udp: true + # sni: example.com # aka server name + # alpn: + # - h2 + # - http/1.1 + # skip-cert-verify: true + proxy-groups: # url-test select which proxy will be used by benchmarking speed to a URL. - name: "auto" diff --git a/adapters/outbound/parser.go b/adapters/outbound/parser.go index 6aeec86641..90b777603c 100644 --- a/adapters/outbound/parser.go +++ b/adapters/outbound/parser.go @@ -52,6 +52,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { break } proxy, err = NewSnell(*snellOption) + case "trojan": + trojanOption := &TrojanOption{} + err = decoder.Decode(mapping, trojanOption) + if err != nil { + break + } + proxy, err = NewTrojan(*trojanOption) default: return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) } diff --git a/adapters/outbound/trojan.go b/adapters/outbound/trojan.go new file mode 100644 index 0000000000..14564bc07d --- /dev/null +++ b/adapters/outbound/trojan.go @@ -0,0 +1,107 @@ +package outbound + +import ( + "context" + "encoding/json" + "fmt" + "net" + "strconv" + + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/trojan" + C "github.com/Dreamacro/clash/constant" +) + +type Trojan struct { + *Base + server string + instance *trojan.Trojan +} + +type TrojanOption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Password string `proxy:"password"` + ALPN []string `proxy:"alpn,omitempty"` + SNI string `proxy:"sni,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + UDP bool `proxy:"udp,omitempty"` +} + +func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", t.server) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", t.server, err) + } + tcpKeepAlive(c) + c, err = t.instance.StreamConn(c) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", t.server, err) + } + + err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) + return newConn(c, t), err +} + +func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) + defer cancel() + c, err := dialer.DialContext(ctx, "tcp", t.server) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", t.server, err) + } + tcpKeepAlive(c) + c, err = t.instance.StreamConn(c) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", t.server, err) + } + + err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) + if err != nil { + return nil, err + } + + pc := t.instance.PacketConn(c) + return newPacketConn(&trojanPacketConn{pc, c}, t), err +} + +func (t *Trojan) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": t.Type().String(), + }) +} + +func NewTrojan(option TrojanOption) (*Trojan, error) { + server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + + tOption := &trojan.Option{ + Password: option.Password, + ALPN: option.ALPN, + ServerName: option.Server, + SkipCertVerify: option.SkipCertVerify, + } + + if option.SNI != "" { + tOption.ServerName = option.SNI + } + + return &Trojan{ + Base: &Base{ + name: option.Name, + tp: C.Trojan, + udp: option.UDP, + }, + server: server, + instance: trojan.New(tOption), + }, nil +} + +type trojanPacketConn struct { + net.PacketConn + conn net.Conn +} + +func (tpc *trojanPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { + return trojan.WritePacket(tpc.conn, serializesSocksAddr(metadata), p) +} diff --git a/component/trojan/trojan.go b/component/trojan/trojan.go new file mode 100644 index 0000000000..b20fa3aebb --- /dev/null +++ b/component/trojan/trojan.go @@ -0,0 +1,144 @@ +package trojan + +import ( + "bytes" + "crypto/sha256" + "crypto/tls" + "encoding/binary" + "encoding/hex" + "errors" + "net" + "sync" + + "github.com/Dreamacro/clash/component/socks5" +) + +var ( + defaultALPN = []string{"h2", "http/1.1"} + crlf = []byte{'\r', '\n'} + + bufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} +) + +type Command = byte + +var ( + CommandTCP byte = 1 + CommandUDP byte = 3 +) + +type Option struct { + Password string + ALPN []string + ServerName string + SkipCertVerify bool +} + +type Trojan struct { + option *Option + hexPassword []byte +} + +func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { + alpn := defaultALPN + if len(t.option.ALPN) != 0 { + alpn = t.option.ALPN + } + + tlsConfig := &tls.Config{ + NextProtos: alpn, + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: t.option.SkipCertVerify, + ServerName: t.option.ServerName, + } + + tlsConn := tls.Client(conn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + return nil, err + } + + return tlsConn, nil +} + +func (t *Trojan) WriteHeader(conn net.Conn, command Command, socks5Addr []byte) error { + buf := bufPool.Get().(*bytes.Buffer) + defer buf.Reset() + defer bufPool.Put(buf) + + buf.Write(t.hexPassword) + buf.Write(crlf) + + buf.WriteByte(command) + buf.Write(socks5Addr) + buf.Write(crlf) + + _, err := conn.Write(buf.Bytes()) + return err +} + +func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn { + return &PacketConn{conn} +} + +func WritePacket(conn net.Conn, socks5Addr, payload []byte) (int, error) { + buf := bufPool.Get().(*bytes.Buffer) + defer buf.Reset() + defer bufPool.Put(buf) + + buf.Write(socks5Addr) + binary.Write(buf, binary.BigEndian, uint16(len(payload))) + buf.Write(crlf) + buf.Write(payload) + + return conn.Write(buf.Bytes()) +} + +func DecodePacket(payload []byte) (net.Addr, []byte, error) { + addr := socks5.SplitAddr(payload) + if addr == nil { + return nil, nil, errors.New("split addr error") + } + + buf := payload[len(addr):] + if len(buf) <= 4 { + return nil, nil, errors.New("packet invalid") + } + + length := binary.BigEndian.Uint16(buf[:2]) + if len(buf) < 4+int(length) { + return nil, nil, errors.New("packet invalid") + } + + return addr.UDPAddr(), buf[4 : 4+length], nil +} + +func New(option *Option) *Trojan { + return &Trojan{option, hexSha224([]byte(option.Password))} +} + +type PacketConn struct { + net.Conn +} + +func (pc *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { + return WritePacket(pc, socks5.ParseAddr(addr.String()), b) +} + +func (pc *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, err := pc.Conn.Read(b) + addr, payload, err := DecodePacket(b) + if err != nil { + return n, nil, err + } + + copy(b, payload) + return len(payload), addr, nil +} + +func hexSha224(data []byte) []byte { + buf := make([]byte, 56) + hash := sha256.New224() + hash.Write(data) + hex.Encode(buf, hash.Sum(nil)) + return buf +} diff --git a/constant/adapters.go b/constant/adapters.go index 31db23b59c..3bc6d27818 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -10,15 +10,18 @@ import ( // Adapter Type const ( Direct AdapterType = iota - Fallback Reject - Selector + Shadowsocks Snell Socks5 Http - URLTest Vmess + Trojan + + Selector + Fallback + URLTest LoadBalance ) @@ -86,12 +89,9 @@ func (at AdapterType) String() string { switch at { case Direct: return "Direct" - case Fallback: - return "Fallback" case Reject: return "Reject" - case Selector: - return "Selector" + case Shadowsocks: return "Shadowsocks" case Snell: @@ -100,12 +100,20 @@ func (at AdapterType) String() string { return "Socks5" case Http: return "Http" - case URLTest: - return "URLTest" case Vmess: return "Vmess" + case Trojan: + return "Trojan" + + case Selector: + return "Selector" + case Fallback: + return "Fallback" + case URLTest: + return "URLTest" case LoadBalance: return "LoadBalance" + default: return "Unknown" } From b06846610821a3d150ee1f338e8e22cbd250c45a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 19 Mar 2020 22:39:09 +0800 Subject: [PATCH 358/535] Improve: add session cache for trojan --- adapters/outbound/trojan.go | 9 +++++---- component/trojan/trojan.go | 10 ++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/adapters/outbound/trojan.go b/adapters/outbound/trojan.go index 14564bc07d..a7a3ae93e4 100644 --- a/adapters/outbound/trojan.go +++ b/adapters/outbound/trojan.go @@ -76,10 +76,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) tOption := &trojan.Option{ - Password: option.Password, - ALPN: option.ALPN, - ServerName: option.Server, - SkipCertVerify: option.SkipCertVerify, + Password: option.Password, + ALPN: option.ALPN, + ServerName: option.Server, + SkipCertVerify: option.SkipCertVerify, + ClientSessionCache: getClientSessionCache(), } if option.SNI != "" { diff --git a/component/trojan/trojan.go b/component/trojan/trojan.go index b20fa3aebb..efb802d539 100644 --- a/component/trojan/trojan.go +++ b/component/trojan/trojan.go @@ -28,10 +28,11 @@ var ( ) type Option struct { - Password string - ALPN []string - ServerName string - SkipCertVerify bool + Password string + ALPN []string + ServerName string + SkipCertVerify bool + ClientSessionCache tls.ClientSessionCache } type Trojan struct { @@ -50,6 +51,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { MinVersion: tls.VersionTLS12, InsecureSkipVerify: t.option.SkipCertVerify, ServerName: t.option.ServerName, + ClientSessionCache: t.option.ClientSessionCache, } tlsConn := tls.Client(conn, tlsConfig) From e54f51af815fc9a11e154b0dbd9f3acfc3d04808 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 20 Mar 2020 00:02:05 +0800 Subject: [PATCH 359/535] Fix: trojan split udp packet --- component/socks5/socks5.go | 6 +- component/trojan/trojan.go | 119 ++++++++++++++++++++++++++++++------- 2 files changed, 101 insertions(+), 24 deletions(-) diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index b150d1f783..76be1286eb 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -178,7 +178,7 @@ func ServerHandshake(rw net.Conn, authenticator auth.Authenticator) (addr Addr, } command = buf[1] - addr, err = readAddr(rw, buf) + addr, err = ReadAddr(rw, buf) if err != nil { return } @@ -260,10 +260,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) ( return nil, err } - return readAddr(rw, buf) + return ReadAddr(rw, buf) } -func readAddr(r io.Reader, b []byte) (Addr, error) { +func ReadAddr(r io.Reader, b []byte) (Addr, error) { if len(b) < MaxAddrLen { return nil, io.ErrShortBuffer } diff --git a/component/trojan/trojan.go b/component/trojan/trojan.go index efb802d539..2ae8d07f11 100644 --- a/component/trojan/trojan.go +++ b/component/trojan/trojan.go @@ -7,12 +7,18 @@ import ( "encoding/binary" "encoding/hex" "errors" + "io" "net" "sync" "github.com/Dreamacro/clash/component/socks5" ) +const ( + // max packet length + maxLength = 8192 +) + var ( defaultALPN = []string{"h2", "http/1.1"} crlf = []byte{'\r', '\n'} @@ -62,7 +68,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { return tlsConn, nil } -func (t *Trojan) WriteHeader(conn net.Conn, command Command, socks5Addr []byte) error { +func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error { buf := bufPool.Get().(*bytes.Buffer) defer buf.Reset() defer bufPool.Put(buf) @@ -74,15 +80,17 @@ func (t *Trojan) WriteHeader(conn net.Conn, command Command, socks5Addr []byte) buf.Write(socks5Addr) buf.Write(crlf) - _, err := conn.Write(buf.Bytes()) + _, err := w.Write(buf.Bytes()) return err } func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn { - return &PacketConn{conn} + return &PacketConn{ + Conn: conn, + } } -func WritePacket(conn net.Conn, socks5Addr, payload []byte) (int, error) { +func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { buf := bufPool.Get().(*bytes.Buffer) defer buf.Reset() defer bufPool.Put(buf) @@ -92,26 +100,67 @@ func WritePacket(conn net.Conn, socks5Addr, payload []byte) (int, error) { buf.Write(crlf) buf.Write(payload) - return conn.Write(buf.Bytes()) + return w.Write(buf.Bytes()) +} + +func WritePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { + if len(payload) <= maxLength { + return writePacket(w, socks5Addr, payload) + } + + offset := 0 + total := len(payload) + for { + cursor := offset + maxLength + if cursor > total { + cursor = total + } + + n, err := writePacket(w, socks5Addr, payload[offset:cursor]) + if err != nil { + return offset + n, err + } + + offset = cursor + if offset == total { + break + } + } + + return total, nil } -func DecodePacket(payload []byte) (net.Addr, []byte, error) { - addr := socks5.SplitAddr(payload) - if addr == nil { - return nil, nil, errors.New("split addr error") +func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) { + addr, err := socks5.ReadAddr(r, payload) + if err != nil { + return nil, 0, 0, errors.New("read addr error") + } + uAddr := addr.UDPAddr() + + if _, err = io.ReadFull(r, payload[:2]); err != nil { + return nil, 0, 0, errors.New("read length error") + } + + total := int(binary.BigEndian.Uint16(payload[:2])) + if total > maxLength { + return nil, 0, 0, errors.New("packet invalid") + } + + // read crlf + if _, err = io.ReadFull(r, payload[:2]); err != nil { + return nil, 0, 0, errors.New("read crlf error") } - buf := payload[len(addr):] - if len(buf) <= 4 { - return nil, nil, errors.New("packet invalid") + length := len(payload) + if total < length { + length = total } - length := binary.BigEndian.Uint16(buf[:2]) - if len(buf) < 4+int(length) { - return nil, nil, errors.New("packet invalid") + if _, err = io.ReadFull(r, payload[:length]); err != nil { + return nil, 0, 0, errors.New("read packet error") } - return addr.UDPAddr(), buf[4 : 4+length], nil + return uAddr, length, total - length, nil } func New(option *Option) *Trojan { @@ -120,6 +169,9 @@ func New(option *Option) *Trojan { type PacketConn struct { net.Conn + remain int + rAddr net.Addr + mux sync.Mutex } func (pc *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { @@ -127,14 +179,39 @@ func (pc *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { } func (pc *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { - n, err := pc.Conn.Read(b) - addr, payload, err := DecodePacket(b) + pc.mux.Lock() + defer pc.mux.Unlock() + if pc.remain != 0 { + length := len(b) + if pc.remain < length { + length = pc.remain + } + + n, err := pc.Conn.Read(b[:length]) + if err != nil { + return 0, nil, err + } + + pc.remain -= n + addr := pc.rAddr + if pc.remain == 0 { + pc.rAddr = nil + } + + return n, addr, nil + } + + addr, n, remain, err := ReadPacket(pc.Conn, b) if err != nil { - return n, nil, err + return 0, nil, err + } + + if remain != 0 { + pc.remain = remain + pc.rAddr = addr } - copy(b, payload) - return len(payload), addr, nil + return n, addr, nil } func hexSha224(data []byte) []byte { From 70a19b999da6a5fec3ca50145f7ce6af7b8be21b Mon Sep 17 00:00:00 2001 From: Birkhoff Lee Date: Fri, 20 Mar 2020 12:35:30 +0800 Subject: [PATCH 360/535] Chore: update README to better describe what Clash do atm (#586) --- README.md | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a67113f7a4..4727d2931e 100644 --- a/README.md +++ b/README.md @@ -19,23 +19,25 @@ ## Features -- Local HTTP/HTTPS/SOCKS server -- GeoIP rule support -- Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol -- Supports Netfilter TCP redirecting -- Comprehensive HTTP API +- Local HTTP/HTTPS/SOCKS server with/without authentication +- VMess, Shadowsocks, Trojan (experimental), Snell protocol support for remote connections. UDP is supported. +- Built-in DNS server that aims to minimize DNS pollution attacks, supports DoH/DoT upstream. Fake IP is also supported. +- Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes +- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency +- Remote providers, allowing users to get node lists remotely instead of hardcoding in config +- Netfilter TCP redirecting. You can deploy Clash on your Internet gateway with `iptables`. +- Comprehensive HTTP API controller ## Install -Clash Requires Go >= 1.13. You can build it from source: +Clash requires Go >= 1.13. You can build it from source: ```sh $ go get -u -v github.com/Dreamacro/clash ``` -Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) - -Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN) +Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) +Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN). Source is not currently available. Check Clash version with: @@ -43,21 +45,17 @@ Check Clash version with: $ clash -v ``` -## Daemon - -Unfortunately, there is no native and elegant way to implement daemons on Golang. +## Daemonize Clash -So we can use third-party daemon tools like PM2, Supervisor or the like. +Unfortunately, there is no native or elegant way to implement daemons on Golang. We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service. -In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way: +In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way: ```sh $ pm2 start clash ``` -If you have Docker installed, you can run clash directly using `docker-compose`. - -[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker) +If you have Docker installed, it's recommended to deploy Clash directly using `docker-compose`: [run Clash in Docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker) ## Config @@ -94,7 +92,7 @@ allow-lan: false # "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address # bind-address: "*" -# Rule / Global/ Direct (default is Rule) +# Rule / Global / Direct (default is Rule) mode: Rule # set log level to stdout (default is info) @@ -347,7 +345,7 @@ rules: ## Documentations https://clash.gitbook.io/ -## Thanks +## Credits [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) From c0a2473160f9da2ea2116358fed33a3e6a77ae5a Mon Sep 17 00:00:00 2001 From: duama <30264485+duament@users.noreply.github.com> Date: Sat, 21 Mar 2020 23:46:49 +0800 Subject: [PATCH 361/535] Feature: support relay (proxy chains) (#539) --- README.md | 10 ++++ adapters/outbound/base.go | 15 ++++- adapters/outbound/direct.go | 2 +- adapters/outbound/http.go | 27 ++++++--- adapters/outbound/reject.go | 2 +- adapters/outbound/shadowsocks.go | 43 ++++++++------ adapters/outbound/snell.go | 33 ++++++----- adapters/outbound/socks5.go | 35 +++++++---- adapters/outbound/trojan.go | 32 ++++++---- adapters/outbound/vmess.go | 20 ++++--- adapters/outboundgroup/fallback.go | 2 +- adapters/outboundgroup/loadbalance.go | 2 +- adapters/outboundgroup/parser.go | 4 +- adapters/outboundgroup/relay.go | 84 +++++++++++++++++++++++++++ adapters/outboundgroup/selector.go | 2 +- adapters/outboundgroup/urltest.go | 2 +- adapters/outboundgroup/util.go | 53 +++++++++++++++++ constant/adapters.go | 5 ++ 18 files changed, 290 insertions(+), 83 deletions(-) create mode 100644 adapters/outboundgroup/relay.go create mode 100644 adapters/outboundgroup/util.go diff --git a/README.md b/README.md index 4727d2931e..a7d6a766ef 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,16 @@ proxies: # skip-cert-verify: true proxy-groups: + # relay chains the proxies. proxies shall not contain a proxy-group. No UDP support. + # Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet + - name: "relay" + type: relay + proxies: + - http + - vmess + - ss1 + - ss2 + # url-test select which proxy will be used by benchmarking speed to a URL. - name: "auto" type: url-test diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 8ef506703e..48576647a6 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -18,6 +18,7 @@ var ( type Base struct { name string + addr string tp C.AdapterType udp bool } @@ -30,6 +31,10 @@ func (b *Base) Type() C.AdapterType { return b.tp } +func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + return c, errors.New("no support") +} + func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { return nil, errors.New("no support") } @@ -44,8 +49,12 @@ func (b *Base) MarshalJSON() ([]byte, error) { }) } -func NewBase(name string, tp C.AdapterType, udp bool) *Base { - return &Base{name, tp, udp} +func (b *Base) Addr() string { + return b.addr +} + +func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base { + return &Base{name, addr, tp, udp} } type conn struct { @@ -61,7 +70,7 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) } -func newConn(c net.Conn, a C.ProxyAdapter) C.Conn { +func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { return &conn{c, []string{a.Name()}} } diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 1d6d381e35..f9413e6eec 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -24,7 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, return nil, err } tcpKeepAlive(c) - return newConn(c, d), nil + return NewConn(c, d), nil } func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index e0ae0c41f9..e005b329b8 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -19,7 +19,6 @@ import ( type Http struct { *Base - addr string user string pass string tlsConfig *tls.Config @@ -35,23 +34,35 @@ type HttpOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialer.DialContext(ctx, "tcp", h.addr) - if err == nil && h.tlsConfig != nil { +func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + if h.tlsConfig != nil { cc := tls.Client(c, h.tlsConfig) - err = cc.Handshake() + err := cc.Handshake() c = cc + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", h.addr, err) + } } + if err := h.shakeHand(metadata, c); err != nil { + return nil, err + } + return c, nil +} + +func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", h.addr) if err != nil { return nil, fmt.Errorf("%s connect error: %w", h.addr, err) } tcpKeepAlive(c) - if err := h.shakeHand(metadata, c); err != nil { + + c, err = h.StreamConn(c, metadata) + if err != nil { return nil, err } - return newConn(c, h), nil + return NewConn(c, h), nil } func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { @@ -113,9 +124,9 @@ func NewHttp(option HttpOption) *Http { return &Http{ Base: &Base{ name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Http, }, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), user: option.UserName, pass: option.Password, tlsConfig: tlsConfig, diff --git a/adapters/outbound/reject.go b/adapters/outbound/reject.go index 7daba1a22f..a403ce94b4 100644 --- a/adapters/outbound/reject.go +++ b/adapters/outbound/reject.go @@ -15,7 +15,7 @@ type Reject struct { } func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - return newConn(&NopConn{}, r), nil + return NewConn(&NopConn{}, r), nil } func (r *Reject) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 5ad18df99d..fca0e60668 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -21,7 +21,6 @@ import ( type ShadowSocks struct { *Base - server string cipher core.Cipher // obfs @@ -60,28 +59,34 @@ type v2rayObfsOption struct { Mux bool `obfs:"mux,omitempty"` } -func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialer.DialContext(ctx, "tcp", ss.server) - if err != nil { - return nil, fmt.Errorf("%s connect error: %w", ss.server, err) - } - tcpKeepAlive(c) +func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { switch ss.obfsMode { case "tls": c = obfs.NewTLSObfs(c, ss.obfsOption.Host) case "http": - _, port, _ := net.SplitHostPort(ss.server) + _, port, _ := net.SplitHostPort(ss.addr) c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) case "websocket": var err error c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", ss.server, err) + return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } } c = ss.cipher.StreamConn(c) - _, err = c.Write(serializesSocksAddr(metadata)) - return newConn(c, ss), err + _, err := c.Write(serializesSocksAddr(metadata)) + return c, err +} + +func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", ss.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) + } + tcpKeepAlive(c) + + c, err = ss.StreamConn(c, metadata) + return NewConn(c, ss), err } func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { @@ -90,7 +95,7 @@ func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { return nil, err } - addr, err := resolveUDPAddr("udp", ss.server) + addr, err := resolveUDPAddr("udp", ss.addr) if err != nil { return nil, err } @@ -106,12 +111,12 @@ func (ss *ShadowSocks) MarshalJSON() ([]byte, error) { } func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { - server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) cipher := option.Cipher password := option.Password ciph, err := core.PickCipher(cipher, nil, password) if err != nil { - return nil, fmt.Errorf("ss %s initialize error: %w", server, err) + return nil, fmt.Errorf("ss %s initialize error: %w", addr, err) } var v2rayOption *v2rayObfs.Option @@ -133,22 +138,22 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { if option.Plugin == "obfs" { opts := simpleObfsOption{Host: "bing.com"} if err := decoder.Decode(option.PluginOpts, &opts); err != nil { - return nil, fmt.Errorf("ss %s initialize obfs error: %w", server, err) + return nil, fmt.Errorf("ss %s initialize obfs error: %w", addr, err) } if opts.Mode != "tls" && opts.Mode != "http" { - return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode) + return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode) } obfsMode = opts.Mode obfsOption = &opts } else if option.Plugin == "v2ray-plugin" { opts := v2rayObfsOption{Host: "bing.com", Mux: true} if err := decoder.Decode(option.PluginOpts, &opts); err != nil { - return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", server, err) + return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err) } if opts.Mode != "websocket" { - return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode) + return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode) } obfsMode = opts.Mode @@ -172,10 +177,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return &ShadowSocks{ Base: &Base{ name: option.Name, + addr: addr, tp: C.Shadowsocks, udp: option.UDP, }, - server: server, cipher: ciph, obfsMode: obfsMode, diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go index f96d8fff02..61c103b85e 100644 --- a/adapters/outbound/snell.go +++ b/adapters/outbound/snell.go @@ -15,7 +15,6 @@ import ( type Snell struct { *Base - server string psk []byte obfsOption *simpleObfsOption } @@ -28,45 +27,51 @@ type SnellOption struct { ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` } -func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialer.DialContext(ctx, "tcp", s.server) - if err != nil { - return nil, fmt.Errorf("%s connect error: %w", s.server, err) - } - tcpKeepAlive(c) +func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { switch s.obfsOption.Mode { case "tls": c = obfs.NewTLSObfs(c, s.obfsOption.Host) case "http": - _, port, _ := net.SplitHostPort(s.server) + _, port, _ := net.SplitHostPort(s.addr) c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port) } c = snell.StreamConn(c, s.psk) port, _ := strconv.Atoi(metadata.DstPort) - err = snell.WriteHeader(c, metadata.String(), uint(port)) - return newConn(c, s), err + err := snell.WriteHeader(c, metadata.String(), uint(port)) + return c, err +} + +func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", s.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", s.addr, err) + } + tcpKeepAlive(c) + + c, err = s.StreamConn(c, metadata) + return NewConn(c, s), err } func NewSnell(option SnellOption) (*Snell, error) { - server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) psk := []byte(option.Psk) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) obfsOption := &simpleObfsOption{Host: "bing.com"} if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil { - return nil, fmt.Errorf("snell %s initialize obfs error: %w", server, err) + return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err) } if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { - return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode) + return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) } return &Snell{ Base: &Base{ name: option.Name, + addr: addr, tp: C.Snell, }, - server: server, psk: psk, obfsOption: obfsOption, }, nil diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index c8bd1bf585..315b05b239 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -17,7 +17,6 @@ import ( type Socks5 struct { *Base - addr string user string pass string tls bool @@ -36,19 +35,16 @@ type Socks5Option struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } -func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialer.DialContext(ctx, "tcp", ss.addr) - - if err == nil && ss.tls { +func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + if ss.tls { cc := tls.Client(c, ss.tlsConfig) - err = cc.Handshake() + err := cc.Handshake() c = cc + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) + } } - if err != nil { - return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) - } - tcpKeepAlive(c) var user *socks5.User if ss.user != "" { user = &socks5.User{ @@ -59,7 +55,22 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { return nil, err } - return newConn(c, ss), nil + return c, nil +} + +func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", ss.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) + } + tcpKeepAlive(c) + + c, err = ss.StreamConn(c, metadata) + if err != nil { + return nil, err + } + + return NewConn(c, ss), nil } func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { @@ -127,10 +138,10 @@ func NewSocks5(option Socks5Option) *Socks5 { return &Socks5{ Base: &Base{ name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Socks5, udp: option.UDP, }, - addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), user: option.UserName, pass: option.Password, tls: option.TLS, diff --git a/adapters/outbound/trojan.go b/adapters/outbound/trojan.go index a7a3ae93e4..ab154b3245 100644 --- a/adapters/outbound/trojan.go +++ b/adapters/outbound/trojan.go @@ -14,7 +14,6 @@ import ( type Trojan struct { *Base - server string instance *trojan.Trojan } @@ -29,32 +28,41 @@ type TrojanOption struct { UDP bool `proxy:"udp,omitempty"` } +func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + c, err := t.instance.StreamConn(c) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", t.addr, err) + } + + err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) + return c, err +} + func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialer.DialContext(ctx, "tcp", t.server) + c, err := dialer.DialContext(ctx, "tcp", t.addr) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", t.server, err) + return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } tcpKeepAlive(c) - c, err = t.instance.StreamConn(c) + c, err = t.StreamConn(c, metadata) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", t.server, err) + return nil, err } - err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) - return newConn(c, t), err + return NewConn(c, t), err } func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) defer cancel() - c, err := dialer.DialContext(ctx, "tcp", t.server) + c, err := dialer.DialContext(ctx, "tcp", t.addr) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", t.server, err) + return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } tcpKeepAlive(c) c, err = t.instance.StreamConn(c) if err != nil { - return nil, fmt.Errorf("%s connect error: %w", t.server, err) + return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) @@ -73,7 +81,7 @@ func (t *Trojan) MarshalJSON() ([]byte, error) { } func NewTrojan(option TrojanOption) (*Trojan, error) { - server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) tOption := &trojan.Option{ Password: option.Password, @@ -90,10 +98,10 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { return &Trojan{ Base: &Base{ name: option.Name, + addr: addr, tp: C.Trojan, udp: option.UDP, }, - server: server, instance: trojan.New(tOption), }, nil } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 73a8f4a9b3..4420514f89 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -16,7 +16,6 @@ import ( type Vmess struct { *Base - server string client *vmess.Client } @@ -35,14 +34,19 @@ type VmessOption struct { SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } +func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + return v.client.New(c, parseVmessAddr(metadata)) +} + func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := dialer.DialContext(ctx, "tcp", v.server) + c, err := dialer.DialContext(ctx, "tcp", v.addr) if err != nil { - return nil, fmt.Errorf("%s connect error", v.server) + return nil, fmt.Errorf("%s connect error", v.addr) } tcpKeepAlive(c) - c, err = v.client.New(c, parseVmessAddr(metadata)) - return newConn(c, v), err + + c, err = v.StreamConn(c, metadata) + return NewConn(c, v), err } func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { @@ -57,9 +61,9 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout) defer cancel() - c, err := dialer.DialContext(ctx, "tcp", v.server) + c, err := dialer.DialContext(ctx, "tcp", v.addr) if err != nil { - return nil, fmt.Errorf("%s connect error", v.server) + return nil, fmt.Errorf("%s connect error", v.addr) } tcpKeepAlive(c) c, err = v.client.New(c, parseVmessAddr(metadata)) @@ -91,10 +95,10 @@ func NewVmess(option VmessOption) (*Vmess, error) { return &Vmess{ Base: &Base{ name: option.Name, + addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Vmess, udp: true, }, - server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), client: client, }, nil } diff --git a/adapters/outboundgroup/fallback.go b/adapters/outboundgroup/fallback.go index 7a344d090b..2fb486492f 100644 --- a/adapters/outboundgroup/fallback.go +++ b/adapters/outboundgroup/fallback.go @@ -77,7 +77,7 @@ func (f *Fallback) findAliveProxy() C.Proxy { func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { return &Fallback{ - Base: outbound.NewBase(name, C.Fallback, false), + Base: outbound.NewBase(name, "", C.Fallback, false), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, } diff --git a/adapters/outboundgroup/loadbalance.go b/adapters/outboundgroup/loadbalance.go index 1495cda234..9e07010571 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -120,7 +120,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { return &LoadBalance{ - Base: outbound.NewBase(name, C.LoadBalance, false), + Base: outbound.NewBase(name, "", C.LoadBalance, false), single: singledo.NewSingle(defaultGetProxiesDuration), maxRetry: 3, providers: providers, diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index f0c012581d..7eac7e9a4c 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -64,7 +64,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providers = append(providers, pd) } else { // select don't need health check - if groupOption.Type == "select" { + if groupOption.Type == "select" || groupOption.Type == "relay" { hc := provider.NewHealthCheck(ps, "", 0) pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { @@ -108,6 +108,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, group = NewFallback(groupName, providers) case "load-balance": group = NewLoadBalance(groupName, providers) + case "relay": + group = NewRelay(groupName, providers) default: return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) } diff --git a/adapters/outboundgroup/relay.go b/adapters/outboundgroup/relay.go new file mode 100644 index 0000000000..37a57e006d --- /dev/null +++ b/adapters/outboundgroup/relay.go @@ -0,0 +1,84 @@ +package outboundgroup + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/provider" + "github.com/Dreamacro/clash/common/singledo" + "github.com/Dreamacro/clash/component/dialer" + C "github.com/Dreamacro/clash/constant" +) + +type Relay struct { + *outbound.Base + single *singledo.Single + providers []provider.ProxyProvider +} + +func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + proxies := r.proxies() + if len(proxies) == 0 { + return nil, errors.New("Proxy does not exist") + } + first := proxies[0] + last := proxies[len(proxies)-1] + + c, err := dialer.DialContext(ctx, "tcp", first.Addr()) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + tcpKeepAlive(c) + + var currentMeta *C.Metadata + for _, proxy := range proxies[1:] { + currentMeta, err = addrToMetadata(proxy.Addr()) + if err != nil { + return nil, err + } + + c, err = first.StreamConn(c, currentMeta) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err) + } + + first = proxy + } + + c, err = last.StreamConn(c, metadata) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err) + } + + return outbound.NewConn(c, r), nil +} + +func (r *Relay) MarshalJSON() ([]byte, error) { + var all []string + for _, proxy := range r.proxies() { + all = append(all, proxy.Name()) + } + return json.Marshal(map[string]interface{}{ + "type": r.Type().String(), + "all": all, + }) +} + +func (r *Relay) proxies() []C.Proxy { + elm, _, _ := r.single.Do(func() (interface{}, error) { + return getProvidersProxies(r.providers), nil + }) + + return elm.([]C.Proxy) +} + +func NewRelay(name string, providers []provider.ProxyProvider) *Relay { + return &Relay{ + Base: outbound.NewBase(name, "", C.Relay, false), + single: singledo.NewSingle(defaultGetProxiesDuration), + providers: providers, + } +} diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index 533a6126dd..6ad78f209b 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -77,7 +77,7 @@ func (s *Selector) proxies() []C.Proxy { func NewSelector(name string, providers []provider.ProxyProvider) *Selector { selected := providers[0].Proxies()[0] return &Selector{ - Base: outbound.NewBase(name, C.Selector, false), + Base: outbound.NewBase(name, "", C.Selector, false), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, selected: selected, diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index c985eea8ad..bfdb20e997 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -86,7 +86,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) { func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest { return &URLTest{ - Base: outbound.NewBase(name, C.URLTest, false), + Base: outbound.NewBase(name, "", C.URLTest, false), single: singledo.NewSingle(defaultGetProxiesDuration), fastSingle: singledo.NewSingle(time.Second * 10), providers: providers, diff --git a/adapters/outboundgroup/util.go b/adapters/outboundgroup/util.go new file mode 100644 index 0000000000..c8a6a85808 --- /dev/null +++ b/adapters/outboundgroup/util.go @@ -0,0 +1,53 @@ +package outboundgroup + +import ( + "fmt" + "net" + "time" + + C "github.com/Dreamacro/clash/constant" +) + +func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) { + host, port, err := net.SplitHostPort(rawAddress) + if err != nil { + err = fmt.Errorf("addrToMetadata failed: %w", err) + return + } + + ip := net.ParseIP(host) + if ip != nil { + if ip.To4() != nil { + addr = &C.Metadata{ + AddrType: C.AtypIPv4, + Host: "", + DstIP: ip, + DstPort: port, + } + return + } else { + addr = &C.Metadata{ + AddrType: C.AtypIPv6, + Host: "", + DstIP: ip, + DstPort: port, + } + return + } + } else { + addr = &C.Metadata{ + AddrType: C.AtypDomainName, + Host: host, + DstIP: nil, + DstPort: port, + } + return + } +} + +func tcpKeepAlive(c net.Conn) { + if tcp, ok := c.(*net.TCPConn); ok { + tcp.SetKeepAlive(true) + tcp.SetKeepAlivePeriod(30 * time.Second) + } +} diff --git a/constant/adapters.go b/constant/adapters.go index 3bc6d27818..ac19de364c 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -19,6 +19,7 @@ const ( Vmess Trojan + Relay Selector Fallback URLTest @@ -62,10 +63,12 @@ type PacketConn interface { type ProxyAdapter interface { Name() string Type() AdapterType + StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error) DialContext(ctx context.Context, metadata *Metadata) (Conn, error) DialUDP(metadata *Metadata) (PacketConn, error) SupportUDP() bool MarshalJSON() ([]byte, error) + Addr() string } type DelayHistory struct { @@ -105,6 +108,8 @@ func (at AdapterType) String() string { case Trojan: return "Trojan" + case Relay: + return "Relay" case Selector: return "Selector" case Fallback: From 86dfb6562c3592bec537bd5e5474ac8f7a2140d6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 22 Mar 2020 17:41:58 +0800 Subject: [PATCH 362/535] Chore: update dependencies --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index ebe586cdf2..111b7bba0a 100644 --- a/go.mod +++ b/go.mod @@ -6,17 +6,17 @@ require ( github.com/Dreamacro/go-shadowsocks2 v0.1.5 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.0.3+incompatible - github.com/go-chi/cors v1.0.0 + github.com/go-chi/cors v1.0.1 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible - github.com/gorilla/websocket v1.4.1 - github.com/miekg/dns v1.1.27 + github.com/gorilla/websocket v1.4.2 + github.com/miekg/dns v1.1.29 github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.5.1 - golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 - golang.org/x/net v0.0.0-20200301022130-244492dfa37a - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e + golang.org/x/crypto v0.0.0-20200320181102-891825fb96df + golang.org/x/net v0.0.0-20200320220750-118fecf932d8 + golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index e6679861ee..43f3451b0b 100644 --- a/go.sum +++ b/go.sum @@ -7,18 +7,18 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= -github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/go-chi/cors v1.0.1 h1:56TT/uWGoLWZpnMI/AwAmCneikXr5eLsiIq27wrKecw= +github.com/go-chi/cors v1.0.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -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/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= +github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= @@ -40,20 +40,20 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnAuivZq/2hz+Iz+OP7rg= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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= From 518354e7ebd3362315f02f34a6a9ce21131f8fc9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 24 Mar 2020 10:13:53 +0800 Subject: [PATCH 363/535] Fix: dns request panic and close #527 --- dns/resolver.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dns/resolver.go b/dns/resolver.go index dc526a7b48..f1f51a98ab 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -115,7 +115,7 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { } }() - ret, err, _ := r.group.Do(q.String(), func() (interface{}, error) { + ret, err, shared := r.group.Do(q.String(), func() (interface{}, error) { isIPReq := isIPRequest(q) if isIPReq { return r.fallbackExchange(m) @@ -126,6 +126,9 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { if err == nil { msg = ret.(*D.Msg) + if shared { + msg = msg.Copy() + } } return From 206767247ea4c7f2a7ee4986ec638e54ad0dda35 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sat, 28 Mar 2020 20:05:38 +0800 Subject: [PATCH 364/535] Fix: udp traffic track (#608) --- tunnel/connection.go | 2 -- tunnel/tracker.go | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 9553ed73f6..ce70a18b13 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -85,7 +85,6 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil { return } - DefaultManager.Upload() <- int64(len(packet.Data())) } func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { @@ -109,7 +108,6 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n if err != nil { return } - DefaultManager.Download() <- int64(n) } } diff --git a/tunnel/tracker.go b/tunnel/tracker.go index e96c28356d..2be522039f 100644 --- a/tunnel/tracker.go +++ b/tunnel/tracker.go @@ -103,6 +103,14 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { return n, err } +func (ut *udpTracker) WriteWithMetadata(p []byte, metadata *C.Metadata) (int, error) { + n, err := ut.PacketConn.WriteWithMetadata(p, metadata) + upload := int64(n) + ut.manager.Upload() <- upload + ut.UploadTotal += upload + return n, err +} + func (ut *udpTracker) Close() error { ut.manager.Leave(ut) return ut.PacketConn.Close() From 19f809b1c80a0a369dd3156b8113085b19ebd1a8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 31 Mar 2020 16:07:21 +0800 Subject: [PATCH 365/535] Feature: refactor vmess & add http network --- README.md | 18 ++++++ adapters/outbound/parser.go | 7 ++- adapters/outbound/shadowsocks.go | 23 +++---- adapters/outbound/vmess.go | 70 +++++++++++++++++---- component/v2ray-plugin/websocket.go | 27 +++++---- component/vmess/http.go | 76 +++++++++++++++++++++++ component/vmess/vmess.go | 94 ++++------------------------- component/vmess/websocket.go | 31 ++++++---- 8 files changed, 213 insertions(+), 133 deletions(-) create mode 100644 component/vmess/http.go diff --git a/README.md b/README.md index a7d6a766ef..f1d8165ee2 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,24 @@ proxies: # ws-path: /path # ws-headers: # Host: v2ray.com + + - name: "vmess-http" + type: vmess + server: server + port: 443 + uuid: uuid + alterId: 32 + cipher: auto + # udp: true + # network: http + # http-opts: + # # method: "GET" + # # path: + # # - '/' + # # - '/video' + # # headers: + # # Connection: + # # - keep-alive # socks5 - name: "socks" diff --git a/adapters/outbound/parser.go b/adapters/outbound/parser.go index 90b777603c..86ee68b5f3 100644 --- a/adapters/outbound/parser.go +++ b/adapters/outbound/parser.go @@ -39,7 +39,12 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { } proxy = NewHttp(*httpOption) case "vmess": - vmessOption := &VmessOption{} + vmessOption := &VmessOption{ + HTTPOpts: HTTPOptions{ + Method: "GET", + Path: []string{"/"}, + }, + } err = decoder.Decode(mapping, vmessOption) if err != nil { break diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index fca0e60668..fdf5ca0ee4 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -2,7 +2,6 @@ package outbound import ( "context" - "crypto/tls" "encoding/json" "errors" "fmt" @@ -156,21 +155,17 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode) } obfsMode = opts.Mode + v2rayOption = &v2rayObfs.Option{ + Host: opts.Host, + Path: opts.Path, + Headers: opts.Headers, + Mux: opts.Mux, + } - var tlsConfig *tls.Config if opts.TLS { - tlsConfig = &tls.Config{ - ServerName: opts.Host, - InsecureSkipVerify: opts.SkipCertVerify, - ClientSessionCache: getClientSessionCache(), - } - } - v2rayOption = &v2rayObfs.Option{ - Host: opts.Host, - Path: opts.Path, - Headers: opts.Headers, - TLSConfig: tlsConfig, - Mux: opts.Mux, + v2rayOption.TLS = true + v2rayOption.SkipCertVerify = opts.SkipCertVerify + v2rayOption.SessionCache = getClientSessionCache() } } diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 4420514f89..5f543dffbf 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "net/http" "strconv" "strings" @@ -17,6 +18,7 @@ import ( type Vmess struct { *Base client *vmess.Client + option *VmessOption } type VmessOption struct { @@ -29,13 +31,60 @@ type VmessOption struct { TLS bool `proxy:"tls,omitempty"` UDP bool `proxy:"udp,omitempty"` Network string `proxy:"network,omitempty"` + HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` WSPath string `proxy:"ws-path,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } +type HTTPOptions struct { + Method string `proxy:"method,omitempty"` + Path []string `proxy:"path,omitempty"` + Headers map[string][]string `proxy:"headers,omitempty"` +} + func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { - return v.client.New(c, parseVmessAddr(metadata)) + var err error + switch v.option.Network { + case "ws": + host, port, _ := net.SplitHostPort(v.addr) + wsOpts := &vmess.WebsocketConfig{ + Host: host, + Port: port, + Path: v.option.WSPath, + } + + if len(v.option.WSHeaders) != 0 { + header := http.Header{} + for key, value := range v.option.WSHeaders { + header.Add(key, value) + } + wsOpts.Headers = header + } + + if v.option.TLS { + wsOpts.TLS = true + wsOpts.SessionCache = getClientSessionCache() + wsOpts.SkipCertVerify = v.option.SkipCertVerify + } + c, err = vmess.StreamWebsocketConn(c, wsOpts) + case "http": + host, _, _ := net.SplitHostPort(v.addr) + httpOpts := &vmess.HTTPConfig{ + Host: host, + Method: v.option.HTTPOpts.Method, + Path: v.option.HTTPOpts.Path, + Headers: v.option.HTTPOpts.Headers, + } + + c, err = vmess.StreamHTTPConn(c, httpOpts), nil + } + + if err != nil { + return nil, err + } + + return v.client.StreamConn(c, parseVmessAddr(metadata)) } func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { @@ -66,7 +115,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { return nil, fmt.Errorf("%s connect error", v.addr) } tcpKeepAlive(c) - c, err = v.client.New(c, parseVmessAddr(metadata)) + c, err = v.StreamConn(c, metadata) if err != nil { return nil, fmt.Errorf("new vmess client error: %v", err) } @@ -76,17 +125,11 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: option.UUID, - AlterID: uint16(option.AlterID), - Security: security, - TLS: option.TLS, - HostName: option.Server, - Port: strconv.Itoa(option.Port), - NetWork: option.Network, - WebSocketPath: option.WSPath, - WebSocketHeaders: option.WSHeaders, - SkipCertVerify: option.SkipCertVerify, - SessionCache: getClientSessionCache(), + UUID: option.UUID, + AlterID: uint16(option.AlterID), + Security: security, + HostName: option.Server, + Port: strconv.Itoa(option.Port), }) if err != nil { return nil, err @@ -100,6 +143,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { udp: true, }, client: client, + option: &option, }, nil } diff --git a/component/v2ray-plugin/websocket.go b/component/v2ray-plugin/websocket.go index df6d05da2f..fbd1f3e325 100644 --- a/component/v2ray-plugin/websocket.go +++ b/component/v2ray-plugin/websocket.go @@ -10,11 +10,14 @@ import ( // Option is options of websocket obfs type Option struct { - Host string - Path string - Headers map[string]string - TLSConfig *tls.Config - Mux bool + Host string + Port string + Path string + Headers map[string]string + TLS bool + SkipCertVerify bool + SessionCache tls.ClientSessionCache + Mux bool } // NewV2rayObfs return a HTTPObfs @@ -25,15 +28,17 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { } config := &vmess.WebsocketConfig{ - Host: option.Host, - Path: option.Path, - TLS: option.TLSConfig != nil, - Headers: header, - TLSConfig: option.TLSConfig, + Host: option.Host, + Port: option.Port, + Path: option.Path, + TLS: option.TLS, + Headers: header, + SkipCertVerify: option.SkipCertVerify, + SessionCache: option.SessionCache, } var err error - conn, err = vmess.NewWebsocketConn(conn, config) + conn, err = vmess.StreamWebsocketConn(conn, config) if err != nil { return nil, err } diff --git a/component/vmess/http.go b/component/vmess/http.go new file mode 100644 index 0000000000..92fe338910 --- /dev/null +++ b/component/vmess/http.go @@ -0,0 +1,76 @@ +package vmess + +import ( + "bytes" + "fmt" + "math/rand" + "net" + "net/http" + "net/textproto" +) + +type httpConn struct { + net.Conn + cfg *HTTPConfig + rhandshake bool + whandshake bool +} + +type HTTPConfig struct { + Method string + Host string + Path []string + Headers map[string][]string +} + +// Read implements net.Conn.Read() +func (hc *httpConn) Read(b []byte) (int, error) { + if hc.rhandshake { + n, err := hc.Conn.Read(b) + return n, err + } + + reader := textproto.NewConn(hc.Conn) + // First line: GET /index.html HTTP/1.0 + if _, err := reader.ReadLine(); err != nil { + return 0, err + } + + if _, err := reader.ReadMIMEHeader(); err != nil { + return 0, err + } + + hc.rhandshake = true + return hc.Conn.Read(b) +} + +// Write implements io.Writer. +func (hc *httpConn) Write(b []byte) (int, error) { + if hc.whandshake { + return hc.Conn.Write(b) + } + + path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))] + u := fmt.Sprintf("http://%s%s", hc.cfg.Host, path) + req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b)) + for key, list := range hc.cfg.Headers { + req.Header.Set(key, list[rand.Intn(len(list))]) + } + req.ContentLength = int64(len(b)) + if err := req.Write(hc.Conn); err != nil { + return 0, err + } + hc.whandshake = true + return len(b), nil +} + +func (hc *httpConn) Close() error { + return hc.Conn.Close() +} + +func StreamHTTPConn(conn net.Conn, cfg *HTTPConfig) net.Conn { + return &httpConn{ + Conn: conn, + cfg: cfg, + } +} diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 49890354b7..9b7ef906a5 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -5,7 +5,6 @@ import ( "fmt" "math/rand" "net" - "net/http" "runtime" "sync" @@ -66,42 +65,23 @@ type DstAddr struct { // Client is vmess connection generator type Client struct { - user []*ID - uuid *uuid.UUID - security Security - tls bool - host string - wsConfig *WebsocketConfig - tlsConfig *tls.Config + user []*ID + uuid *uuid.UUID + security Security } // Config of vmess type Config struct { - UUID string - AlterID uint16 - Security string - TLS bool - HostName string - Port string - NetWork string - WebSocketPath string - WebSocketHeaders map[string]string - SkipCertVerify bool - SessionCache tls.ClientSessionCache + UUID string + AlterID uint16 + Security string + Port string + HostName string } -// New return a Conn with net.Conn and DstAddr -func (c *Client) New(conn net.Conn, dst *DstAddr) (net.Conn, error) { - var err error +// StreamConn return a Conn with net.Conn and DstAddr +func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) { r := rand.Intn(len(c.user)) - if c.wsConfig != nil { - conn, err = NewWebsocketConn(conn, c.wsConfig) - if err != nil { - return nil, err - } - } else if c.tls { - conn = tls.Client(conn, c.tlsConfig) - } return newConn(conn, c.user[r], dst, c.security) } @@ -129,57 +109,9 @@ func NewClient(config Config) (*Client, error) { return nil, fmt.Errorf("Unknown security type: %s", config.Security) } - if config.NetWork != "" && config.NetWork != "ws" { - return nil, fmt.Errorf("Unknown network type: %s", config.NetWork) - } - - header := http.Header{} - for k, v := range config.WebSocketHeaders { - header.Add(k, v) - } - - host := net.JoinHostPort(config.HostName, config.Port) - - var tlsConfig *tls.Config - if config.TLS { - tlsConfig = &tls.Config{ - ServerName: config.HostName, - InsecureSkipVerify: config.SkipCertVerify, - ClientSessionCache: config.SessionCache, - } - if tlsConfig.ClientSessionCache == nil { - tlsConfig.ClientSessionCache = getClientSessionCache() - } - if host := header.Get("Host"); host != "" { - tlsConfig.ServerName = host - } - } - - var wsConfig *WebsocketConfig - if config.NetWork == "ws" { - wsConfig = &WebsocketConfig{ - Host: host, - Path: config.WebSocketPath, - Headers: header, - TLS: config.TLS, - TLSConfig: tlsConfig, - } - } - return &Client{ - user: newAlterIDs(newID(&uid), config.AlterID), - uuid: &uid, - security: security, - tls: config.TLS, - host: host, - wsConfig: wsConfig, - tlsConfig: tlsConfig, + user: newAlterIDs(newID(&uid), config.AlterID), + uuid: &uid, + security: security, }, nil } - -func getClientSessionCache() tls.ClientSessionCache { - once.Do(func() { - clientSessionCache = tls.NewLRUClientSessionCache(128) - }) - return clientSessionCache -} diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index 30d2c3ae7c..625905b881 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -25,11 +25,13 @@ type websocketConn struct { } type WebsocketConfig struct { - Host string - Path string - Headers http.Header - TLS bool - TLSConfig *tls.Config + Host string + Port string + Path string + Headers http.Header + TLS bool + SkipCertVerify bool + SessionCache tls.ClientSessionCache } // Read implements net.Conn.Read() @@ -111,7 +113,7 @@ func (wsc *websocketConn) SetWriteDeadline(t time.Time) error { return wsc.conn.SetWriteDeadline(t) } -func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { +func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { dialer := &websocket.Dialer{ NetDial: func(network, addr string) (net.Conn, error) { return conn, nil @@ -124,17 +126,20 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { scheme := "ws" if c.TLS { scheme = "wss" - dialer.TLSClientConfig = c.TLSConfig - } + dialer.TLSClientConfig = &tls.Config{ + ServerName: c.Host, + InsecureSkipVerify: c.SkipCertVerify, + ClientSessionCache: c.SessionCache, + } - host, port, _ := net.SplitHostPort(c.Host) - if (scheme == "ws" && port != "80") || (scheme == "wss" && port != "443") { - host = c.Host + if host := c.Headers.Get("Host"); host != "" { + dialer.TLSClientConfig.ServerName = host + } } uri := url.URL{ Scheme: scheme, - Host: host, + Host: net.JoinHostPort(c.Host, c.Port), Path: c.Path, } @@ -151,7 +156,7 @@ func NewWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { if resp != nil { reason = resp.Status } - return nil, fmt.Errorf("Dial %s error: %s", host, reason) + return nil, fmt.Errorf("Dial %s error: %s", uri.Host, reason) } return &websocketConn{ From 5591e15452780625bf5b569991dbbec92fae0e54 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 3 Apr 2020 16:04:24 +0800 Subject: [PATCH 366/535] Fix: vmess pure TLS mode --- adapters/outbound/vmess.go | 13 ++++++++++++- component/vmess/tls.go | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 component/vmess/tls.go diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 5f543dffbf..a9365526d1 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -77,7 +77,18 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { Headers: v.option.HTTPOpts.Headers, } - c, err = vmess.StreamHTTPConn(c, httpOpts), nil + c = vmess.StreamHTTPConn(c, httpOpts) + default: + // handle TLS + if v.option.TLS { + host, _, _ := net.SplitHostPort(v.addr) + tlsOpts := &vmess.TLSConfig{ + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + SessionCache: getClientSessionCache(), + } + c, err = vmess.StreamTLSConn(c, tlsOpts) + } } if err != nil { diff --git a/component/vmess/tls.go b/component/vmess/tls.go new file mode 100644 index 0000000000..8ed1977702 --- /dev/null +++ b/component/vmess/tls.go @@ -0,0 +1,24 @@ +package vmess + +import ( + "crypto/tls" + "net" +) + +type TLSConfig struct { + Host string + SkipCertVerify bool + SessionCache tls.ClientSessionCache +} + +func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { + tlsConfig := &tls.Config{ + ServerName: cfg.Host, + InsecureSkipVerify: cfg.SkipCertVerify, + ClientSessionCache: cfg.SessionCache, + } + + tlsConn := tls.Client(conn, tlsConfig) + err := tlsConn.Handshake() + return tlsConn, err +} From 65dab4e34f5cd1a09e4d6b8523f16c6f872880e3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 8 Apr 2020 15:45:59 +0800 Subject: [PATCH 367/535] Feature: domain trie support dot dot wildcard --- component/domain-trie/tire.go | 59 +++++++++++++++++------- component/domain-trie/trie_test.go | 74 +++++++++++++++--------------- 2 files changed, 79 insertions(+), 54 deletions(-) diff --git a/component/domain-trie/tire.go b/component/domain-trie/tire.go index f4e87ff5e4..b4f2bce413 100644 --- a/component/domain-trie/tire.go +++ b/component/domain-trie/tire.go @@ -6,8 +6,9 @@ import ( ) const ( - wildcard = "*" - domainStep = "." + wildcard = "*" + dotWildcard = "" + domainStep = "." ) var ( @@ -21,8 +22,23 @@ type Trie struct { root *Node } -func isValidDomain(domain string) bool { - return domain != "" && domain[0] != '.' && domain[len(domain)-1] != '.' +func validAndSplitDomain(domain string) ([]string, bool) { + if domain != "" && domain[len(domain)-1] == '.' { + return nil, false + } + + parts := strings.Split(domain, domainStep) + if len(parts) == 1 { + return nil, false + } + + for _, part := range parts[1:] { + if part == "" { + return nil, false + } + } + + return parts, true } // Insert adds a node to the trie. @@ -30,12 +46,13 @@ func isValidDomain(domain string) bool { // 1. www.example.com // 2. *.example.com // 3. subdomain.*.example.com +// 4. .example.com func (t *Trie) Insert(domain string, data interface{}) error { - if !isValidDomain(domain) { + parts, valid := validAndSplitDomain(domain) + if !valid { return ErrInvalidDomain } - parts := strings.Split(domain, domainStep) node := t.root // reverse storage domain part to save space for i := len(parts) - 1; i >= 0; i-- { @@ -55,28 +72,38 @@ func (t *Trie) Insert(domain string, data interface{}) error { // Priority as: // 1. static part // 2. wildcard domain +// 2. dot wildcard domain func (t *Trie) Search(domain string) *Node { - if !isValidDomain(domain) { + parts, valid := validAndSplitDomain(domain) + if !valid || parts[0] == "" { return nil } - parts := strings.Split(domain, domainStep) n := t.root + var dotWildcardNode *Node for i := len(parts) - 1; i >= 0; i-- { part := parts[i] - var child *Node - if !n.hasChild(part) { - if !n.hasChild(wildcard) { - return nil - } + if node := n.getChild(dotWildcard); node != nil { + dotWildcardNode = node + } - child = n.getChild(wildcard) + if n.hasChild(part) { + n = n.getChild(part) } else { - child = n.getChild(part) + n = n.getChild(wildcard) + } + + if n == nil { + break } + } - n = child + if n == nil { + if dotWildcardNode != nil { + return dotWildcardNode + } + return nil } if n.Data == nil { diff --git a/component/domain-trie/trie_test.go b/component/domain-trie/trie_test.go index 228106cd9a..927e4341ed 100644 --- a/component/domain-trie/trie_test.go +++ b/component/domain-trie/trie_test.go @@ -3,6 +3,8 @@ package trie import ( "net" "testing" + + "github.com/stretchr/testify/assert" ) var localIP = net.IP{127, 0, 0, 1} @@ -19,17 +21,9 @@ func TestTrie_Basic(t *testing.T) { } node := tree.Search("example.com") - if node == nil { - t.Error("should not recv nil") - } - - if !node.Data.(net.IP).Equal(localIP) { - t.Error("should equal 127.0.0.1") - } - - if tree.Insert("", localIP) == nil { - t.Error("should return error") - } + assert.NotNil(t, node) + assert.True(t, node.Data.(net.IP).Equal(localIP)) + assert.NotNil(t, tree.Insert("", localIP)) } func TestTrie_Wildcard(t *testing.T) { @@ -38,50 +32,54 @@ func TestTrie_Wildcard(t *testing.T) { "*.example.com", "sub.*.example.com", "*.dev", + ".org", + ".example.net", } for _, domain := range domains { tree.Insert(domain, localIP) } - if tree.Search("sub.example.com") == nil { - t.Error("should not recv nil") - } + assert.NotNil(t, tree.Search("sub.example.com")) + assert.NotNil(t, tree.Search("sub.foo.example.com")) + assert.NotNil(t, tree.Search("test.org")) + assert.NotNil(t, tree.Search("test.example.net")) + assert.Nil(t, tree.Search("foo.sub.example.com")) + assert.Nil(t, tree.Search("foo.example.dev")) + assert.Nil(t, tree.Search("example.com")) +} - if tree.Search("sub.foo.example.com") == nil { - t.Error("should not recv nil") +func TestTrie_Priority(t *testing.T) { + tree := New() + domains := []string{ + ".dev", + "example.dev", + "*.example.dev", + "test.example.dev", } - if tree.Search("foo.sub.example.com") != nil { - t.Error("should recv nil") + assertFn := func(domain string, data int) { + node := tree.Search(domain) + assert.NotNil(t, node) + assert.Equal(t, data, node.Data) } - if tree.Search("foo.example.dev") != nil { - t.Error("should recv nil") + for idx, domain := range domains { + tree.Insert(domain, idx) } - if tree.Search("example.com") != nil { - t.Error("should recv nil") - } + assertFn("test.dev", 0) + assertFn("foo.bar.dev", 0) + assertFn("example.dev", 1) + assertFn("foo.example.dev", 2) + assertFn("test.example.dev", 3) } func TestTrie_Boundary(t *testing.T) { tree := New() tree.Insert("*.dev", localIP) - if err := tree.Insert(".", localIP); err == nil { - t.Error("should recv err") - } - - if err := tree.Insert(".com", localIP); err == nil { - t.Error("should recv err") - } - - if tree.Search("dev") != nil { - t.Error("should recv nil") - } - - if tree.Search(".dev") != nil { - t.Error("should recv nil") - } + assert.NotNil(t, tree.Insert(".", localIP)) + assert.NotNil(t, tree.Insert("..dev", localIP)) + assert.Nil(t, tree.Search("dev")) } From 3ccd7def8642b77bd9675237faddfe1e35bf3243 Mon Sep 17 00:00:00 2001 From: black-desk <814727823@qq.com> Date: Wed, 8 Apr 2020 15:49:12 +0800 Subject: [PATCH 368/535] Fix: typo (#624) --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index cc7254bb4b..3c4821959b 100644 --- a/config/config.go +++ b/config/config.go @@ -295,7 +295,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ proxyList = append(proxyList, proxy.Name()) } - // keep the origional order of ProxyGroups in config file + // keep the original order of ProxyGroups in config file for idx, mapping := range groupsConfig { groupName, existName := mapping["name"].(string) if !existName { From 2750c7ead04f441f1760e7c005076bf437e2a984 Mon Sep 17 00:00:00 2001 From: duama <30264485+duament@users.noreply.github.com> Date: Sat, 11 Apr 2020 21:45:56 +0800 Subject: [PATCH 369/535] Fix: set SO_REUSEADDR for UDP listeners on linux (#630) --- common/sockopt/reuseaddr_linux.go | 19 +++++++++++++++++++ common/sockopt/reuseaddr_other.go | 11 +++++++++++ dns/server.go | 7 +++++++ proxy/redir/udp_linux.go | 6 +++++- proxy/socks/udp.go | 6 ++++++ 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 common/sockopt/reuseaddr_linux.go create mode 100644 common/sockopt/reuseaddr_other.go diff --git a/common/sockopt/reuseaddr_linux.go b/common/sockopt/reuseaddr_linux.go new file mode 100644 index 0000000000..a1d19bfdab --- /dev/null +++ b/common/sockopt/reuseaddr_linux.go @@ -0,0 +1,19 @@ +package sockopt + +import ( + "net" + "syscall" +) + +func UDPReuseaddr(c *net.UDPConn) (err error) { + rc, err := c.SyscallConn() + if err != nil { + return + } + + rc.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + }) + + return +} diff --git a/common/sockopt/reuseaddr_other.go b/common/sockopt/reuseaddr_other.go new file mode 100644 index 0000000000..2b1369508d --- /dev/null +++ b/common/sockopt/reuseaddr_other.go @@ -0,0 +1,11 @@ +// +build !linux + +package sockopt + +import ( + "net" +) + +func UDPReuseaddr(c *net.UDPConn) (err error) { + return +} diff --git a/dns/server.go b/dns/server.go index 2f8585c07b..c2bd08c8fd 100644 --- a/dns/server.go +++ b/dns/server.go @@ -3,6 +3,8 @@ package dns import ( "net" + "github.com/Dreamacro/clash/common/sockopt" + D "github.com/miekg/dns" ) @@ -58,6 +60,11 @@ func ReCreateServer(addr string, resolver *Resolver) error { return err } + err = sockopt.UDPReuseaddr(p) + if err != nil { + return err + } + address = addr handler := newHandler(resolver) server = &Server{handler: handler} diff --git a/proxy/redir/udp_linux.go b/proxy/redir/udp_linux.go index 83a82a7237..228daca456 100644 --- a/proxy/redir/udp_linux.go +++ b/proxy/redir/udp_linux.go @@ -31,7 +31,11 @@ func setsockopt(c *net.UDPConn, addr string) error { } rc.Control(func(fd uintptr) { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) + err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) + } if err == nil && isIPv6 { err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) } diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 46e681ff64..1e623fb5ce 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -5,6 +5,7 @@ import ( adapters "github.com/Dreamacro/clash/adapters/inbound" "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/common/sockopt" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" @@ -22,6 +23,11 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { return nil, err } + err = sockopt.UDPReuseaddr(l.(*net.UDPConn)) + if err != nil { + return nil, err + } + sl := &SockUDPListener{l, addr, false} go func() { for { From 1825535abdfcf62461d6a61c94a1689165083dcb Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 16 Apr 2020 18:19:36 +0800 Subject: [PATCH 370/535] Improve: recycle buffer after packet used --- constant/adapters.go | 4 ++-- proxy/redir/udp.go | 9 ++++----- proxy/redir/utils.go | 14 ++++++-------- proxy/socks/udp.go | 10 +++++----- proxy/socks/utils.go | 17 ++++++++--------- tunnel/connection.go | 2 ++ 6 files changed, 27 insertions(+), 29 deletions(-) diff --git a/constant/adapters.go b/constant/adapters.go index ac19de364c..7900117c5e 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -135,8 +135,8 @@ type UDPPacket interface { // this is important when using Fake-IP. WriteBack(b []byte, addr net.Addr) (n int, err error) - // Close closes the underlaying connection. - Close() error + // Drop call after packet is used, could recycle buffer in this function. + Drop() // LocalAddr returns the source IP/Port of packet LocalAddr() net.Addr diff --git a/proxy/redir/udp.go b/proxy/redir/udp.go index 89209aa963..9b34299694 100644 --- a/proxy/redir/udp.go +++ b/proxy/redir/udp.go @@ -66,10 +66,9 @@ func (l *RedirUDPListener) Address() string { func handleRedirUDP(pc net.PacketConn, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) { target := socks5.ParseAddrToSocksAddr(rAddr) - packet := &fakeConn{ - PacketConn: pc, - lAddr: lAddr, - buf: buf, + pkt := &packet{ + lAddr: lAddr, + buf: buf, } - tunnel.AddPacket(adapters.NewPacket(target, packet, C.REDIR)) + tunnel.AddPacket(adapters.NewPacket(target, pkt, C.REDIR)) } diff --git a/proxy/redir/utils.go b/proxy/redir/utils.go index f6a60d460d..5c737889ea 100644 --- a/proxy/redir/utils.go +++ b/proxy/redir/utils.go @@ -6,18 +6,17 @@ import ( "github.com/Dreamacro/clash/common/pool" ) -type fakeConn struct { - net.PacketConn +type packet struct { lAddr *net.UDPAddr buf []byte } -func (c *fakeConn) Data() []byte { +func (c *packet) Data() []byte { return c.buf } // WriteBack opens a new socket binding `addr` to wirte UDP packet back -func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { +func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) if err != nil { n = 0 @@ -29,12 +28,11 @@ func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { } // LocalAddr returns the source IP/Port of UDP Packet -func (c *fakeConn) LocalAddr() net.Addr { +func (c *packet) LocalAddr() net.Addr { return c.lAddr } -func (c *fakeConn) Close() error { - err := c.PacketConn.Close() +func (c *packet) Drop() { pool.BufPool.Put(c.buf[:cap(c.buf)]) - return err + return } diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 1e623fb5ce..9d77139c2a 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -63,11 +63,11 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { pool.BufPool.Put(buf[:cap(buf)]) return } - packet := &fakeConn{ - PacketConn: pc, - rAddr: addr, - payload: payload, - bufRef: buf, + packet := &packet{ + pc: pc, + rAddr: addr, + payload: payload, + bufRef: buf, } tunnel.AddPacket(adapters.NewPacket(target, packet, C.SOCKS)) } diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index b4ef429b5e..3ee52f5fac 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -7,33 +7,32 @@ import ( "github.com/Dreamacro/clash/component/socks5" ) -type fakeConn struct { - net.PacketConn +type packet struct { + pc net.PacketConn rAddr net.Addr payload []byte bufRef []byte } -func (c *fakeConn) Data() []byte { +func (c *packet) Data() []byte { return c.payload } // WriteBack wirtes UDP packet with source(ip, port) = `addr` -func (c *fakeConn) WriteBack(b []byte, addr net.Addr) (n int, err error) { +func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { return } - return c.PacketConn.WriteTo(packet, c.rAddr) + return c.pc.WriteTo(packet, c.rAddr) } // LocalAddr returns the source IP/Port of UDP Packet -func (c *fakeConn) LocalAddr() net.Addr { +func (c *packet) LocalAddr() net.Addr { return c.rAddr } -func (c *fakeConn) Close() error { - err := c.PacketConn.Close() +func (c *packet) Drop() { pool.BufPool.Put(c.bufRef[:cap(c.bufRef)]) - return err + return } diff --git a/tunnel/connection.go b/tunnel/connection.go index ce70a18b13..f9fa503832 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -82,6 +82,8 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) { + defer packet.Drop() + if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil { return } From 5c03613858be0a494ddb42a511a065c890c7ac86 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 16 Apr 2020 18:31:40 +0800 Subject: [PATCH 371/535] Chore: picker support get first error --- common/picker/picker.go | 15 +++++++++++++-- common/picker/picker_test.go | 1 + dns/resolver.go | 7 ++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/common/picker/picker.go b/common/picker/picker.go index 0c846cb9cc..9f38aede8a 100644 --- a/common/picker/picker.go +++ b/common/picker/picker.go @@ -15,8 +15,10 @@ type Picker struct { wg sync.WaitGroup - once sync.Once - result interface{} + once sync.Once + errOnce sync.Once + result interface{} + err error } func newPicker(ctx context.Context, cancel func()) *Picker { @@ -49,6 +51,11 @@ func (p *Picker) Wait() interface{} { return p.result } +// Error return the first error (if all success return nil) +func (p *Picker) Error() error { + return p.err +} + // Go calls the given function in a new goroutine. // The first call to return a nil error cancels the group; its result will be returned by Wait. func (p *Picker) Go(f func() (interface{}, error)) { @@ -64,6 +71,10 @@ func (p *Picker) Go(f func() (interface{}, error)) { p.cancel() } }) + } else { + p.errOnce.Do(func() { + p.err = err + }) } }() } diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go index 8f0ba9586f..82122d7fb3 100644 --- a/common/picker/picker_test.go +++ b/common/picker/picker_test.go @@ -36,4 +36,5 @@ func TestPicker_Timeout(t *testing.T) { number := picker.Wait() assert.Nil(t, number) + assert.NotNil(t, picker.Error()) } diff --git a/dns/resolver.go b/dns/resolver.go index f1f51a98ab..eec7d412c0 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "errors" + "fmt" "math/rand" "net" "strings" @@ -182,7 +183,11 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err elm := fast.Wait() if elm == nil { - return nil, errors.New("All DNS requests failed") + err := errors.New("All DNS requests failed") + if fErr := fast.Error(); fErr != nil { + err = fmt.Errorf("%w, first error: %s", err, fErr.Error()) + } + return nil, err } msg = elm.(*D.Msg) From 84f627f302cda03f8be509f5bba2e69e5fe4ccf9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 16 Apr 2020 19:12:25 +0800 Subject: [PATCH 372/535] Feature: verify mmdb on initial --- component/mmdb/mmdb.go | 8 ++++++++ config/initial.go | 30 +++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go index 6bc2ad7388..08743985a9 100644 --- a/component/mmdb/mmdb.go +++ b/component/mmdb/mmdb.go @@ -22,6 +22,14 @@ func LoadFromBytes(buffer []byte) { }) } +func Verify() bool { + instance, err := geoip2.Open(C.Path.MMDB()) + if err == nil { + instance.Close() + } + return err == nil +} + func Instance() *geoip2.Reader { once.Do(func() { var err error diff --git a/config/initial.go b/config/initial.go index 09b2e4f370..26421f681d 100644 --- a/config/initial.go +++ b/config/initial.go @@ -6,6 +6,7 @@ import ( "net/http" "os" + "github.com/Dreamacro/clash/component/mmdb" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) @@ -27,6 +28,28 @@ func downloadMMDB(path string) (err error) { return err } +func initMMDB() error { + if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { + log.Infoln("Can't find MMDB, start download") + if err := downloadMMDB(C.Path.MMDB()); err != nil { + return fmt.Errorf("Can't download MMDB: %s", err.Error()) + } + } + + if !mmdb.Verify() { + log.Warnln("MMDB invalid, remove and download") + if err := os.Remove(C.Path.MMDB()); err != nil { + return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error()) + } + + if err := downloadMMDB(C.Path.MMDB()); err != nil { + return fmt.Errorf("Can't download MMDB: %s", err.Error()) + } + } + + return nil +} + // Init prepare necessary files func Init(dir string) error { // initial homedir @@ -48,11 +71,8 @@ func Init(dir string) error { } // initial mmdb - if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { - log.Infoln("Can't find MMDB, start download") - if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("Can't download MMDB: %s", err.Error()) - } + if err := initMMDB(); err != nil { + return fmt.Errorf("Can't initial MMDB: %w", err) } return nil } From b1cf2ec83721dd906038a261ea43e84aa889fdff Mon Sep 17 00:00:00 2001 From: Texot Date: Fri, 17 Apr 2020 11:29:59 +0800 Subject: [PATCH 373/535] Fix: dns tcp-tls X509.HostnameError (#638) --- dns/util.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dns/util.go b/dns/util.go index ad52f051f8..b1541091cb 100644 --- a/dns/util.go +++ b/dns/util.go @@ -133,6 +133,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { ClientSessionCache: globalSessionCache, // alpn identifier, see https://tools.ietf.org/html/draft-hoffman-dprive-dns-tls-alpn-00#page-6 NextProtos: []string{"dns"}, + ServerName: host, }, UDPSize: 4096, Timeout: 5 * time.Second, From 27dd1d7944bb212f9bbe5cd0c5fc9e4b0a9b0183 Mon Sep 17 00:00:00 2001 From: Siji Date: Mon, 20 Apr 2020 21:22:23 +0800 Subject: [PATCH 374/535] Improve: add basic auth support for provider URL (#645) --- adapters/provider/vehicle.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/adapters/provider/vehicle.go b/adapters/provider/vehicle.go index cc873a9093..175f50cead 100644 --- a/adapters/provider/vehicle.go +++ b/adapters/provider/vehicle.go @@ -4,6 +4,7 @@ import ( "context" "io/ioutil" "net/http" + "net/url" "time" "github.com/Dreamacro/clash/component/dialer" @@ -75,10 +76,21 @@ func (h *HTTPVehicle) Read() ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) defer cancel() - req, err := http.NewRequest(http.MethodGet, h.url, nil) + uri, err := url.Parse(h.url) if err != nil { return nil, err } + + req, err := http.NewRequest(http.MethodGet, uri.String(), nil) + if err != nil { + return nil, err + } + + if user := uri.User; user != nil { + password, _ := user.Password() + req.SetBasicAuth(user.Username(), password) + } + req = req.WithContext(ctx) transport := &http.Transport{ From 8eddcd77bfa2cb8a03027ba5bc0dc6cc062f2608 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 24 Apr 2020 23:48:55 +0800 Subject: [PATCH 375/535] Chore: dialer hook should return a error --- component/dialer/dialer.go | 48 +++++++++++++++++++++++++++----------- component/dialer/hook.go | 26 +++++++++++++-------- dns/client.go | 10 ++++++-- 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 6dcecdc165..5cb9badbd8 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -8,22 +8,26 @@ import ( "github.com/Dreamacro/clash/component/resolver" ) -func Dialer() *net.Dialer { +func Dialer() (*net.Dialer, error) { dialer := &net.Dialer{} if DialerHook != nil { - DialerHook(dialer) + if err := DialerHook(dialer); err != nil { + return nil, err + } } - return dialer + return dialer, nil } -func ListenConfig() *net.ListenConfig { +func ListenConfig() (*net.ListenConfig, error) { cfg := &net.ListenConfig{} if ListenConfigHook != nil { - ListenConfigHook(cfg) + if err := ListenConfigHook(cfg); err != nil { + return nil, err + } } - return cfg + return cfg, nil } func Dial(network, address string) (net.Conn, error) { @@ -38,7 +42,10 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) return nil, err } - dialer := Dialer() + dialer, err := Dialer() + if err != nil { + return nil, err + } var ip net.IP switch network { @@ -53,7 +60,9 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) } if DialHook != nil { - DialHook(dialer, network, ip) + if err := DialHook(dialer, network, ip); err != nil { + return nil, err + } } return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) case "tcp", "udp": @@ -64,13 +73,17 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) } func ListenPacket(network, address string) (net.PacketConn, error) { - lc := ListenConfig() + lc, err := ListenConfig() + if err != nil { + return nil, err + } if ListenPacketHook != nil && address == "" { - ip := ListenPacketHook() - if ip != nil { - address = net.JoinHostPort(ip.String(), "0") + ip, err := ListenPacketHook() + if err != nil { + return nil, err } + address = net.JoinHostPort(ip.String(), "0") } return lc.ListenPacket(context.Background(), network, address) } @@ -95,7 +108,6 @@ func dualStackDailContext(ctx context.Context, network, address string) (net.Con var primary, fallback dialResult startRacer := func(ctx context.Context, network, host string, ipv6 bool) { - dialer := Dialer() result := dialResult{ipv6: ipv6, done: true} defer func() { select { @@ -107,6 +119,12 @@ func dualStackDailContext(ctx context.Context, network, address string) (net.Con } }() + dialer, err := Dialer() + if err != nil { + result.error = err + return + } + var ip net.IP if ipv6 { ip, result.error = resolver.ResolveIPv6(host) @@ -119,7 +137,9 @@ func dualStackDailContext(ctx context.Context, network, address string) (net.Con result.resolved = true if DialHook != nil { - DialHook(dialer, network, ip) + if result.error = DialHook(dialer, network, ip); result.error != nil { + return + } } result.Conn, result.error = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) } diff --git a/component/dialer/hook.go b/component/dialer/hook.go index 3546a393ee..d4c955ab80 100644 --- a/component/dialer/hook.go +++ b/component/dialer/hook.go @@ -8,10 +8,10 @@ import ( "github.com/Dreamacro/clash/common/singledo" ) -type DialerHookFunc = func(dialer *net.Dialer) -type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) -type ListenConfigHookFunc = func(*net.ListenConfig) -type ListenPacketHookFunc = func() net.IP +type DialerHookFunc = func(dialer *net.Dialer) error +type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error +type ListenConfigHookFunc = func(*net.ListenConfig) error +type ListenPacketHookFunc = func() (net.IP, error) var ( DialerHook DialerHookFunc @@ -70,7 +70,7 @@ func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { func ListenPacketWithInterface(name string) ListenPacketHookFunc { single := singledo.NewSingle(5 * time.Second) - return func() net.IP { + return func() (net.IP, error) { elm, err, _ := single.Do(func() (interface{}, error) { iface, err := net.InterfaceByName(name) if err != nil { @@ -86,7 +86,7 @@ func ListenPacketWithInterface(name string) ListenPacketHookFunc { }) if err != nil { - return nil + return nil, err } addrs := elm.([]net.Addr) @@ -97,17 +97,17 @@ func ListenPacketWithInterface(name string) ListenPacketHookFunc { continue } - return addr.IP + return addr.IP, nil } - return nil + return nil, ErrAddrNotFound } } func DialerWithInterface(name string) DialHookFunc { single := singledo.NewSingle(5 * time.Second) - return func(dialer *net.Dialer, network string, ip net.IP) { + return func(dialer *net.Dialer, network string, ip net.IP) error { elm, err, _ := single.Do(func() (interface{}, error) { iface, err := net.InterfaceByName(name) if err != nil { @@ -123,7 +123,7 @@ func DialerWithInterface(name string) DialHookFunc { }) if err != nil { - return + return err } addrs := elm.([]net.Addr) @@ -132,11 +132,17 @@ func DialerWithInterface(name string) DialHookFunc { case "tcp", "tcp4", "tcp6": if addr, err := lookupTCPAddr(ip, addrs); err == nil { dialer.LocalAddr = addr + } else { + return err } case "udp", "udp4", "udp6": if addr, err := lookupUDPAddr(ip, addrs); err == nil { dialer.LocalAddr = addr + } else { + return err } } + + return nil } } diff --git a/dns/client.go b/dns/client.go index f12b0e013c..a40888dddf 100644 --- a/dns/client.go +++ b/dns/client.go @@ -34,13 +34,19 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err } } - d := dialer.Dialer() + d, err := dialer.Dialer() + if err != nil { + return nil, err + } + if dialer.DialHook != nil { network := "udp" if strings.HasPrefix(c.Client.Net, "tcp") { network = "tcp" } - dialer.DialHook(d, network, ip) + if err := dialer.DialHook(d, network, ip); err != nil { + return nil, err + } } c.Client.Dialer = d From 3fc6d550037d812cb60d98a7dc83d071a42c7a6a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 24 Apr 2020 23:49:19 +0800 Subject: [PATCH 376/535] Fix: domain wildcard behavior --- component/domain-trie/tire.go | 15 +++++++++++---- component/domain-trie/trie_test.go | 2 ++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/component/domain-trie/tire.go b/component/domain-trie/tire.go index b4f2bce413..e7322e5d82 100644 --- a/component/domain-trie/tire.go +++ b/component/domain-trie/tire.go @@ -81,6 +81,7 @@ func (t *Trie) Search(domain string) *Node { n := t.root var dotWildcardNode *Node + var wildcardNode *Node for i := len(parts) - 1; i >= 0; i-- { part := parts[i] @@ -88,10 +89,16 @@ func (t *Trie) Search(domain string) *Node { dotWildcardNode = node } - if n.hasChild(part) { - n = n.getChild(part) - } else { - n = n.getChild(wildcard) + child := n.getChild(part) + if child == nil && wildcardNode != nil { + child = wildcardNode.getChild(part) + } + wildcardNode = n.getChild(wildcard) + + n = child + if n == nil { + n = wildcardNode + wildcardNode = nil } if n == nil { diff --git a/component/domain-trie/trie_test.go b/component/domain-trie/trie_test.go index 927e4341ed..fa0f88f773 100644 --- a/component/domain-trie/trie_test.go +++ b/component/domain-trie/trie_test.go @@ -34,6 +34,7 @@ func TestTrie_Wildcard(t *testing.T) { "*.dev", ".org", ".example.net", + ".apple.*", } for _, domain := range domains { @@ -44,6 +45,7 @@ func TestTrie_Wildcard(t *testing.T) { assert.NotNil(t, tree.Search("sub.foo.example.com")) assert.NotNil(t, tree.Search("test.org")) assert.NotNil(t, tree.Search("test.example.net")) + assert.NotNil(t, tree.Search("test.apple.com")) assert.Nil(t, tree.Search("foo.sub.example.com")) assert.Nil(t, tree.Search("foo.example.dev")) assert.Nil(t, tree.Search("example.com")) From 2b33bfae6b7637fa8bbabbde865a04668bb7566b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 24 Apr 2020 23:49:35 +0800 Subject: [PATCH 377/535] Fix: API auth bypass --- hub/route/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hub/route/server.go b/hub/route/server.go index 7c892657ef..a73e80592a 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -110,9 +110,9 @@ func authentication(next http.Handler) http.Handler { header := r.Header.Get("Authorization") text := strings.SplitN(header, " ", 2) - hasUnvalidHeader := text[0] != "Bearer" - hasUnvalidSecret := len(text) == 2 && text[1] != serverSecret - if hasUnvalidHeader || hasUnvalidSecret { + hasInvalidHeader := text[0] != "Bearer" + hasInvalidSecret := len(text) != 2 || text[1] != serverSecret + if hasInvalidHeader || hasInvalidSecret { render.Status(r, http.StatusUnauthorized) render.JSON(w, r, ErrUnauthorized) return From 0e56c195bbdeb1eca358926baae0f815b9625251 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 25 Apr 2020 00:30:40 +0800 Subject: [PATCH 378/535] Improve: pool buffer alloc --- common/pool/alloc.go | 65 +++++++++++++++++++++++++++++++++++ common/pool/alloc_test.go | 48 ++++++++++++++++++++++++++ common/pool/pool.go | 15 ++++---- component/simple-obfs/http.go | 8 ++--- component/simple-obfs/tls.go | 12 +++---- component/vmess/aead.go | 12 +++---- component/vmess/chunk.go | 16 ++++----- proxy/redir/udp.go | 4 +-- proxy/redir/utils.go | 2 +- proxy/socks/udp.go | 6 ++-- proxy/socks/utils.go | 2 +- tunnel/connection.go | 16 ++++----- 12 files changed, 158 insertions(+), 48 deletions(-) create mode 100644 common/pool/alloc.go create mode 100644 common/pool/alloc_test.go diff --git a/common/pool/alloc.go b/common/pool/alloc.go new file mode 100644 index 0000000000..e9a7d52ce1 --- /dev/null +++ b/common/pool/alloc.go @@ -0,0 +1,65 @@ +package pool + +// Inspired by https://github.com/xtaci/smux/blob/master/alloc.go + +import ( + "errors" + "math/bits" + "sync" +) + +var defaultAllocator *Allocator + +func init() { + defaultAllocator = NewAllocator() +} + +// Allocator for incoming frames, optimized to prevent overwriting after zeroing +type Allocator struct { + buffers []sync.Pool +} + +// NewAllocator initiates a []byte allocator for frames less than 65536 bytes, +// the waste(memory fragmentation) of space allocation is guaranteed to be +// no more than 50%. +func NewAllocator() *Allocator { + alloc := new(Allocator) + alloc.buffers = make([]sync.Pool, 17) // 1B -> 64K + for k := range alloc.buffers { + i := k + alloc.buffers[k].New = func() interface{} { + return make([]byte, 1< 65536 { + return nil + } + + bits := msb(size) + if size == 1< 65536 || cap(buf) != 1<= realLen { - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) return n, nil } diff --git a/component/vmess/chunk.go b/component/vmess/chunk.go index 7f30d0f0fd..cd7ea57b11 100644 --- a/component/vmess/chunk.go +++ b/component/vmess/chunk.go @@ -34,7 +34,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) { n := copy(b, cr.buf[cr.offset:]) cr.offset += n if cr.offset == len(cr.buf) { - pool.BufPool.Put(cr.buf[:cap(cr.buf)]) + pool.Put(cr.buf) cr.buf = nil } return n, nil @@ -59,15 +59,15 @@ func (cr *chunkReader) Read(b []byte) (int, error) { return size, nil } - buf := pool.BufPool.Get().([]byte) - _, err = io.ReadFull(cr.Reader, buf[:size]) + buf := pool.Get(size) + _, err = io.ReadFull(cr.Reader, buf) if err != nil { - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) return 0, err } - n := copy(b, cr.buf[:]) + n := copy(b, buf) cr.offset = n - cr.buf = buf[:size] + cr.buf = buf return n, nil } @@ -76,8 +76,8 @@ type chunkWriter struct { } func (cw *chunkWriter) Write(b []byte) (n int, err error) { - buf := pool.BufPool.Get().([]byte) - defer pool.BufPool.Put(buf[:cap(buf)]) + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) length := len(b) for { if length == 0 { diff --git a/proxy/redir/udp.go b/proxy/redir/udp.go index 9b34299694..bcba6ffd1a 100644 --- a/proxy/redir/udp.go +++ b/proxy/redir/udp.go @@ -34,10 +34,10 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { go func() { oob := make([]byte, 1024) for { - buf := pool.BufPool.Get().([]byte) + buf := pool.Get(pool.RelayBufferSize) n, oobn, _, lAddr, err := c.ReadMsgUDP(buf, oob) if err != nil { - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) if rl.closed { break } diff --git a/proxy/redir/utils.go b/proxy/redir/utils.go index 5c737889ea..e78563f4ea 100644 --- a/proxy/redir/utils.go +++ b/proxy/redir/utils.go @@ -33,6 +33,6 @@ func (c *packet) LocalAddr() net.Addr { } func (c *packet) Drop() { - pool.BufPool.Put(c.buf[:cap(c.buf)]) + pool.Put(c.buf) return } diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 9d77139c2a..90ccad1b7b 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -31,10 +31,10 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { sl := &SockUDPListener{l, addr, false} go func() { for { - buf := pool.BufPool.Get().([]byte) + buf := pool.Get(pool.RelayBufferSize) n, remoteAddr, err := l.ReadFrom(buf) if err != nil { - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) if sl.closed { break } @@ -60,7 +60,7 @@ func handleSocksUDP(pc net.PacketConn, buf []byte, addr net.Addr) { target, payload, err := socks5.DecodeUDPPacket(buf) if err != nil { // Unresolved UDP packet, return buffer to the pool - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) return } packet := &packet{ diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index 3ee52f5fac..6b77a40f92 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -33,6 +33,6 @@ func (c *packet) LocalAddr() net.Addr { } func (c *packet) Drop() { - pool.BufPool.Put(c.bufRef[:cap(c.bufRef)]) + pool.Put(c.bufRef) return } diff --git a/tunnel/connection.go b/tunnel/connection.go index f9fa503832..65d45d4686 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -61,9 +61,9 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } // even if resp.Write write body to the connection, but some http request have to Copy to close it - buf := pool.BufPool.Get().([]byte) + buf := pool.Get(pool.RelayBufferSize) _, err = io.CopyBuffer(request, resp.Body, buf) - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) if err != nil && err != io.EOF { break } @@ -90,8 +90,8 @@ func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata } func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { - buf := pool.BufPool.Get().([]byte) - defer pool.BufPool.Put(buf[:cap(buf)]) + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) defer natTable.Delete(key) defer pc.Close() @@ -122,16 +122,16 @@ func relay(leftConn, rightConn net.Conn) { ch := make(chan error) go func() { - buf := pool.BufPool.Get().([]byte) + buf := pool.Get(pool.RelayBufferSize) _, err := io.CopyBuffer(leftConn, rightConn, buf) - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) leftConn.SetReadDeadline(time.Now()) ch <- err }() - buf := pool.BufPool.Get().([]byte) + buf := pool.Get(pool.RelayBufferSize) io.CopyBuffer(rightConn, leftConn, buf) - pool.BufPool.Put(buf[:cap(buf)]) + pool.Put(buf) rightConn.SetReadDeadline(time.Now()) <-ch } From 2047b8eda1c7a28e484ebe5361d28be75c9b6d75 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Sat, 25 Apr 2020 00:39:30 +0800 Subject: [PATCH 379/535] Chore: remove unused parameter netType (#651) --- adapters/inbound/socket.go | 4 ++-- proxy/redir/tcp.go | 2 +- proxy/socks/tcp.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 43fbd0f5f0..134be489f4 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -19,9 +19,9 @@ func (s *SocketAdapter) Metadata() *C.Metadata { } // NewSocket is SocketAdapter generator -func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, netType C.NetWork) *SocketAdapter { +func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter { metadata := parseSocksAddr(target) - metadata.NetWork = netType + metadata.NetWork = C.TCP metadata.Type = source if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { metadata.SrcIP = ip diff --git a/proxy/redir/tcp.go b/proxy/redir/tcp.go index 583521986d..e53d000d6c 100644 --- a/proxy/redir/tcp.go +++ b/proxy/redir/tcp.go @@ -55,5 +55,5 @@ func handleRedir(conn net.Conn) { return } conn.(*net.TCPConn).SetKeepAlive(true) - tunnel.Add(inbound.NewSocket(target, conn, C.REDIR, C.TCP)) + tunnel.Add(inbound.NewSocket(target, conn, C.REDIR)) } diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index eeff4bc454..50317177bb 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -64,5 +64,5 @@ func handleSocks(conn net.Conn) { io.Copy(ioutil.Discard, conn) return } - tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS, C.TCP)) + tunnel.Add(adapters.NewSocket(target, conn, C.SOCKS)) } From 5036f62a9c32841cfac164873619b2f5af4dc1f8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 25 Apr 2020 00:43:32 +0800 Subject: [PATCH 380/535] Chore: update dependencies --- go.mod | 10 +++++----- go.sum | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 111b7bba0a..29550a4752 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,17 @@ go 1.14 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.5 github.com/eapache/queue v1.1.0 // indirect - github.com/go-chi/chi v4.0.3+incompatible - github.com/go-chi/cors v1.0.1 + github.com/go-chi/chi v4.1.1+incompatible + github.com/go-chi/cors v1.1.1 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.2.0+incompatible github.com/gorilla/websocket v1.4.2 github.com/miekg/dns v1.1.29 github.com/oschwald/geoip2-golang v1.4.0 - github.com/sirupsen/logrus v1.4.2 + github.com/sirupsen/logrus v1.5.0 github.com/stretchr/testify v1.5.1 - golang.org/x/crypto v0.0.0-20200320181102-891825fb96df - golang.org/x/net v0.0.0-20200320220750-118fecf932d8 + golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 + golang.org/x/net v0.0.0-20200421231249-e086a090c8fd golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum index 43f3451b0b..24898afdaf 100644 --- a/go.sum +++ b/go.sum @@ -5,10 +5,10 @@ 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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= -github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/cors v1.0.1 h1:56TT/uWGoLWZpnMI/AwAmCneikXr5eLsiIq27wrKecw= -github.com/go-chi/cors v1.0.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= +github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4= +github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= +github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= @@ -25,11 +25,9 @@ github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7 github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= 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/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= @@ -40,16 +38,16 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU= -golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnAuivZq/2hz+Iz+OP7rg= -golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= @@ -63,6 +61,8 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPT golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 18603c9a46abcc78a3e9b34e32efef379ed07dbc Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 26 Apr 2020 22:38:15 +0800 Subject: [PATCH 381/535] Improve: provider can be auto GC --- adapters/provider/fetcher.go | 154 ++++++++++++++++++++++++ adapters/provider/provider.go | 215 ++++++++++------------------------ config/config.go | 9 -- hub/executor/executor.go | 7 -- 4 files changed, 218 insertions(+), 167 deletions(-) create mode 100644 adapters/provider/fetcher.go diff --git a/adapters/provider/fetcher.go b/adapters/provider/fetcher.go new file mode 100644 index 0000000000..aeaf1f4257 --- /dev/null +++ b/adapters/provider/fetcher.go @@ -0,0 +1,154 @@ +package provider + +import ( + "bytes" + "crypto/md5" + "io/ioutil" + "os" + "time" + + "github.com/Dreamacro/clash/log" +) + +var ( + fileMode os.FileMode = 0666 +) + +type parser = func([]byte) (interface{}, error) + +type fetcher struct { + name string + vehicle Vehicle + updatedAt *time.Time + ticker *time.Ticker + hash [16]byte + parser parser + onUpdate func(interface{}) +} + +func (f *fetcher) Name() string { + return f.name +} + +func (f *fetcher) VehicleType() VehicleType { + return f.vehicle.Type() +} + +func (f *fetcher) Initial() (interface{}, error) { + var buf []byte + var err error + var isLocal bool + if stat, err := os.Stat(f.vehicle.Path()); err == nil { + buf, err = ioutil.ReadFile(f.vehicle.Path()) + modTime := stat.ModTime() + f.updatedAt = &modTime + isLocal = true + } else { + buf, err = f.vehicle.Read() + } + + if err != nil { + return nil, err + } + + proxies, err := f.parser(buf) + if err != nil { + if !isLocal { + return nil, err + } + + // parse local file error, fallback to remote + buf, err = f.vehicle.Read() + if err != nil { + return nil, err + } + + proxies, err = f.parser(buf) + if err != nil { + return nil, err + } + } + + if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil { + return nil, err + } + + f.hash = md5.Sum(buf) + + // pull proxies automatically + if f.ticker != nil { + go f.pullLoop() + } + + return proxies, nil +} + +func (f *fetcher) Update() (interface{}, bool, error) { + buf, err := f.vehicle.Read() + if err != nil { + return nil, false, err + } + + now := time.Now() + hash := md5.Sum(buf) + if bytes.Equal(f.hash[:], hash[:]) { + f.updatedAt = &now + return nil, true, nil + } + + proxies, err := f.parser(buf) + if err != nil { + return nil, false, err + } + + if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil { + return nil, false, err + } + + f.updatedAt = &now + f.hash = hash + + return proxies, false, nil +} + +func (f *fetcher) Destroy() error { + if f.ticker != nil { + f.ticker.Stop() + } + return nil +} + +func (f *fetcher) pullLoop() { + for range f.ticker.C { + elm, same, err := f.Update() + if err != nil { + log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error()) + continue + } + + if same { + log.Debugln("[Provider] %s's proxies doesn't change", f.Name()) + continue + } + + log.Infoln("[Provider] %s's proxies update", f.Name()) + if f.onUpdate != nil { + f.onUpdate(elm) + } + } +} + +func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher { + var ticker *time.Ticker + if interval != 0 { + ticker = time.NewTicker(interval) + } + + return &fetcher{ + name: name, + ticker: ticker, + vehicle: vehicle, + parser: parser, + onUpdate: onUpdate, + } +} diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index 8ec4a1089b..4968a48dda 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -1,26 +1,20 @@ package provider import ( - "bytes" - "crypto/md5" "encoding/json" "errors" "fmt" - "io/ioutil" - "os" + "runtime" "time" "github.com/Dreamacro/clash/adapters/outbound" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" "gopkg.in/yaml.v2" ) const ( ReservedName = "default" - - fileMode = 0666 ) // Provider Type @@ -49,8 +43,7 @@ type Provider interface { VehicleType() VehicleType Type() ProviderType Initial() error - Reload() error - Destroy() error + Update() error } // ProxyProvider interface @@ -58,24 +51,24 @@ type ProxyProvider interface { Provider Proxies() []C.Proxy HealthCheck() - Update() error } type ProxySchema struct { Proxies []map[string]interface{} `yaml:"proxies"` } +// for auto gc type ProxySetProvider struct { - name string - vehicle Vehicle - hash [16]byte + *proxySetProvider +} + +type proxySetProvider struct { + *fetcher proxies []C.Proxy healthCheck *HealthCheck - ticker *time.Ticker - updatedAt *time.Time } -func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) { +func (pp *proxySetProvider) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ "name": pp.Name(), "type": pp.Type().String(), @@ -85,134 +78,41 @@ func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) { }) } -func (pp *ProxySetProvider) Name() string { +func (pp *proxySetProvider) Name() string { return pp.name } -func (pp *ProxySetProvider) Reload() error { - return nil -} - -func (pp *ProxySetProvider) HealthCheck() { +func (pp *proxySetProvider) HealthCheck() { pp.healthCheck.check() } -func (pp *ProxySetProvider) Update() error { - return pp.pull() -} - -func (pp *ProxySetProvider) Destroy() error { - pp.healthCheck.close() - - if pp.ticker != nil { - pp.ticker.Stop() +func (pp *proxySetProvider) Update() error { + elm, same, err := pp.fetcher.Update() + if err == nil && !same { + pp.onUpdate(elm) } - - return nil + return err } -func (pp *ProxySetProvider) Initial() error { - var buf []byte - var err error - var isLocal bool - if stat, err := os.Stat(pp.vehicle.Path()); err == nil { - buf, err = ioutil.ReadFile(pp.vehicle.Path()) - modTime := stat.ModTime() - pp.updatedAt = &modTime - isLocal = true - } else { - buf, err = pp.vehicle.Read() - } - - if err != nil { - return err - } - - proxies, err := pp.parse(buf) +func (pp *proxySetProvider) Initial() error { + elm, err := pp.fetcher.Initial() if err != nil { - if !isLocal { - return err - } - - // parse local file error, fallback to remote - buf, err = pp.vehicle.Read() - if err != nil { - return err - } - - proxies, err = pp.parse(buf) - if err != nil { - return err - } - } - - if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil { return err } - pp.hash = md5.Sum(buf) - pp.setProxies(proxies) - - // pull proxies automatically - if pp.ticker != nil { - go pp.pullLoop() - } - + pp.onUpdate(elm) return nil } -func (pp *ProxySetProvider) VehicleType() VehicleType { - return pp.vehicle.Type() -} - -func (pp *ProxySetProvider) Type() ProviderType { +func (pp *proxySetProvider) Type() ProviderType { return Proxy } -func (pp *ProxySetProvider) Proxies() []C.Proxy { +func (pp *proxySetProvider) Proxies() []C.Proxy { return pp.proxies } -func (pp *ProxySetProvider) pullLoop() { - for range pp.ticker.C { - if err := pp.pull(); err != nil { - log.Warnln("[Provider] %s pull error: %s", pp.Name(), err.Error()) - } - } -} - -func (pp *ProxySetProvider) pull() error { - buf, err := pp.vehicle.Read() - if err != nil { - return err - } - - now := time.Now() - hash := md5.Sum(buf) - if bytes.Equal(pp.hash[:], hash[:]) { - log.Debugln("[Provider] %s's proxies doesn't change", pp.Name()) - pp.updatedAt = &now - return nil - } - - proxies, err := pp.parse(buf) - if err != nil { - return err - } - log.Infoln("[Provider] %s's proxies update", pp.Name()) - - if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil { - return err - } - - pp.updatedAt = &now - pp.hash = hash - pp.setProxies(proxies) - - return nil -} - -func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) { +func proxiesParse(buf []byte) (interface{}, error) { schema := &ProxySchema{} if err := yaml.Unmarshal(buf, schema); err != nil { @@ -239,38 +139,52 @@ func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) { return proxies, nil } -func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) { +func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { pp.proxies = proxies pp.healthCheck.setProxy(proxies) go pp.healthCheck.check() } -func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { - var ticker *time.Ticker - if interval != 0 { - ticker = time.NewTicker(interval) - } +func stopProxyProvider(pd *ProxySetProvider) { + pd.healthCheck.close() + pd.fetcher.Destroy() +} +func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider { if hc.auto() { go hc.process() } - return &ProxySetProvider{ - name: name, - vehicle: vehicle, + pd := &proxySetProvider{ proxies: []C.Proxy{}, healthCheck: hc, - ticker: ticker, } + + onUpdate := func(elm interface{}) { + ret := elm.([]C.Proxy) + pd.setProxies(ret) + } + + fetcher := newFetcher(name, interval, vehicle, proxiesParse, onUpdate) + pd.fetcher = fetcher + + wrapper := &ProxySetProvider{pd} + runtime.SetFinalizer(wrapper, stopProxyProvider) + return wrapper } +// for auto gc type CompatibleProvider struct { + *compatibleProvider +} + +type compatibleProvider struct { name string healthCheck *HealthCheck proxies []C.Proxy } -func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) { +func (cp *compatibleProvider) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ "name": cp.Name(), "type": cp.Type().String(), @@ -279,43 +193,38 @@ func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) { }) } -func (cp *CompatibleProvider) Name() string { +func (cp *compatibleProvider) Name() string { return cp.name } -func (cp *CompatibleProvider) Reload() error { - return nil -} - -func (cp *CompatibleProvider) Destroy() error { - cp.healthCheck.close() - return nil -} - -func (cp *CompatibleProvider) HealthCheck() { +func (cp *compatibleProvider) HealthCheck() { cp.healthCheck.check() } -func (cp *CompatibleProvider) Update() error { +func (cp *compatibleProvider) Update() error { return nil } -func (cp *CompatibleProvider) Initial() error { +func (cp *compatibleProvider) Initial() error { return nil } -func (cp *CompatibleProvider) VehicleType() VehicleType { +func (cp *compatibleProvider) VehicleType() VehicleType { return Compatible } -func (cp *CompatibleProvider) Type() ProviderType { +func (cp *compatibleProvider) Type() ProviderType { return Proxy } -func (cp *CompatibleProvider) Proxies() []C.Proxy { +func (cp *compatibleProvider) Proxies() []C.Proxy { return cp.proxies } +func stopCompatibleProvider(pd *CompatibleProvider) { + pd.healthCheck.close() +} + func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) { if len(proxies) == 0 { return nil, errors.New("Provider need one proxy at least") @@ -325,9 +234,13 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co go hc.process() } - return &CompatibleProvider{ + pd := &compatibleProvider{ name: name, proxies: proxies, healthCheck: hc, - }, nil + } + + wrapper := &CompatibleProvider{pd} + runtime.SetFinalizer(wrapper, stopCompatibleProvider) + return wrapper, nil } diff --git a/config/config.go b/config/config.go index 3c4821959b..0321629272 100644 --- a/config/config.go +++ b/config/config.go @@ -268,15 +268,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ providersConfig = cfg.ProxyProviderOld } - defer func() { - // Destroy already created provider when err != nil - if err != nil { - for _, provider := range providersMap { - provider.Destroy() - } - } - }() - proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) proxyList = append(proxyList, "DIRECT", "REJECT") diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 5916bfbb26..f073c1ce96 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -158,13 +158,6 @@ func updateHosts(tree *trie.Trie) { } func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) { - oldProviders := tunnel.Providers() - - // close providers goroutine - for _, provider := range oldProviders { - provider.Destroy() - } - tunnel.UpdateProxies(proxies, providers) } From d1fd57c432f05418b83cc48958c983fccd78db33 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 27 Apr 2020 21:23:03 +0800 Subject: [PATCH 382/535] Fix: select group can use provider real-time --- adapters/outboundgroup/selector.go | 31 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index 6ad78f209b..88ac815d68 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -14,12 +14,12 @@ import ( type Selector struct { *outbound.Base single *singledo.Single - selected C.Proxy + selected string providers []provider.ProxyProvider } func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := s.selected.DialContext(ctx, metadata) + c, err := s.selectedProxy().DialContext(ctx, metadata) if err == nil { c.AppendToChains(s) } @@ -27,7 +27,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con } func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := s.selected.DialUDP(metadata) + pc, err := s.selectedProxy().DialUDP(metadata) if err == nil { pc.AppendToChains(s) } @@ -35,12 +35,12 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } func (s *Selector) SupportUDP() bool { - return s.selected.SupportUDP() + return s.selectedProxy().SupportUDP() } func (s *Selector) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range s.proxies() { + for _, proxy := range getProvidersProxies(s.providers) { all = append(all, proxy.Name()) } @@ -52,13 +52,13 @@ func (s *Selector) MarshalJSON() ([]byte, error) { } func (s *Selector) Now() string { - return s.selected.Name() + return s.selectedProxy().Name() } func (s *Selector) Set(name string) error { - for _, proxy := range s.proxies() { + for _, proxy := range getProvidersProxies(s.providers) { if proxy.Name() == name { - s.selected = proxy + s.selected = name return nil } } @@ -66,16 +66,23 @@ func (s *Selector) Set(name string) error { return errors.New("Proxy does not exist") } -func (s *Selector) proxies() []C.Proxy { +func (s *Selector) selectedProxy() C.Proxy { elm, _, _ := s.single.Do(func() (interface{}, error) { - return getProvidersProxies(s.providers), nil + proxies := getProvidersProxies(s.providers) + for _, proxy := range proxies { + if proxy.Name() == s.selected { + return proxy, nil + } + } + + return proxies[0], nil }) - return elm.([]C.Proxy) + return elm.(C.Proxy) } func NewSelector(name string, providers []provider.ProxyProvider) *Selector { - selected := providers[0].Proxies()[0] + selected := providers[0].Proxies()[0].Name() return &Selector{ Base: outbound.NewBase(name, "", C.Selector, false), single: singledo.NewSingle(defaultGetProxiesDuration), From e5379558f6e2cde51fead0f29485a04b2df45fe3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 27 Apr 2020 21:28:24 +0800 Subject: [PATCH 383/535] Fix: redir-host should lookup hosts --- tunnel/tunnel.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 7dbb61e0e9..c5e4abab9e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -147,6 +147,9 @@ func preHandleMetadata(metadata *C.Metadata) error { metadata.AddrType = C.AtypDomainName if enhancedMode.FakeIPEnabled() { metadata.DstIP = nil + } else if node := resolver.DefaultHosts.Search(host); node != nil { + // redir-host should lookup the hosts + metadata.DstIP = node.Data.(net.IP) } } else if enhancedMode.IsFakeIP(metadata.DstIP) { return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) From 51b6b8521bffced130110e75a220de95d0899c09 Mon Sep 17 00:00:00 2001 From: comwrg Date: Mon, 27 Apr 2020 22:20:35 +0800 Subject: [PATCH 384/535] Fix: typo (#657) --- tunnel/connection.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tunnel/connection.go b/tunnel/connection.go index 65d45d4686..0af760cc10 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -18,8 +18,8 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { req := request.R host := req.Host - inboundReeder := bufio.NewReader(request) - outboundReeder := bufio.NewReader(outbound) + inboundReader := bufio.NewReader(request) + outboundReader := bufio.NewReader(outbound) for { keepAlive := strings.TrimSpace(strings.ToLower(req.Header.Get("Proxy-Connection"))) == "keep-alive" @@ -33,7 +33,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } handleResponse: - resp, err := http.ReadResponse(outboundReeder, req) + resp, err := http.ReadResponse(outboundReader, req) if err != nil { break } @@ -68,7 +68,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { break } - req, err = http.ReadRequest(inboundReeder) + req, err = http.ReadRequest(inboundReader) if err != nil { break } From 41a9488cfad4be2b7ae70c293857b18f8f40252f Mon Sep 17 00:00:00 2001 From: Richard Yu Date: Mon, 27 Apr 2020 22:23:09 +0800 Subject: [PATCH 385/535] Feature: add more command-line options (#656) add command-line options to override `external-controller`, `secret` and `external-ui` (#531) --- hub/hub.go | 27 ++++++++++++++++++++++++++- main.go | 33 ++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/hub/hub.go b/hub/hub.go index d15fa364c7..b7cdea27b1 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -3,15 +3,40 @@ package hub import ( "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/hub/route" + "github.com/Dreamacro/clash/config" ) +type Option func(*config.Config) + +func WithExternalUI(externalUI string) Option { + return func(cfg *config.Config) { + cfg.General.ExternalUI = externalUI + } +} + +func WithExternalController(externalController string) Option { + return func(cfg *config.Config) { + cfg.General.ExternalController = externalController + } +} + +func WithSecret(secret string) Option { + return func(cfg *config.Config) { + cfg.General.Secret = secret + } +} + // Parse call at the beginning of clash -func Parse() error { +func Parse(options ...Option) error { cfg, err := executor.Parse() if err != nil { return err } + for _, option := range options { + option(cfg) + } + if cfg.General.ExternalUI != "" { route.SetUIPath(cfg.General.ExternalUI) } diff --git a/main.go b/main.go index ec705b6ef7..28229bfb1c 100644 --- a/main.go +++ b/main.go @@ -18,18 +18,30 @@ import ( ) var ( - version bool - testConfig bool - homeDir string - configFile string + flagset map[string]bool + version bool + testConfig bool + homeDir string + configFile string + externalUI string + externalController string + secret string ) func init() { flag.StringVar(&homeDir, "d", "", "set configuration directory") flag.StringVar(&configFile, "f", "", "specify configuration file") + flag.StringVar(&externalUI, "ext-ui", "", "override external ui directory") + flag.StringVar(&externalController, "ext-ctl", "", "override external controller address") + flag.StringVar(&secret, "secret", "", "override secret for RESTful API") flag.BoolVar(&version, "v", false, "show current version of clash") flag.BoolVar(&testConfig, "t", false, "test configuration and exit") flag.Parse() + + flagset = map[string]bool{} + flag.Visit(func(f *flag.Flag) { + flagset[f.Name] = true + }) } func main() { @@ -71,7 +83,18 @@ func main() { return } - if err := hub.Parse(); err != nil { + var options []hub.Option + if flagset["ext-ui"] { + options = append(options, hub.WithExternalUI(externalUI)) + } + if flagset["ext-ctl"] { + options = append(options, hub.WithExternalController(externalController)) + } + if flagset["secret"] { + options = append(options, hub.WithSecret(secret)) + } + + if err := hub.Parse(options...); err != nil { log.Fatalln("Parse config error: %s", err.Error()) } From 7d51ab5846b1b298bbc5de99d4bd9a158d4b9015 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Wed, 29 Apr 2020 11:21:37 +0800 Subject: [PATCH 386/535] Fix: dns return empty success for AAAA & recursion in fake ip mode (#663) --- dns/middleware.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dns/middleware.go b/dns/middleware.go index 90d5a7fea4..4d97e65d59 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -18,7 +18,14 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { q := r.Question[0] if q.Qtype == D.TypeAAAA { - D.HandleFailed(w, r) + msg := &D.Msg{} + msg.Answer = []D.RR{} + + msg.SetRcode(r, D.RcodeSuccess) + msg.Authoritative = true + msg.RecursionAvailable = true + + w.WriteMsg(msg) return } else if q.Qtype != D.TypeA { next(w, r) @@ -39,8 +46,10 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { msg.Answer = []D.RR{rr} setMsgTTL(msg, 1) - msg.SetRcode(r, msg.Rcode) + msg.SetRcode(r, D.RcodeSuccess) msg.Authoritative = true + msg.RecursionAvailable = true + w.WriteMsg(msg) return } From 94e0e4b0008c49d074b0797aa12b49978391ffc6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 30 Apr 2020 20:13:27 +0800 Subject: [PATCH 387/535] Fix: make selector `react immediately` --- adapters/outboundgroup/selector.go | 1 + common/singledo/singledo.go | 4 ++++ common/singledo/singledo_test.go | 19 +++++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index 88ac815d68..1215dadbb8 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -59,6 +59,7 @@ func (s *Selector) Set(name string) error { for _, proxy := range getProvidersProxies(s.providers) { if proxy.Name() == name { s.selected = name + s.single.Reset() return nil } } diff --git a/common/singledo/singledo.go b/common/singledo/singledo.go index 849781151a..58a9543468 100644 --- a/common/singledo/singledo.go +++ b/common/singledo/singledo.go @@ -50,6 +50,10 @@ func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, s return call.val, call.err, false } +func (s *Single) Reset() { + s.last = time.Time{} +} + func NewSingle(wait time.Duration) *Single { return &Single{wait: wait} } diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go index c1e48ca85e..637557c40b 100644 --- a/common/singledo/singledo_test.go +++ b/common/singledo/singledo_test.go @@ -19,7 +19,7 @@ func TestBasic(t *testing.T) { } var wg sync.WaitGroup - const n = 10 + const n = 5 wg.Add(n) for i := 0; i < n; i++ { go func() { @@ -33,7 +33,7 @@ func TestBasic(t *testing.T) { wg.Wait() assert.Equal(t, 1, foo) - assert.Equal(t, 9, shardCount) + assert.Equal(t, 4, shardCount) } func TestTimer(t *testing.T) { @@ -51,3 +51,18 @@ func TestTimer(t *testing.T) { assert.Equal(t, 1, foo) assert.True(t, shard) } + +func TestReset(t *testing.T) { + single := NewSingle(time.Millisecond * 30) + foo := 0 + call := func() (interface{}, error) { + foo++ + return nil, nil + } + + single.Do(call) + single.Reset() + single.Do(call) + + assert.Equal(t, 2, foo) +} From b085addbb029c56c57d017d58db4ccea736db67a Mon Sep 17 00:00:00 2001 From: Kr328 Date: Tue, 5 May 2020 12:39:25 +0800 Subject: [PATCH 388/535] Fix: use domain first on direct dial (#672) --- adapters/outbound/direct.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index f9413e6eec..9e896c3acd 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -14,10 +14,7 @@ type Direct struct { } func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - address := net.JoinHostPort(metadata.Host, metadata.DstPort) - if metadata.DstIP != nil { - address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort) - } + address := net.JoinHostPort(metadata.String(), metadata.DstPort) c, err := dialer.DialContext(ctx, "tcp", address) if err != nil { From b979ff0bc2a93c89aa8b0a73b42d5ab642e539ca Mon Sep 17 00:00:00 2001 From: Comzyh Date: Thu, 7 May 2020 15:10:14 +0800 Subject: [PATCH 389/535] Feature: implemented a strategy similar to optimistic DNS (#647) --- common/cache/lrucache.go | 81 +++++++++++++++++++++++++---------- common/cache/lrucache_test.go | 28 ++++++++++++ dns/resolver.go | 43 ++++++++++++------- dns/util.go | 12 +++--- 4 files changed, 120 insertions(+), 44 deletions(-) diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 6fe1d74cb4..1b1e492acc 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -42,6 +42,14 @@ func WithSize(maxSize int) Option { } } +// WithStale decide whether Stale return is enabled. +// If this feature is enabled, element will not get Evicted according to `WithAge`. +func WithStale(stale bool) Option { + return func(l *LruCache) { + l.staleReturn = stale + } +} + // LruCache is a thread-safe, in-memory lru-cache that evicts the // least recently used entries from memory when (if set) the entries are // older than maxAge (in seconds). Use the New constructor to create one. @@ -52,6 +60,7 @@ type LruCache struct { cache map[interface{}]*list.Element lru *list.List // Front is least-recent updateAgeOnGet bool + staleReturn bool onEvict EvictCallback } @@ -72,29 +81,26 @@ func NewLRUCache(options ...Option) *LruCache { // Get returns the interface{} representation of a cached response and a bool // set to true if the key was found. func (c *LruCache) Get(key interface{}) (interface{}, bool) { - c.mu.Lock() - defer c.mu.Unlock() - - le, ok := c.cache[key] - if !ok { + entry := c.get(key) + if entry == nil { return nil, false } + value := entry.value - if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() { - c.deleteElement(le) - c.maybeDeleteOldest() - - return nil, false - } + return value, true +} - c.lru.MoveToBack(le) - entry := le.Value.(*entry) - if c.maxAge > 0 && c.updateAgeOnGet { - entry.expires = time.Now().Unix() + c.maxAge +// GetWithExpire returns the interface{} representation of a cached response, +// a time.Time Give expected expires, +// and a bool set to true if the key was found. +// This method will NOT check the maxAge of element and will NOT update the expires. +func (c *LruCache) GetWithExpire(key interface{}) (interface{}, time.Time, bool) { + entry := c.get(key) + if entry == nil { + return nil, time.Time{}, false } - value := entry.value - return value, true + return entry.value, time.Unix(entry.expires, 0), true } // Exist returns if key exist in cache but not put item to the head of linked list @@ -108,21 +114,26 @@ func (c *LruCache) Exist(key interface{}) bool { // Set stores the interface{} representation of a response for a given key. func (c *LruCache) Set(key interface{}, value interface{}) { - c.mu.Lock() - defer c.mu.Unlock() - expires := int64(0) if c.maxAge > 0 { expires = time.Now().Unix() + c.maxAge } + c.SetWithExpire(key, value, time.Unix(expires, 0)) +} + +// SetWithExpire stores the interface{} representation of a response for a given key and given exires. +// The expires time will round to second. +func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) { + c.mu.Lock() + defer c.mu.Unlock() if le, ok := c.cache[key]; ok { c.lru.MoveToBack(le) e := le.Value.(*entry) e.value = value - e.expires = expires + e.expires = expires.Unix() } else { - e := &entry{key: key, value: value, expires: expires} + e := &entry{key: key, value: value, expires: expires.Unix()} c.cache[key] = c.lru.PushBack(e) if c.maxSize > 0 { @@ -135,6 +146,30 @@ func (c *LruCache) Set(key interface{}, value interface{}) { c.maybeDeleteOldest() } +func (c *LruCache) get(key interface{}) *entry { + c.mu.Lock() + defer c.mu.Unlock() + + le, ok := c.cache[key] + if !ok { + return nil + } + + if !c.staleReturn && c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() { + c.deleteElement(le) + c.maybeDeleteOldest() + + return nil + } + + c.lru.MoveToBack(le) + entry := le.Value.(*entry) + if c.maxAge > 0 && c.updateAgeOnGet { + entry.expires = time.Now().Unix() + c.maxAge + } + return entry +} + // Delete removes the value associated with a key. func (c *LruCache) Delete(key string) { c.mu.Lock() @@ -147,7 +182,7 @@ func (c *LruCache) Delete(key string) { } func (c *LruCache) maybeDeleteOldest() { - if c.maxAge > 0 { + if !c.staleReturn && c.maxAge > 0 { now := time.Now().Unix() for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() { c.deleteElement(le) diff --git a/common/cache/lrucache_test.go b/common/cache/lrucache_test.go index b296d6b984..c3c629d905 100644 --- a/common/cache/lrucache_test.go +++ b/common/cache/lrucache_test.go @@ -136,3 +136,31 @@ func TestEvict(t *testing.T) { assert.Equal(t, temp, 3) } + +func TestSetWithExpire(t *testing.T) { + c := NewLRUCache(WithAge(1)) + now := time.Now().Unix() + + tenSecBefore := time.Unix(now-10, 0) + c.SetWithExpire(1, 2, tenSecBefore) + + // res is expected not to exist, and expires should be empty time.Time + res, expires, exist := c.GetWithExpire(1) + assert.Equal(t, nil, res) + assert.Equal(t, time.Time{}, expires) + assert.Equal(t, false, exist) + +} + +func TestStale(t *testing.T) { + c := NewLRUCache(WithAge(1), WithStale(true)) + now := time.Now().Unix() + + tenSecBefore := time.Unix(now-10, 0) + c.SetWithExpire(1, 2, tenSecBefore) + + res, expires, exist := c.GetWithExpire(1) + assert.Equal(t, 2, res) + assert.Equal(t, tenSecBefore, expires) + assert.Equal(t, true, exist) +} diff --git a/dns/resolver.go b/dns/resolver.go index eec7d412c0..7f50a1a0e1 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -42,7 +42,7 @@ type Resolver struct { fallback []dnsClient fallbackFilters []fallbackFilter group singleflight.Group - cache *cache.Cache + lruCache *cache.LruCache } // ResolveIP request with TypeA and TypeAAAA, priority return TypeA @@ -96,22 +96,35 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { } q := m.Question[0] - cache, expireTime := r.cache.GetWithExpire(q.String()) - if cache != nil { + cache, expireTime, hit := r.lruCache.GetWithExpire(q.String()) + if hit { + now := time.Now() msg = cache.(*D.Msg).Copy() - setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) + if expireTime.Before(now) { + setMsgTTL(msg, uint32(1)) // Continue fetch + go r.exchangeWithoutCache(m) + } else { + setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) + } return } + return r.exchangeWithoutCache(m) +} + +// ExchangeWithoutCache a batch of dns request, and it do NOT GET from cache +func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { + q := m.Question[0] + defer func() { if msg == nil { return } - putMsgToCache(r.cache, q.String(), msg) + putMsgToCache(r.lruCache, q.String(), msg) if r.mapping { ips := r.msgToIP(msg) for _, ip := range ips { - putMsgToCache(r.cache, ip.String(), msg) + putMsgToCache(r.lruCache, ip.String(), msg) } } }() @@ -141,7 +154,7 @@ func (r *Resolver) IPToHost(ip net.IP) (string, bool) { return r.pool.LookBack(ip) } - cache := r.cache.Get(ip.String()) + cache, _ := r.lruCache.Get(ip.String()) if cache == nil { return "", false } @@ -294,17 +307,17 @@ type Config struct { func New(config Config) *Resolver { defaultResolver := &Resolver{ - main: transform(config.Default, nil), - cache: cache.New(time.Second * 60), + main: transform(config.Default, nil), + lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)), } r := &Resolver{ - ipv6: config.IPv6, - main: transform(config.Main, defaultResolver), - cache: cache.New(time.Second * 60), - mapping: config.EnhancedMode == MAPPING, - fakeip: config.EnhancedMode == FAKEIP, - pool: config.Pool, + ipv6: config.IPv6, + main: transform(config.Main, defaultResolver), + lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)), + mapping: config.EnhancedMode == MAPPING, + fakeip: config.EnhancedMode == FAKEIP, + pool: config.Pool, } if len(config.Fallback) != 0 { diff --git a/dns/util.go b/dns/util.go index b1541091cb..b6adf8d64a 100644 --- a/dns/util.go +++ b/dns/util.go @@ -79,21 +79,21 @@ func (e EnhancedMode) String() string { } } -func putMsgToCache(c *cache.Cache, key string, msg *D.Msg) { - var ttl time.Duration +func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) { + var ttl uint32 switch { case len(msg.Answer) != 0: - ttl = time.Duration(msg.Answer[0].Header().Ttl) * time.Second + ttl = msg.Answer[0].Header().Ttl case len(msg.Ns) != 0: - ttl = time.Duration(msg.Ns[0].Header().Ttl) * time.Second + ttl = msg.Ns[0].Header().Ttl case len(msg.Extra) != 0: - ttl = time.Duration(msg.Extra[0].Header().Ttl) * time.Second + ttl = msg.Extra[0].Header().Ttl default: log.Debugln("[DNS] response msg error: %#v", msg) return } - c.Put(key, msg.Copy(), ttl) + c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl))) } func setMsgTTL(msg *D.Msg, ttl uint32) { From 752f87a8dc3c34c4a603f486c7acfa41d90cbb54 Mon Sep 17 00:00:00 2001 From: duama <30264485+duament@users.noreply.github.com> Date: Thu, 7 May 2020 21:42:52 +0800 Subject: [PATCH 390/535] Feature: support proxy-group in relay (#597) --- README.md | 2 +- adapters/outbound/base.go | 4 ++++ adapters/outboundgroup/fallback.go | 5 ++++ adapters/outboundgroup/loadbalance.go | 33 ++++++++++++--------------- adapters/outboundgroup/relay.go | 20 +++++++++++++--- adapters/outboundgroup/selector.go | 4 ++++ adapters/outboundgroup/urltest.go | 4 ++++ constant/adapters.go | 2 ++ 8 files changed, 52 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f1d8165ee2..7446116ba7 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ proxies: # skip-cert-verify: true proxy-groups: - # relay chains the proxies. proxies shall not contain a proxy-group. No UDP support. + # relay chains the proxies. proxies shall not contain a relay. No UDP support. # Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet - name: "relay" type: relay diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 48576647a6..dddd852ea5 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -53,6 +53,10 @@ func (b *Base) Addr() string { return b.addr } +func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy { + return nil +} + func NewBase(name string, addr string, tp C.AdapterType, udp bool) *Base { return &Base{name, addr, tp, udp} } diff --git a/adapters/outboundgroup/fallback.go b/adapters/outboundgroup/fallback.go index 2fb486492f..acf577e1f7 100644 --- a/adapters/outboundgroup/fallback.go +++ b/adapters/outboundgroup/fallback.go @@ -56,6 +56,11 @@ func (f *Fallback) MarshalJSON() ([]byte, error) { }) } +func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { + proxy := f.findAliveProxy() + return proxy +} + func (f *Fallback) proxies() []C.Proxy { elm, _, _ := f.single.Do(func() (interface{}, error) { return getProvidersProxies(f.providers), nil diff --git a/adapters/outboundgroup/loadbalance.go b/adapters/outboundgroup/loadbalance.go index 9e07010571..8a7fe974c1 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -59,18 +59,9 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c } }() - key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) - proxies := lb.proxies() - buckets := int32(len(proxies)) - for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { - idx := jumpHash(key, buckets) - proxy := proxies[idx] - if proxy.Alive() { - c, err = proxy.DialContext(ctx, metadata) - return - } - } - c, err = proxies[0].DialContext(ctx, metadata) + proxy := lb.Unwrap(metadata) + + c, err = proxy.DialContext(ctx, metadata) return } @@ -81,6 +72,16 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error } }() + proxy := lb.Unwrap(metadata) + + return proxy.DialUDP(metadata) +} + +func (lb *LoadBalance) SupportUDP() bool { + return true +} + +func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) proxies := lb.proxies() buckets := int32(len(proxies)) @@ -88,15 +89,11 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error idx := jumpHash(key, buckets) proxy := proxies[idx] if proxy.Alive() { - return proxy.DialUDP(metadata) + return proxy } } - return proxies[0].DialUDP(metadata) -} - -func (lb *LoadBalance) SupportUDP() bool { - return true + return proxies[0] } func (lb *LoadBalance) proxies() []C.Proxy { diff --git a/adapters/outboundgroup/relay.go b/adapters/outboundgroup/relay.go index 37a57e006d..eb9b8eb8a3 100644 --- a/adapters/outboundgroup/relay.go +++ b/adapters/outboundgroup/relay.go @@ -20,7 +20,7 @@ type Relay struct { } func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - proxies := r.proxies() + proxies := r.proxies(metadata) if len(proxies) == 0 { return nil, errors.New("Proxy does not exist") } @@ -58,7 +58,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, func (r *Relay) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range r.proxies() { + for _, proxy := range r.rawProxies() { all = append(all, proxy.Name()) } return json.Marshal(map[string]interface{}{ @@ -67,7 +67,7 @@ func (r *Relay) MarshalJSON() ([]byte, error) { }) } -func (r *Relay) proxies() []C.Proxy { +func (r *Relay) rawProxies() []C.Proxy { elm, _, _ := r.single.Do(func() (interface{}, error) { return getProvidersProxies(r.providers), nil }) @@ -75,6 +75,20 @@ func (r *Relay) proxies() []C.Proxy { return elm.([]C.Proxy) } +func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy { + proxies := r.rawProxies() + + for n, proxy := range proxies { + subproxy := proxy.Unwrap(metadata) + for subproxy != nil { + proxies[n] = subproxy + subproxy = subproxy.Unwrap(metadata) + } + } + + return proxies +} + func NewRelay(name string, providers []provider.ProxyProvider) *Relay { return &Relay{ Base: outbound.NewBase(name, "", C.Relay, false), diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index 1215dadbb8..239abcf742 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -67,6 +67,10 @@ func (s *Selector) Set(name string) error { return errors.New("Proxy does not exist") } +func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { + return s.selectedProxy() +} + func (s *Selector) selectedProxy() C.Proxy { elm, _, _ := s.single.Do(func() (interface{}, error) { proxies := getProvidersProxies(s.providers) diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index bfdb20e997..ab1e2db3cb 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -38,6 +38,10 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { return pc, err } +func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy { + return u.fast() +} + func (u *URLTest) proxies() []C.Proxy { elm, _, _ := u.single.Do(func() (interface{}, error) { return getProvidersProxies(u.providers), nil diff --git a/constant/adapters.go b/constant/adapters.go index 7900117c5e..be2d0e70e0 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -69,6 +69,8 @@ type ProxyAdapter interface { SupportUDP() bool MarshalJSON() ([]byte, error) Addr() string + // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. + Unwrap(metadata *Metadata) Proxy } type DelayHistory struct { From 646bd4eeb4bbea6628987be55a167c45e718f77a Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 7 May 2020 21:58:53 +0800 Subject: [PATCH 391/535] Chore: update dependencies and README.md --- README.md | 3 ++- go.mod | 8 ++++---- go.sum | 31 ++++++++++--------------------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7446116ba7..96f907d8bf 100644 --- a/README.md +++ b/README.md @@ -120,9 +120,10 @@ experimental: # - "user2:pass2" # # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) -# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com) +# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com) # hosts: # '*.clash.dev': 127.0.0.1 +# '.dev': 127.0.0.1 # 'alpha.clash.dev': '::1' # dns: diff --git a/go.mod b/go.mod index 29550a4752..f4bc0ab888 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,14 @@ require ( github.com/go-chi/chi v4.1.1+incompatible github.com/go-chi/cors v1.1.1 github.com/go-chi/render v1.0.1 - github.com/gofrs/uuid v3.2.0+incompatible + github.com/gofrs/uuid v3.3.0+incompatible github.com/gorilla/websocket v1.4.2 github.com/miekg/dns v1.1.29 github.com/oschwald/geoip2-golang v1.4.0 - github.com/sirupsen/logrus v1.5.0 + github.com/sirupsen/logrus v1.6.0 github.com/stretchr/testify v1.5.1 - golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 - golang.org/x/net v0.0.0-20200421231249-e086a090c8fd + golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 + golang.org/x/net v0.0.0-20200506145744-7e3656a0809f golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.2.8 diff --git a/go.sum b/go.sum index 24898afdaf..ead335a01c 100644 --- a/go.sum +++ b/go.sum @@ -11,12 +11,12 @@ github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= @@ -25,41 +25,31 @@ github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7 github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= 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/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= -github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= -golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= -golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/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/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -70,7 +60,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 3638b077cdc57cfe84227b989cf2788513f05a65 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 8 May 2020 21:52:17 +0800 Subject: [PATCH 392/535] Chore: update premium link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96f907d8bf..3c906c33e6 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ $ go get -u -v github.com/Dreamacro/clash ``` Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) -Pre-built TUN mode binaries are available here: [TUN release](https://github.com/Dreamacro/clash/releases/tag/TUN). Source is not currently available. +Pre-built Premium binaries are available here: [Premium release](https://github.com/Dreamacro/clash/releases/tag/premium). Source is not currently available. Check Clash version with: From 3a27cfc4a13050a06c254b08acbc010eda045920 Mon Sep 17 00:00:00 2001 From: bytew021 <34959513+bytew021@users.noreply.github.com> Date: Tue, 12 May 2020 11:29:53 +0800 Subject: [PATCH 393/535] Feature: add Mixed(http+socks5) proxy listening (#685) --- component/socks5/socks5.go | 2 ++ config/config.go | 4 +++ hub/executor/executor.go | 5 +++ hub/route/configs.go | 2 ++ proxy/http/server.go | 4 +-- proxy/listener.go | 56 +++++++++++++++++++++++++++++++ proxy/mixed/conn.go | 41 ++++++++++++++++++++++ proxy/mixed/mixed.go | 69 ++++++++++++++++++++++++++++++++++++++ proxy/socks/tcp.go | 8 +++-- 9 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 proxy/mixed/conn.go create mode 100644 proxy/mixed/mixed.go diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index 76be1286eb..6c58a848c1 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -21,6 +21,8 @@ func (err Error) Error() string { // Command is request commands as defined in RFC 1928 section 4. type Command = uint8 +const Version = 5 + // SOCKS request commands as defined in RFC 1928 section 4. const ( CmdConnect Command = 1 diff --git a/config/config.go b/config/config.go index 0321629272..473e404ca1 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ type General struct { Port int `json:"port"` SocksPort int `json:"socks-port"` RedirPort int `json:"redir-port"` + MixedPort int `json:"mixed-port"` Authentication []string `json:"authentication"` AllowLan bool `json:"allow-lan"` BindAddress string `json:"bind-address"` @@ -97,6 +98,7 @@ type RawConfig struct { Port int `yaml:"port"` SocksPort int `yaml:"socks-port"` RedirPort int `yaml:"redir-port"` + MixedPort int `yaml:"mixed-port"` Authentication []string `yaml:"authentication"` AllowLan bool `yaml:"allow-lan"` BindAddress string `yaml:"bind-address"` @@ -217,6 +219,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { port := cfg.Port socksPort := cfg.SocksPort redirPort := cfg.RedirPort + mixedPort := cfg.MixedPort allowLan := cfg.AllowLan bindAddress := cfg.BindAddress externalController := cfg.ExternalController @@ -237,6 +240,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Port: port, SocksPort: socksPort, RedirPort: redirPort, + MixedPort: mixedPort, AllowLan: allowLan, BindAddress: bindAddress, Mode: mode, diff --git a/hub/executor/executor.go b/hub/executor/executor.go index f073c1ce96..ab84884889 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -99,6 +99,7 @@ func GetGeneral() *config.General { Port: ports.Port, SocksPort: ports.SocksPort, RedirPort: ports.RedirPort, + MixedPort: ports.MixedPort, Authentication: authenticator, AllowLan: P.AllowLan(), BindAddress: P.BindAddress(), @@ -186,6 +187,10 @@ func updateGeneral(general *config.General) { if err := P.ReCreateRedir(general.RedirPort); err != nil { log.Errorln("Start Redir server error: %s", err.Error()) } + + if err := P.ReCreateMixed(general.MixedPort); err != nil { + log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) + } } func updateUsers(users []auth.AuthUser) { diff --git a/hub/route/configs.go b/hub/route/configs.go index dd97a840b4..c551d66729 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -26,6 +26,7 @@ type configSchema struct { Port *int `json:"port"` SocksPort *int `json:"socks-port"` RedirPort *int `json:"redir-port"` + MixedPort *int `json:"mixed-port"` AllowLan *bool `json:"allow-lan"` BindAddress *string `json:"bind-address"` Mode *tunnel.TunnelMode `json:"mode"` @@ -65,6 +66,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) + P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) if general.Mode != nil { tunnel.SetMode(*general.Mode) diff --git a/proxy/http/server.go b/proxy/http/server.go index de7e0fca52..1791f62ede 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -41,7 +41,7 @@ func NewHttpProxy(addr string) (*HttpListener, error) { } continue } - go handleConn(c, hl.cache) + go HandleConn(c, hl.cache) } }() @@ -69,7 +69,7 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache return } -func handleConn(conn net.Conn, cache *cache.Cache) { +func HandleConn(conn net.Conn, cache *cache.Cache) { br := bufio.NewReader(conn) request, err := http.ReadRequest(br) if err != nil || request.URL.Host == "" { diff --git a/proxy/listener.go b/proxy/listener.go index 1089e39dbb..5eda54266b 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -7,6 +7,7 @@ import ( "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/mixed" "github.com/Dreamacro/clash/proxy/redir" "github.com/Dreamacro/clash/proxy/socks" ) @@ -20,6 +21,8 @@ var ( httpListener *http.HttpListener redirListener *redir.RedirListener redirUDPListener *redir.RedirUDPListener + mixedListener *mixed.MixedListener + mixedUDPLister *socks.SockUDPListener ) type listener interface { @@ -31,6 +34,7 @@ type Ports struct { Port int `json:"port"` SocksPort int `json:"socks-port"` RedirPort int `json:"redir-port"` + MixedPort int `json:"mixed-port"` } func AllowLan() bool { @@ -159,6 +163,52 @@ func ReCreateRedir(port int) error { return nil } +func ReCreateMixed(port int) error { + addr := genAddr(bindAddress, port, allowLan) + + shouldTCPIgnore := false + shouldUDPIgnore := false + + if mixedListener != nil { + if mixedListener.Address() != addr { + mixedListener.Close() + mixedListener = nil + } else { + shouldTCPIgnore = true + } + } + if mixedUDPLister != nil { + if mixedUDPLister.Address() != addr { + mixedUDPLister.Close() + mixedUDPLister = nil + } else { + shouldUDPIgnore = true + } + } + + if shouldTCPIgnore && shouldUDPIgnore { + return nil + } + + if portIsZero(addr) { + return nil + } + + var err error + mixedListener, err = mixed.NewMixedProxy(addr) + if err != nil { + return err + } + + mixedUDPLister, err = socks.NewSocksUDPProxy(addr) + if err != nil { + mixedListener.Close() + return err + } + + return nil +} + // GetPorts return the ports of proxy servers func GetPorts() *Ports { ports := &Ports{} @@ -181,6 +231,12 @@ func GetPorts() *Ports { ports.RedirPort = port } + if mixedListener != nil { + _, portStr, _ := net.SplitHostPort(mixedListener.Address()) + port, _ := strconv.Atoi(portStr) + ports.MixedPort = port + } + return ports } diff --git a/proxy/mixed/conn.go b/proxy/mixed/conn.go new file mode 100644 index 0000000000..f009a0060a --- /dev/null +++ b/proxy/mixed/conn.go @@ -0,0 +1,41 @@ +package mixed + +import ( + "bufio" + "net" +) + +type BufferedConn struct { + r *bufio.Reader + net.Conn +} + +func NewBufferedConn(c net.Conn) *BufferedConn { + return &BufferedConn{bufio.NewReader(c), c} +} + +// Reader returns the internal bufio.Reader. +func (c *BufferedConn) Reader() *bufio.Reader { + return c.r +} + +// Peek returns the next n bytes without advancing the reader. +func (c *BufferedConn) Peek(n int) ([]byte, error) { + return c.r.Peek(n) +} + +func (c *BufferedConn) Read(p []byte) (int, error) { + return c.r.Read(p) +} + +func (c *BufferedConn) ReadByte() (byte, error) { + return c.r.ReadByte() +} + +func (c *BufferedConn) UnreadByte() error { + return c.r.UnreadByte() +} + +func (c *BufferedConn) Buffered() int { + return c.r.Buffered() +} diff --git a/proxy/mixed/mixed.go b/proxy/mixed/mixed.go new file mode 100644 index 0000000000..3328724e17 --- /dev/null +++ b/proxy/mixed/mixed.go @@ -0,0 +1,69 @@ +package mixed + +import ( + "net" + "time" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/socks5" + "github.com/Dreamacro/clash/log" + + "github.com/Dreamacro/clash/proxy/http" + "github.com/Dreamacro/clash/proxy/socks" +) + +type MixedListener struct { + net.Listener + address string + closed bool + cache *cache.Cache +} + +func NewMixedProxy(addr string) (*MixedListener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + ml := &MixedListener{l, addr, false, cache.New(30 * time.Second)} + go func() { + log.Infoln("Mixed(http+socks5) proxy listening at: %s", addr) + + for { + c, err := ml.Accept() + if err != nil { + if ml.closed { + break + } + continue + } + go handleConn(c, ml.cache) + } + }() + + return ml, nil +} + +func (l *MixedListener) Close() { + l.closed = true + l.Listener.Close() +} + +func (l *MixedListener) Address() string { + return l.address +} + +func handleConn(conn net.Conn, cache *cache.Cache) { + bufConn := NewBufferedConn(conn) + head, err := bufConn.Peek(1) + if err != nil { + return + } + + if head[0] == socks5.Version { + socks.HandleSocks(bufConn) + return + } + + http.HandleConn(bufConn, cache) +} diff --git a/proxy/socks/tcp.go b/proxy/socks/tcp.go index 50317177bb..0f6c4fb4f0 100644 --- a/proxy/socks/tcp.go +++ b/proxy/socks/tcp.go @@ -36,7 +36,7 @@ func NewSocksProxy(addr string) (*SockListener, error) { } continue } - go handleSocks(c) + go HandleSocks(c) } }() @@ -52,13 +52,15 @@ func (l *SockListener) Address() string { return l.address } -func handleSocks(conn net.Conn) { +func HandleSocks(conn net.Conn) { target, command, err := socks5.ServerHandshake(conn, authStore.Authenticator()) if err != nil { conn.Close() return } - conn.(*net.TCPConn).SetKeepAlive(true) + if c, ok := conn.(*net.TCPConn); ok { + c.SetKeepAlive(true) + } if command == socks5.CmdUDPAssociate { defer conn.Close() io.Copy(ioutil.Discard, conn) From 5073c3cde8dd8d9f1e9c1382b26f13f324546dba Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 20 May 2020 15:13:33 +0800 Subject: [PATCH 394/535] Chore: add trimpath for go build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ce0626b986..f4c981954b 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME=clash BINDIR=bin VERSION=$(shell git describe --tags || echo "unknown version") BUILDTIME=$(shell date -u) -GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ +GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ -w -s' From 8d0c6c6e661e2fc6740bffc9a0ad3a0a2196d0a3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 28 May 2020 12:13:05 +0800 Subject: [PATCH 395/535] Feature: domain trie support wildcard alias --- README.md | 4 ++- component/fakeip/pool.go | 6 ++-- component/resolver/resolver.go | 2 +- .../{domain-trie/tire.go => trie/domain.go} | 33 +++++++++++++------ .../trie_test.go => trie/domain_test.go} | 3 ++ component/{domain-trie => trie}/node.go | 0 config/config.go | 8 ++--- hub/executor/executor.go | 4 +-- hub/hub.go | 2 +- 9 files changed, 40 insertions(+), 22 deletions(-) rename component/{domain-trie/tire.go => trie/domain.go} (75%) rename component/{domain-trie/trie_test.go => trie/domain_test.go} (94%) rename component/{domain-trie => trie}/node.go (100%) diff --git a/README.md b/README.md index 3c906c33e6..768cbbf4fb 100644 --- a/README.md +++ b/README.md @@ -119,12 +119,14 @@ experimental: # - "user1:pass1" # - "user2:pass2" -# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) +# # hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) # # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com) +# # +.foo.com equal .foo.com and foo.com # hosts: # '*.clash.dev': 127.0.0.1 # '.dev': 127.0.0.1 # 'alpha.clash.dev': '::1' +# '+.foo.dev': 127.0.0.1 # dns: # enable: true # set true to enable dns (default is false) diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index b92b55b395..be7bdd8d1c 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/Dreamacro/clash/common/cache" - trie "github.com/Dreamacro/clash/component/domain-trie" + "github.com/Dreamacro/clash/component/trie" ) // Pool is a implementation about fake ip generator without storage @@ -16,7 +16,7 @@ type Pool struct { gateway uint32 offset uint32 mux sync.Mutex - host *trie.Trie + host *trie.DomainTrie cache *cache.LruCache } @@ -120,7 +120,7 @@ func uintToIP(v uint32) net.IP { } // New return Pool instance -func New(ipnet *net.IPNet, size int, host *trie.Trie) (*Pool, error) { +func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) { min := ipToUint(ipnet.IP) + 2 ones, bits := ipnet.Mask.Size() diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index f7fc1e8981..da9fed21d5 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -5,7 +5,7 @@ import ( "net" "strings" - trie "github.com/Dreamacro/clash/component/domain-trie" + "github.com/Dreamacro/clash/component/trie" ) var ( diff --git a/component/domain-trie/tire.go b/component/trie/domain.go similarity index 75% rename from component/domain-trie/tire.go rename to component/trie/domain.go index e7322e5d82..5dc605533c 100644 --- a/component/domain-trie/tire.go +++ b/component/trie/domain.go @@ -6,9 +6,10 @@ import ( ) const ( - wildcard = "*" - dotWildcard = "" - domainStep = "." + wildcard = "*" + dotWildcard = "" + complexWildcard = "+" + domainStep = "." ) var ( @@ -16,9 +17,9 @@ var ( ErrInvalidDomain = errors.New("invalid domain") ) -// Trie contains the main logic for adding and searching nodes for domain segments. +// DomainTrie contains the main logic for adding and searching nodes for domain segments. // support wildcard domain (e.g *.google.com) -type Trie struct { +type DomainTrie struct { root *Node } @@ -47,12 +48,25 @@ func validAndSplitDomain(domain string) ([]string, bool) { // 2. *.example.com // 3. subdomain.*.example.com // 4. .example.com -func (t *Trie) Insert(domain string, data interface{}) error { +// 5. +.example.com +func (t *DomainTrie) Insert(domain string, data interface{}) error { parts, valid := validAndSplitDomain(domain) if !valid { return ErrInvalidDomain } + if parts[0] == complexWildcard { + t.insert(parts[1:], data) + parts[0] = dotWildcard + t.insert(parts, data) + } else { + t.insert(parts, data) + } + + return nil +} + +func (t *DomainTrie) insert(parts []string, data interface{}) { node := t.root // reverse storage domain part to save space for i := len(parts) - 1; i >= 0; i-- { @@ -65,7 +79,6 @@ func (t *Trie) Insert(domain string, data interface{}) error { } node.Data = data - return nil } // Search is the most important part of the Trie. @@ -73,7 +86,7 @@ func (t *Trie) Insert(domain string, data interface{}) error { // 1. static part // 2. wildcard domain // 2. dot wildcard domain -func (t *Trie) Search(domain string) *Node { +func (t *DomainTrie) Search(domain string) *Node { parts, valid := validAndSplitDomain(domain) if !valid || parts[0] == "" { return nil @@ -121,6 +134,6 @@ func (t *Trie) Search(domain string) *Node { } // New returns a new, empty Trie. -func New() *Trie { - return &Trie{root: newNode(nil)} +func New() *DomainTrie { + return &DomainTrie{root: newNode(nil)} } diff --git a/component/domain-trie/trie_test.go b/component/trie/domain_test.go similarity index 94% rename from component/domain-trie/trie_test.go rename to component/trie/domain_test.go index fa0f88f773..ded5282b88 100644 --- a/component/domain-trie/trie_test.go +++ b/component/trie/domain_test.go @@ -35,6 +35,7 @@ func TestTrie_Wildcard(t *testing.T) { ".org", ".example.net", ".apple.*", + "+.foo.com", } for _, domain := range domains { @@ -46,6 +47,8 @@ func TestTrie_Wildcard(t *testing.T) { assert.NotNil(t, tree.Search("test.org")) assert.NotNil(t, tree.Search("test.example.net")) assert.NotNil(t, tree.Search("test.apple.com")) + assert.NotNil(t, tree.Search("test.foo.com")) + assert.NotNil(t, tree.Search("foo.com")) assert.Nil(t, tree.Search("foo.sub.example.com")) assert.Nil(t, tree.Search("foo.example.dev")) assert.Nil(t, tree.Search("example.com")) diff --git a/component/domain-trie/node.go b/component/trie/node.go similarity index 100% rename from component/domain-trie/node.go rename to component/trie/node.go diff --git a/config/config.go b/config/config.go index 473e404ca1..c110d257bc 100644 --- a/config/config.go +++ b/config/config.go @@ -12,8 +12,8 @@ import ( "github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" - trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/fakeip" + "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" @@ -69,7 +69,7 @@ type Config struct { General *General DNS *DNS Experimental *Experimental - Hosts *trie.Trie + Hosts *trie.DomainTrie Rules []C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy @@ -450,7 +450,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { return rules, nil } -func parseHosts(cfg *RawConfig) (*trie.Trie, error) { +func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) { tree := trie.New() if len(cfg.Hosts) != 0 { for domain, ipStr := range cfg.Hosts { @@ -586,7 +586,7 @@ func parseDNS(cfg RawDNS) (*DNS, error) { return nil, err } - var host *trie.Trie + var host *trie.DomainTrie // fake ip skip host filter if len(cfg.FakeIPFilter) != 0 { host = trie.New() diff --git a/hub/executor/executor.go b/hub/executor/executor.go index ab84884889..2612ee9e69 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -9,8 +9,8 @@ import ( "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" - trie "github.com/Dreamacro/clash/component/domain-trie" "github.com/Dreamacro/clash/component/resolver" + "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/dns" @@ -154,7 +154,7 @@ func updateDNS(c *config.DNS) { } } -func updateHosts(tree *trie.Trie) { +func updateHosts(tree *trie.DomainTrie) { resolver.DefaultHosts = tree } diff --git a/hub/hub.go b/hub/hub.go index b7cdea27b1..471fdb5e17 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -1,9 +1,9 @@ package hub import ( + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/hub/route" - "github.com/Dreamacro/clash/config" ) type Option func(*config.Config) From 5628f97da113a523fdf26af4041e465234d83c84 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 29 May 2020 17:47:50 +0800 Subject: [PATCH 396/535] Feature: add tolerance for url-test --- README.md | 1 + adapters/outboundgroup/parser.go | 3 ++- adapters/outboundgroup/urltest.go | 44 +++++++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 768cbbf4fb..12503c955b 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,7 @@ proxy-groups: - ss1 - ss2 - vmess1 + # tolerance: 150 url: 'http://www.gstatic.com/generate_204' interval: 300 diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index 7eac7e9a4c..0a33b94ed4 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -101,7 +101,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, var group C.ProxyAdapter switch groupOption.Type { case "url-test": - group = NewURLTest(groupName, providers) + opts := parseURLTestOption(config) + group = NewURLTest(groupName, providers, opts...) case "select": group = NewSelector(groupName, providers) case "fallback": diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index ab1e2db3cb..750d809bff 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -11,8 +11,19 @@ import ( C "github.com/Dreamacro/clash/constant" ) +type urlTestOption func(*URLTest) + +func urlTestWithTolerance(tolerance uint16) urlTestOption { + return func(u *URLTest) { + u.tolerance = tolerance + } +} + type URLTest struct { *outbound.Base + tolerance uint16 + lastDelay uint16 + fastNode C.Proxy single *singledo.Single fastSingle *singledo.Single providers []provider.ProxyProvider @@ -52,6 +63,13 @@ func (u *URLTest) proxies() []C.Proxy { func (u *URLTest) fast() C.Proxy { elm, _, _ := u.fastSingle.Do(func() (interface{}, error) { + // tolerance + if u.tolerance != 0 && u.fastNode != nil { + if u.fastNode.LastDelay() < u.lastDelay+u.tolerance { + return u.fastNode, nil + } + } + proxies := u.proxies() fast := proxies[0] min := fast.LastDelay() @@ -66,6 +84,9 @@ func (u *URLTest) fast() C.Proxy { min = delay } } + + u.fastNode = fast + u.lastDelay = fast.LastDelay() return fast, nil }) @@ -88,11 +109,30 @@ func (u *URLTest) MarshalJSON() ([]byte, error) { }) } -func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest { - return &URLTest{ +func parseURLTestOption(config map[string]interface{}) []urlTestOption { + opts := []urlTestOption{} + + // tolerance + if elm, ok := config["tolerance"]; ok { + if tolerance, ok := elm.(int); ok { + opts = append(opts, urlTestWithTolerance(uint16(tolerance))) + } + } + + return opts +} + +func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { + urlTest := &URLTest{ Base: outbound.NewBase(name, "", C.URLTest, false), single: singledo.NewSingle(defaultGetProxiesDuration), fastSingle: singledo.NewSingle(time.Second * 10), providers: providers, } + + for _, option := range options { + option(urlTest) + } + + return urlTest } From 008731c2497cf2b114dc9dbbd554c9ff285edeb6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 29 May 2020 21:56:29 +0800 Subject: [PATCH 397/535] Fix: make os.Stat return correct err on provider --- adapters/provider/fetcher.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/adapters/provider/fetcher.go b/adapters/provider/fetcher.go index aeaf1f4257..1ee170f510 100644 --- a/adapters/provider/fetcher.go +++ b/adapters/provider/fetcher.go @@ -35,10 +35,12 @@ func (f *fetcher) VehicleType() VehicleType { } func (f *fetcher) Initial() (interface{}, error) { - var buf []byte - var err error - var isLocal bool - if stat, err := os.Stat(f.vehicle.Path()); err == nil { + var ( + buf []byte + err error + isLocal bool + ) + if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { buf, err = ioutil.ReadFile(f.vehicle.Path()) modTime := stat.ModTime() f.updatedAt = &modTime From 71d30e6654e347c44856b725b87d70af880050d4 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 1 Jun 2020 00:27:04 +0800 Subject: [PATCH 398/535] Feature: support vmess tls custom servername --- README.md | 1 + adapters/outbound/vmess.go | 7 +++++++ component/vmess/websocket.go | 5 ++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12503c955b..fa72efdb97 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ proxies: # udp: true # tls: true # skip-cert-verify: true + # servername: example.com # priority over wss host # network: ws # ws-path: /path # ws-headers: diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index a9365526d1..51ff044bc2 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -35,6 +35,7 @@ type VmessOption struct { WSPath string `proxy:"ws-path,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + ServerName string `proxy:"servername,omitempty"` } type HTTPOptions struct { @@ -66,6 +67,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { wsOpts.TLS = true wsOpts.SessionCache = getClientSessionCache() wsOpts.SkipCertVerify = v.option.SkipCertVerify + wsOpts.ServerName = v.option.ServerName } c, err = vmess.StreamWebsocketConn(c, wsOpts) case "http": @@ -87,6 +89,11 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { SkipCertVerify: v.option.SkipCertVerify, SessionCache: getClientSessionCache(), } + + if v.option.ServerName != "" { + tlsOpts.Host = v.option.ServerName + } + c, err = vmess.StreamTLSConn(c, tlsOpts) } } diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index 625905b881..499f15c8dd 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -31,6 +31,7 @@ type WebsocketConfig struct { Headers http.Header TLS bool SkipCertVerify bool + ServerName string SessionCache tls.ClientSessionCache } @@ -132,7 +133,9 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { ClientSessionCache: c.SessionCache, } - if host := c.Headers.Get("Host"); host != "" { + if c.ServerName != "" { + dialer.TLSClientConfig.ServerName = c.ServerName + } else if host := c.Headers.Get("Host"); host != "" { dialer.TLSClientConfig.ServerName = host } } From 46244a649699028563e347264f3639c0b9010b44 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 1 Jun 2020 00:32:37 +0800 Subject: [PATCH 399/535] Chore: `mode` use lower case (backward compatible) --- README.md | 4 ++-- tunnel/mode.go | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fa72efdb97..d069e87f93 100644 --- a/README.md +++ b/README.md @@ -92,8 +92,8 @@ allow-lan: false # "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address # bind-address: "*" -# Rule / Global / Direct (default is Rule) -mode: Rule +# rule / global / direct (default is rule) +mode: rule # set log level to stdout (default is info) # info / warning / error / debug / silent diff --git a/tunnel/mode.go b/tunnel/mode.go index 655c9f5762..6e07a060af 100644 --- a/tunnel/mode.go +++ b/tunnel/mode.go @@ -3,6 +3,7 @@ package tunnel import ( "encoding/json" "errors" + "strings" ) type TunnelMode int @@ -26,7 +27,7 @@ const ( func (m *TunnelMode) UnmarshalJSON(data []byte) error { var tp string json.Unmarshal(data, &tp) - mode, exist := ModeMapping[tp] + mode, exist := ModeMapping[strings.ToLower(tp)] if !exist { return errors.New("invalid mode") } @@ -38,7 +39,7 @@ func (m *TunnelMode) UnmarshalJSON(data []byte) error { func (m *TunnelMode) UnmarshalYAML(unmarshal func(interface{}) error) error { var tp string unmarshal(&tp) - mode, exist := ModeMapping[tp] + mode, exist := ModeMapping[strings.ToLower(tp)] if !exist { return errors.New("invalid mode") } @@ -59,11 +60,11 @@ func (m TunnelMode) MarshalYAML() (interface{}, error) { func (m TunnelMode) String() string { switch m { case Global: - return "Global" + return "global" case Rule: - return "Rule" + return "rule" case Direct: - return "Direct" + return "direct" default: return "Unknown" } From 3e7970612af997719ed7e6aca2cd37b9df5c7961 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 1 Jun 2020 00:39:41 +0800 Subject: [PATCH 400/535] Chore: provider error adjust --- config/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.go b/config/config.go index c110d257bc..47373429c0 100644 --- a/config/config.go +++ b/config/config.go @@ -312,7 +312,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ pd, err := provider.ParseProxyProvider(name, mapping) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err) } providersMap[name] = pd @@ -321,7 +321,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ for _, provider := range providersMap { log.Infoln("Start initial provider %s", provider.Name()) if err := provider.Initial(); err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("initial proxy provider %s error: %w", provider.Name(), err) } } From fb0289bb4c35a09babd652aa931dfea22f7d0421 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 1 Jun 2020 13:43:26 +0800 Subject: [PATCH 401/535] Chore: open ForceAttemptHTTP2 on DoH --- dns/doh.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dns/doh.go b/dns/doh.go index 8715393edc..ee26e7c8cd 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -44,7 +44,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) { return nil, err } - req, err := http.NewRequest(http.MethodPost, dc.url+"?bla=foo:443", bytes.NewReader(buf)) + req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf)) if err != nil { return req, err } @@ -75,7 +75,8 @@ func newDoHClient(url string, r *Resolver) *dohClient { return &dohClient{ url: url, transport: &http.Transport{ - TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, + TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache}, + ForceAttemptHTTP2: true, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { host, port, err := net.SplitHostPort(addr) if err != nil { From 147a7ce779b7ff89f82481ecf7c26e8f3b8da71e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 3 Jun 2020 18:49:20 +0800 Subject: [PATCH 402/535] Fix: panic of socks5 client missing authentication --- component/socks5/socks5.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/component/socks5/socks5.go b/component/socks5/socks5.go index 6c58a848c1..2950819af9 100644 --- a/component/socks5/socks5.go +++ b/component/socks5/socks5.go @@ -229,6 +229,10 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) ( } if buf[1] == 2 { + if user == nil { + return nil, ErrAuth + } + // password protocol version authMsg := &bytes.Buffer{} authMsg.WriteByte(1) From 1a217e21e9bb845c729559c19c5746dab25b81a7 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 4 Jun 2020 10:38:30 +0800 Subject: [PATCH 403/535] Chore: use actions build docker image --- .github/workflows/docker.yml | 51 ++++++++++++++++++++++++++++++++++++ .github/workflows/go.yml | 8 +++--- 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..dec252b2f9 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,51 @@ +name: Publish Docker Image +on: + push: + branches: + - dev + tags: + - '*' +jobs: + + build: + name: Build + runs-on: ubuntu-latest + steps: + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Set up docker buildx + id: buildx + uses: crazy-max/ghaction-docker-buildx@v2 + with: + buildx-version: latest + skip-cache: false + qemu-version: latest + + - name: Docker login + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + run: | + echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin + + - name: Docker buildx image and push on dev branch + if: github.ref == 'refs/heads/dev' + run: | + docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev . + + - name: Replace tag without `v` + if: startsWith(github.ref, 'refs/tags/') + uses: actions/github-script@v1 + id: version + with: + script: | + return context.payload.ref.replace(/\/refs\/tags\//v, '') + result-encoding: string + + - name: Docker buildx image and push on release + if: startsWith(github.ref, 'refs/tags/') + run: | + docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} . + docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest . diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1fd7f402c3..33aaded3a2 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -7,15 +7,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v2 with: go-version: 1.14.x - name: Check out code into the Go module directory - uses: actions/checkout@v1 - + uses: actions/checkout@v2 + - name: Cache go module - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} From 7ddbc12cdb3ca53ec37861baa6cc0aead595b215 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 4 Jun 2020 10:57:43 +0800 Subject: [PATCH 404/535] Chore: rm unused Dockerfile --- .github/workflows/docker.yml | 2 ++ Dockerfile.arm32v7 | 20 -------------------- Dockerfile.arm64v8 | 20 -------------------- 3 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 Dockerfile.arm32v7 delete mode 100644 Dockerfile.arm64v8 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dec252b2f9..c6d21223df 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,6 +14,8 @@ jobs: - name: Check out code into the Go module directory uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Set up docker buildx id: buildx diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7 deleted file mode 100644 index e2f2d299f3..0000000000 --- a/Dockerfile.arm32v7 +++ /dev/null @@ -1,20 +0,0 @@ -FROM golang:alpine as builder - -RUN apk add --no-cache make git && \ - wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \ - wget -O /qemu-arm-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-arm-static && \ - chmod +x /qemu-arm-static - -WORKDIR /clash-src -COPY . /clash-src -RUN go mod download && \ - make linux-armv7 && \ - mv ./bin/clash-linux-armv7 /clash - -FROM arm32v7/alpine:latest - -COPY --from=builder /qemu-arm-static /usr/bin/ -COPY --from=builder /Country.mmdb /root/.config/clash/ -COPY --from=builder /clash / -RUN apk add --no-cache ca-certificates -ENTRYPOINT ["/clash"] diff --git a/Dockerfile.arm64v8 b/Dockerfile.arm64v8 deleted file mode 100644 index b532e4198f..0000000000 --- a/Dockerfile.arm64v8 +++ /dev/null @@ -1,20 +0,0 @@ -FROM golang:alpine as builder - -RUN apk add --no-cache make git && \ - wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb && \ - wget -O /qemu-aarch64-static https://github.com/multiarch/qemu-user-static/releases/latest/download/qemu-aarch64-static && \ - chmod +x /qemu-aarch64-static - -WORKDIR /clash-src -COPY . /clash-src -RUN go mod download && \ - make linux-armv8 && \ - mv ./bin/clash-linux-armv8 /clash - -FROM arm64v8/alpine:latest - -COPY --from=builder /qemu-aarch64-static /usr/bin/ -COPY --from=builder /Country.mmdb /root/.config/clash/ -COPY --from=builder /clash / -RUN apk add --no-cache ca-certificates -ENTRYPOINT ["/clash"] From c1b4c94b9cbb43f20026cfc983ae357ff8e599bc Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 5 Jun 2020 12:49:24 +0800 Subject: [PATCH 405/535] Chore: remove unused hooks directory --- hooks/pre_build | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 hooks/pre_build diff --git a/hooks/pre_build b/hooks/pre_build deleted file mode 100644 index b7cf923d69..0000000000 --- a/hooks/pre_build +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -# Register qemu-*-static for all supported processors except the -# current one, but also remove all registered binfmt_misc before -docker run --rm --privileged multiarch/qemu-user-static:register --reset --credential yes From 98614a1f3fd2537abec53eb153b401abe7cf50ef Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 5 Jun 2020 17:43:50 +0800 Subject: [PATCH 406/535] Chore: move rule parser to `rules` --- config/config.go | 36 +----------------------------------- rules/parser.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 35 deletions(-) create mode 100644 rules/parser.go diff --git a/config/config.go b/config/config.go index 47373429c0..f1562bc2a3 100644 --- a/config/config.go +++ b/config/config.go @@ -404,42 +404,8 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { rule = trimArr(rule) params = trimArr(params) - var ( - parseErr error - parsed C.Rule - ) - - switch rule[0] { - case "DOMAIN": - parsed = R.NewDomain(payload, target) - case "DOMAIN-SUFFIX": - parsed = R.NewDomainSuffix(payload, target) - case "DOMAIN-KEYWORD": - parsed = R.NewDomainKeyword(payload, target) - case "GEOIP": - noResolve := R.HasNoResolve(params) - parsed = R.NewGEOIP(payload, target, noResolve) - case "IP-CIDR", "IP-CIDR6": - noResolve := R.HasNoResolve(params) - parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRNoResolve(noResolve)) - // deprecated when bump to 1.0 - case "SOURCE-IP-CIDR": - fallthrough - case "SRC-IP-CIDR": - parsed, parseErr = R.NewIPCIDR(payload, target, R.WithIPCIDRSourceIP(true), R.WithIPCIDRNoResolve(true)) - case "SRC-PORT": - parsed, parseErr = R.NewPort(payload, target, true) - case "DST-PORT": - parsed, parseErr = R.NewPort(payload, target, false) - case "MATCH": - fallthrough - // deprecated when bump to 1.0 - case "FINAL": - parsed = R.NewMatch(target) - default: - parseErr = fmt.Errorf("unsupported rule type %s", rule[0]) - } + parsed, parseErr := R.ParseRule(rule[0], payload, target, params) if parseErr != nil { return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } diff --git a/rules/parser.go b/rules/parser.go new file mode 100644 index 0000000000..3814056dd1 --- /dev/null +++ b/rules/parser.go @@ -0,0 +1,47 @@ +package rules + +import ( + "fmt" + + C "github.com/Dreamacro/clash/constant" +) + +func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { + var ( + parseErr error + parsed C.Rule + ) + + switch tp { + case "DOMAIN": + parsed = NewDomain(payload, target) + case "DOMAIN-SUFFIX": + parsed = NewDomainSuffix(payload, target) + case "DOMAIN-KEYWORD": + parsed = NewDomainKeyword(payload, target) + case "GEOIP": + noResolve := HasNoResolve(params) + parsed = NewGEOIP(payload, target, noResolve) + case "IP-CIDR", "IP-CIDR6": + noResolve := HasNoResolve(params) + parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) + // deprecated when bump to 1.0 + case "SOURCE-IP-CIDR": + fallthrough + case "SRC-IP-CIDR": + parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) + case "SRC-PORT": + parsed, parseErr = NewPort(payload, target, true) + case "DST-PORT": + parsed, parseErr = NewPort(payload, target, false) + case "MATCH": + fallthrough + // deprecated when bump to 1.0 + case "FINAL": + parsed = NewMatch(target) + default: + parseErr = fmt.Errorf("unsupported rule type %s", tp) + } + + return parsed, parseErr +} From 8f32e6a60fff79b9a5ecdda00c891ce7f4260a21 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 7 Jun 2020 00:35:32 +0800 Subject: [PATCH 407/535] Improve: safe write provider file --- adapters/provider/fetcher.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/adapters/provider/fetcher.go b/adapters/provider/fetcher.go index 1ee170f510..2256ff2a7e 100644 --- a/adapters/provider/fetcher.go +++ b/adapters/provider/fetcher.go @@ -5,6 +5,7 @@ import ( "crypto/md5" "io/ioutil" "os" + "path/filepath" "time" "github.com/Dreamacro/clash/log" @@ -12,6 +13,7 @@ import ( var ( fileMode os.FileMode = 0666 + dirMode os.FileMode = 0755 ) type parser = func([]byte) (interface{}, error) @@ -71,7 +73,7 @@ func (f *fetcher) Initial() (interface{}, error) { } } - if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil { + if err := safeWrite(f.vehicle.Path(), buf); err != nil { return nil, err } @@ -103,7 +105,7 @@ func (f *fetcher) Update() (interface{}, bool, error) { return nil, false, err } - if err := ioutil.WriteFile(f.vehicle.Path(), buf, fileMode); err != nil { + if err := safeWrite(f.vehicle.Path(), buf); err != nil { return nil, false, err } @@ -140,6 +142,18 @@ func (f *fetcher) pullLoop() { } } +func safeWrite(path string, buf []byte) error { + dir := filepath.Dir(path) + + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, dirMode); err != nil { + return err + } + } + + return ioutil.WriteFile(path, buf, fileMode) +} + func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser parser, onUpdate func(interface{})) *fetcher { var ticker *time.Ticker if interval != 0 { From 2dece02df645678aedf97951e90dc4ddd1379905 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 7 Jun 2020 16:54:41 +0800 Subject: [PATCH 408/535] Chore: code adjustments --- README.md | 5 ++++- component/trojan/trojan.go | 4 ++-- constant/metadata.go | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d069e87f93..80c02f9cbe 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,9 @@ port: 7890 # port of SOCKS5 socks-port: 7891 +# (HTTP and SOCKS5 in one port) +# mixed-port: 7890 + # redir port for Linux and macOS # redir-port: 7892 @@ -394,4 +397,4 @@ https://clash.gitbook.io/ - [x] Redir proxy - [x] UDP support - [x] Connection manager -- [ ] Event API +- ~~[ ] Event API~~ diff --git a/component/trojan/trojan.go b/component/trojan/trojan.go index 2ae8d07f11..ef440d62b2 100644 --- a/component/trojan/trojan.go +++ b/component/trojan/trojan.go @@ -70,8 +70,8 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error { buf := bufPool.Get().(*bytes.Buffer) - defer buf.Reset() defer bufPool.Put(buf) + defer buf.Reset() buf.Write(t.hexPassword) buf.Write(crlf) @@ -92,8 +92,8 @@ func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn { func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { buf := bufPool.Get().(*bytes.Buffer) - defer buf.Reset() defer bufPool.Put(buf) + defer buf.Reset() buf.Write(socks5Addr) binary.Write(buf, binary.BigEndian, uint16(len(payload))) diff --git a/constant/metadata.go b/constant/metadata.go index 61ce434361..647b332d0e 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -23,8 +23,8 @@ const ( type NetWork int -func (n *NetWork) String() string { - if *n == TCP { +func (n NetWork) String() string { + if n == TCP { return "tcp" } return "udp" From fb628e9c62d2576aff3643d4ff30610a0a92c76f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 7 Jun 2020 17:25:51 +0800 Subject: [PATCH 409/535] Feature: add default hosts `localhost` --- component/trie/domain.go | 6 +++++- component/trie/domain_test.go | 3 +++ config/config.go | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/component/trie/domain.go b/component/trie/domain.go index 5dc605533c..e8dcf213b0 100644 --- a/component/trie/domain.go +++ b/component/trie/domain.go @@ -30,7 +30,11 @@ func validAndSplitDomain(domain string) ([]string, bool) { parts := strings.Split(domain, domainStep) if len(parts) == 1 { - return nil, false + if parts[0] == "" { + return nil, false + } + + return parts, true } for _, part := range parts[1:] { diff --git a/component/trie/domain_test.go b/component/trie/domain_test.go index ded5282b88..38b347e15f 100644 --- a/component/trie/domain_test.go +++ b/component/trie/domain_test.go @@ -14,6 +14,7 @@ func TestTrie_Basic(t *testing.T) { domains := []string{ "example.com", "google.com", + "localhost", } for _, domain := range domains { @@ -24,6 +25,8 @@ func TestTrie_Basic(t *testing.T) { assert.NotNil(t, node) assert.True(t, node.Data.(net.IP).Equal(localIP)) assert.NotNil(t, tree.Insert("", localIP)) + assert.Nil(t, tree.Search("")) + assert.NotNil(t, tree.Search("localhost")) } func TestTrie_Wildcard(t *testing.T) { diff --git a/config/config.go b/config/config.go index f1562bc2a3..f18d0369f3 100644 --- a/config/config.go +++ b/config/config.go @@ -418,6 +418,12 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) { tree := trie.New() + + // add default hosts + if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil { + println(err.Error()) + } + if len(cfg.Hosts) != 0 { for domain, ipStr := range cfg.Hosts { ip := net.ParseIP(ipStr) From 48cff50a4ce4c2f9d799f7e08a400663e0dcdea6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 7 Jun 2020 17:28:56 +0800 Subject: [PATCH 410/535] Feature: connections add rule payload --- tunnel/tracker.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/tunnel/tracker.go b/tunnel/tracker.go index 2be522039f..c88716973f 100644 --- a/tunnel/tracker.go +++ b/tunnel/tracker.go @@ -21,6 +21,7 @@ type trackerInfo struct { Start time.Time `json:"start"` Chain C.Chain `json:"chains"` Rule string `json:"rule"` + RulePayload string `json:"rulePayload"` } type tcpTracker struct { @@ -56,10 +57,6 @@ func (tt *tcpTracker) Close() error { func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { uuid, _ := uuid.NewV4() - ruleType := "" - if rule != nil { - ruleType = rule.RuleType().String() - } t := &tcpTracker{ Conn: conn, @@ -69,10 +66,15 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), - Rule: ruleType, + Rule: "", }, } + if rule != nil { + t.trackerInfo.Rule = rule.RuleType().String() + t.trackerInfo.RulePayload = rule.Payload() + } + manager.Join(t) return t } @@ -118,10 +120,6 @@ func (ut *udpTracker) Close() error { func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { uuid, _ := uuid.NewV4() - ruleType := "" - if rule != nil { - ruleType = rule.RuleType().String() - } ut := &udpTracker{ PacketConn: conn, @@ -131,10 +129,15 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru Start: time.Now(), Metadata: metadata, Chain: conn.Chains(), - Rule: ruleType, + Rule: "", }, } + if rule != nil { + ut.trackerInfo.Rule = rule.RuleType().String() + ut.trackerInfo.RulePayload = rule.Payload() + } + manager.Join(ut) return ut } From ecac8eb8e5f42721e6253b154d533e2e74eb4d74 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 7 Jun 2020 17:56:21 +0800 Subject: [PATCH 411/535] Fix: add lock for inbound proxy recreate --- proxy/listener.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/proxy/listener.go b/proxy/listener.go index 5eda54266b..88d0f522d6 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "strconv" + "sync" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/proxy/http" @@ -23,6 +24,13 @@ var ( redirUDPListener *redir.RedirUDPListener mixedListener *mixed.MixedListener mixedUDPLister *socks.SockUDPListener + + // lock for recreate function + socksMux sync.Mutex + httpMux sync.Mutex + redirMux sync.Mutex + mixedMux sync.Mutex + tunMux sync.Mutex ) type listener interface { @@ -54,6 +62,9 @@ func SetBindAddress(host string) { } func ReCreateHTTP(port int) error { + httpMux.Lock() + defer httpMux.Unlock() + addr := genAddr(bindAddress, port, allowLan) if httpListener != nil { @@ -78,6 +89,9 @@ func ReCreateHTTP(port int) error { } func ReCreateSocks(port int) error { + socksMux.Lock() + defer socksMux.Unlock() + addr := genAddr(bindAddress, port, allowLan) shouldTCPIgnore := false @@ -127,6 +141,9 @@ func ReCreateSocks(port int) error { } func ReCreateRedir(port int) error { + redirMux.Lock() + defer redirMux.Unlock() + addr := genAddr(bindAddress, port, allowLan) if redirListener != nil { @@ -164,6 +181,9 @@ func ReCreateRedir(port int) error { } func ReCreateMixed(port int) error { + mixedMux.Lock() + defer mixedMux.Unlock() + addr := genAddr(bindAddress, port, allowLan) shouldTCPIgnore := false From 1854199c476e6b7e69bb72622ccdaae5506e0622 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 7 Jun 2020 18:14:04 +0800 Subject: [PATCH 412/535] Chore: update dependencies --- go.mod | 10 +++++----- go.sum | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index f4bc0ab888..90ecdf0a6e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.5 github.com/eapache/queue v1.1.0 // indirect - github.com/go-chi/chi v4.1.1+incompatible + github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.1.1 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.3.0+incompatible @@ -13,10 +13,10 @@ require ( github.com/miekg/dns v1.1.29 github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.6.0 - github.com/stretchr/testify v1.5.1 - golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 - golang.org/x/net v0.0.0-20200506145744-7e3656a0809f + github.com/stretchr/testify v1.6.1 + golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 + golang.org/x/net v0.0.0-20200602114024-627f9648deb9 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a gopkg.in/eapache/channels.v1 v1.1.0 - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index ead335a01c..a62815d4f0 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ 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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4= -github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= @@ -27,22 +27,23 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88= -golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -61,5 +62,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 58c077b45e6c583c9176f3938387f6265eeae009 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 8 Jun 2020 13:53:04 +0800 Subject: [PATCH 413/535] Fix: actions tag replace --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c6d21223df..4afdb0cd59 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,7 +43,7 @@ jobs: id: version with: script: | - return context.payload.ref.replace(/\/refs\/tags\//v, '') + return context.payload.ref.replace(/\/refs\/tags\/v/, '') result-encoding: string - name: Docker buildx image and push on release From f1b792bd2689b45767cdb5e0984925adfe63a679 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 11 Jun 2020 11:10:08 +0800 Subject: [PATCH 414/535] Fix: trim FQDN on http proxy request --- adapters/inbound/util.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index 16b440e917..a9901d0ab8 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -4,6 +4,7 @@ import ( "net" "net/http" "strconv" + "strings" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" @@ -38,6 +39,9 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { port = "80" } + // trim FQDN (#737) + host = strings.TrimRight(host, ".") + metadata := &C.Metadata{ NetWork: C.TCP, AddrType: C.AtypDomainName, From 4f674755ce82f0399bc89c861df0ae5a3f5ce228 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 11 Jun 2020 12:11:44 +0800 Subject: [PATCH 415/535] Fix: trim . for socks5 host --- adapters/inbound/util.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adapters/inbound/util.go b/adapters/inbound/util.go index a9901d0ab8..6241ac8037 100644 --- a/adapters/inbound/util.go +++ b/adapters/inbound/util.go @@ -17,7 +17,8 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata { switch target[0] { case socks5.AtypDomainName: - metadata.Host = string(target[2 : 2+target[1]]) + // trim for FQDN + metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) case socks5.AtypIPv4: ip := net.IP(target[1 : 1+net.IPv4len]) From 1c760935f4e5f429cd23ecd2c3930003cb95e49b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 11 Jun 2020 22:07:20 +0800 Subject: [PATCH 416/535] Chore: add error msg when dial vmess --- adapters/outbound/vmess.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 51ff044bc2..023b1ceaf2 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -108,7 +108,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { c, err := dialer.DialContext(ctx, "tcp", v.addr) if err != nil { - return nil, fmt.Errorf("%s connect error", v.addr) + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } tcpKeepAlive(c) @@ -130,7 +130,7 @@ func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { defer cancel() c, err := dialer.DialContext(ctx, "tcp", v.addr) if err != nil { - return nil, fmt.Errorf("%s connect error", v.addr) + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } tcpKeepAlive(c) c, err = v.StreamConn(c, metadata) From 59bda1d5473ad6e5109372ba0594d38c782481e3 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 12 Jun 2020 23:39:03 +0800 Subject: [PATCH 417/535] Change: local resolve DNS in UDP request due to TURN failed --- adapters/outbound/base.go | 9 ++------- adapters/outbound/direct.go | 12 ------------ adapters/outbound/shadowsocks.go | 8 -------- adapters/outbound/socks5.go | 8 -------- adapters/outbound/trojan.go | 11 +---------- adapters/outbound/vmess.go | 4 ---- constant/adapters.go | 3 ++- dns/util.go | 7 ++----- tunnel/connection.go | 21 ++++++++++++++++++--- tunnel/tracker.go | 8 -------- 10 files changed, 25 insertions(+), 66 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index dddd852ea5..2c380a4143 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -78,13 +78,8 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { return &conn{c, []string{a.Name()}} } -type PacketConn interface { - net.PacketConn - WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) -} - type packetConn struct { - PacketConn + net.PacketConn chain C.Chain } @@ -96,7 +91,7 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) { c.chain = append(c.chain, a.Name()) } -func newPacketConn(pc PacketConn, a C.ProxyAdapter) C.PacketConn { +func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { return &packetConn{pc, []string{a.Name()}} } diff --git a/adapters/outbound/direct.go b/adapters/outbound/direct.go index 9e896c3acd..5273a593a2 100644 --- a/adapters/outbound/direct.go +++ b/adapters/outbound/direct.go @@ -5,7 +5,6 @@ import ( "net" "github.com/Dreamacro/clash/component/dialer" - "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" ) @@ -36,17 +35,6 @@ type directPacketConn struct { net.PacketConn } -func (dp *directPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { - if !metadata.Resolved() { - ip, err := resolver.ResolveIP(metadata.Host) - if err != nil { - return 0, err - } - metadata.DstIP = ip - } - return dp.WriteTo(p, metadata.UDPAddr()) -} - func NewDirect() *Direct { return &Direct{ Base: &Base{ diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index fdf5ca0ee4..8a62046434 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -197,14 +197,6 @@ func (spc *ssPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return spc.PacketConn.WriteTo(packet[3:], spc.rAddr) } -func (spc *ssPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { - packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p) - if err != nil { - return - } - return spc.PacketConn.WriteTo(packet[3:], spc.rAddr) -} - func (spc *ssPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, _, e := spc.PacketConn.ReadFrom(b) if e != nil { diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 315b05b239..78a5946882 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -164,14 +164,6 @@ func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return uc.PacketConn.WriteTo(packet, uc.rAddr) } -func (uc *socksPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { - packet, err := socks5.EncodeUDPPacket(socks5.ParseAddr(metadata.RemoteAddress()), p) - if err != nil { - return - } - return uc.PacketConn.WriteTo(packet, uc.rAddr) -} - func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, _, e := uc.PacketConn.ReadFrom(b) if e != nil { diff --git a/adapters/outbound/trojan.go b/adapters/outbound/trojan.go index ab154b3245..7b61b5738e 100644 --- a/adapters/outbound/trojan.go +++ b/adapters/outbound/trojan.go @@ -71,7 +71,7 @@ func (t *Trojan) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } pc := t.instance.PacketConn(c) - return newPacketConn(&trojanPacketConn{pc, c}, t), err + return newPacketConn(pc, t), err } func (t *Trojan) MarshalJSON() ([]byte, error) { @@ -105,12 +105,3 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { instance: trojan.New(tOption), }, nil } - -type trojanPacketConn struct { - net.PacketConn - conn net.Conn -} - -func (tpc *trojanPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { - return trojan.WritePacket(tpc.conn, serializesSocksAddr(metadata), p) -} diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 023b1ceaf2..8eca97dff2 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -202,10 +202,6 @@ func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { return uc.Conn.Write(b) } -func (uc *vmessPacketConn) WriteWithMetadata(p []byte, metadata *C.Metadata) (n int, err error) { - return uc.Conn.Write(p) -} - func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { n, err := uc.Conn.Read(b) return n, uc.rAddr, err diff --git a/constant/adapters.go b/constant/adapters.go index be2d0e70e0..ad80974a96 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -57,7 +57,8 @@ type Conn interface { type PacketConn interface { net.PacketConn Connection - WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error) + // Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed + // WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error) } type ProxyAdapter interface { diff --git a/dns/util.go b/dns/util.go index b6adf8d64a..23c5b12fcd 100644 --- a/dns/util.go +++ b/dns/util.go @@ -89,7 +89,7 @@ func putMsgToCache(c *cache.LruCache, key string, msg *D.Msg) { case len(msg.Extra) != 0: ttl = msg.Extra[0].Header().Ttl default: - log.Debugln("[DNS] response msg error: %#v", msg) + log.Debugln("[DNS] response msg empty: %#v", msg) return } @@ -111,10 +111,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) { } func isIPRequest(q D.Question) bool { - if q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) { - return true - } - return false + return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) } func transform(servers []NameServer, resolver *Resolver) []dnsClient { diff --git a/tunnel/connection.go b/tunnel/connection.go index 0af760cc10..bdba863a6e 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -2,6 +2,7 @@ package tunnel import ( "bufio" + "errors" "io" "net" "net/http" @@ -9,6 +10,7 @@ import ( "time" adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/common/pool" @@ -81,12 +83,25 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { } } -func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) { +func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { defer packet.Drop() - if _, err := pc.WriteWithMetadata(packet.Data(), metadata); err != nil { - return + // local resolve UDP dns + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(metadata.Host) + if err != nil { + return err + } + metadata.DstIP = ip } + + addr := metadata.UDPAddr() + if addr == nil { + return errors.New("udp addr invalid") + } + + _, err := pc.WriteTo(packet.Data(), addr) + return err } func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr net.Addr) { diff --git a/tunnel/tracker.go b/tunnel/tracker.go index c88716973f..142b7110ec 100644 --- a/tunnel/tracker.go +++ b/tunnel/tracker.go @@ -105,14 +105,6 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { return n, err } -func (ut *udpTracker) WriteWithMetadata(p []byte, metadata *C.Metadata) (int, error) { - n, err := ut.PacketConn.WriteWithMetadata(p, metadata) - upload := int64(n) - ut.manager.Upload() <- upload - ut.UploadTotal += upload - return n, err -} - func (ut *udpTracker) Close() error { ut.manager.Leave(ut) return ut.PacketConn.Close() From 4323dd24d0e02dc472ea3e476e780dca7b04a126 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 14 Jun 2020 00:32:04 +0800 Subject: [PATCH 418/535] Fix: don't auto health check on provider health check disabled --- adapters/provider/provider.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index 4968a48dda..f5321e73a4 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -142,7 +142,9 @@ func proxiesParse(buf []byte) (interface{}, error) { func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { pp.proxies = proxies pp.healthCheck.setProxy(proxies) - go pp.healthCheck.check() + if pp.healthCheck.auto() { + go pp.healthCheck.check() + } } func stopProxyProvider(pd *ProxySetProvider) { From 9f1d85ab6e602472ed73e7ae7166fb9c9e393de9 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Sun, 14 Jun 2020 00:41:53 +0800 Subject: [PATCH 419/535] Fix: fake-ip-filter on fakeip mode should lookup ip-host mapping (#743) --- dns/resolver.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dns/resolver.go b/dns/resolver.go index 7f50a1a0e1..52a08c0d20 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -121,7 +121,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { } putMsgToCache(r.lruCache, q.String(), msg) - if r.mapping { + if r.mapping || r.fakeip { ips := r.msgToIP(msg) for _, ip := range ips { putMsgToCache(r.lruCache, ip.String(), msg) @@ -151,7 +151,10 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { // IPToHost return fake-ip or redir-host mapping host func (r *Resolver) IPToHost(ip net.IP) (string, bool) { if r.fakeip { - return r.pool.LookBack(ip) + record, existed := r.pool.LookBack(ip) + if existed { + return record, true + } } cache, _ := r.lruCache.Get(ip.String()) From 99b34e8d8b8f53caf90fe66a595aedf451ca6f67 Mon Sep 17 00:00:00 2001 From: limgmk <45861815+Limgmk@users.noreply.github.com> Date: Mon, 15 Jun 2020 10:34:15 +0800 Subject: [PATCH 420/535] Fix: cannot listen socks5 port on wsl (#748) --- dns/server.go | 3 ++- proxy/socks/udp.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dns/server.go b/dns/server.go index c2bd08c8fd..7af83f96ac 100644 --- a/dns/server.go +++ b/dns/server.go @@ -4,6 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/common/sockopt" + "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" ) @@ -62,7 +63,7 @@ func ReCreateServer(addr string, resolver *Resolver) error { err = sockopt.UDPReuseaddr(p) if err != nil { - return err + log.Warnln("Failed to Reuse UDP Address: %s", err) } address = addr diff --git a/proxy/socks/udp.go b/proxy/socks/udp.go index 90ccad1b7b..38bc113135 100644 --- a/proxy/socks/udp.go +++ b/proxy/socks/udp.go @@ -8,6 +8,7 @@ import ( "github.com/Dreamacro/clash/common/sockopt" "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" ) @@ -25,7 +26,7 @@ func NewSocksUDPProxy(addr string) (*SockUDPListener, error) { err = sockopt.UDPReuseaddr(l.(*net.UDPConn)) if err != nil { - return nil, err + log.Warnln("Failed to Reuse UDP Address: %s", err) } sl := &SockUDPListener{l, addr, false} From 60bad66bc30b04b8c05d739a21773f07e0f10ba9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 18 Jun 2020 18:11:02 +0800 Subject: [PATCH 421/535] Change: ipv6 logic --- README.md | 4 +- component/resolver/resolver.go | 18 +++++++- config/config.go | 79 ++++++++++++++++++---------------- dns/middleware.go | 16 ++++++- dns/server.go | 1 + hub/executor/executor.go | 39 +++++++++++------ 6 files changed, 104 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 80c02f9cbe..a1bfaaf232 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ allow-lan: false # "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address # bind-address: "*" +# ipv6: false # when ipv6 is false, each clash dial with ipv6, but it's not affect the response of the dns server, default is false + # rule / global / direct (default is rule) mode: rule @@ -133,7 +135,7 @@ experimental: # dns: # enable: true # set true to enable dns (default is false) - # ipv6: false # default is false + # ipv6: false # it only affect the dns server response, default is false # listen: 0.0.0.0:53 # # default-nameserver: # resolve dns nameserver host, should fill pure IP # # - 114.114.114.114 diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index da9fed21d5..61258898ec 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -12,13 +12,18 @@ var ( // DefaultResolver aim to resolve ip DefaultResolver Resolver + // DisableIPv6 means don't resolve ipv6 host + // default value is true + DisableIPv6 = true + // DefaultHosts aim to resolve hosts DefaultHosts = trie.New() ) var ( - ErrIPNotFound = errors.New("couldn't find ip") - ErrIPVersion = errors.New("ip version error") + ErrIPNotFound = errors.New("couldn't find ip") + ErrIPVersion = errors.New("ip version error") + ErrIPv6Disabled = errors.New("ipv6 disabled") ) type Resolver interface { @@ -63,6 +68,10 @@ func ResolveIPv4(host string) (net.IP, error) { // ResolveIPv6 with a host, return ipv6 func ResolveIPv6(host string) (net.IP, error) { + if DisableIPv6 { + return nil, ErrIPv6Disabled + } + if node := DefaultHosts.Search(host); node != nil { if ip := node.Data.(net.IP).To16(); ip != nil { return ip, nil @@ -102,7 +111,12 @@ func ResolveIP(host string) (net.IP, error) { } if DefaultResolver != nil { + if DisableIPv6 { + return DefaultResolver.ResolveIPv4(host) + } return DefaultResolver.ResolveIP(host) + } else if DisableIPv6 { + return ResolveIPv4(host) } ip := net.ParseIP(host) diff --git a/config/config.go b/config/config.go index f18d0369f3..7b3913c4d0 100644 --- a/config/config.go +++ b/config/config.go @@ -25,18 +25,29 @@ import ( // General config type General struct { - Port int `json:"port"` - SocksPort int `json:"socks-port"` - RedirPort int `json:"redir-port"` - MixedPort int `json:"mixed-port"` - Authentication []string `json:"authentication"` - AllowLan bool `json:"allow-lan"` - BindAddress string `json:"bind-address"` - Mode T.TunnelMode `json:"mode"` - LogLevel log.LogLevel `json:"log-level"` - ExternalController string `json:"-"` - ExternalUI string `json:"-"` - Secret string `json:"-"` + Inbound + Controller + Mode T.TunnelMode `json:"mode"` + LogLevel log.LogLevel `json:"log-level"` + IPv6 bool `json:"ipv6"` +} + +// Inbound +type Inbound struct { + Port int `json:"port"` + SocksPort int `json:"socks-port"` + RedirPort int `json:"redir-port"` + MixedPort int `json:"mixed-port"` + Authentication []string `json:"authentication"` + AllowLan bool `json:"allow-lan"` + BindAddress string `json:"bind-address"` +} + +// Controller +type Controller struct { + ExternalController string `json:"-"` + ExternalUI string `json:"-"` + Secret string `json:"-"` } // DNS config @@ -104,6 +115,7 @@ type RawConfig struct { BindAddress string `yaml:"bind-address"` Mode T.TunnelMode `yaml:"mode"` LogLevel log.LogLevel `yaml:"log-level"` + IPv6 bool `yaml:"ipv6"` ExternalController string `yaml:"external-controller"` ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` @@ -216,18 +228,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } func parseGeneral(cfg *RawConfig) (*General, error) { - port := cfg.Port - socksPort := cfg.SocksPort - redirPort := cfg.RedirPort - mixedPort := cfg.MixedPort - allowLan := cfg.AllowLan - bindAddress := cfg.BindAddress - externalController := cfg.ExternalController externalUI := cfg.ExternalUI - secret := cfg.Secret - mode := cfg.Mode - logLevel := cfg.LogLevel + // checkout externalUI exist if externalUI != "" { externalUI = C.Path.Resolve(externalUI) @@ -236,20 +239,24 @@ func parseGeneral(cfg *RawConfig) (*General, error) { } } - general := &General{ - Port: port, - SocksPort: socksPort, - RedirPort: redirPort, - MixedPort: mixedPort, - AllowLan: allowLan, - BindAddress: bindAddress, - Mode: mode, - LogLevel: logLevel, - ExternalController: externalController, - ExternalUI: externalUI, - Secret: secret, - } - return general, nil + return &General{ + Inbound: Inbound{ + Port: cfg.Port, + SocksPort: cfg.SocksPort, + RedirPort: cfg.RedirPort, + MixedPort: cfg.MixedPort, + AllowLan: cfg.AllowLan, + BindAddress: cfg.BindAddress, + }, + Controller: Controller{ + ExternalController: cfg.ExternalController, + ExternalUI: cfg.ExternalUI, + Secret: cfg.Secret, + }, + Mode: cfg.Mode, + LogLevel: cfg.LogLevel, + IPv6: cfg.IPv6, + }, nil } func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[string]provider.ProxyProvider, err error) { diff --git a/dns/middleware.go b/dns/middleware.go index 4d97e65d59..7cafe7f9f0 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -58,9 +58,23 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { func withResolver(resolver *Resolver) handler { return func(w D.ResponseWriter, r *D.Msg) { + q := r.Question[0] + + // return a empty AAAA msg when ipv6 disabled + if !resolver.ipv6 && q.Qtype == D.TypeAAAA { + msg := &D.Msg{} + msg.Answer = []D.RR{} + + msg.SetRcode(r, D.RcodeSuccess) + msg.Authoritative = true + msg.RecursionAvailable = true + + w.WriteMsg(msg) + return + } + msg, err := resolver.Exchange(r) if err != nil { - q := r.Question[0] log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err) D.HandleFailed(w, r) return diff --git a/dns/server.go b/dns/server.go index 7af83f96ac..6e6a02b69e 100644 --- a/dns/server.go +++ b/dns/server.go @@ -43,6 +43,7 @@ func ReCreateServer(addr string, resolver *Resolver) error { if server.Server != nil { server.Shutdown() + server = &Server{} address = "" } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 2612ee9e69..584191acb6 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path/filepath" + "sync" "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" @@ -20,6 +21,10 @@ import ( "github.com/Dreamacro/clash/tunnel" ) +var ( + mux sync.Mutex +) + // forward compatibility before 1.0 func readRawConfig(path string) ([]byte, error) { data, err := ioutil.ReadFile(path) @@ -77,10 +82,11 @@ func ParseWithBytes(buf []byte) (*config.Config, error) { // ApplyConfig dispatch configure to all parts func ApplyConfig(cfg *config.Config, force bool) { + mux.Lock() + defer mux.Unlock() + updateUsers(cfg.Users) - if force { - updateGeneral(cfg.General) - } + updateGeneral(cfg.General, force) updateProxies(cfg.Proxies, cfg.Providers) updateRules(cfg.Rules) updateDNS(cfg.DNS) @@ -96,15 +102,17 @@ func GetGeneral() *config.General { } general := &config.General{ - Port: ports.Port, - SocksPort: ports.SocksPort, - RedirPort: ports.RedirPort, - MixedPort: ports.MixedPort, - Authentication: authenticator, - AllowLan: P.AllowLan(), - BindAddress: P.BindAddress(), - Mode: tunnel.Mode(), - LogLevel: log.Level(), + Inbound: config.Inbound{ + Port: ports.Port, + SocksPort: ports.SocksPort, + RedirPort: ports.RedirPort, + MixedPort: ports.MixedPort, + Authentication: authenticator, + AllowLan: P.AllowLan(), + BindAddress: P.BindAddress(), + }, + Mode: tunnel.Mode(), + LogLevel: log.Level(), } return general @@ -166,9 +174,14 @@ func updateRules(rules []C.Rule) { tunnel.UpdateRules(rules) } -func updateGeneral(general *config.General) { +func updateGeneral(general *config.General, force bool) { log.SetLevel(general.LogLevel) tunnel.SetMode(general.Mode) + resolver.DisableIPv6 = !general.IPv6 + + if !force { + return + } allowLan := general.AllowLan P.SetAllowLan(allowLan) From 18bb285a9049f34b9decff5f73904275f3b31181 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 18 Jun 2020 21:33:57 +0800 Subject: [PATCH 422/535] Fix: `external-ui` should relative with clash HomeDir --- hub/route/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hub/route/server.go b/hub/route/server.go index a73e80592a..827cdb12dd 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -36,7 +36,7 @@ type Traffic struct { } func SetUIPath(path string) { - uiPath = path + uiPath = C.Path.Resolve(path) } func Start(addr string, secret string) { From 5f3db724227b32ba610f582af05dbc9ce5317de0 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 21 Jun 2020 12:38:14 +0800 Subject: [PATCH 423/535] Fix: docker multiplatform build --- Dockerfile | 5 +++-- Makefile | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ba3c66cd1b..e94a0d0856 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,10 +3,11 @@ FROM golang:alpine as builder RUN apk add --no-cache make git && \ wget -O /Country.mmdb https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb WORKDIR /clash-src +COPY --from=tonistiigi/xx:golang / / COPY . /clash-src RUN go mod download && \ - make linux-amd64 && \ - mv ./bin/clash-linux-amd64 /clash + make docker && \ + mv ./bin/clash-docker /clash FROM alpine:latest diff --git a/Makefile b/Makefile index f4c981954b..fe6839f408 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,9 @@ WINDOWS_ARCH_LIST = \ all: linux-amd64 darwin-amd64 windows-amd64 # Most used +docker: + $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + darwin-amd64: GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ From 3dfff84cc3d0aa2ee6fedb5ad535a7b75706b309 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Wed, 24 Jun 2020 18:41:23 +0800 Subject: [PATCH 424/535] Fix: domain trie should backtrack to parent if match fail (#758) --- component/trie/domain.go | 46 +++++++++++++++-------------------- component/trie/domain_test.go | 5 ++++ 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/component/trie/domain.go b/component/trie/domain.go index e8dcf213b0..8df22792d1 100644 --- a/component/trie/domain.go +++ b/component/trie/domain.go @@ -96,45 +96,37 @@ func (t *DomainTrie) Search(domain string) *Node { return nil } - n := t.root - var dotWildcardNode *Node - var wildcardNode *Node - for i := len(parts) - 1; i >= 0; i-- { - part := parts[i] + n := t.search(t.root, parts) - if node := n.getChild(dotWildcard); node != nil { - dotWildcardNode = node - } + if n.Data == nil { + return nil + } - child := n.getChild(part) - if child == nil && wildcardNode != nil { - child = wildcardNode.getChild(part) - } - wildcardNode = n.getChild(wildcard) + return n +} - n = child - if n == nil { - n = wildcardNode - wildcardNode = nil - } +func (t *DomainTrie) search(node *Node, parts []string) *Node { + if len(parts) == 0 { + return node + } - if n == nil { - break + if c := node.getChild(parts[len(parts)-1]); c != nil { + if n := t.search(c, parts[:len(parts)-1]); n != nil { + return n } } - if n == nil { - if dotWildcardNode != nil { - return dotWildcardNode + if c := node.getChild(wildcard); c != nil { + if n := t.search(c, parts[:len(parts)-1]); n != nil { + return n } - return nil } - if n.Data == nil { - return nil + if c := node.getChild(dotWildcard); c != nil { + return c } - return n + return nil } // New returns a new, empty Trie. diff --git a/component/trie/domain_test.go b/component/trie/domain_test.go index 38b347e15f..3e15071734 100644 --- a/component/trie/domain_test.go +++ b/component/trie/domain_test.go @@ -39,6 +39,10 @@ func TestTrie_Wildcard(t *testing.T) { ".example.net", ".apple.*", "+.foo.com", + "+.stun.*.*", + "+.stun.*.*.*", + "+.stun.*.*.*.*", + "stun.l.google.com", } for _, domain := range domains { @@ -52,6 +56,7 @@ func TestTrie_Wildcard(t *testing.T) { assert.NotNil(t, tree.Search("test.apple.com")) assert.NotNil(t, tree.Search("test.foo.com")) assert.NotNil(t, tree.Search("foo.com")) + assert.NotNil(t, tree.Search("global.stun.website.com")) assert.Nil(t, tree.Search("foo.sub.example.com")) assert.Nil(t, tree.Search("foo.example.dev")) assert.Nil(t, tree.Search("example.com")) From 14c9cf1b977612afe2e5ebd2a338e8c36174b234 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Wed, 24 Jun 2020 19:46:37 +0800 Subject: [PATCH 425/535] Fix: domain trie crash if not match in #758 (#762) --- component/trie/domain.go | 2 +- component/trie/domain_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/component/trie/domain.go b/component/trie/domain.go index 8df22792d1..909cccf07b 100644 --- a/component/trie/domain.go +++ b/component/trie/domain.go @@ -98,7 +98,7 @@ func (t *DomainTrie) Search(domain string) *Node { n := t.search(t.root, parts) - if n.Data == nil { + if n == nil || n.Data == nil { return nil } diff --git a/component/trie/domain_test.go b/component/trie/domain_test.go index 3e15071734..61340cde81 100644 --- a/component/trie/domain_test.go +++ b/component/trie/domain_test.go @@ -27,6 +27,7 @@ func TestTrie_Basic(t *testing.T) { assert.NotNil(t, tree.Insert("", localIP)) assert.Nil(t, tree.Search("")) assert.NotNil(t, tree.Search("localhost")) + assert.Nil(t, tree.Search("www.google.com")) } func TestTrie_Wildcard(t *testing.T) { From 2781090405d7db68227fa5520f920ceb0a2c731d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 27 Jun 2020 14:19:31 +0800 Subject: [PATCH 426/535] Chore: move experimental features to stable --- README.md | 9 +++------ config/config.go | 23 ++++++++++------------- hub/executor/executor.go | 21 +++++++++------------ tunnel/tunnel.go | 13 ------------- 4 files changed, 22 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index a1bfaaf232..402fe7a6ba 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ $ go get -u -v github.com/Dreamacro/clash ``` Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) -Pre-built Premium binaries are available here: [Premium release](https://github.com/Dreamacro/clash/releases/tag/premium). Source is not currently available. +Pre-built Premium binaries are available here: [premium release](https://github.com/Dreamacro/clash/releases/tag/premium). Source is not currently available. Check Clash version with: @@ -47,7 +47,7 @@ $ clash -v ## Daemonize Clash -Unfortunately, there is no native or elegant way to implement daemons on Golang. We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service. +We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service. ([Wiki](https://github.com/Dreamacro/clash/wiki/Clash-as-a-daemon)) In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way: @@ -114,10 +114,7 @@ external-controller: 127.0.0.1:9090 # Secret for RESTful API (Optional) # secret: "" -# experimental feature -experimental: - ignore-resolve-fail: true # ignore dns resolve fail, default value is true - # interface-name: en0 # outbound interface name +# interface-name: en0 # outbound interface name # authentication of local SOCKS5/HTTP(S) server # authentication: diff --git a/config/config.go b/config/config.go index 7b3913c4d0..b2aea8756c 100644 --- a/config/config.go +++ b/config/config.go @@ -27,9 +27,10 @@ import ( type General struct { Inbound Controller - Mode T.TunnelMode `json:"mode"` - LogLevel log.LogLevel `json:"log-level"` - IPv6 bool `json:"ipv6"` + Mode T.TunnelMode `json:"mode"` + LogLevel log.LogLevel `json:"log-level"` + IPv6 bool `json:"ipv6"` + Interface string `json:"interface-name"` } // Inbound @@ -70,10 +71,7 @@ type FallbackFilter struct { } // Experimental config -type Experimental struct { - IgnoreResolveFail bool `yaml:"ignore-resolve-fail"` - Interface string `yaml:"interface-name"` -} +type Experimental struct{} // Config is clash config manager type Config struct { @@ -119,6 +117,7 @@ type RawConfig struct { ExternalController string `yaml:"external-controller"` ExternalUI string `yaml:"external-ui"` Secret string `yaml:"secret"` + Interface string `yaml:"interface-name"` ProxyProvider map[string]map[string]interface{} `yaml:"proxy-providers"` Hosts map[string]string `yaml:"hosts"` @@ -157,9 +156,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Rule: []string{}, Proxy: []map[string]interface{}{}, ProxyGroup: []map[string]interface{}{}, - Experimental: Experimental{ - IgnoreResolveFail: true, - }, DNS: RawDNS{ Enable: false, FakeIPRange: "198.18.0.1/16", @@ -253,9 +249,10 @@ func parseGeneral(cfg *RawConfig) (*General, error) { ExternalUI: cfg.ExternalUI, Secret: cfg.Secret, }, - Mode: cfg.Mode, - LogLevel: cfg.LogLevel, - IPv6: cfg.IPv6, + Mode: cfg.Mode, + LogLevel: cfg.LogLevel, + IPv6: cfg.IPv6, + Interface: cfg.Interface, }, nil } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 584191acb6..2d71bde047 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -118,18 +118,7 @@ func GetGeneral() *config.General { return general } -func updateExperimental(c *config.Config) { - cfg := c.Experimental - - tunnel.UpdateExperimental(cfg.IgnoreResolveFail) - if cfg.Interface != "" && c.DNS.Enable { - dialer.DialHook = dialer.DialerWithInterface(cfg.Interface) - dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface) - } else { - dialer.DialHook = nil - dialer.ListenPacketHook = nil - } -} +func updateExperimental(c *config.Config) {} func updateDNS(c *config.DNS) { if c.Enable == false { @@ -179,6 +168,14 @@ func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) resolver.DisableIPv6 = !general.IPv6 + if cfg.Interface != "" { + dialer.DialHook = dialer.DialerWithInterface(cfg.Interface) + dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface) + } else { + dialer.DialHook = nil + dialer.ListenPacketHook = nil + } + if !force { return } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index c5e4abab9e..a8ed5c883d 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -28,9 +28,6 @@ var ( configMux sync.RWMutex enhancedMode *dns.Resolver - // experimental features - ignoreResolveFail bool - // Outbound Rule mode = Rule @@ -82,13 +79,6 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid configMux.Unlock() } -// UpdateExperimental handle update experimental config -func UpdateExperimental(value bool) { - configMux.Lock() - ignoreResolveFail = value - configMux.Unlock() -} - // Mode return current mode func Mode() TunnelMode { return mode @@ -318,9 +308,6 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { if !resolved && shouldResolveIP(rule, metadata) { ip, err := resolver.ResolveIP(metadata.Host) if err != nil { - if !ignoreResolveFail { - return nil, nil, fmt.Errorf("[DNS] resolve %s error: %s", metadata.Host, err.Error()) - } log.Debugln("[DNS] resolve %s error: %s", metadata.Host, err.Error()) } else { log.Debugln("[DNS] %s --> %s", metadata.Host, ip.String()) From 7c62fe41b40e6b20c8c88e0e5b5ee96ac0c46dd9 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 27 Jun 2020 14:28:10 +0800 Subject: [PATCH 427/535] Chore: remove forward compatibility code --- README.md | 3 --- adapters/outbound/shadowsocks.go | 15 --------------- config/config.go | 29 ----------------------------- hub/executor/executor.go | 28 ++++------------------------ rules/parser.go | 6 ------ 5 files changed, 4 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 402fe7a6ba..377d544750 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,6 @@ proxies: password: "password" # udp: true - # old obfs configuration format remove after prerelease - name: "ss2" type: ss server: server @@ -368,8 +367,6 @@ rules: - GEOIP,CN,DIRECT - DST-PORT,80,DIRECT - SRC-PORT,7777,DIRECT - # FINAL would remove after prerelease - # you also can use `FINAL,Proxy` or `FINAL,,Proxy` now - MATCH,auto ``` diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 8a62046434..68c907e2cd 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -37,10 +37,6 @@ type ShadowSocksOption struct { UDP bool `proxy:"udp,omitempty"` Plugin string `proxy:"plugin,omitempty"` PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"` - - // deprecated when bump to 1.0 - Obfs string `proxy:"obfs,omitempty"` - ObfsHost string `proxy:"obfs-host,omitempty"` } type simpleObfsOption struct { @@ -122,17 +118,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { var obfsOption *simpleObfsOption obfsMode := "" - // forward compatibility before 1.0 - if option.Obfs != "" { - obfsMode = option.Obfs - obfsOption = &simpleObfsOption{ - Host: "bing.com", - } - if option.ObfsHost != "" { - obfsOption.Host = option.ObfsHost - } - } - decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) if option.Plugin == "obfs" { opts := simpleObfsOption{Host: "bing.com"} diff --git a/config/config.go b/config/config.go index b2aea8756c..a715e40c77 100644 --- a/config/config.go +++ b/config/config.go @@ -126,12 +126,6 @@ type RawConfig struct { Proxy []map[string]interface{} `yaml:"proxies"` ProxyGroup []map[string]interface{} `yaml:"proxy-groups"` Rule []string `yaml:"rules"` - - // remove after 1.0 - ProxyProviderOld map[string]map[string]interface{} `yaml:"proxy-provider"` - ProxyOld []map[string]interface{} `yaml:"Proxy"` - ProxyGroupOld []map[string]interface{} `yaml:"Proxy Group"` - RuleOld []string `yaml:"Rule"` } // Parse config @@ -168,11 +162,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { "8.8.8.8", }, }, - - // remove after 1.0 - RuleOld: []string{}, - ProxyOld: []map[string]interface{}{}, - ProxyGroupOld: []map[string]interface{}{}, } if err := yaml.Unmarshal(buf, &rawCfg); err != nil { @@ -264,18 +253,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ groupsConfig := cfg.ProxyGroup providersConfig := cfg.ProxyProvider - if len(proxiesConfig) == 0 { - proxiesConfig = cfg.ProxyOld - } - - if len(groupsConfig) == 0 { - groupsConfig = cfg.ProxyGroupOld - } - - if len(providersConfig) == 0 { - providersConfig = cfg.ProxyProviderOld - } - proxies["DIRECT"] = outbound.NewProxy(outbound.NewDirect()) proxies["REJECT"] = outbound.NewProxy(outbound.NewReject()) proxyList = append(proxyList, "DIRECT", "REJECT") @@ -371,14 +348,8 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { rules := []C.Rule{} - rulesConfig := cfg.Rule - // remove after 1.0 - if len(rulesConfig) == 0 { - rulesConfig = cfg.RuleOld - } - // parse rules for idx, line := range rulesConfig { rule := trimArr(strings.Split(line, ",")) diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 2d71bde047..8131ad39c2 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "path/filepath" "sync" "github.com/Dreamacro/clash/adapters/provider" @@ -25,30 +24,11 @@ var ( mux sync.Mutex ) -// forward compatibility before 1.0 -func readRawConfig(path string) ([]byte, error) { - data, err := ioutil.ReadFile(path) - if err == nil && len(data) != 0 { - return data, nil - } - - if filepath.Ext(path) != ".yaml" { - return nil, err - } - - path = path[:len(path)-5] + ".yml" - if _, fallbackErr := os.Stat(path); fallbackErr == nil { - return ioutil.ReadFile(path) - } - - return data, err -} - func readConfig(path string) ([]byte, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err } - data, err := readRawConfig(path) + data, err := ioutil.ReadFile(path) if err != nil { return nil, err } @@ -168,9 +148,9 @@ func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) resolver.DisableIPv6 = !general.IPv6 - if cfg.Interface != "" { - dialer.DialHook = dialer.DialerWithInterface(cfg.Interface) - dialer.ListenPacketHook = dialer.ListenPacketWithInterface(cfg.Interface) + if general.Interface != "" { + dialer.DialHook = dialer.DialerWithInterface(general.Interface) + dialer.ListenPacketHook = dialer.ListenPacketWithInterface(general.Interface) } else { dialer.DialHook = nil dialer.ListenPacketHook = nil diff --git a/rules/parser.go b/rules/parser.go index 3814056dd1..2f09e79366 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -25,9 +25,6 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { case "IP-CIDR", "IP-CIDR6": noResolve := HasNoResolve(params) parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRNoResolve(noResolve)) - // deprecated when bump to 1.0 - case "SOURCE-IP-CIDR": - fallthrough case "SRC-IP-CIDR": parsed, parseErr = NewIPCIDR(payload, target, WithIPCIDRSourceIP(true), WithIPCIDRNoResolve(true)) case "SRC-PORT": @@ -35,9 +32,6 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { case "DST-PORT": parsed, parseErr = NewPort(payload, target, false) case "MATCH": - fallthrough - // deprecated when bump to 1.0 - case "FINAL": parsed = NewMatch(target) default: parseErr = fmt.Errorf("unsupported rule type %s", tp) From 59968fff1cf8567fe390fd44fa97324ca5157d44 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 27 Jun 2020 21:09:04 +0800 Subject: [PATCH 428/535] Fix: github actions tag build --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4afdb0cd59..02dedf1162 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,7 +43,7 @@ jobs: id: version with: script: | - return context.payload.ref.replace(/\/refs\/tags\/v/, '') + return context.payload.ref.replace(/\/?refs\/tags\/v/, '') result-encoding: string - name: Docker buildx image and push on release From 3f0584ac09022f363387bc7aa1f293b14ae02c07 Mon Sep 17 00:00:00 2001 From: Birkhoff Lee Date: Sun, 28 Jun 2020 10:39:30 +0800 Subject: [PATCH 429/535] Chore: move documentations to wiki (#766) --- README.md | 367 ++---------------------------------------------------- 1 file changed, 11 insertions(+), 356 deletions(-) diff --git a/README.md b/README.md index 377d544750..8cde4a9920 100644 --- a/README.md +++ b/README.md @@ -19,372 +19,27 @@ ## Features -- Local HTTP/HTTPS/SOCKS server with/without authentication -- VMess, Shadowsocks, Trojan (experimental), Snell protocol support for remote connections. UDP is supported. -- Built-in DNS server that aims to minimize DNS pollution attacks, supports DoH/DoT upstream. Fake IP is also supported. +- Local HTTP/HTTPS/SOCKS server with authentication support +- VMess, Shadowsocks, Trojan, Snell protocol support for remote connections +- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP. - Rules based off domains, GEOIP, IP CIDR or ports to forward packets to different nodes - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency - Remote providers, allowing users to get node lists remotely instead of hardcoding in config -- Netfilter TCP redirecting. You can deploy Clash on your Internet gateway with `iptables`. -- Comprehensive HTTP API controller +- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. +- Comprehensive HTTP RESTful API controller -## Install - -Clash requires Go >= 1.13. You can build it from source: - -```sh -$ go get -u -v github.com/Dreamacro/clash -``` - -Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases) -Pre-built Premium binaries are available here: [premium release](https://github.com/Dreamacro/clash/releases/tag/premium). Source is not currently available. - -Check Clash version with: - -```sh -$ clash -v -``` - -## Daemonize Clash - -We recommend using third-party daemon management tools like PM2, Supervisor or the like to keep Clash running as a service. ([Wiki](https://github.com/Dreamacro/clash/wiki/Clash-as-a-daemon)) - -In the case of [pm2](https://github.com/Unitech/pm2), start the daemon this way: - -```sh -$ pm2 start clash -``` - -If you have Docker installed, it's recommended to deploy Clash directly using `docker-compose`: [run Clash in Docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker) - -## Config - -The default configuration directory is `$HOME/.config/clash`. - -The name of the configuration file is `config.yaml`. - -If you want to use another directory, use `-d` to control the configuration directory. - -For example, you can use the current directory as the configuration directory: - -```sh -$ clash -d . -``` - -
- This is an example configuration file (click to expand) - -```yml -# port of HTTP -port: 7890 - -# port of SOCKS5 -socks-port: 7891 - -# (HTTP and SOCKS5 in one port) -# mixed-port: 7890 - -# redir port for Linux and macOS -# redir-port: 7892 - -allow-lan: false - -# Only applicable when setting allow-lan to true -# "*": bind all IP addresses -# 192.168.122.11: bind a single IPv4 address -# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address -# bind-address: "*" - -# ipv6: false # when ipv6 is false, each clash dial with ipv6, but it's not affect the response of the dns server, default is false - -# rule / global / direct (default is rule) -mode: rule - -# set log level to stdout (default is info) -# info / warning / error / debug / silent -log-level: info - -# RESTful API for clash -external-controller: 127.0.0.1:9090 - -# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui` -# input is a relative path to the configuration directory or an absolute path -# external-ui: folder - -# Secret for RESTful API (Optional) -# secret: "" - -# interface-name: en0 # outbound interface name - -# authentication of local SOCKS5/HTTP(S) server -# authentication: -# - "user1:pass1" -# - "user2:pass2" - -# # hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com) -# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com > .example.com) -# # +.foo.com equal .foo.com and foo.com -# hosts: -# '*.clash.dev': 127.0.0.1 -# '.dev': 127.0.0.1 -# 'alpha.clash.dev': '::1' -# '+.foo.dev': 127.0.0.1 - -# dns: - # enable: true # set true to enable dns (default is false) - # ipv6: false # it only affect the dns server response, default is false - # listen: 0.0.0.0:53 - # # default-nameserver: # resolve dns nameserver host, should fill pure IP - # # - 114.114.114.114 - # # - 8.8.8.8 - # enhanced-mode: redir-host # or fake-ip - # # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it - # fake-ip-filter: # fake ip white domain list - # - '*.lan' - # - localhost.ptlogin2.qq.com - # nameserver: - # - 114.114.114.114 - # - tls://dns.rubyfish.cn:853 # dns over tls - # - https://1.1.1.1/dns-query # dns over https - # fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN - # - tcp://1.1.1.1 - # fallback-filter: - # geoip: true # default - # ipcidr: # ips in these subnets will be considered polluted - # - 240.0.0.0/4 - -proxies: - # shadowsocks - # The supported ciphers(encrypt methods): - # aes-128-gcm aes-192-gcm aes-256-gcm - # aes-128-cfb aes-192-cfb aes-256-cfb - # aes-128-ctr aes-192-ctr aes-256-ctr - # rc4-md5 chacha20-ietf xchacha20 - # chacha20-ietf-poly1305 xchacha20-ietf-poly1305 - - name: "ss1" - type: ss - server: server - port: 443 - cipher: chacha20-ietf-poly1305 - password: "password" - # udp: true - - - name: "ss2" - type: ss - server: server - port: 443 - cipher: chacha20-ietf-poly1305 - password: "password" - plugin: obfs - plugin-opts: - mode: tls # or http - # host: bing.com - - - name: "ss3" - type: ss - server: server - port: 443 - cipher: chacha20-ietf-poly1305 - password: "password" - plugin: v2ray-plugin - plugin-opts: - mode: websocket # no QUIC now - # tls: true # wss - # skip-cert-verify: true - # host: bing.com - # path: "/" - # mux: true - # headers: - # custom: value - - # vmess - # cipher support auto/aes-128-gcm/chacha20-poly1305/none - - name: "vmess" - type: vmess - server: server - port: 443 - uuid: uuid - alterId: 32 - cipher: auto - # udp: true - # tls: true - # skip-cert-verify: true - # servername: example.com # priority over wss host - # network: ws - # ws-path: /path - # ws-headers: - # Host: v2ray.com - - - name: "vmess-http" - type: vmess - server: server - port: 443 - uuid: uuid - alterId: 32 - cipher: auto - # udp: true - # network: http - # http-opts: - # # method: "GET" - # # path: - # # - '/' - # # - '/video' - # # headers: - # # Connection: - # # - keep-alive - - # socks5 - - name: "socks" - type: socks5 - server: server - port: 443 - # username: username - # password: password - # tls: true - # skip-cert-verify: true - # udp: true - - # http - - name: "http" - type: http - server: server - port: 443 - # username: username - # password: password - # tls: true # https - # skip-cert-verify: true - - # snell - - name: "snell" - type: snell - server: server - port: 44046 - psk: yourpsk - # obfs-opts: - # mode: http # or tls - # host: bing.com - - # trojan - - name: "trojan" - type: trojan - server: server - port: 443 - password: yourpsk - # udp: true - # sni: example.com # aka server name - # alpn: - # - h2 - # - http/1.1 - # skip-cert-verify: true - -proxy-groups: - # relay chains the proxies. proxies shall not contain a relay. No UDP support. - # Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet - - name: "relay" - type: relay - proxies: - - http - - vmess - - ss1 - - ss2 - - # url-test select which proxy will be used by benchmarking speed to a URL. - - name: "auto" - type: url-test - proxies: - - ss1 - - ss2 - - vmess1 - # tolerance: 150 - url: 'http://www.gstatic.com/generate_204' - interval: 300 - - # fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group. - - name: "fallback-auto" - type: fallback - proxies: - - ss1 - - ss2 - - vmess1 - url: 'http://www.gstatic.com/generate_204' - interval: 300 - - # load-balance: The request of the same eTLD will be dial on the same proxy. - - name: "load-balance" - type: load-balance - proxies: - - ss1 - - ss2 - - vmess1 - url: 'http://www.gstatic.com/generate_204' - interval: 300 - - # select is used for selecting proxy or proxy group - # you can use RESTful API to switch proxy, is recommended for use in GUI. - - name: Proxy - type: select - proxies: - - ss1 - - ss2 - - vmess1 - - auto - - - name: UseProvider - type: select - use: - - provider1 - proxies: - - Proxy - - DIRECT - -proxy-providers: - provider1: - type: http - url: "url" - interval: 3600 - path: ./hk.yaml - health-check: - enable: true - interval: 600 - url: http://www.gstatic.com/generate_204 - test: - type: file - path: /test.yaml - health-check: - enable: true - interval: 36000 - url: http://www.gstatic.com/generate_204 - -rules: - - DOMAIN-SUFFIX,google.com,auto - - DOMAIN-KEYWORD,google,auto - - DOMAIN,google.com,auto - - DOMAIN-SUFFIX,ad.com,REJECT - # rename SOURCE-IP-CIDR and would remove after prerelease - - SRC-IP-CIDR,192.168.1.201/32,DIRECT - # optional param "no-resolve" for IP rules (GEOIP IP-CIDR) - - IP-CIDR,127.0.0.0/8,DIRECT - - GEOIP,CN,DIRECT - - DST-PORT,80,DIRECT - - SRC-PORT,7777,DIRECT - - MATCH,auto -``` -
- -## Advanced -[Provider](https://github.com/Dreamacro/clash/wiki/Provider) - -## Documentations -https://clash.gitbook.io/ +## Getting Started +Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). ## Credits -[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) - -[v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) +* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) +* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) ## License +This software is released under the GPL-3.0 license. + [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large) ## TODO From 93c987a6cb702443f788d1a7847855b3f8d7c907 Mon Sep 17 00:00:00 2001 From: Birkhoff Lee Date: Sun, 28 Jun 2020 10:59:04 +0800 Subject: [PATCH 430/535] Fix: typo in dialer.go (#767) --- component/dialer/dialer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 5cb9badbd8..f0f575a347 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -66,7 +66,7 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) } return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port)) case "tcp", "udp": - return dualStackDailContext(ctx, network, address) + return dualStackDialContext(ctx, network, address) default: return nil, errors.New("network invalid") } @@ -88,7 +88,7 @@ func ListenPacket(network, address string) (net.PacketConn, error) { return lc.ListenPacket(context.Background(), network, address) } -func dualStackDailContext(ctx context.Context, network, address string) (net.Conn, error) { +func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err From f44cd9180cdb331744b22877a68db8dc0d380eae Mon Sep 17 00:00:00 2001 From: Birkhoff Lee Date: Tue, 30 Jun 2020 13:37:53 +0800 Subject: [PATCH 431/535] Chore: update GitHub issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 44 +++++++++++++++------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 932ada0147..2e5b75545d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,48 +7,54 @@ assignees: '' --- - + -我都确认过了,我要继续提交。 - ------------------------------------------------------------------ + +Make sure to add **all the information needed to understand the bug** so that someone can help. If the info is missing we'll add the 'Needs more information' label and close the issue until there is enough information. +--> -### clash core config +### Clash config -``` +
+ config.yaml + +```yaml …… ``` +
+ ### Clash log ``` @@ -57,9 +63,7 @@ Paste the Clash core log below with the log level set to `DEBUG`. ### 环境 Environment -* Clash Core 的操作系统 (the OS that the Clash core is running on) -…… -* 使用者的操作系统 (the OS running on the client) +* 操作系统 (the OS that the Clash core is running on) …… * 网路环境或拓扑 (network conditions/topology) …… @@ -67,7 +71,7 @@ Paste the Clash core log below with the log level set to `DEBUG`. …… * ISP 有没有进行 DNS 污染 (is your ISP performing DNS pollution?) …… -* 其他 +* 其他 (any other information that would be useful) …… ### 说明 Description @@ -85,7 +89,7 @@ Paste the Clash core log below with the log level set to `DEBUG`. **我预期会发生……?** -**实际上发生了什麽?** +**实际上发生了什么?** ### 可能的解决方案 Possible Solution From acd51bbc90dad86c593aba1ce84fc578f36702d6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 1 Jul 2020 00:01:36 +0800 Subject: [PATCH 432/535] Fix: obfs host should not have 80 port --- component/simple-obfs/http.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/component/simple-obfs/http.go b/component/simple-obfs/http.go index ab1b59b725..1189435781 100644 --- a/component/simple-obfs/http.go +++ b/component/simple-obfs/http.go @@ -67,7 +67,10 @@ func (ho *HTTPObfs) Write(b []byte) (int, error) { req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2)) req.Header.Set("Upgrade", "websocket") req.Header.Set("Connection", "Upgrade") - req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) + req.Host = ho.host + if ho.port != "80" { + req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) + } req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) req.ContentLength = int64(len(b)) err := req.Write(ho.Conn) From 35449bfa177ecead9d1de05ec74382958540e90b Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Thu, 9 Jul 2020 10:27:05 +0800 Subject: [PATCH 433/535] Feature: add github stale action --- .github/workflows/stale.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..075218595b --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,19 @@ + +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' + days-before-stale: 120 + days-before-close: 5 From e6aa452b5193d61e7d84ba9c4aae6ee6cbb20d5d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 13 Jul 2020 00:25:54 +0800 Subject: [PATCH 434/535] Fix: ticker leak --- adapters/provider/fetcher.go | 40 +++++++++++++++++++++--------------- hub/route/connections.go | 1 + hub/route/server.go | 1 + 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/adapters/provider/fetcher.go b/adapters/provider/fetcher.go index 2256ff2a7e..3cbf5fbe15 100644 --- a/adapters/provider/fetcher.go +++ b/adapters/provider/fetcher.go @@ -23,6 +23,7 @@ type fetcher struct { vehicle Vehicle updatedAt *time.Time ticker *time.Ticker + done chan struct{} hash [16]byte parser parser onUpdate func(interface{}) @@ -117,27 +118,33 @@ func (f *fetcher) Update() (interface{}, bool, error) { func (f *fetcher) Destroy() error { if f.ticker != nil { - f.ticker.Stop() + f.done <- struct{}{} } return nil } func (f *fetcher) pullLoop() { - for range f.ticker.C { - elm, same, err := f.Update() - if err != nil { - log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error()) - continue - } - - if same { - log.Debugln("[Provider] %s's proxies doesn't change", f.Name()) - continue - } - - log.Infoln("[Provider] %s's proxies update", f.Name()) - if f.onUpdate != nil { - f.onUpdate(elm) + for { + select { + case <-f.ticker.C: + elm, same, err := f.Update() + if err != nil { + log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error()) + continue + } + + if same { + log.Debugln("[Provider] %s's proxies doesn't change", f.Name()) + continue + } + + log.Infoln("[Provider] %s's proxies update", f.Name()) + if f.onUpdate != nil { + f.onUpdate(elm) + } + case <-f.done: + f.ticker.Stop() + return } } } @@ -165,6 +172,7 @@ func newFetcher(name string, interval time.Duration, vehicle Vehicle, parser par ticker: ticker, vehicle: vehicle, parser: parser, + done: make(chan struct{}, 1), onUpdate: onUpdate, } } diff --git a/hub/route/connections.go b/hub/route/connections.go index 6642b6f2db..2179262527 100644 --- a/hub/route/connections.go +++ b/hub/route/connections.go @@ -63,6 +63,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) { } tick := time.NewTicker(time.Millisecond * time.Duration(interval)) + defer tick.Stop() for range tick.C { if err := sendSnapshot(); err != nil { break diff --git a/hub/route/server.go b/hub/route/server.go index 827cdb12dd..5948f12190 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -142,6 +142,7 @@ func traffic(w http.ResponseWriter, r *http.Request) { } tick := time.NewTicker(time.Second) + defer tick.Stop() t := T.DefaultManager buf := &bytes.Buffer{} var err error From 02c7fd8d70e09c91f9c07b737fc68fcdd6bf68e2 Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 17 Jul 2020 17:34:40 +0800 Subject: [PATCH 435/535] Fix: write msg cache multiple times (#812) Co-authored-by: john.xu --- dns/resolver.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/dns/resolver.go b/dns/resolver.go index 52a08c0d20..0a105d6cf5 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -115,11 +115,16 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { q := m.Question[0] - defer func() { - if msg == nil { - return + ret, err, shared := r.group.Do(q.String(), func() (interface{}, error) { + isIPReq := isIPRequest(q) + if isIPReq { + return r.fallbackExchange(m) } + msg, err := r.batchExchange(r.main, m) + if err != nil { + return nil, err + } putMsgToCache(r.lruCache, q.String(), msg) if r.mapping || r.fakeip { ips := r.msgToIP(msg) @@ -127,15 +132,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { putMsgToCache(r.lruCache, ip.String(), msg) } } - }() - - ret, err, shared := r.group.Do(q.String(), func() (interface{}, error) { - isIPReq := isIPRequest(q) - if isIPReq { - return r.fallbackExchange(m) - } - - return r.batchExchange(r.main, m) + return msg, nil }) if err == nil { From 3a3e2c05af124714d3169b75a527ec488b88c3af Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 18 Jul 2020 19:22:09 +0800 Subject: [PATCH 436/535] Chore: add rule payload in log --- tunnel/tunnel.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index a8ed5c883d..029e1b0b8c 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -218,7 +218,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { switch true { case rule != nil: - log.Infoln("[UDP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rawPc.Chains().String()) + log.Infoln("[UDP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) case mode == Global: log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) case mode == Direct: @@ -271,7 +271,7 @@ func handleTCPConn(localConn C.ServerAdapter) { switch true { case rule != nil: - log.Infoln("[TCP] %s --> %v match %s using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), remoteConn.Chains().String()) + log.Infoln("[TCP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String()) case mode == Global: log.Infoln("[TCP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) case mode == Direct: From 6c7a8fffe09f5fede1cbc10d2814e3757d7e5613 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 18 Jul 2020 19:32:40 +0800 Subject: [PATCH 437/535] Chore: should not write file on file provider --- adapters/provider/fetcher.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adapters/provider/fetcher.go b/adapters/provider/fetcher.go index 3cbf5fbe15..c0f18c0128 100644 --- a/adapters/provider/fetcher.go +++ b/adapters/provider/fetcher.go @@ -74,8 +74,10 @@ func (f *fetcher) Initial() (interface{}, error) { } } - if err := safeWrite(f.vehicle.Path(), buf); err != nil { - return nil, err + if f.vehicle.Type() != File { + if err := safeWrite(f.vehicle.Path(), buf); err != nil { + return nil, err + } } f.hash = md5.Sum(buf) From cf9e1545a44c449293f4051939e23d16070c2f67 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sat, 18 Jul 2020 20:56:13 +0800 Subject: [PATCH 438/535] Improve: fix go test race detect --- common/observable/observable_test.go | 21 ++++++++++++--------- common/singledo/singledo.go | 3 +++ common/singledo/singledo_test.go | 7 ++++--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index d965fa3b0f..67e2934131 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -1,8 +1,8 @@ package observable import ( - "runtime" "sync" + "sync/atomic" "testing" "time" @@ -38,20 +38,20 @@ func TestObservable_MutilSubscribe(t *testing.T) { src := NewObservable(iter) ch1, _ := src.Subscribe() ch2, _ := src.Subscribe() - count := 0 + var count int32 var wg sync.WaitGroup wg.Add(2) waitCh := func(ch <-chan interface{}) { for range ch { - count++ + atomic.AddInt32(&count, 1) } wg.Done() } go waitCh(ch1) go waitCh(ch2) wg.Wait() - assert.Equal(t, count, 10) + assert.Equal(t, int32(10), count) } func TestObservable_UnSubscribe(t *testing.T) { @@ -82,9 +82,6 @@ func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { } func TestObservable_SubscribeGoroutineLeak(t *testing.T) { - // waiting for other goroutine recycle - time.Sleep(120 * time.Millisecond) - init := runtime.NumGoroutine() iter := iterator([]interface{}{1, 2, 3, 4, 5}) src := NewObservable(iter) max := 100 @@ -107,6 +104,12 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) { go waitCh(ch) } wg.Wait() - now := runtime.NumGoroutine() - assert.Equal(t, init, now) + + for _, sub := range list { + _, more := <-sub + assert.False(t, more) + } + + _, more := <-list[0] + assert.False(t, more) } diff --git a/common/singledo/singledo.go b/common/singledo/singledo.go index 58a9543468..0db56c1fe9 100644 --- a/common/singledo/singledo.go +++ b/common/singledo/singledo.go @@ -44,9 +44,12 @@ func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, s s.mux.Unlock() call.val, call.err = fn() call.wg.Done() + + s.mux.Lock() s.call = nil s.result = &Result{call.val, call.err} s.last = now + s.mux.Unlock() return call.val, call.err, false } diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go index 637557c40b..c9c58e587e 100644 --- a/common/singledo/singledo_test.go +++ b/common/singledo/singledo_test.go @@ -2,6 +2,7 @@ package singledo import ( "sync" + "sync/atomic" "testing" "time" @@ -11,7 +12,7 @@ import ( func TestBasic(t *testing.T) { single := NewSingle(time.Millisecond * 30) foo := 0 - shardCount := 0 + var shardCount int32 = 0 call := func() (interface{}, error) { foo++ time.Sleep(time.Millisecond * 5) @@ -25,7 +26,7 @@ func TestBasic(t *testing.T) { go func() { _, _, shard := single.Do(call) if shard { - shardCount++ + atomic.AddInt32(&shardCount, 1) } wg.Done() }() @@ -33,7 +34,7 @@ func TestBasic(t *testing.T) { wg.Wait() assert.Equal(t, 1, foo) - assert.Equal(t, 4, shardCount) + assert.Equal(t, int32(4), shardCount) } func TestTimer(t *testing.T) { From ae1e1dc9f6f2a920b1fc37c552c3763ffa86fa92 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 19 Jul 2020 13:17:05 +0800 Subject: [PATCH 439/535] Feature: support PROCESS-NAME on macOS --- config/config.go | 4 + constant/rule.go | 3 + rules/base.go | 5 +- rules/parser.go | 2 + rules/process_darwin.go | 171 ++++++++++++++++++++++++++++++++++++++++ rules/process_other.go | 11 +++ 6 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 rules/process_darwin.go create mode 100644 rules/process_other.go diff --git a/config/config.go b/config/config.go index a715e40c77..3db0e5bd8f 100644 --- a/config/config.go +++ b/config/config.go @@ -382,6 +382,10 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { parsed, parseErr := R.ParseRule(rule[0], payload, target, params) if parseErr != nil { + if parseErr == R.ErrPlatformNotSupport { + log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line) + continue + } return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } diff --git a/constant/rule.go b/constant/rule.go index 4db333b40f..6774589dd7 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -10,6 +10,7 @@ const ( SrcIPCIDR SrcPort DstPort + Process MATCH ) @@ -33,6 +34,8 @@ func (rt RuleType) String() string { return "SrcPort" case DstPort: return "DstPort" + case Process: + return "Process" case MATCH: return "Match" default: diff --git a/rules/base.go b/rules/base.go index 793cad7e76..dbe91abfa3 100644 --- a/rules/base.go +++ b/rules/base.go @@ -5,8 +5,9 @@ import ( ) var ( - errPayload = errors.New("payload error") - errParams = errors.New("params error") + errPayload = errors.New("payload error") + errParams = errors.New("params error") + ErrPlatformNotSupport = errors.New("not support on this platform") noResolve = "no-resolve" ) diff --git a/rules/parser.go b/rules/parser.go index 2f09e79366..6e78b8fae8 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -31,6 +31,8 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed, parseErr = NewPort(payload, target, true) case "DST-PORT": parsed, parseErr = NewPort(payload, target, false) + case "PROCESS-NAME": + parsed, parseErr = NewProcess(payload, target) case "MATCH": parsed = NewMatch(target) default: diff --git a/rules/process_darwin.go b/rules/process_darwin.go new file mode 100644 index 0000000000..cc99a6f75b --- /dev/null +++ b/rules/process_darwin.go @@ -0,0 +1,171 @@ +package rules + +import ( + "bytes" + "encoding/binary" + "errors" + "net" + "path/filepath" + "strconv" + "strings" + "syscall" + "unsafe" + + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" +) + +type Process struct { + adapter string + process string +} + +func (ps *Process) RuleType() C.RuleType { + return C.Process +} + +func (ps *Process) Match(metadata *C.Metadata) bool { + name, err := getExecPathFromAddress(metadata.SrcIP, metadata.SrcPort, metadata.NetWork == C.TCP) + if err != nil { + log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) + return false + } + + return strings.ToLower(name) == ps.process +} + +func (p *Process) Adapter() string { + return p.adapter +} + +func (p *Process) Payload() string { + return p.process +} + +func (p *Process) NoResolveIP() bool { + return true +} + +func NewProcess(process string, adapter string) (*Process, error) { + return &Process{ + adapter: adapter, + process: process, + }, nil +} + +const ( + procpidpathinfo = 0xb + procpidpathinfosize = 1024 + proccallnumpidinfo = 0x2 +) + +func getExecPathFromPID(pid uint32) (string, error) { + buf := make([]byte, procpidpathinfosize) + _, _, errno := syscall.Syscall6( + syscall.SYS_PROC_INFO, + proccallnumpidinfo, + uintptr(pid), + procpidpathinfo, + 0, + uintptr(unsafe.Pointer(&buf[0])), + procpidpathinfosize) + if errno != 0 { + return "", errno + } + firstZero := bytes.IndexByte(buf, 0) + if firstZero <= 0 { + return "", nil + } + + return filepath.Base(string(buf[:firstZero])), nil +} + +func getExecPathFromAddress(ip net.IP, portStr string, isTCP bool) (string, error) { + port, err := strconv.Atoi(portStr) + if err != nil { + return "", err + } + + spath := "net.inet.tcp.pcblist_n" + if !isTCP { + spath = "net.inet.udp.pcblist_n" + } + + value, err := syscall.Sysctl(spath) + if err != nil { + return "", err + } + + buf := []byte(value) + + var kinds uint32 = 0 + so, inp := 0, 0 + for i := roundUp8(xinpgenSize(buf)); i < uint32(len(buf)) && xinpgenSize(buf[i:]) > 24; i += roundUp8(xinpgenSize(buf[i:])) { + thisKind := binary.LittleEndian.Uint32(buf[i+4 : i+8]) + if kinds&thisKind == 0 { + kinds |= thisKind + switch thisKind { + case 0x1: + // XSO_SOCKET + so = int(i) + case 0x10: + // XSO_INPCB + inp = int(i) + default: + break + } + } + + // all blocks needed by tcp/udp + if (isTCP && kinds != 0x3f) || (!isTCP && kinds != 0x1f) { + continue + } + kinds = 0 + + // xsocket_n.xso_protocol + proto := binary.LittleEndian.Uint32(buf[so+36 : so+40]) + if proto != syscall.IPPROTO_TCP && proto != syscall.IPPROTO_UDP { + continue + } + + srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20]) + if uint16(port) != srcPort { + continue + } + + // xinpcb_n.inp_vflag + flag := buf[inp+44] + + var srcIP net.IP + if flag&0x1 > 0 { + // ipv4 + srcIP = net.IP(buf[inp+76 : inp+80]) + } else if flag&0x2 > 0 { + // ipv6 + srcIP = net.IP(buf[inp+64 : inp+80]) + } else { + continue + } + + if !ip.Equal(srcIP) { + continue + } + + // xsocket_n.so_last_pid + pid := binary.LittleEndian.Uint32(buf[so+68 : so+72]) + return getExecPathFromPID(pid) + } + + return "", errors.New("process not found") +} + +func xinpgenSize(b []byte) uint32 { + return binary.LittleEndian.Uint32(b[:4]) +} + +func roundUp8(n uint32) uint32 { + if n == 0 { + return uint32(8) + } + return (n + 7) & ((^uint32(8)) + 1) +} diff --git a/rules/process_other.go b/rules/process_other.go new file mode 100644 index 0000000000..3a91802522 --- /dev/null +++ b/rules/process_other.go @@ -0,0 +1,11 @@ +// +build !darwin + +package rules + +import ( + C "github.com/Dreamacro/clash/constant" +) + +func NewProcess(process string, adapter string) (C.Rule, error) { + return nil, ErrPlatformNotSupport +} From 20eff200b100a6e8b59ffc36b63b93c6e9fbe2a7 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Mon, 20 Jul 2020 21:16:36 +0800 Subject: [PATCH 440/535] Fix: dns should put msg to cache while exchangeWithoutCache (#820) --- dns/resolver.go | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/dns/resolver.go b/dns/resolver.go index 0a105d6cf5..da7f5db811 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -115,24 +115,29 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { q := m.Question[0] - ret, err, shared := r.group.Do(q.String(), func() (interface{}, error) { + ret, err, shared := r.group.Do(q.String(), func() (result interface{}, err error) { + defer func() { + if err != nil { + return + } + + msg := result.(*D.Msg) + + putMsgToCache(r.lruCache, q.String(), msg) + if r.mapping || r.fakeip { + ips := r.msgToIP(msg) + for _, ip := range ips { + putMsgToCache(r.lruCache, ip.String(), msg) + } + } + }() + isIPReq := isIPRequest(q) if isIPReq { return r.fallbackExchange(m) } - msg, err := r.batchExchange(r.main, m) - if err != nil { - return nil, err - } - putMsgToCache(r.lruCache, q.String(), msg) - if r.mapping || r.fakeip { - ips := r.msgToIP(msg) - for _, ip := range ips { - putMsgToCache(r.lruCache, ip.String(), msg) - } - } - return msg, nil + return r.batchExchange(r.main, m) }) if err == nil { From 4f734106188607a5f94d2eb4ec67b734c151b022 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Wed, 22 Jul 2020 19:05:10 +0800 Subject: [PATCH 441/535] Feature: add PROCESS-NAME rule for linux (#822) --- rules/base.go | 1 + rules/process_linux.go | 291 +++++++++++++++++++++++++++++++++++++++++ rules/process_other.go | 2 +- 3 files changed, 293 insertions(+), 1 deletion(-) create mode 100644 rules/process_linux.go diff --git a/rules/base.go b/rules/base.go index dbe91abfa3..9fa5e6ea9b 100644 --- a/rules/base.go +++ b/rules/base.go @@ -8,6 +8,7 @@ var ( errPayload = errors.New("payload error") errParams = errors.New("params error") ErrPlatformNotSupport = errors.New("not support on this platform") + ErrInvalidNetwork = errors.New("invalid network") noResolve = "no-resolve" ) diff --git a/rules/process_linux.go b/rules/process_linux.go new file mode 100644 index 0000000000..6c71b73cc1 --- /dev/null +++ b/rules/process_linux.go @@ -0,0 +1,291 @@ +package rules + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "io/ioutil" + "net" + "path" + "path/filepath" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/common/pool" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" +) + +// from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 +func init() { + var x uint32 = 0x01020304 + if *(*byte)(unsafe.Pointer(&x)) == 0x01 { + nativeEndian = binary.BigEndian + } else { + nativeEndian = binary.LittleEndian + } +} + +type SocketResolver func(metadata *C.Metadata) (inode, uid int, err error) +type ProcessNameResolver func(inode, uid int) (name string, err error) + +// export for android +var ( + DefaultSocketResolver SocketResolver = resolveSocketByNetlink + DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSeach +) + +type Process struct { + adapter string + process string +} + +func (p *Process) RuleType() C.RuleType { + return C.Process +} + +func (p *Process) Match(metadata *C.Metadata) bool { + key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) + cached, hit := processCache.Get(key) + if !hit { + processName, err := resolveProcessName(metadata) + if err != nil { + log.Debugln("[%s] Resolve process of %s failure: %s", C.Process.String(), key, err.Error()) + } + + processCache.Set(key, processName) + + cached = processName + } + + return strings.EqualFold(cached.(string), p.process) +} + +func (p *Process) Adapter() string { + return p.adapter +} + +func (p *Process) Payload() string { + return p.process +} + +func (p *Process) NoResolveIP() bool { + return true +} + +func NewProcess(process string, adapter string) (*Process, error) { + return &Process{ + adapter: adapter, + process: process, + }, nil +} + +const ( + sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 + socketDiagByFamily = 20 + pathProc = "/proc" +) + +var nativeEndian binary.ByteOrder = binary.LittleEndian + +var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) + +func resolveProcessName(metadata *C.Metadata) (string, error) { + inode, uid, err := DefaultSocketResolver(metadata) + if err != nil { + return "", err + } + + return DefaultProcessNameResolver(inode, uid) +} + +func resolveSocketByNetlink(metadata *C.Metadata) (int, int, error) { + var family byte + var protocol byte + + switch metadata.NetWork { + case C.TCP: + protocol = syscall.IPPROTO_TCP + case C.UDP: + protocol = syscall.IPPROTO_UDP + default: + return 0, 0, ErrInvalidNetwork + } + + if metadata.SrcIP.To4() != nil { + family = syscall.AF_INET + } else { + family = syscall.AF_INET6 + } + + srcPort, err := strconv.Atoi(metadata.SrcPort) + if err != nil { + return 0, 0, err + } + + req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort)) + + socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) + if err != nil { + return 0, 0, err + } + defer syscall.Close(socket) + + syscall.SetNonblock(socket, true) + syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Usec: 50}) + syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Usec: 50}) + + if err := syscall.Connect(socket, &syscall.SockaddrNetlink{ + Family: syscall.AF_NETLINK, + Pad: 0, + Pid: 0, + Groups: 0, + }); err != nil { + return 0, 0, err + } + + if _, err := syscall.Write(socket, req); err != nil { + return 0, 0, err + } + + rb := pool.Get(pool.RelayBufferSize) + defer pool.Put(rb) + + n, err := syscall.Read(socket, rb) + if err != nil { + return 0, 0, err + } + + messages, err := syscall.ParseNetlinkMessage(rb[:n]) + if err != nil { + return 0, 0, err + } else if len(messages) == 0 { + return 0, 0, io.ErrUnexpectedEOF + } + + message := messages[0] + if message.Header.Type&syscall.NLMSG_ERROR != 0 { + return 0, 0, syscall.ESRCH + } + + uid, inode := unpackSocketDiagResponse(&messages[0]) + + return int(uid), int(inode), nil +} + +func packSocketDiagRequest(family, protocol byte, source net.IP, sourcePort uint16) []byte { + s := make([]byte, 16) + + if v4 := source.To4(); v4 != nil { + copy(s, v4) + } else { + copy(s, source) + } + + buf := make([]byte, sizeOfSocketDiagRequest) + + nativeEndian.PutUint32(buf[0:4], sizeOfSocketDiagRequest) + nativeEndian.PutUint16(buf[4:6], socketDiagByFamily) + nativeEndian.PutUint16(buf[6:8], syscall.NLM_F_REQUEST|syscall.NLM_F_DUMP) + nativeEndian.PutUint32(buf[8:12], 0) + nativeEndian.PutUint32(buf[12:16], 0) + + buf[16] = family + buf[17] = protocol + buf[18] = 0 + buf[19] = 0 + nativeEndian.PutUint32(buf[20:24], 0xFFFFFFFF) + + binary.BigEndian.PutUint16(buf[24:26], sourcePort) + binary.BigEndian.PutUint16(buf[26:28], 0) + + copy(buf[28:44], s) + copy(buf[44:60], net.IPv6zero) + + nativeEndian.PutUint32(buf[60:64], 0) + nativeEndian.PutUint64(buf[64:72], 0xFFFFFFFFFFFFFFFF) + + return buf +} + +func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { + if len(msg.Data) < 72 { + return 0, 0 + } + + data := msg.Data + + uid = nativeEndian.Uint32(data[64:68]) + inode = nativeEndian.Uint32(data[68:72]) + + return +} + +func resolveProcessNameByProcSeach(inode, _ int) (string, error) { + files, err := ioutil.ReadDir(pathProc) + if err != nil { + return "", err + } + + buffer := make([]byte, syscall.PathMax) + socket := []byte(fmt.Sprintf("socket:[%d]", inode)) + + for _, f := range files { + if !isPid(f.Name()) { + continue + } + + processPath := path.Join(pathProc, f.Name()) + fdPath := path.Join(processPath, "fd") + + fds, err := ioutil.ReadDir(fdPath) + if err != nil { + continue + } + + for _, fd := range fds { + n, err := syscall.Readlink(path.Join(fdPath, fd.Name()), buffer) + if err != nil { + continue + } + + if bytes.Compare(buffer[:n], socket) == 0 { + cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline")) + if err != nil { + return "", err + } + + return splitCmdline(cmdline), nil + } + } + } + + return "", syscall.ESRCH +} + +func splitCmdline(cmdline []byte) string { + indexOfEndOfString := len(cmdline) + + for i, c := range cmdline { + if c == 0 { + indexOfEndOfString = i + break + } + } + + return filepath.Base(string(cmdline[:indexOfEndOfString])) +} + +func isPid(s string) bool { + for _, s := range s { + if s < '0' || s > '9' { + return false + } + } + + return true +} diff --git a/rules/process_other.go b/rules/process_other.go index 3a91802522..ed1c2cc5c5 100644 --- a/rules/process_other.go +++ b/rules/process_other.go @@ -1,4 +1,4 @@ -// +build !darwin +// +build !darwin,!linux package rules From 6521acf8f163052c94da9638575aa17b299390b1 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Wed, 22 Jul 2020 20:22:34 +0800 Subject: [PATCH 442/535] Improve: check uid on process search & fix typo (#824) --- rules/process_linux.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rules/process_linux.go b/rules/process_linux.go index 6c71b73cc1..cc41da9062 100644 --- a/rules/process_linux.go +++ b/rules/process_linux.go @@ -36,7 +36,7 @@ type ProcessNameResolver func(inode, uid int) (name string, err error) // export for android var ( DefaultSocketResolver SocketResolver = resolveSocketByNetlink - DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSeach + DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch ) type Process struct { @@ -225,7 +225,7 @@ func unpackSocketDiagResponse(msg *syscall.NetlinkMessage) (inode, uid uint32) { return } -func resolveProcessNameByProcSeach(inode, _ int) (string, error) { +func resolveProcessNameByProcSearch(inode, uid int) (string, error) { files, err := ioutil.ReadDir(pathProc) if err != nil { return "", err @@ -235,7 +235,11 @@ func resolveProcessNameByProcSeach(inode, _ int) (string, error) { socket := []byte(fmt.Sprintf("socket:[%d]", inode)) for _, f := range files { - if !isPid(f.Name()) { + if !f.IsDir() || !isPid(f.Name()) { + continue + } + + if f.Sys().(*syscall.Stat_t).Uid != uint32(uid) { continue } From ee72865f48e02cb21195b5a88bc04ae7bae53dde Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 22 Jul 2020 20:29:39 +0800 Subject: [PATCH 443/535] Fix: recycle buf on http obfs --- component/simple-obfs/http.go | 1 + 1 file changed, 1 insertion(+) diff --git a/component/simple-obfs/http.go b/component/simple-obfs/http.go index 1189435781..a06bad2394 100644 --- a/component/simple-obfs/http.go +++ b/component/simple-obfs/http.go @@ -28,6 +28,7 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) { n := copy(b, ho.buf[ho.offset:]) ho.offset += n if ho.offset == len(ho.buf) { + pool.Put(ho.buf) ho.buf = nil } return n, nil From 0e4b9daaadd6aefc772852ec94d70ad2593ecad1 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 22 Jul 2020 20:35:27 +0800 Subject: [PATCH 444/535] Improve: add cache for macOS PROCESS-NAME --- rules/process_darwin.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/rules/process_darwin.go b/rules/process_darwin.go index cc99a6f75b..dea589e7c3 100644 --- a/rules/process_darwin.go +++ b/rules/process_darwin.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "fmt" "net" "path/filepath" "strconv" @@ -11,10 +12,14 @@ import ( "syscall" "unsafe" + "github.com/Dreamacro/clash/common/cache" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) +// store process name for when dealing with multiple PROCESS-NAME rules +var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) + type Process struct { adapter string process string @@ -25,13 +30,19 @@ func (ps *Process) RuleType() C.RuleType { } func (ps *Process) Match(metadata *C.Metadata) bool { - name, err := getExecPathFromAddress(metadata.SrcIP, metadata.SrcPort, metadata.NetWork == C.TCP) - if err != nil { - log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) - return false + key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) + cached, hit := processCache.Get(key) + if !hit { + name, err := getExecPathFromAddress(metadata.SrcIP, metadata.SrcPort, metadata.NetWork == C.TCP) + if err != nil { + log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) + return false + } + + cached = name } - return strings.ToLower(name) == ps.process + return strings.EqualFold(cached.(string), ps.process) } func (p *Process) Adapter() string { From b4221d4b748d00cf57ffcccff38e31ba150a069f Mon Sep 17 00:00:00 2001 From: Sandothers Date: Wed, 22 Jul 2020 21:34:37 +0800 Subject: [PATCH 445/535] Chore: README.md style fixed (#825) make every item in TODO list has the same style --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8cde4a9920..38b0716d3e 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,4 @@ This software is released under the GPL-3.0 license. - [x] Redir proxy - [x] UDP support - [x] Connection manager -- ~~[ ] Event API~~ +- [ ] ~~Event API~~ From 33a6579a3a59d220ce018958b17343dc247409b8 Mon Sep 17 00:00:00 2001 From: goomadao <39483078+goomadao@users.noreply.github.com> Date: Wed, 22 Jul 2020 23:02:15 +0800 Subject: [PATCH 446/535] Feature: add ssr support (#805) * Refactor ssr stream cipher to expose iv and key References: https://github.com/Dreamacro/go-shadowsocks2 https://github.com/sh4d0wfiend/go-shadowsocksr2 * Implement ssr obfs Reference: https://github.com/mzz2017/shadowsocksR * Implement ssr protocol References: https://github.com/mzz2017/shadowsocksR https://github.com/shadowsocksRb/shadowsocksr-libev https://github.com/shadowsocksr-backup/shadowsocksr --- adapters/outbound/parser.go | 7 + adapters/outbound/shadowsocksr.go | 134 +++++++ component/ssr/obfs/base.go | 11 + component/ssr/obfs/http_post.go | 9 + component/ssr/obfs/http_simple.go | 402 +++++++++++++++++++ component/ssr/obfs/obfs.go | 37 ++ component/ssr/obfs/plain.go | 25 ++ component/ssr/obfs/random_head.go | 75 ++++ component/ssr/obfs/stream.go | 72 ++++ component/ssr/obfs/tls12_ticket_auth.go | 291 ++++++++++++++ component/ssr/protocol/auth_aes128_md5.go | 300 +++++++++++++++ component/ssr/protocol/auth_aes128_sha1.go | 22 ++ component/ssr/protocol/auth_chain_a.go | 428 +++++++++++++++++++++ component/ssr/protocol/auth_chain_b.go | 72 ++++ component/ssr/protocol/auth_sha1_v4.go | 253 ++++++++++++ component/ssr/protocol/base.go | 10 + component/ssr/protocol/origin.go | 36 ++ component/ssr/protocol/packet.go | 42 ++ component/ssr/protocol/protocol.go | 61 +++ component/ssr/protocol/stream.go | 68 ++++ component/ssr/tools/encrypt.go | 33 ++ constant/adapters.go | 3 + go.mod | 2 +- go.sum | 4 +- 24 files changed, 2394 insertions(+), 3 deletions(-) create mode 100644 adapters/outbound/shadowsocksr.go create mode 100644 component/ssr/obfs/base.go create mode 100644 component/ssr/obfs/http_post.go create mode 100644 component/ssr/obfs/http_simple.go create mode 100644 component/ssr/obfs/obfs.go create mode 100644 component/ssr/obfs/plain.go create mode 100644 component/ssr/obfs/random_head.go create mode 100644 component/ssr/obfs/stream.go create mode 100644 component/ssr/obfs/tls12_ticket_auth.go create mode 100644 component/ssr/protocol/auth_aes128_md5.go create mode 100644 component/ssr/protocol/auth_aes128_sha1.go create mode 100644 component/ssr/protocol/auth_chain_a.go create mode 100644 component/ssr/protocol/auth_chain_b.go create mode 100644 component/ssr/protocol/auth_sha1_v4.go create mode 100644 component/ssr/protocol/base.go create mode 100644 component/ssr/protocol/origin.go create mode 100644 component/ssr/protocol/packet.go create mode 100644 component/ssr/protocol/protocol.go create mode 100644 component/ssr/protocol/stream.go create mode 100644 component/ssr/tools/encrypt.go diff --git a/adapters/outbound/parser.go b/adapters/outbound/parser.go index 86ee68b5f3..19a779ee01 100644 --- a/adapters/outbound/parser.go +++ b/adapters/outbound/parser.go @@ -24,6 +24,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { break } proxy, err = NewShadowSocks(*ssOption) + case "ssr": + ssrOption := &ShadowSocksROption{} + err = decoder.Decode(mapping, ssrOption) + if err != nil { + break + } + proxy, err = NewShadowSocksR(*ssrOption) case "socks5": socksOption := &Socks5Option{} err = decoder.Decode(mapping, socksOption) diff --git a/adapters/outbound/shadowsocksr.go b/adapters/outbound/shadowsocksr.go new file mode 100644 index 0000000000..1a422cdf5f --- /dev/null +++ b/adapters/outbound/shadowsocksr.go @@ -0,0 +1,134 @@ +package outbound + +import ( + "context" + "encoding/json" + "fmt" + "net" + "strconv" + + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/ssr/obfs" + "github.com/Dreamacro/clash/component/ssr/protocol" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/go-shadowsocks2/core" + "github.com/Dreamacro/go-shadowsocks2/shadowstream" +) + +type ShadowSocksR struct { + *Base + cipher *core.StreamCipher + obfs obfs.Obfs + protocol protocol.Protocol +} + +type ShadowSocksROption struct { + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Password string `proxy:"password"` + Cipher string `proxy:"cipher"` + Obfs string `proxy:"obfs"` + ObfsParam string `proxy:"obfs-param,omitempty"` + Protocol string `proxy:"protocol"` + ProtocolParam string `proxy:"protocol-param,omitempty"` + UDP bool `proxy:"udp,omitempty"` +} + +func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + c = obfs.NewConn(c, ssr.obfs) + c = ssr.cipher.StreamConn(c) + conn, ok := c.(*shadowstream.Conn) + if !ok { + return nil, fmt.Errorf("invalid connection type") + } + iv, err := conn.ObtainWriteIV() + if err != nil { + return nil, err + } + c = protocol.NewConn(c, ssr.protocol, iv) + _, err = c.Write(serializesSocksAddr(metadata)) + return c, err +} + +func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := dialer.DialContext(ctx, "tcp", ssr.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) + } + tcpKeepAlive(c) + + c, err = ssr.StreamConn(c, metadata) + return NewConn(c, ssr), err +} + +func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { + pc, err := dialer.ListenPacket("udp", "") + if err != nil { + return nil, err + } + + addr, err := resolveUDPAddr("udp", ssr.addr) + if err != nil { + return nil, err + } + + pc = ssr.cipher.PacketConn(pc) + pc = protocol.NewPacketConn(pc, ssr.protocol) + return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil +} + +func (ssr *ShadowSocksR) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]string{ + "type": ssr.Type().String(), + }) +} + +func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { + addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + cipher := option.Cipher + password := option.Password + coreCiph, err := core.PickCipher(cipher, nil, password) + if err != nil { + return nil, fmt.Errorf("ssr %s initialize cipher error: %w", addr, err) + } + ciph, ok := coreCiph.(*core.StreamCipher) + if !ok { + return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher) + } + + obfs, err := obfs.PickObfs(option.Obfs, &obfs.Base{ + IVSize: ciph.IVSize(), + Key: ciph.Key, + HeadLen: 30, + Host: option.Server, + Port: option.Port, + Param: option.ObfsParam, + }) + if err != nil { + return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err) + } + + protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{ + IV: nil, + Key: ciph.Key, + TCPMss: 1460, + Param: option.ProtocolParam, + }) + if err != nil { + return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err) + } + protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead()) + + return &ShadowSocksR{ + Base: &Base{ + name: option.Name, + addr: addr, + tp: C.ShadowsocksR, + udp: option.UDP, + }, + cipher: ciph, + obfs: obfs, + protocol: protocol, + }, nil +} diff --git a/component/ssr/obfs/base.go b/component/ssr/obfs/base.go new file mode 100644 index 0000000000..0f58118e03 --- /dev/null +++ b/component/ssr/obfs/base.go @@ -0,0 +1,11 @@ +package obfs + +// Base information for obfs +type Base struct { + IVSize int + Key []byte + HeadLen int + Host string + Port int + Param string +} diff --git a/component/ssr/obfs/http_post.go b/component/ssr/obfs/http_post.go new file mode 100644 index 0000000000..e50ece1c1e --- /dev/null +++ b/component/ssr/obfs/http_post.go @@ -0,0 +1,9 @@ +package obfs + +func init() { + register("http_post", newHTTPPost) +} + +func newHTTPPost(b *Base) Obfs { + return &httpObfs{Base: b, post: true} +} diff --git a/component/ssr/obfs/http_simple.go b/component/ssr/obfs/http_simple.go new file mode 100644 index 0000000000..c485a66141 --- /dev/null +++ b/component/ssr/obfs/http_simple.go @@ -0,0 +1,402 @@ +package obfs + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "math/rand" + "strings" +) + +type httpObfs struct { + *Base + firstRequest bool + firstResponse bool + post bool +} + +func init() { + register("http_simple", newHTTPSimple) +} + +func newHTTPSimple(b *Base) Obfs { + return &httpObfs{Base: b} +} + +func (h *httpObfs) initForConn() Obfs { + return &httpObfs{ + Base: h.Base, + firstRequest: true, + firstResponse: true, + post: h.post, + } +} + +func (h *httpObfs) GetObfsOverhead() int { + return 0 +} + +func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) { + if h.firstResponse { + idx := bytes.Index(b, []byte("\r\n\r\n")) + if idx == -1 { + return nil, false, io.EOF + } + h.firstResponse = false + return b[idx+4:], false, nil + } + return b, false, nil +} + +func (h *httpObfs) Encode(b []byte) ([]byte, error) { + if h.firstRequest { + bSize := len(b) + var headData []byte + + if headSize := h.IVSize + h.HeadLen; bSize-headSize > 64 { + headData = make([]byte, headSize+rand.Intn(64)) + } else { + headData = make([]byte, bSize) + } + copy(headData, b[:len(headData)]) + host := h.Host + var customHead string + + if len(h.Param) > 0 { + customHeads := strings.Split(h.Param, "#") + if len(customHeads) > 2 { + customHeads = customHeads[:2] + } + customHosts := h.Param + if len(customHeads) > 1 { + customHosts = customHeads[0] + customHead = customHeads[1] + } + hosts := strings.Split(customHosts, ",") + if len(hosts) > 0 { + host = strings.TrimSpace(hosts[rand.Intn(len(hosts))]) + } + } + + method := "GET /" + if h.post { + method = "POST /" + } + requestPathIndex := rand.Intn(len(requestPath)/2) * 2 + httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n", + method, + requestPath[requestPathIndex], + data2URLEncode(headData), + requestPath[requestPathIndex+1], + host, h.Port) + if len(customHead) > 0 { + httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n" + } else { + var contentType string + if h.post { + contentType = "Content-Type: multipart/form-data; boundary=" + boundary() + "\r\n" + } + httpBuf = httpBuf + "User-agent: " + requestUserAgent[rand.Intn(len(requestUserAgent))] + "\r\n" + + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + + "Accept-Language: en-US,en;q=0.8\r\n" + + "Accept-Encoding: gzip, deflate\r\n" + + contentType + + "DNT: 1\r\n" + + "Connection: keep-alive\r\n" + + "\r\n" + } + + var encoded []byte + if len(headData) < bSize { + encoded = make([]byte, len(httpBuf)+(bSize-len(headData))) + copy(encoded, []byte(httpBuf)) + copy(encoded[len(httpBuf):], b[len(headData):]) + } else { + encoded = []byte(httpBuf) + } + h.firstRequest = false + return encoded, nil + } + + return b, nil +} + +func data2URLEncode(data []byte) (ret string) { + for i := 0; i < len(data); i++ { + ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]})) + } + return +} + +func boundary() (ret string) { + set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + for i := 0; i < 32; i++ { + ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))]) + } + return +} + +var ( + requestPath = []string{ + "", "", + "login.php?redir=", "", + "register.php?code=", "", + "?keyword=", "", + "search?src=typd&q=", "&lang=en", + "s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=", + "post.php?id=", "&goto=view.php", + } + requestUserAgent = []string{ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36", + "Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1", + "Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + } +) diff --git a/component/ssr/obfs/obfs.go b/component/ssr/obfs/obfs.go new file mode 100644 index 0000000000..1e8c43349c --- /dev/null +++ b/component/ssr/obfs/obfs.go @@ -0,0 +1,37 @@ +package obfs + +import ( + "errors" + "fmt" + "strings" +) + +var ( + errTLS12TicketAuthIncorrectMagicNumber = errors.New("tls1.2_ticket_auth incorrect magic number") + errTLS12TicketAuthTooShortData = errors.New("tls1.2_ticket_auth too short data") + errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") +) + +// Obfs provides methods for decoding and encoding +type Obfs interface { + initForConn() Obfs + GetObfsOverhead() int + Decode(b []byte) ([]byte, bool, error) + Encode(b []byte) ([]byte, error) +} + +type obfsCreator func(b *Base) Obfs + +var obfsList = make(map[string]obfsCreator) + +func register(name string, c obfsCreator) { + obfsList[name] = c +} + +// PickObfs returns an obfs of the given name +func PickObfs(name string, b *Base) (Obfs, error) { + if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok { + return obfsCreator(b), nil + } + return nil, fmt.Errorf("Obfs %s not supported", name) +} diff --git a/component/ssr/obfs/plain.go b/component/ssr/obfs/plain.go new file mode 100644 index 0000000000..372feff2d1 --- /dev/null +++ b/component/ssr/obfs/plain.go @@ -0,0 +1,25 @@ +package obfs + +type plain struct{} + +func init() { + register("plain", newPlain) +} + +func newPlain(b *Base) Obfs { + return &plain{} +} + +func (p *plain) initForConn() Obfs { return &plain{} } + +func (p *plain) GetObfsOverhead() int { + return 0 +} + +func (p *plain) Encode(b []byte) ([]byte, error) { + return b, nil +} + +func (p *plain) Decode(b []byte) ([]byte, bool, error) { + return b, false, nil +} diff --git a/component/ssr/obfs/random_head.go b/component/ssr/obfs/random_head.go new file mode 100644 index 0000000000..deaab2bdc0 --- /dev/null +++ b/component/ssr/obfs/random_head.go @@ -0,0 +1,75 @@ +package obfs + +import ( + "encoding/binary" + "hash/crc32" + "math/rand" +) + +type randomHead struct { + *Base + firstRequest bool + firstResponse bool + headerSent bool + buffer []byte +} + +func init() { + register("random_head", newRandomHead) +} + +func newRandomHead(b *Base) Obfs { + return &randomHead{Base: b} +} + +func (r *randomHead) initForConn() Obfs { + return &randomHead{ + Base: r.Base, + firstRequest: true, + firstResponse: true, + } +} + +func (r *randomHead) GetObfsOverhead() int { + return 0 +} + +func (r *randomHead) Encode(b []byte) (encoded []byte, err error) { + if !r.firstRequest { + return b, nil + } + + bSize := len(b) + if r.headerSent { + if bSize > 0 { + d := make([]byte, len(r.buffer)+bSize) + copy(d, r.buffer) + copy(d[len(r.buffer):], b) + r.buffer = d + } else { + encoded = r.buffer + r.buffer = nil + r.firstRequest = false + } + } else { + size := rand.Intn(96) + 8 + encoded = make([]byte, size) + rand.Read(encoded) + crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF + binary.LittleEndian.PutUint32(encoded[size-4:], crc) + + d := make([]byte, bSize) + copy(d, b) + r.buffer = d + } + r.headerSent = true + return encoded, nil +} + +func (r *randomHead) Decode(b []byte) ([]byte, bool, error) { + if r.firstResponse { + r.firstResponse = false + return b, true, nil + } + return b, false, nil +} diff --git a/component/ssr/obfs/stream.go b/component/ssr/obfs/stream.go new file mode 100644 index 0000000000..f94cf85c84 --- /dev/null +++ b/component/ssr/obfs/stream.go @@ -0,0 +1,72 @@ +package obfs + +import ( + "net" + + "github.com/Dreamacro/clash/common/pool" +) + +// NewConn wraps a stream-oriented net.Conn with obfs decoding/encoding +func NewConn(c net.Conn, o Obfs) net.Conn { + return &Conn{Conn: c, Obfs: o.initForConn()} +} + +// Conn represents an obfs connection +type Conn struct { + net.Conn + Obfs + buf []byte + offset int +} + +func (c *Conn) Read(b []byte) (int, error) { + if c.buf != nil { + n := copy(b, c.buf[c.offset:]) + c.offset += n + if c.offset == len(c.buf) { + pool.Put(c.buf) + c.buf = nil + } + return n, nil + } + + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + n, err := c.Conn.Read(buf) + if err != nil { + return 0, err + } + decoded, sendback, err := c.Decode(buf[:n]) + // decoded may be part of buf + decodedData := pool.Get(len(decoded)) + copy(decodedData, decoded) + if err != nil { + pool.Put(decodedData) + return 0, err + } + if sendback { + c.Write(nil) + pool.Put(decodedData) + return 0, nil + } + n = copy(b, decodedData) + if len(decodedData) > len(b) { + c.buf = decodedData + c.offset = n + } else { + pool.Put(decodedData) + } + return n, err +} + +func (c *Conn) Write(b []byte) (int, error) { + encoded, err := c.Encode(b) + if err != nil { + return 0, err + } + _, err = c.Conn.Write(encoded) + if err != nil { + return 0, err + } + return len(b), nil +} diff --git a/component/ssr/obfs/tls12_ticket_auth.go b/component/ssr/obfs/tls12_ticket_auth.go new file mode 100644 index 0000000000..9cb31d4477 --- /dev/null +++ b/component/ssr/obfs/tls12_ticket_auth.go @@ -0,0 +1,291 @@ +package obfs + +import ( + "bytes" + "crypto/hmac" + "encoding/binary" + "fmt" + "io" + "log" + "math/rand" + "strings" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" +) + +type tlsAuthData struct { + localClientID [32]byte +} + +type tls12Ticket struct { + *Base + *tlsAuthData + handshakeStatus int + sendSaver bytes.Buffer + recvBuffer bytes.Buffer + buffer bytes.Buffer +} + +func init() { + register("tls1.2_ticket_auth", newTLS12Ticket) + register("tls1.2_ticket_fastauth", newTLS12Ticket) +} + +func newTLS12Ticket(b *Base) Obfs { + return &tls12Ticket{ + Base: b, + } +} + +func (t *tls12Ticket) initForConn() Obfs { + r := &tls12Ticket{ + Base: t.Base, + tlsAuthData: &tlsAuthData{}, + } + rand.Read(r.localClientID[:]) + return r +} + +func (t *tls12Ticket) GetObfsOverhead() int { + return 5 +} + +func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) { + if t.handshakeStatus == -1 { + return b, false, nil + } + t.buffer.Reset() + if t.handshakeStatus == 8 { + t.recvBuffer.Write(b) + for t.recvBuffer.Len() > 5 { + var h [5]byte + t.recvBuffer.Read(h[:]) + if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) { + log.Println("incorrect magic number", h[:3], ", 0x170303 is expected") + return nil, false, errTLS12TicketAuthIncorrectMagicNumber + } + size := int(binary.BigEndian.Uint16(h[3:5])) + if t.recvBuffer.Len() < size { + // 不够读,下回再读吧 + unread := t.recvBuffer.Bytes() + t.recvBuffer.Reset() + t.recvBuffer.Write(h[:]) + t.recvBuffer.Write(unread) + break + } + d := pool.Get(size) + t.recvBuffer.Read(d) + t.buffer.Write(d) + pool.Put(d) + } + return t.buffer.Bytes(), false, nil + } + + if len(b) < 11+32+1+32 { + return nil, false, errTLS12TicketAuthTooShortData + } + + hash := t.hmacSHA1(b[11 : 11+22]) + + if !hmac.Equal(b[33:33+tools.HmacSHA1Len], hash) { + return nil, false, errTLS12TicketAuthHMACError + } + return nil, true, nil +} + +func (t *tls12Ticket) Encode(b []byte) ([]byte, error) { + t.buffer.Reset() + switch t.handshakeStatus { + case 8: + if len(b) < 1024 { + d := []byte{0x17, 0x3, 0x3, 0, 0} + binary.BigEndian.PutUint16(d[3:5], uint16(len(b)&0xFFFF)) + t.buffer.Write(d) + t.buffer.Write(b) + return t.buffer.Bytes(), nil + } + start := 0 + var l int + for len(b)-start > 2048 { + l = rand.Intn(4096) + 100 + if l > len(b)-start { + l = len(b) - start + } + packData(&t.buffer, b[start:start+l]) + start += l + } + if len(b)-start > 0 { + l = len(b) - start + packData(&t.buffer, b[start:start+l]) + } + return t.buffer.Bytes(), nil + case 1: + if len(b) > 0 { + if len(b) < 1024 { + packData(&t.sendSaver, b) + } else { + start := 0 + var l int + for len(b)-start > 2048 { + l = rand.Intn(4096) + 100 + if l > len(b)-start { + l = len(b) - start + } + packData(&t.buffer, b[start:start+l]) + start += l + } + if len(b)-start > 0 { + l = len(b) - start + packData(&t.buffer, b[start:start+l]) + } + io.Copy(&t.sendSaver, &t.buffer) + } + return []byte{}, nil + } + hmacData := make([]byte, 43) + handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20") + copy(hmacData, handshakeFinish) + rand.Read(hmacData[11:33]) + h := t.hmacSHA1(hmacData[:33]) + copy(hmacData[33:], h) + t.buffer.Write(hmacData) + io.Copy(&t.buffer, &t.sendSaver) + t.handshakeStatus = 8 + return t.buffer.Bytes(), nil + case 0: + tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00") + tlsData1 := []byte("\xff\x01\x00\x01\x00") + tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0") + // tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18") + + var tlsData [2048]byte + tlsDataLen := 0 + copy(tlsData[0:], tlsData1) + tlsDataLen += len(tlsData1) + sni := t.sni(t.getHost()) + copy(tlsData[tlsDataLen:], sni) + tlsDataLen += len(sni) + copy(tlsData[tlsDataLen:], tlsData2) + tlsDataLen += len(tlsData2) + ticketLen := rand.Intn(164)*2 + 64 + tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff) + tlsData[tlsDataLen-2] = uint8(ticketLen >> 8) + //ticketLen := 208 + rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen]) + tlsDataLen += ticketLen + copy(tlsData[tlsDataLen:], tlsData3) + tlsDataLen += len(tlsData3) + + length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen + encodedData := make([]byte, length) + pdata := length - tlsDataLen + l := tlsDataLen + copy(encodedData[pdata:], tlsData[:tlsDataLen]) + encodedData[pdata-1] = uint8(tlsDataLen) + encodedData[pdata-2] = uint8(tlsDataLen >> 8) + pdata -= 2 + l += 2 + copy(encodedData[pdata-len(tlsData0):], tlsData0) + pdata -= len(tlsData0) + l += len(tlsData0) + copy(encodedData[pdata-32:], t.localClientID[:]) + pdata -= 32 + l += 32 + encodedData[pdata-1] = 0x20 + pdata-- + l++ + copy(encodedData[pdata-32:], t.packAuthData()) + pdata -= 32 + l += 32 + encodedData[pdata-1] = 0x3 + encodedData[pdata-2] = 0x3 // tls version + pdata -= 2 + l += 2 + encodedData[pdata-1] = uint8(l) + encodedData[pdata-2] = uint8(l >> 8) + encodedData[pdata-3] = 0 + encodedData[pdata-4] = 1 + pdata -= 4 + l += 4 + encodedData[pdata-1] = uint8(l) + encodedData[pdata-2] = uint8(l >> 8) + pdata -= 2 + l += 2 + encodedData[pdata-1] = 0x1 + encodedData[pdata-2] = 0x3 // tls version + pdata -= 2 + l += 2 + encodedData[pdata-1] = 0x16 // tls handshake + pdata-- + l++ + packData(&t.sendSaver, b) + t.handshakeStatus = 1 + return encodedData, nil + default: + return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus) + } +} + +func (t *tls12Ticket) hmacSHA1(data []byte) []byte { + key := make([]byte, len(t.Key)+32) + copy(key, t.Key) + copy(key[len(t.Key):], t.localClientID[:]) + + sha1Data := tools.HmacSHA1(key, data) + return sha1Data[:tools.HmacSHA1Len] +} + +func (t *tls12Ticket) sni(u string) []byte { + bURL := []byte(u) + length := len(bURL) + ret := make([]byte, length+9) + copy(ret[9:9+length], bURL) + binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF)) + length += 3 + binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF)) + length += 2 + binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF)) + return ret +} + +func (t *tls12Ticket) getHost() string { + host := t.Host + if len(t.Param) > 0 { + hosts := strings.Split(t.Param, ",") + if len(hosts) > 0 { + + host = hosts[rand.Intn(len(hosts))] + host = strings.TrimSpace(host) + } + } + if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 { + host = "" + } + return host +} + +func (t *tls12Ticket) packAuthData() (ret []byte) { + retSize := 32 + ret = make([]byte, retSize) + + now := time.Now().Unix() + binary.BigEndian.PutUint32(ret[:4], uint32(now)) + + rand.Read(ret[4 : 4+18]) + + hash := t.hmacSHA1(ret[:retSize-tools.HmacSHA1Len]) + copy(ret[retSize-tools.HmacSHA1Len:], hash) + + return +} + +func packData(buffer *bytes.Buffer, suffix []byte) { + d := []byte{0x17, 0x3, 0x3, 0, 0} + binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF)) + buffer.Write(d) + buffer.Write(suffix) + return +} diff --git a/component/ssr/protocol/auth_aes128_md5.go b/component/ssr/protocol/auth_aes128_md5.go new file mode 100644 index 0000000000..cbe56921c4 --- /dev/null +++ b/component/ssr/protocol/auth_aes128_md5.go @@ -0,0 +1,300 @@ +package protocol + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/binary" + "math/rand" + "strconv" + "strings" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/go-shadowsocks2/core" +) + +type authAES128 struct { + *Base + *recvInfo + *authData + hasSentHeader bool + packID uint32 + userKey []byte + uid [4]byte + salt string + hmac hmacMethod + hashDigest hashDigestMethod +} + +func init() { + register("auth_aes128_md5", newAuthAES128MD5) +} + +func newAuthAES128MD5(b *Base) Protocol { + return &authAES128{ + Base: b, + authData: &authData{}, + salt: "auth_aes128_md5", + hmac: tools.HmacMD5, + hashDigest: tools.MD5Sum, + } +} + +func (a *authAES128) initForConn(iv []byte) Protocol { + return &authAES128{ + Base: &Base{ + IV: iv, + Key: a.Key, + TCPMss: a.TCPMss, + Overhead: a.Overhead, + Param: a.Param, + }, + recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)}, + authData: a.authData, + packID: 1, + salt: a.salt, + hmac: a.hmac, + hashDigest: a.hashDigest, + } +} + +func (a *authAES128) GetProtocolOverhead() int { + return 9 +} + +func (a *authAES128) SetOverhead(overhead int) { + a.Overhead = overhead +} + +func (a *authAES128) Decode(b []byte) ([]byte, int, error) { + a.buffer.Reset() + bSize := len(b) + readSize := 0 + key := pool.Get(len(a.userKey) + 4) + defer pool.Put(key) + copy(key, a.userKey) + for bSize > 4 { + binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID) + + h := a.hmac(key, b[:2]) + if !bytes.Equal(h[:2], b[2:4]) { + return nil, 0, errAuthAES128HMACError + } + length := int(binary.LittleEndian.Uint16(b[:2])) + if length >= 8192 || length < 8 { + return nil, 0, errAuthAES128DataLengthError + } + if length > bSize { + break + } + a.recvID++ + pos := int(b[4]) + if pos < 255 { + pos += 4 + } else { + pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4 + } + + a.buffer.Write(b[pos : length-4]) + b = b[length:] + bSize -= length + readSize += length + } + return a.buffer.Bytes(), readSize, nil +} + +func (a *authAES128) Encode(b []byte) ([]byte, error) { + a.buffer.Reset() + bSize := len(b) + offset := 0 + if bSize > 0 && !a.hasSentHeader { + authSize := bSize + if authSize > 1200 { + authSize = 1200 + } + a.hasSentHeader = true + a.buffer.Write(a.packAuthData(b[:authSize])) + bSize -= authSize + offset += authSize + } + const blockSize = 4096 + for bSize > blockSize { + packSize, randSize := a.packedDataSize(b[offset : offset+blockSize]) + pack := pool.Get(packSize) + a.packData(b[offset:offset+blockSize], pack, randSize) + a.buffer.Write(pack) + pool.Put(pack) + bSize -= blockSize + offset += blockSize + } + if bSize > 0 { + packSize, randSize := a.packedDataSize(b[offset:]) + pack := pool.Get(packSize) + a.packData(b[offset:], pack, randSize) + a.buffer.Write(pack) + pool.Put(pack) + } + return a.buffer.Bytes(), nil +} + +func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) { + bSize := len(b) + h := a.hmac(a.Key, b[:bSize-4]) + if !bytes.Equal(h[:4], b[bSize-4:]) { + return nil, 0, errAuthAES128HMACError + } + return b[:bSize-4], bSize - 4, nil +} + +func (a *authAES128) EncodePacket(b []byte) ([]byte, error) { + a.initUserKeyAndID() + + var buf bytes.Buffer + buf.Write(b) + buf.Write(a.uid[:]) + h := a.hmac(a.userKey, buf.Bytes()) + buf.Write(h[:4]) + return buf.Bytes(), nil +} + +func (a *authAES128) initUserKeyAndID() { + if a.userKey == nil { + params := strings.Split(a.Param, ":") + if len(params) >= 2 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) + a.userKey = a.hashDigest([]byte(params[1])) + } + } + + if a.userKey == nil { + rand.Read(a.uid[:]) + a.userKey = make([]byte, len(a.Key)) + copy(a.userKey, a.Key) + } + } +} + +func (a *authAES128) packedDataSize(data []byte) (packSize, randSize int) { + dataSize := len(data) + randSize = 1 + if dataSize <= 1200 { + if a.packID > 4 { + randSize += rand.Intn(32) + } else { + if dataSize > 900 { + randSize += rand.Intn(128) + } else { + randSize += rand.Intn(512) + } + } + } + packSize = randSize + dataSize + 8 + return +} + +func (a *authAES128) packData(data, ret []byte, randSize int) { + dataSize := len(data) + retSize := len(ret) + // 0~1, ret_size + binary.LittleEndian.PutUint16(ret[0:], uint16(retSize&0xFFFF)) + // 2~3, hmac + key := pool.Get(len(a.userKey) + 4) + defer pool.Put(key) + copy(key, a.userKey) + binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID) + h := a.hmac(key, ret[:2]) + copy(ret[2:4], h[:2]) + // 4~rand_size+4, rand number + rand.Read(ret[4 : 4+randSize]) + // 4, rand_size + if randSize < 128 { + ret[4] = byte(randSize & 0xFF) + } else { + // 4, magic number 0xFF + ret[4] = 0xFF + // 5~6, rand_size + binary.LittleEndian.PutUint16(ret[5:], uint16(randSize&0xFFFF)) + } + // rand_size+4~ret_size-4, data + if dataSize > 0 { + copy(ret[randSize+4:], data) + } + a.packID++ + h = a.hmac(key, ret[:retSize-4]) + copy(ret[retSize-4:], h[:4]) +} + +func (a *authAES128) packAuthData(data []byte) (ret []byte) { + dataSize := len(data) + var randSize int + + if dataSize > 400 { + randSize = rand.Intn(512) + } else { + randSize = rand.Intn(1024) + } + + dataOffset := randSize + 16 + 4 + 4 + 7 + retSize := dataOffset + dataSize + 4 + ret = make([]byte, retSize) + encrypt := make([]byte, 24) + key := make([]byte, len(a.IV)+len(a.Key)) + copy(key, a.IV) + copy(key[len(a.IV):], a.Key) + + rand.Read(ret[dataOffset-randSize:]) + a.mutex.Lock() + defer a.mutex.Unlock() + a.connectionID++ + if a.connectionID > 0xFF000000 { + a.clientID = nil + } + if len(a.clientID) == 0 { + a.clientID = make([]byte, 8) + rand.Read(a.clientID) + b := make([]byte, 4) + rand.Read(b) + a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF + } + copy(encrypt[4:], a.clientID) + binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) + + now := time.Now().Unix() + binary.LittleEndian.PutUint32(encrypt[:4], uint32(now)) + + binary.LittleEndian.PutUint16(encrypt[12:], uint16(retSize&0xFFFF)) + binary.LittleEndian.PutUint16(encrypt[14:], uint16(randSize&0xFFFF)) + + a.initUserKeyAndID() + + aesCipherKey := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+a.salt, 16) + block, err := aes.NewCipher(aesCipherKey) + if err != nil { + return nil + } + encryptData := make([]byte, 16) + iv := make([]byte, aes.BlockSize) + cbc := cipher.NewCBCEncrypter(block, iv) + cbc.CryptBlocks(encryptData, encrypt[:16]) + copy(encrypt[:4], a.uid[:]) + copy(encrypt[4:4+16], encryptData) + + h := a.hmac(key, encrypt[:20]) + copy(encrypt[20:], h[:4]) + + rand.Read(ret[:1]) + h = a.hmac(key, ret[:1]) + copy(ret[1:], h[:7-1]) + + copy(ret[7:], encrypt) + copy(ret[dataOffset:], data) + + h = a.hmac(a.userKey, ret[:retSize-4]) + copy(ret[retSize-4:], h[:4]) + + return +} diff --git a/component/ssr/protocol/auth_aes128_sha1.go b/component/ssr/protocol/auth_aes128_sha1.go new file mode 100644 index 0000000000..d549b58805 --- /dev/null +++ b/component/ssr/protocol/auth_aes128_sha1.go @@ -0,0 +1,22 @@ +package protocol + +import ( + "bytes" + + "github.com/Dreamacro/clash/component/ssr/tools" +) + +func init() { + register("auth_aes128_sha1", newAuthAES128SHA1) +} + +func newAuthAES128SHA1(b *Base) Protocol { + return &authAES128{ + Base: b, + recvInfo: &recvInfo{buffer: new(bytes.Buffer)}, + authData: &authData{}, + salt: "auth_aes128_sha1", + hmac: tools.HmacSHA1, + hashDigest: tools.SHA1Sum, + } +} diff --git a/component/ssr/protocol/auth_chain_a.go b/component/ssr/protocol/auth_chain_a.go new file mode 100644 index 0000000000..a00bad6844 --- /dev/null +++ b/component/ssr/protocol/auth_chain_a.go @@ -0,0 +1,428 @@ +package protocol + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rc4" + "encoding/base64" + "encoding/binary" + "math/rand" + "strconv" + "strings" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/go-shadowsocks2/core" +) + +type authChain struct { + *Base + *recvInfo + *authData + randomClient shift128PlusContext + randomServer shift128PlusContext + enc cipher.Stream + dec cipher.Stream + headerSent bool + lastClientHash []byte + lastServerHash []byte + userKey []byte + uid [4]byte + salt string + hmac hmacMethod + hashDigest hashDigestMethod + rnd rndMethod + dataSizeList []int + dataSizeList2 []int + chunkID uint32 +} + +func init() { + register("auth_chain_a", newAuthChainA) +} + +func newAuthChainA(b *Base) Protocol { + return &authChain{ + Base: b, + authData: &authData{}, + salt: "auth_chain_a", + hmac: tools.HmacMD5, + hashDigest: tools.SHA1Sum, + rnd: authChainAGetRandLen, + } +} + +func (a *authChain) initForConn(iv []byte) Protocol { + r := &authChain{ + Base: &Base{ + IV: iv, + Key: a.Key, + TCPMss: a.TCPMss, + Overhead: a.Overhead, + Param: a.Param, + }, + recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)}, + authData: a.authData, + salt: a.salt, + hmac: a.hmac, + hashDigest: a.hashDigest, + rnd: a.rnd, + } + if r.salt == "auth_chain_b" { + initDataSize(r) + } + return r +} + +func (a *authChain) GetProtocolOverhead() int { + return 4 +} + +func (a *authChain) SetOverhead(overhead int) { + a.Overhead = overhead +} + +func (a *authChain) Decode(b []byte) ([]byte, int, error) { + a.buffer.Reset() + key := pool.Get(len(a.userKey) + 4) + defer pool.Put(key) + readSize := 0 + copy(key, a.userKey) + for len(b) > 4 { + binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID) + dataLen := (int)((uint(b[1]^a.lastServerHash[15]) << 8) + uint(b[0]^a.lastServerHash[14])) + randLen := a.getServerRandLen(dataLen, a.Overhead) + length := randLen + dataLen + if length >= 4096 { + return nil, 0, errAuthChainDataLengthError + } + length += 4 + if length > len(b) { + break + } + + hash := a.hmac(key, b[:length-2]) + if !bytes.Equal(hash[:2], b[length-2:length]) { + return nil, 0, errAuthChainHMACError + } + var dataPos int + if dataLen > 0 && randLen > 0 { + dataPos = 2 + getRandStartPos(&a.randomServer, randLen) + } else { + dataPos = 2 + } + d := pool.Get(dataLen) + a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen]) + a.buffer.Write(d) + pool.Put(d) + if a.recvID == 1 { + a.TCPMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2))) + } + a.lastServerHash = hash + a.recvID++ + b = b[length:] + readSize += length + } + return a.buffer.Bytes(), readSize, nil +} + +func (a *authChain) Encode(b []byte) ([]byte, error) { + a.buffer.Reset() + bSize := len(b) + offset := 0 + if bSize > 0 && !a.headerSent { + headSize := 1200 + if headSize > bSize { + headSize = bSize + } + a.buffer.Write(a.packAuthData(b[:headSize])) + offset += headSize + bSize -= headSize + a.headerSent = true + } + var unitSize = a.TCPMss - a.Overhead + for bSize > unitSize { + dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize]) + d := pool.Get(dataLen) + a.packData(d, b[offset:offset+unitSize], randLength) + a.buffer.Write(d) + pool.Put(d) + bSize -= unitSize + offset += unitSize + } + if bSize > 0 { + dataLen, randLength := a.packedDataLen(b[offset:]) + d := pool.Get(dataLen) + a.packData(d, b[offset:], randLength) + a.buffer.Write(d) + pool.Put(d) + } + return a.buffer.Bytes(), nil +} + +func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) { + bSize := len(b) + if bSize < 9 { + return nil, 0, errAuthChainDataLengthError + } + h := a.hmac(a.userKey, b[:bSize-1]) + if h[0] != b[bSize-1] { + return nil, 0, errAuthChainHMACError + } + hash := a.hmac(a.Key, b[bSize-8:bSize-1]) + cipherKey := a.getRC4CipherKey(hash) + dec, _ := rc4.NewCipher(cipherKey) + randLength := udpGetRandLen(&a.randomServer, hash) + bSize -= 8 + randLength + dec.XORKeyStream(b, b[:bSize]) + return b, bSize, nil +} + +func (a *authChain) EncodePacket(b []byte) ([]byte, error) { + a.initUserKeyAndID() + authData := pool.Get(3) + defer pool.Put(authData) + rand.Read(authData) + hash := a.hmac(a.Key, authData) + uid := pool.Get(4) + defer pool.Put(uid) + for i := 0; i < 4; i++ { + uid[i] = a.uid[i] ^ hash[i] + } + + cipherKey := a.getRC4CipherKey(hash) + enc, _ := rc4.NewCipher(cipherKey) + var buf bytes.Buffer + enc.XORKeyStream(b, b) + buf.Write(b) + + randLength := udpGetRandLen(&a.randomClient, hash) + randBytes := pool.Get(randLength) + defer pool.Put(randBytes) + buf.Write(randBytes) + + buf.Write(authData) + buf.Write(uid) + + h := a.hmac(a.userKey, buf.Bytes()) + buf.Write(h[:1]) + return buf.Bytes(), nil +} + +func (a *authChain) getRC4CipherKey(hash []byte) []byte { + base64UserKey := base64.StdEncoding.EncodeToString(a.userKey) + return a.calcRC4CipherKey(hash, base64UserKey) +} + +func (a *authChain) calcRC4CipherKey(hash []byte, base64UserKey string) []byte { + password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16)) + defer pool.Put(password) + copy(password, base64UserKey) + base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16]) + return core.Kdf(string(password), 16) +} + +func (a *authChain) initUserKeyAndID() { + if a.userKey == nil { + params := strings.Split(a.Param, ":") + if len(params) >= 2 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) + a.userKey = []byte(params[1])[:len(a.userKey)] + } + } + + if a.userKey == nil { + rand.Read(a.uid[:]) + a.userKey = make([]byte, len(a.Key)) + copy(a.userKey, a.Key) + } + } +} + +func (a *authChain) getClientRandLen(dataLength int, overhead int) int { + return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead) +} + +func (a *authChain) getServerRandLen(dataLength int, overhead int) int { + return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead) +} + +func (a *authChain) packedDataLen(data []byte) (chunkLength, randLength int) { + dataLength := len(data) + randLength = a.getClientRandLen(dataLength, a.Overhead) + chunkLength = randLength + dataLength + 2 + 2 + return +} + +func (a *authChain) packData(outData []byte, data []byte, randLength int) { + dataLength := len(data) + outLength := randLength + dataLength + 2 + outData[0] = byte(dataLength) ^ a.lastClientHash[14] + outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15] + + { + if dataLength > 0 { + randPart1Length := getRandStartPos(&a.randomClient, randLength) + rand.Read(outData[2 : 2+randPart1Length]) + a.enc.XORKeyStream(outData[2+randPart1Length:], data) + rand.Read(outData[2+randPart1Length+dataLength : outLength]) + } else { + rand.Read(outData[2 : 2+randLength]) + } + } + + userKeyLen := uint8(len(a.userKey)) + key := pool.Get(int(userKeyLen + 4)) + defer pool.Put(key) + copy(key, a.userKey) + a.chunkID++ + binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID) + a.lastClientHash = a.hmac(key, outData[:outLength]) + copy(outData[outLength:], a.lastClientHash[:2]) + return +} + +const authHeadLength = 4 + 8 + 4 + 16 + 4 + +func (a *authChain) packAuthData(data []byte) (outData []byte) { + outData = make([]byte, authHeadLength, authHeadLength+1500) + a.mutex.Lock() + defer a.mutex.Unlock() + a.connectionID++ + if a.connectionID > 0xFF000000 { + rand.Read(a.clientID) + b := make([]byte, 4) + rand.Read(b) + a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF + } + var key = make([]byte, len(a.IV)+len(a.Key)) + copy(key, a.IV) + copy(key[len(a.IV):], a.Key) + + encrypt := make([]byte, 20) + t := time.Now().Unix() + binary.LittleEndian.PutUint32(encrypt[:4], uint32(t)) + copy(encrypt[4:8], a.clientID) + binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) + binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead)) + binary.LittleEndian.PutUint16(encrypt[14:], 0) + + // first 12 bytes + { + rand.Read(outData[:4]) + a.lastClientHash = a.hmac(key, outData[:4]) + copy(outData[4:], a.lastClientHash[:8]) + } + var base64UserKey string + // uid & 16 bytes auth data + { + a.initUserKeyAndID() + uid := make([]byte, 4) + for i := 0; i < 4; i++ { + uid[i] = a.uid[i] ^ a.lastClientHash[8+i] + } + base64UserKey = base64.StdEncoding.EncodeToString(a.userKey) + aesCipherKey := core.Kdf(base64UserKey+a.salt, 16) + block, err := aes.NewCipher(aesCipherKey) + if err != nil { + return + } + encryptData := make([]byte, 16) + iv := make([]byte, aes.BlockSize) + cbc := cipher.NewCBCEncrypter(block, iv) + cbc.CryptBlocks(encryptData, encrypt[:16]) + copy(encrypt[:4], uid[:]) + copy(encrypt[4:4+16], encryptData) + } + // final HMAC + { + a.lastServerHash = a.hmac(a.userKey, encrypt[:20]) + + copy(outData[12:], encrypt) + copy(outData[12+20:], a.lastServerHash[:4]) + } + + // init cipher + cipherKey := a.calcRC4CipherKey(a.lastClientHash, base64UserKey) + a.enc, _ = rc4.NewCipher(cipherKey) + a.dec, _ = rc4.NewCipher(cipherKey) + + // data + chunkLength, randLength := a.packedDataLen(data) + if chunkLength <= 1500 { + outData = outData[:authHeadLength+chunkLength] + } else { + newOutData := make([]byte, authHeadLength+chunkLength) + copy(newOutData, outData[:authHeadLength]) + outData = newOutData + } + a.packData(outData[authHeadLength:], data, randLength) + return +} + +func getRandStartPos(random *shift128PlusContext, randLength int) int { + if randLength > 0 { + return int(random.Next() % 8589934609 % uint64(randLength)) + } + return 0 +} + +func authChainAGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { + if dataLength > 1440 { + return 0 + } + random.InitFromBinDatalen(lastHash[:16], dataLength) + if dataLength > 1300 { + return int(random.Next() % 31) + } + if dataLength > 900 { + return int(random.Next() % 127) + } + if dataLength > 400 { + return int(random.Next() % 521) + } + return int(random.Next() % 1021) +} + +func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int { + random.InitFromBin(lastHash[:16]) + return int(random.Next() % 127) +} + +type shift128PlusContext struct { + v [2]uint64 +} + +func (ctx *shift128PlusContext) InitFromBin(bin []byte) { + var fillBin [16]byte + copy(fillBin[:], bin) + + ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) + ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) +} + +func (ctx *shift128PlusContext) InitFromBinDatalen(bin []byte, datalen int) { + var fillBin [16]byte + copy(fillBin[:], bin) + binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen)) + + ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) + ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) + + for i := 0; i < 4; i++ { + ctx.Next() + } +} + +func (ctx *shift128PlusContext) Next() uint64 { + x := ctx.v[0] + y := ctx.v[1] + ctx.v[0] = y + x ^= x << 23 + x ^= y ^ (x >> 17) ^ (y >> 26) + ctx.v[1] = x + return x + y +} diff --git a/component/ssr/protocol/auth_chain_b.go b/component/ssr/protocol/auth_chain_b.go new file mode 100644 index 0000000000..4f01392931 --- /dev/null +++ b/component/ssr/protocol/auth_chain_b.go @@ -0,0 +1,72 @@ +package protocol + +import ( + "sort" + + "github.com/Dreamacro/clash/component/ssr/tools" +) + +func init() { + register("auth_chain_b", newAuthChainB) +} + +func newAuthChainB(b *Base) Protocol { + return &authChain{ + Base: b, + authData: &authData{}, + salt: "auth_chain_b", + hmac: tools.HmacMD5, + hashDigest: tools.SHA1Sum, + rnd: authChainBGetRandLen, + } +} + +func initDataSize(r *authChain) { + random := &r.randomServer + random.InitFromBin(r.Key) + len := random.Next()%8 + 4 + r.dataSizeList = make([]int, len) + for i := 0; i < int(len); i++ { + r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440) + } + sort.Ints(r.dataSizeList) + + len = random.Next()%16 + 8 + r.dataSizeList2 = make([]int, len) + for i := 0; i < int(len); i++ { + r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440) + } + sort.Ints(r.dataSizeList2) +} + +func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { + if dataLength > 1440 { + return 0 + } + random.InitFromBinDatalen(lastHash[:16], dataLength) + pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead }) + finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList)) + if finalPos < uint64(len(dataSizeList)) { + return dataSizeList[finalPos] - dataLength - overhead + } + + pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead }) + finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2)) + if finalPos < uint64(len(dataSizeList2)) { + return dataSizeList2[finalPos] - dataLength - overhead + } + if finalPos < uint64(pos+len(dataSizeList2)-1) { + return 0 + } + + if dataLength > 1300 { + return int(random.Next() % 31) + } + if dataLength > 900 { + return int(random.Next() % 127) + } + if dataLength > 400 { + return int(random.Next() % 521) + } + return int(random.Next() % 1021) +} diff --git a/component/ssr/protocol/auth_sha1_v4.go b/component/ssr/protocol/auth_sha1_v4.go new file mode 100644 index 0000000000..0cff1c86f0 --- /dev/null +++ b/component/ssr/protocol/auth_sha1_v4.go @@ -0,0 +1,253 @@ +package protocol + +import ( + "bytes" + "encoding/binary" + "hash/adler32" + "hash/crc32" + "math/rand" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" +) + +type authSHA1V4 struct { + *Base + *authData + headerSent bool + buffer bytes.Buffer +} + +func init() { + register("auth_sha1_v4", newAuthSHA1V4) +} + +func newAuthSHA1V4(b *Base) Protocol { + return &authSHA1V4{Base: b, authData: &authData{}} +} + +func (a *authSHA1V4) initForConn(iv []byte) Protocol { + return &authSHA1V4{ + Base: &Base{ + IV: iv, + Key: a.Key, + TCPMss: a.TCPMss, + Overhead: a.Overhead, + Param: a.Param, + }, + authData: a.authData, + } +} + +func (a *authSHA1V4) GetProtocolOverhead() int { + return 7 +} + +func (a *authSHA1V4) SetOverhead(overhead int) { + a.Overhead = overhead +} + +func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) { + a.buffer.Reset() + bSize := len(b) + originalSize := bSize + for bSize > 4 { + crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF + if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) { + return nil, 0, errAuthSHA1v4CRC32Error + } + length := int(binary.BigEndian.Uint16(b[:2])) + if length >= 8192 || length < 8 { + return nil, 0, errAuthSHA1v4DataLengthError + } + if length > bSize { + break + } + + if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) { + pos := int(b[4]) + if pos != 0xFF { + pos += 4 + } else { + pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4 + } + retSize := length - pos - 4 + a.buffer.Write(b[pos : pos+retSize]) + bSize -= length + b = b[length:] + } else { + return nil, 0, errAuthSHA1v4IncorrectChecksum + } + } + return a.buffer.Bytes(), originalSize - bSize, nil +} + +func (a *authSHA1V4) Encode(b []byte) ([]byte, error) { + a.buffer.Reset() + bSize := len(b) + offset := 0 + if !a.headerSent && bSize > 0 { + headSize := getHeadSize(b, 30) + if headSize > bSize { + headSize = bSize + } + a.buffer.Write(a.packAuthData(b[:headSize])) + offset += headSize + bSize -= headSize + a.headerSent = true + } + const blockSize = 4096 + for bSize > blockSize { + packSize, randSize := a.packedDataSize(b[offset : offset+blockSize]) + pack := pool.Get(packSize) + a.packData(b[offset:offset+blockSize], pack, randSize) + a.buffer.Write(pack) + pool.Put(pack) + offset += blockSize + bSize -= blockSize + } + if bSize > 0 { + packSize, randSize := a.packedDataSize(b[offset:]) + pack := pool.Get(packSize) + a.packData(b[offset:], pack, randSize) + a.buffer.Write(pack) + pool.Put(pack) + } + return a.buffer.Bytes(), nil +} + +func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) { + return b, len(b), nil +} + +func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) { + return b, nil +} + +func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) { + dataSize := len(data) + randSize = 1 + if dataSize <= 1300 { + if dataSize > 400 { + randSize += rand.Intn(128) + } else { + randSize += rand.Intn(1024) + } + } + packSize = randSize + dataSize + 8 + return +} + +func (a *authSHA1V4) packData(data, ret []byte, randSize int) { + dataSize := len(data) + retSize := len(ret) + // 0~1, ret size + binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF)) + // 2~3, crc of ret size + crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF + binary.LittleEndian.PutUint16(ret[2:4], uint16(crc)) + // 4, rand size + if randSize < 128 { + ret[4] = uint8(randSize & 0xFF) + } else { + ret[4] = uint8(0xFF) + binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF)) + } + // (rand size+4)~(ret size-4), data + if dataSize > 0 { + copy(ret[randSize+4:], data) + } + // (ret size-4)~end, adler32 of full data + adler := adler32.Checksum(ret[:retSize-4]) + binary.LittleEndian.PutUint32(ret[retSize-4:], adler) +} + +func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) { + dataSize := len(data) + randSize := 1 + if dataSize <= 1300 { + if dataSize > 400 { + randSize += rand.Intn(128) + } else { + randSize += rand.Intn(1024) + } + } + dataOffset := randSize + 4 + 2 + retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len + ret = make([]byte, retSize) + a.mutex.Lock() + defer a.mutex.Unlock() + a.connectionID++ + if a.connectionID > 0xFF000000 { + a.clientID = nil + } + if len(a.clientID) == 0 { + a.clientID = make([]byte, 8) + rand.Read(a.clientID) + b := make([]byte, 4) + rand.Read(b) + a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF + } + // 0~1, ret size + binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF)) + + // 2~6, crc of (ret size+salt+key) + salt := []byte("auth_sha1_v4") + crcData := make([]byte, len(salt)+len(a.Key)+2) + copy(crcData[:2], ret[:2]) + copy(crcData[2:], salt) + copy(crcData[2+len(salt):], a.Key) + crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF + // 2~6, crc of (ret size+salt+key) + binary.LittleEndian.PutUint32(ret[2:], crc) + // 6~(rand size+6), rand numbers + rand.Read(ret[dataOffset-randSize : dataOffset]) + // 6, rand size + if randSize < 128 { + ret[6] = byte(randSize & 0xFF) + } else { + // 6, magic number 0xFF + ret[6] = 0xFF + // 7~8, rand size + binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF)) + } + // rand size+6~(rand size+10), time stamp + now := time.Now().Unix() + binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now)) + // rand size+10~(rand size+14), client ID + copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4]) + // rand size+14~(rand size+18), connection ID + binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID) + // rand size+18~(rand size+18)+data length, data + copy(ret[dataOffset+12:], data) + + key := make([]byte, len(a.IV)+len(a.Key)) + copy(key, a.IV) + copy(key[len(a.IV):], a.Key) + + h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len]) + // (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac + copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len]) + return ret +} + +func getHeadSize(data []byte, defaultValue int) int { + if data == nil || len(data) < 2 { + return defaultValue + } + headType := data[0] & 0x07 + switch headType { + case 1: + // IPv4 1+4+2 + return 7 + case 4: + // IPv6 1+16+2 + return 19 + case 3: + // domain name, variant length + return 4 + int(data[1]) + } + + return defaultValue +} diff --git a/component/ssr/protocol/base.go b/component/ssr/protocol/base.go new file mode 100644 index 0000000000..ec7aad5d32 --- /dev/null +++ b/component/ssr/protocol/base.go @@ -0,0 +1,10 @@ +package protocol + +// Base information for protocol +type Base struct { + IV []byte + Key []byte + TCPMss int + Overhead int + Param string +} diff --git a/component/ssr/protocol/origin.go b/component/ssr/protocol/origin.go new file mode 100644 index 0000000000..0d1fe217f3 --- /dev/null +++ b/component/ssr/protocol/origin.go @@ -0,0 +1,36 @@ +package protocol + +type origin struct{ *Base } + +func init() { + register("origin", newOrigin) +} + +func newOrigin(b *Base) Protocol { + return &origin{} +} + +func (o *origin) initForConn(iv []byte) Protocol { return &origin{} } + +func (o *origin) GetProtocolOverhead() int { + return 0 +} + +func (o *origin) SetOverhead(overhead int) { +} + +func (o *origin) Decode(b []byte) ([]byte, int, error) { + return b, len(b), nil +} + +func (o *origin) Encode(b []byte) ([]byte, error) { + return b, nil +} + +func (o *origin) DecodePacket(b []byte) ([]byte, int, error) { + return b, len(b), nil +} + +func (o *origin) EncodePacket(b []byte) ([]byte, error) { + return b, nil +} diff --git a/component/ssr/protocol/packet.go b/component/ssr/protocol/packet.go new file mode 100644 index 0000000000..c3e5c702fd --- /dev/null +++ b/component/ssr/protocol/packet.go @@ -0,0 +1,42 @@ +package protocol + +import ( + "net" + + "github.com/Dreamacro/clash/common/pool" +) + +// NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding +func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn { + return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)} +} + +// PacketConn represents a protocol packet connection +type PacketConn struct { + net.PacketConn + Protocol +} + +func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + buf, err := c.EncodePacket(b) + if err != nil { + return 0, err + } + _, err = c.PacketConn.WriteTo(buf, addr) + return len(b), err +} + +func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { + n, addr, err := c.PacketConn.ReadFrom(b) + if err != nil { + return n, addr, err + } + bb, length, err := c.DecodePacket(b[:n]) + if err != nil { + return n, addr, err + } + copy(b, bb) + return length, addr, err +} diff --git a/component/ssr/protocol/protocol.go b/component/ssr/protocol/protocol.go new file mode 100644 index 0000000000..b2aa8e937b --- /dev/null +++ b/component/ssr/protocol/protocol.go @@ -0,0 +1,61 @@ +package protocol + +import ( + "bytes" + "errors" + "fmt" + "strings" + "sync" +) + +var ( + errAuthAES128HMACError = errors.New("auth_aes128_* post decrypt hmac error") + errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") + errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") + errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") + errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") + errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch") + errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error") +) + +type authData struct { + clientID []byte + connectionID uint32 + mutex sync.Mutex +} + +type recvInfo struct { + recvID uint32 + buffer *bytes.Buffer +} + +type hmacMethod func(key []byte, data []byte) []byte +type hashDigestMethod func(data []byte) []byte +type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int + +// Protocol provides methods for decoding, encoding and iv setting +type Protocol interface { + initForConn(iv []byte) Protocol + GetProtocolOverhead() int + SetOverhead(int) + Decode([]byte) ([]byte, int, error) + Encode([]byte) ([]byte, error) + DecodePacket([]byte) ([]byte, int, error) + EncodePacket([]byte) ([]byte, error) +} + +type protocolCreator func(b *Base) Protocol + +var protocolList = make(map[string]protocolCreator) + +func register(name string, c protocolCreator) { + protocolList[name] = c +} + +// PickProtocol returns a protocol of the given name +func PickProtocol(name string, b *Base) (Protocol, error) { + if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok { + return protocolCreator(b), nil + } + return nil, fmt.Errorf("Protocol %s not supported", name) +} diff --git a/component/ssr/protocol/stream.go b/component/ssr/protocol/stream.go new file mode 100644 index 0000000000..c76d35eb1b --- /dev/null +++ b/component/ssr/protocol/stream.go @@ -0,0 +1,68 @@ +package protocol + +import ( + "bytes" + "net" + + "github.com/Dreamacro/clash/common/pool" +) + +// NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding +func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn { + return &Conn{Conn: c, Protocol: p.initForConn(iv)} +} + +// Conn represents a protocol connection +type Conn struct { + net.Conn + Protocol + buf []byte + offset int + underDecoded bytes.Buffer +} + +func (c *Conn) Read(b []byte) (int, error) { + if c.buf != nil { + n := copy(b, c.buf[c.offset:]) + c.offset += n + if c.offset == len(c.buf) { + c.buf = nil + } + return n, nil + } + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + n, err := c.Conn.Read(buf) + if err != nil { + return 0, err + } + c.underDecoded.Write(buf[:n]) + underDecoded := c.underDecoded.Bytes() + decoded, length, err := c.Decode(underDecoded) + if err != nil { + c.underDecoded.Reset() + return 0, nil + } + if length == 0 { + return 0, nil + } + c.underDecoded.Next(length) + n = copy(b, decoded) + if len(decoded) > len(b) { + c.buf = decoded + c.offset = n + } + return n, nil +} + +func (c *Conn) Write(b []byte) (int, error) { + encoded, err := c.Encode(b) + if err != nil { + return 0, err + } + _, err = c.Conn.Write(encoded) + if err != nil { + return 0, err + } + return len(b), nil +} diff --git a/component/ssr/tools/encrypt.go b/component/ssr/tools/encrypt.go new file mode 100644 index 0000000000..5fef1654bc --- /dev/null +++ b/component/ssr/tools/encrypt.go @@ -0,0 +1,33 @@ +package tools + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/sha1" +) + +const HmacSHA1Len = 10 + +func HmacMD5(key, data []byte) []byte { + hmacMD5 := hmac.New(md5.New, key) + hmacMD5.Write(data) + return hmacMD5.Sum(nil)[:16] +} + +func HmacSHA1(key, data []byte) []byte { + hmacSHA1 := hmac.New(sha1.New, key) + hmacSHA1.Write(data) + return hmacSHA1.Sum(nil)[:20] +} + +func MD5Sum(b []byte) []byte { + h := md5.New() + h.Write(b) + return h.Sum(nil) +} + +func SHA1Sum(b []byte) []byte { + h := sha1.New() + h.Write(b) + return h.Sum(nil) +} diff --git a/constant/adapters.go b/constant/adapters.go index ad80974a96..a12975bac2 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -13,6 +13,7 @@ const ( Reject Shadowsocks + ShadowsocksR Snell Socks5 Http @@ -100,6 +101,8 @@ func (at AdapterType) String() string { case Shadowsocks: return "Shadowsocks" + case ShadowsocksR: + return "ShadowsocksR" case Snell: return "Snell" case Socks5: diff --git a/go.mod b/go.mod index 90ecdf0a6e..d290d4c55c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Dreamacro/clash go 1.14 require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.5 + github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.1.1 diff --git a/go.sum b/go.sum index a62815d4f0..1080e4b38f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.5 h1:BizWSjmwzAyQoslz6YhJYMiAGT99j9cnm9zlxVr+kyI= -github.com/Dreamacro/go-shadowsocks2 v0.1.5/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= +github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a h1:JhQFrFOkCpRB8qsN6PrzHFzjy/8iQpFFk5cbOiplh6s= +github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= 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= From 8f0098092da8352ac631bc8318f9e3602194a617 Mon Sep 17 00:00:00 2001 From: ayanamist Date: Sat, 25 Jul 2020 17:47:11 +0800 Subject: [PATCH 447/535] Fix: protect alive with atomic value (#834) --- adapters/outbound/base.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 2c380a4143..d51a26ddc8 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -6,6 +6,7 @@ import ( "errors" "net" "net/http" + "sync/atomic" "time" "github.com/Dreamacro/clash/common/queue" @@ -98,11 +99,11 @@ func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { type Proxy struct { C.ProxyAdapter history *queue.Queue - alive bool + alive uint32 } func (p *Proxy) Alive() bool { - return p.alive + return atomic.LoadUint32(&p.alive) > 0 } func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { @@ -114,7 +115,7 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { conn, err := p.ProxyAdapter.DialContext(ctx, metadata) if err != nil { - p.alive = false + atomic.StoreUint32(&p.alive, 0) } return conn, err } @@ -131,7 +132,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory { // LastDelay return last history record. if proxy is not alive, return the max value of uint16. func (p *Proxy) LastDelay() (delay uint16) { var max uint16 = 0xffff - if !p.alive { + if atomic.LoadUint32(&p.alive) == 0 { return max } @@ -162,7 +163,11 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { // URLTest get the delay for the specified URL func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { defer func() { - p.alive = err == nil + if err == nil { + atomic.StoreUint32(&p.alive, 1) + } else { + atomic.StoreUint32(&p.alive, 0) + } record := C.DelayHistory{Time: time.Now()} if err == nil { record.Delay = t @@ -218,5 +223,5 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { } func NewProxy(adapter C.ProxyAdapter) *Proxy { - return &Proxy{adapter, queue.New(10), true} + return &Proxy{adapter, queue.New(10), 1} } From 78c30341581ae6a4e0408a2682dbd96a44b50795 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 27 Jul 2020 11:57:55 +0800 Subject: [PATCH 448/535] Chore: rename NoResolveIP to ShouldResolveIP --- constant/rule.go | 2 +- rules/domain.go | 4 ++-- rules/domain_keyword.go | 4 ++-- rules/domain_suffix.go | 4 ++-- rules/final.go | 4 ++-- rules/geoip.go | 4 ++-- rules/ipcidr.go | 4 ++-- rules/port.go | 4 ++-- rules/process_darwin.go | 4 ++-- rules/process_linux.go | 4 ++-- tunnel/tunnel.go | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/constant/rule.go b/constant/rule.go index 6774589dd7..d7c43416dc 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -48,5 +48,5 @@ type Rule interface { Match(metadata *Metadata) bool Adapter() string Payload() string - NoResolveIP() bool + ShouldResolveIP() bool } diff --git a/rules/domain.go b/rules/domain.go index 14c0ffb93b..f23ab18ded 100644 --- a/rules/domain.go +++ b/rules/domain.go @@ -30,8 +30,8 @@ func (d *Domain) Payload() string { return d.domain } -func (d *Domain) NoResolveIP() bool { - return true +func (d *Domain) ShouldResolveIP() bool { + return false } func NewDomain(domain string, adapter string) *Domain { diff --git a/rules/domain_keyword.go b/rules/domain_keyword.go index dc7578e3bf..b3d6fbaad7 100644 --- a/rules/domain_keyword.go +++ b/rules/domain_keyword.go @@ -31,8 +31,8 @@ func (dk *DomainKeyword) Payload() string { return dk.keyword } -func (dk *DomainKeyword) NoResolveIP() bool { - return true +func (dk *DomainKeyword) ShouldResolveIP() bool { + return false } func NewDomainKeyword(keyword string, adapter string) *DomainKeyword { diff --git a/rules/domain_suffix.go b/rules/domain_suffix.go index 6999f29f44..a1f9f28f1f 100644 --- a/rules/domain_suffix.go +++ b/rules/domain_suffix.go @@ -31,8 +31,8 @@ func (ds *DomainSuffix) Payload() string { return ds.suffix } -func (ds *DomainSuffix) NoResolveIP() bool { - return true +func (ds *DomainSuffix) ShouldResolveIP() bool { + return false } func NewDomainSuffix(suffix string, adapter string) *DomainSuffix { diff --git a/rules/final.go b/rules/final.go index 62484999d0..91c0b9d97c 100644 --- a/rules/final.go +++ b/rules/final.go @@ -24,8 +24,8 @@ func (f *Match) Payload() string { return "" } -func (f *Match) NoResolveIP() bool { - return true +func (f *Match) ShouldResolveIP() bool { + return false } func NewMatch(adapter string) *Match { diff --git a/rules/geoip.go b/rules/geoip.go index cfe8b52b5e..be4b5029fb 100644 --- a/rules/geoip.go +++ b/rules/geoip.go @@ -32,8 +32,8 @@ func (g *GEOIP) Payload() string { return g.country } -func (g *GEOIP) NoResolveIP() bool { - return g.noResolveIP +func (g *GEOIP) ShouldResolveIP() bool { + return !g.noResolveIP } func NewGEOIP(country string, adapter string, noResolveIP bool) *GEOIP { diff --git a/rules/ipcidr.go b/rules/ipcidr.go index 18763f1ecf..ff3b83c44b 100644 --- a/rules/ipcidr.go +++ b/rules/ipcidr.go @@ -50,8 +50,8 @@ func (i *IPCIDR) Payload() string { return i.ipnet.String() } -func (i *IPCIDR) NoResolveIP() bool { - return i.noResolveIP +func (i *IPCIDR) ShouldResolveIP() bool { + return !i.noResolveIP } func NewIPCIDR(s string, adapter string, opts ...IPCIDROption) (*IPCIDR, error) { diff --git a/rules/port.go b/rules/port.go index e6e3b0407b..281a4c4df3 100644 --- a/rules/port.go +++ b/rules/port.go @@ -34,8 +34,8 @@ func (p *Port) Payload() string { return p.port } -func (p *Port) NoResolveIP() bool { - return true +func (p *Port) ShouldResolveIP() bool { + return false } func NewPort(port string, adapter string, isSource bool) (*Port, error) { diff --git a/rules/process_darwin.go b/rules/process_darwin.go index dea589e7c3..cc08fd41fc 100644 --- a/rules/process_darwin.go +++ b/rules/process_darwin.go @@ -53,8 +53,8 @@ func (p *Process) Payload() string { return p.process } -func (p *Process) NoResolveIP() bool { - return true +func (p *Process) ShouldResolveIP() bool { + return false } func NewProcess(process string, adapter string) (*Process, error) { diff --git a/rules/process_linux.go b/rules/process_linux.go index cc41da9062..ff83875058 100644 --- a/rules/process_linux.go +++ b/rules/process_linux.go @@ -73,8 +73,8 @@ func (p *Process) Payload() string { return p.process } -func (p *Process) NoResolveIP() bool { - return true +func (p *Process) ShouldResolveIP() bool { + return false } func NewProcess(process string, adapter string) (*Process, error) { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 029e1b0b8c..055acbdb65 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -289,7 +289,7 @@ func handleTCPConn(localConn C.ServerAdapter) { } func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { - return !rule.NoResolveIP() && metadata.Host != "" && metadata.DstIP == nil + return !rule.ShouldResolveIP() && metadata.Host != "" && metadata.DstIP == nil } func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { From 6532947e71007eb65c3bce060a1b1f510a8bcd78 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Mon, 27 Jul 2020 13:47:00 +0800 Subject: [PATCH 449/535] Fix: invert should resolve ip (#836) --- tunnel/tunnel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 055acbdb65..64911bc7de 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -289,7 +289,7 @@ func handleTCPConn(localConn C.ServerAdapter) { } func shouldResolveIP(rule C.Rule, metadata *C.Metadata) bool { - return !rule.ShouldResolveIP() && metadata.Host != "" && metadata.DstIP == nil + return rule.ShouldResolveIP() && metadata.Host != "" && metadata.DstIP == nil } func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { From b1d9dfd6bf2b880cf21db4cbbdee46a179d12b7c Mon Sep 17 00:00:00 2001 From: icpz Date: Wed, 29 Jul 2020 11:27:18 +0800 Subject: [PATCH 450/535] Improve: simplify macOS process searching --- rules/process_darwin.go | 80 +++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/rules/process_darwin.go b/rules/process_darwin.go index cc08fd41fc..808a3b5fa9 100644 --- a/rules/process_darwin.go +++ b/rules/process_darwin.go @@ -33,7 +33,7 @@ func (ps *Process) Match(metadata *C.Metadata) bool { key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) cached, hit := processCache.Get(key) if !hit { - name, err := getExecPathFromAddress(metadata.SrcIP, metadata.SrcPort, metadata.NetWork == C.TCP) + name, err := getExecPathFromAddress(metadata) if err != nil { log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) return false @@ -91,17 +91,25 @@ func getExecPathFromPID(pid uint32) (string, error) { return filepath.Base(string(buf[:firstZero])), nil } -func getExecPathFromAddress(ip net.IP, portStr string, isTCP bool) (string, error) { - port, err := strconv.Atoi(portStr) +func getExecPathFromAddress(metadata *C.Metadata) (string, error) { + ip := metadata.SrcIP + port, err := strconv.Atoi(metadata.SrcPort) if err != nil { return "", err } - spath := "net.inet.tcp.pcblist_n" - if !isTCP { + var spath string + switch metadata.NetWork { + case C.TCP: + spath = "net.inet.tcp.pcblist_n" + case C.UDP: spath = "net.inet.udp.pcblist_n" + default: + return "", ErrInvalidNetwork } + isIPv4 := ip.To4() != nil + value, err := syscall.Sysctl(spath) if err != nil { return "", err @@ -109,35 +117,19 @@ func getExecPathFromAddress(ip net.IP, portStr string, isTCP bool) (string, erro buf := []byte(value) - var kinds uint32 = 0 - so, inp := 0, 0 - for i := roundUp8(xinpgenSize(buf)); i < uint32(len(buf)) && xinpgenSize(buf[i:]) > 24; i += roundUp8(xinpgenSize(buf[i:])) { - thisKind := binary.LittleEndian.Uint32(buf[i+4 : i+8]) - if kinds&thisKind == 0 { - kinds |= thisKind - switch thisKind { - case 0x1: - // XSO_SOCKET - so = int(i) - case 0x10: - // XSO_INPCB - inp = int(i) - default: - break - } - } - - // all blocks needed by tcp/udp - if (isTCP && kinds != 0x3f) || (!isTCP && kinds != 0x1f) { - continue - } - kinds = 0 - - // xsocket_n.xso_protocol - proto := binary.LittleEndian.Uint32(buf[so+36 : so+40]) - if proto != syscall.IPPROTO_TCP && proto != syscall.IPPROTO_UDP { - continue - } + // from darwin-xnu/bsd/netinet/in_pcblist.c:get_pcblist_n + // size/offset are round up (aligned) to 8 bytes in darwin + // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + + // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) + itemSize := 384 + if metadata.NetWork == C.TCP { + // rup8(sizeof(xtcpcb_n)) + itemSize += 208 + } + // skip the first and last xinpgen(24 bytes) block + for i := 24; i < len(buf)-24; i += itemSize { + // offset of xinpcb_n and xsocket_n + inp, so := i, i+104 srcPort := binary.BigEndian.Uint16(buf[inp+18 : inp+20]) if uint16(port) != srcPort { @@ -148,13 +140,14 @@ func getExecPathFromAddress(ip net.IP, portStr string, isTCP bool) (string, erro flag := buf[inp+44] var srcIP net.IP - if flag&0x1 > 0 { + switch { + case flag&0x1 > 0 && isIPv4: // ipv4 srcIP = net.IP(buf[inp+76 : inp+80]) - } else if flag&0x2 > 0 { + case flag&0x2 > 0 && !isIPv4: // ipv6 srcIP = net.IP(buf[inp+64 : inp+80]) - } else { + default: continue } @@ -163,20 +156,13 @@ func getExecPathFromAddress(ip net.IP, portStr string, isTCP bool) (string, erro } // xsocket_n.so_last_pid - pid := binary.LittleEndian.Uint32(buf[so+68 : so+72]) + pid := readNativeUint32(buf[so+68 : so+72]) return getExecPathFromPID(pid) } return "", errors.New("process not found") } -func xinpgenSize(b []byte) uint32 { - return binary.LittleEndian.Uint32(b[:4]) -} - -func roundUp8(n uint32) uint32 { - if n == 0 { - return uint32(8) - } - return (n + 7) & ((^uint32(8)) + 1) +func readNativeUint32(b []byte) uint32 { + return *(*uint32)(unsafe.Pointer(&b[0])) } From 77d6f9ae6f12a14f5e69146a2ff10b7e80bc476a Mon Sep 17 00:00:00 2001 From: icpz Date: Thu, 30 Jul 2020 15:54:26 +0800 Subject: [PATCH 451/535] Fix: handle snell server reported error message properly (#848) --- component/snell/snell.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/component/snell/snell.go b/component/snell/snell.go index 6b40b383d1..0e629702eb 100644 --- a/component/snell/snell.go +++ b/component/snell/snell.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "fmt" "io" "net" "sync" @@ -49,10 +50,16 @@ func (s *Snell) Read(b []byte) (int, error) { } // CommandError + // 1 byte error code if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { return 0, err } + errcode := int(s.buffer[0]) + // 1 byte error message length + if _, err := io.ReadFull(s.Conn, s.buffer[:]); err != nil { + return 0, err + } length := int(s.buffer[0]) msg := make([]byte, length) @@ -60,7 +67,7 @@ func (s *Snell) Read(b []byte) (int, error) { return 0, err } - return 0, errors.New(string(msg)) + return 0, fmt.Errorf("server reported code: %d, message: %s", errcode, string(msg)) } func WriteHeader(conn net.Conn, host string, port uint) error { From 791d203b5f3968b33b5fb88358b2a8270c736b43 Mon Sep 17 00:00:00 2001 From: icpz Date: Thu, 30 Jul 2020 17:15:06 +0800 Subject: [PATCH 452/535] Fix: update cache if a process was found (#850) --- rules/process_darwin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rules/process_darwin.go b/rules/process_darwin.go index 808a3b5fa9..1b18a0062f 100644 --- a/rules/process_darwin.go +++ b/rules/process_darwin.go @@ -39,6 +39,8 @@ func (ps *Process) Match(metadata *C.Metadata) bool { return false } + processCache.Set(key, name) + cached = name } From 622ac45258381d3d4a0de5272194bf835e2078e8 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 31 Jul 2020 20:01:19 +0800 Subject: [PATCH 453/535] Feature: PROCESS-NAME for freebsd (#855) --- rules/process_freebsd_amd64.go | 188 +++++++++++++++++++++++++++++++++ rules/process_other.go | 1 + 2 files changed, 189 insertions(+) create mode 100644 rules/process_freebsd_amd64.go diff --git a/rules/process_freebsd_amd64.go b/rules/process_freebsd_amd64.go new file mode 100644 index 0000000000..c719b3a411 --- /dev/null +++ b/rules/process_freebsd_amd64.go @@ -0,0 +1,188 @@ +package rules + +import ( + "encoding/binary" + "errors" + "fmt" + "net" + "path/filepath" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/Dreamacro/clash/common/cache" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" +) + +// store process name for when dealing with multiple PROCESS-NAME rules +var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) + +type Process struct { + adapter string + process string +} + +func (ps *Process) RuleType() C.RuleType { + return C.Process +} + +func (ps *Process) Match(metadata *C.Metadata) bool { + key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) + cached, hit := processCache.Get(key) + if !hit { + name, err := getExecPathFromAddress(metadata) + if err != nil { + log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) + return false + } + + processCache.Set(key, name) + + cached = name + } + + return strings.EqualFold(cached.(string), ps.process) +} + +func (p *Process) Adapter() string { + return p.adapter +} + +func (p *Process) Payload() string { + return p.process +} + +func (p *Process) ShouldResolveIP() bool { + return false +} + +func NewProcess(process string, adapter string) (*Process, error) { + return &Process{ + adapter: adapter, + process: process, + }, nil +} + +func getExecPathFromPID(pid uint32) (string, error) { + buf := make([]byte, 2048) + size := uint64(len(buf)) + // CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid + mib := [4]uint32{1, 14, 12, pid} + + _, _, errno := syscall.Syscall6( + syscall.SYS___SYSCTL, + uintptr(unsafe.Pointer(&mib[0])), + uintptr(len(mib)), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&size)), + 0, + 0) + if errno != 0 || size == 0 { + return "", errno + } + + return filepath.Base(string(buf[:size-1])), nil +} + +func searchSocketPid(socket uint64) (uint32, error) { + value, err := syscall.Sysctl("kern.file") + if err != nil { + return 0, err + } + + buf := []byte(value) + + // struct xfile + itemSize := 128 + for i := 0; i < len(buf); i += itemSize { + // xfile.xf_data + data := binary.BigEndian.Uint64(buf[i+56 : i+64]) + if data == socket { + // xfile.xf_pid + pid := readNativeUint32(buf[i+8 : i+12]) + return pid, nil + } + } + return 0, errors.New("pid not found") +} + +func getExecPathFromAddress(metadata *C.Metadata) (string, error) { + ip := metadata.SrcIP + port, err := strconv.Atoi(metadata.SrcPort) + if err != nil { + return "", err + } + + var spath string + var itemSize int + var inpOffset int + switch metadata.NetWork { + case C.TCP: + spath = "net.inet.tcp.pcblist" + // struct xtcpcb + itemSize = 744 + inpOffset = 8 + case C.UDP: + spath = "net.inet.udp.pcblist" + // struct xinpcb + itemSize = 400 + inpOffset = 0 + default: + return "", ErrInvalidNetwork + } + + isIPv4 := ip.To4() != nil + + value, err := syscall.Sysctl(spath) + if err != nil { + return "", err + } + + buf := []byte(value) + + // skip the first and last xinpgen(64 bytes) block + for i := 64; i < len(buf)-64; i += itemSize { + inp := i + inpOffset + + srcPort := binary.BigEndian.Uint16(buf[inp+254 : inp+256]) + + if uint16(port) != srcPort { + continue + } + + // xinpcb.inp_vflag + flag := buf[inp+392] + + var srcIP net.IP + switch { + case flag&0x1 > 0 && isIPv4: + // ipv4 + srcIP = net.IP(buf[inp+284 : inp+288]) + case flag&0x2 > 0 && !isIPv4: + // ipv6 + srcIP = net.IP(buf[inp+272 : inp+288]) + default: + continue + } + + if !ip.Equal(srcIP) { + continue + } + + // xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison + socket := binary.BigEndian.Uint64(buf[inp+16 : inp+24]) + pid, err := searchSocketPid(socket) + if err != nil { + return "", err + } + return getExecPathFromPID(pid) + } + + return "", errors.New("process not found") +} + +func readNativeUint32(b []byte) uint32 { + return *(*uint32)(unsafe.Pointer(&b[0])) +} diff --git a/rules/process_other.go b/rules/process_other.go index ed1c2cc5c5..c45ccc4c8a 100644 --- a/rules/process_other.go +++ b/rules/process_other.go @@ -1,4 +1,5 @@ // +build !darwin,!linux +// +build !freebsd !amd64 package rules From 92a23f1eabef09f93d6bb22a9a31581743c0f206 Mon Sep 17 00:00:00 2001 From: icpz Date: Thu, 6 Aug 2020 19:59:20 +0800 Subject: [PATCH 454/535] Feature: PROCESS-NAME for windows (#840) --- go.mod | 1 + rules/process_other.go | 2 +- rules/process_windows.go | 286 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 rules/process_windows.go diff --git a/go.mod b/go.mod index d290d4c55c..faf471cea3 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a + golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/rules/process_other.go b/rules/process_other.go index c45ccc4c8a..a392250b71 100644 --- a/rules/process_other.go +++ b/rules/process_other.go @@ -1,4 +1,4 @@ -// +build !darwin,!linux +// +build !darwin,!linux,!windows // +build !freebsd !amd64 package rules diff --git a/rules/process_windows.go b/rules/process_windows.go new file mode 100644 index 0000000000..b1e4b93172 --- /dev/null +++ b/rules/process_windows.go @@ -0,0 +1,286 @@ +package rules + +import ( + "errors" + "fmt" + "net" + "path/filepath" + "strconv" + "strings" + "sync" + "syscall" + "unsafe" + + "github.com/Dreamacro/clash/common/cache" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + + "golang.org/x/sys/windows" +) + +const ( + tcpTableFunc = "GetExtendedTcpTable" + tcpTablePidConn = 4 + udpTableFunc = "GetExtendedUdpTable" + udpTablePid = 1 + queryProcNameFunc = "QueryFullProcessImageNameW" +) + +var ( + processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) + errNotFound = errors.New("process not found") + matchMeta = func(p *Process, m *C.Metadata) bool { return false } + + getExTcpTable uintptr + getExUdpTable uintptr + queryProcName uintptr + + once sync.Once +) + +func initWin32API() error { + h, err := windows.LoadLibrary("iphlpapi.dll") + if err != nil { + return fmt.Errorf("LoadLibrary iphlpapi.dll failed: %s", err.Error()) + } + + getExTcpTable, err = windows.GetProcAddress(h, tcpTableFunc) + if err != nil { + return fmt.Errorf("GetProcAddress of %s failed: %s", tcpTableFunc, err.Error()) + } + + getExUdpTable, err = windows.GetProcAddress(h, udpTableFunc) + if err != nil { + return fmt.Errorf("GetProcAddress of %s failed: %s", udpTableFunc, err.Error()) + } + + h, err = windows.LoadLibrary("kernel32.dll") + if err != nil { + return fmt.Errorf("LoadLibrary kernel32.dll failed: %s", err.Error()) + } + + queryProcName, err = windows.GetProcAddress(h, queryProcNameFunc) + if err != nil { + return fmt.Errorf("GetProcAddress of %s failed: %s", queryProcNameFunc, err.Error()) + } + + return nil +} + +type Process struct { + adapter string + process string +} + +func (p *Process) RuleType() C.RuleType { + return C.Process +} + +func (p *Process) Adapter() string { + return p.adapter +} + +func (p *Process) Payload() string { + return p.process +} + +func (p *Process) ShouldResolveIP() bool { + return false +} + +func match(p *Process, metadata *C.Metadata) bool { + key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) + cached, hit := processCache.Get(key) + if !hit { + processName, err := resolveProcessName(metadata) + if err != nil { + log.Debugln("[%s] Resolve process of %s failed: %s", C.Process.String(), key, err.Error()) + } + + processCache.Set(key, processName) + cached = processName + } + return strings.EqualFold(cached.(string), p.process) +} + +func (p *Process) Match(metadata *C.Metadata) bool { + return matchMeta(p, metadata) +} + +func NewProcess(process string, adapter string) (*Process, error) { + once.Do(func() { + err := initWin32API() + if err != nil { + log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) + log.Warnln("All PROCESS-NAMES rules will be skiped") + return + } + matchMeta = match + }) + return &Process{ + adapter: adapter, + process: process, + }, nil +} + +func resolveProcessName(metadata *C.Metadata) (string, error) { + ip := metadata.SrcIP + family := windows.AF_INET + if ip.To4() == nil { + family = windows.AF_INET6 + } + + var class int + var fn uintptr + switch metadata.NetWork { + case C.TCP: + fn = getExTcpTable + class = tcpTablePidConn + case C.UDP: + fn = getExUdpTable + class = udpTablePid + default: + return "", ErrInvalidNetwork + } + + srcPort, err := strconv.Atoi(metadata.SrcPort) + if err != nil { + return "", err + } + + buf, err := getTransportTable(fn, family, class) + if err != nil { + return "", err + } + + s := newSearcher(family == windows.AF_INET, metadata.NetWork == C.TCP) + + pid, err := s.Search(buf, ip, uint16(srcPort)) + if err != nil { + return "", err + } + return getExecPathFromPID(pid) +} + +type searcher struct { + itemSize int + port int + ip int + ipSize int + pid int + tcpState int +} + +func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) { + n := int(readNativeUint32(b[:4])) + itemSize := s.itemSize + for i := 0; i < n; i++ { + row := b[4+itemSize*i : 4+itemSize*(i+1)] + + if s.tcpState >= 0 { + tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4]) + // MIB_TCP_STATE_ESTAB, only check established connections for TCP + if tcpState != 5 { + continue + } + } + + // according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian. + // this field can be illustrated as follows depends on different machine endianess: + // little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB) + // big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB) + // so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32 + srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4]))) + if srcPort != port { + continue + } + + srcIP := net.IP(row[s.ip : s.ip+s.ipSize]) + if !ip.Equal(srcIP) { + continue + } + + pid := readNativeUint32(row[s.pid : s.pid+4]) + return pid, nil + } + return 0, errNotFound +} + +func newSearcher(isV4, isTCP bool) *searcher { + var itemSize, port, ip, ipSize, pid int + tcpState := -1 + switch { + case isV4 && isTCP: + // struct MIB_TCPROW_OWNER_PID + itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0 + case isV4 && !isTCP: + // struct MIB_UDPROW_OWNER_PID + itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8 + case !isV4 && isTCP: + // struct MIB_TCP6ROW_OWNER_PID + itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48 + case !isV4 && !isTCP: + // struct MIB_UDP6ROW_OWNER_PID + itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24 + } + + return &searcher{ + itemSize: itemSize, + port: port, + ip: ip, + ipSize: ipSize, + pid: pid, + tcpState: tcpState, + } +} + +func getTransportTable(fn uintptr, family int, class int) ([]byte, error) { + for size, buf := uint32(8), make([]byte, 8); ; { + ptr := unsafe.Pointer(&buf[0]) + err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0) + + switch err { + case 0: + return buf, nil + case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER): + buf = make([]byte, size) + default: + return nil, fmt.Errorf("syscall error: %d", err) + } + } +} + +func readNativeUint32(b []byte) uint32 { + return *(*uint32)(unsafe.Pointer(&b[0])) +} + +func getExecPathFromPID(pid uint32) (string, error) { + // kernel process starts with a colon in order to distinguish with normal processes + switch pid { + case 0: + // reserved pid for system idle process + return ":System Idle Process", nil + case 4: + // reserved pid for windows kernel image + return ":System", nil + } + h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) + if err != nil { + return "", err + } + defer windows.CloseHandle(h) + + buf := make([]uint16, syscall.MAX_LONG_PATH) + size := uint32(len(buf)) + r1, _, err := syscall.Syscall6( + queryProcName, 4, + uintptr(h), + uintptr(1), + uintptr(unsafe.Pointer(&buf[0])), + uintptr(unsafe.Pointer(&size)), + 0, 0) + if r1 == 0 { + return "", err + } + return filepath.Base(syscall.UTF16ToString(buf[:size])), nil +} From 83a684c5516c09d13d4e8d3666c35181be383ab9 Mon Sep 17 00:00:00 2001 From: Fndroid Date: Thu, 6 Aug 2020 20:12:03 +0800 Subject: [PATCH 455/535] Change: adjust tolerance logic (#864) --- adapters/outboundgroup/urltest.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index 750d809bff..7073b5ec86 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -22,7 +22,6 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption { type URLTest struct { *outbound.Base tolerance uint16 - lastDelay uint16 fastNode C.Proxy single *singledo.Single fastSingle *singledo.Single @@ -63,13 +62,6 @@ func (u *URLTest) proxies() []C.Proxy { func (u *URLTest) fast() C.Proxy { elm, _, _ := u.fastSingle.Do(func() (interface{}, error) { - // tolerance - if u.tolerance != 0 && u.fastNode != nil { - if u.fastNode.LastDelay() < u.lastDelay+u.tolerance { - return u.fastNode, nil - } - } - proxies := u.proxies() fast := proxies[0] min := fast.LastDelay() @@ -85,9 +77,12 @@ func (u *URLTest) fast() C.Proxy { } } - u.fastNode = fast - u.lastDelay = fast.LastDelay() - return fast, nil + // tolerance + if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay() + u.tolerance { + u.fastNode = fast + } + + return u.fastNode, nil }) return elm.(C.Proxy) From 4ba6f248bc23bc4bd3dfddf38984a5413d2fae92 Mon Sep 17 00:00:00 2001 From: goomadao <39483078+goomadao@users.noreply.github.com> Date: Tue, 11 Aug 2020 10:17:40 +0800 Subject: [PATCH 456/535] Fix: ssr bounds out of range panic (#882) --- component/ssr/protocol/auth_aes128_md5.go | 14 ++++++++++++-- component/ssr/protocol/protocol.go | 4 +++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/component/ssr/protocol/auth_aes128_md5.go b/component/ssr/protocol/auth_aes128_md5.go index cbe56921c4..0740f0b37a 100644 --- a/component/ssr/protocol/auth_aes128_md5.go +++ b/component/ssr/protocol/auth_aes128_md5.go @@ -81,8 +81,9 @@ func (a *authAES128) Decode(b []byte) ([]byte, int, error) { h := a.hmac(key, b[:2]) if !bytes.Equal(h[:2], b[2:4]) { - return nil, 0, errAuthAES128HMACError + return nil, 0, errAuthAES128IncorrectMAC } + length := int(binary.LittleEndian.Uint16(b[:2])) if length >= 8192 || length < 8 { return nil, 0, errAuthAES128DataLengthError @@ -90,6 +91,12 @@ func (a *authAES128) Decode(b []byte) ([]byte, int, error) { if length > bSize { break } + + h = a.hmac(key, b[:bSize-4]) + if !bytes.Equal(h[:4], b[bSize-4:]) { + return nil, 0, errAuthAES128IncorrectChecksum + } + a.recvID++ pos := int(b[4]) if pos < 255 { @@ -98,6 +105,9 @@ func (a *authAES128) Decode(b []byte) ([]byte, int, error) { pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4 } + if pos > length-4 { + return nil, 0, errAuthAES128PositionTooLarge + } a.buffer.Write(b[pos : length-4]) b = b[length:] bSize -= length @@ -144,7 +154,7 @@ func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) { bSize := len(b) h := a.hmac(a.Key, b[:bSize-4]) if !bytes.Equal(h[:4], b[bSize-4:]) { - return nil, 0, errAuthAES128HMACError + return nil, 0, errAuthAES128IncorrectMAC } return b[:bSize-4], bSize - 4, nil } diff --git a/component/ssr/protocol/protocol.go b/component/ssr/protocol/protocol.go index b2aa8e937b..943303da45 100644 --- a/component/ssr/protocol/protocol.go +++ b/component/ssr/protocol/protocol.go @@ -9,8 +9,10 @@ import ( ) var ( - errAuthAES128HMACError = errors.New("auth_aes128_* post decrypt hmac error") + errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac") errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") + errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum") + errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt posision is too large") errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") From 89cf06036d5f2ead79ba99528e815070d9c087c6 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Tue, 11 Aug 2020 10:28:17 +0800 Subject: [PATCH 457/535] Feature: dns server could lookup hosts (#872) --- config/config.go | 17 +++++++++---- dns/middleware.go | 52 ++++++++++++++++++++++++++++++++++++++++ dns/resolver.go | 4 ++++ hub/executor/executor.go | 1 + 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index 3db0e5bd8f..c853cbd5cf 100644 --- a/config/config.go +++ b/config/config.go @@ -62,6 +62,7 @@ type DNS struct { EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` FakeIPRange *fakeip.Pool + Hosts *trie.DomainTrie } // FallbackFilter config @@ -88,6 +89,7 @@ type Config struct { type RawDNS struct { Enable bool `yaml:"enable"` IPv6 bool `yaml:"ipv6"` + UseHosts bool `yaml:"use-hosts"` NameServer []string `yaml:"nameserver"` Fallback []string `yaml:"fallback"` FallbackFilter RawFallbackFilter `yaml:"fallback-filter"` @@ -152,6 +154,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { ProxyGroup: []map[string]interface{}{}, DNS: RawDNS{ Enable: false, + UseHosts: true, FakeIPRange: "198.18.0.1/16", FallbackFilter: RawFallbackFilter{ GeoIP: true, @@ -195,17 +198,17 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } config.Rules = rules - dnsCfg, err := parseDNS(rawCfg.DNS) + hosts, err := parseHosts(rawCfg) if err != nil { return nil, err } - config.DNS = dnsCfg + config.Hosts = hosts - hosts, err := parseHosts(rawCfg) + dnsCfg, err := parseDNS(rawCfg.DNS, hosts) if err != nil { return nil, err } - config.Hosts = hosts + config.DNS = dnsCfg config.Users = parseAuthentication(rawCfg.Authentication) @@ -494,7 +497,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { return ipNets, nil } -func parseDNS(cfg RawDNS) (*DNS, error) { +func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { if cfg.Enable && len(cfg.NameServer) == 0 { return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") } @@ -559,6 +562,10 @@ func parseDNS(cfg RawDNS) (*DNS, error) { dnsCfg.FallbackFilter.IPCIDR = fallbackip } + if cfg.UseHosts { + dnsCfg.Hosts = hosts + } + return dnsCfg, nil } diff --git a/dns/middleware.go b/dns/middleware.go index 7cafe7f9f0..ad04b85697 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -1,9 +1,11 @@ package dns import ( + "net" "strings" "github.com/Dreamacro/clash/component/fakeip" + "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" @@ -12,6 +14,52 @@ import ( type handler func(w D.ResponseWriter, r *D.Msg) type middleware func(next handler) handler +func withHosts(hosts *trie.DomainTrie) middleware { + return func(next handler) handler { + return func(w D.ResponseWriter, r *D.Msg) { + q := r.Question[0] + + if !isIPRequest(q) { + next(w, r) + return + } + + record := hosts.Search(strings.TrimRight(q.Name, ".")) + if record == nil { + next(w, r) + return + } + + ip := record.Data.(net.IP) + msg := r.Copy() + + if v4 := ip.To4(); v4 != nil && q.Qtype == D.TypeA { + rr := &D.A{} + rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} + rr.A = v4 + + msg.Answer = []D.RR{rr} + } else if v6 := ip.To16(); v6 != nil && q.Qtype == D.TypeAAAA { + rr := &D.AAAA{} + rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: dnsDefaultTTL} + rr.AAAA = v6 + + msg.Answer = []D.RR{rr} + } else { + next(w, r) + return + } + + msg.SetRcode(r, D.RcodeSuccess) + msg.Authoritative = true + msg.RecursionAvailable = true + + w.WriteMsg(msg) + return + } + } +} + func withFakeIP(fakePool *fakeip.Pool) middleware { return func(next handler) handler { return func(w D.ResponseWriter, r *D.Msg) { @@ -100,6 +148,10 @@ func compose(middlewares []middleware, endpoint handler) handler { func newHandler(resolver *Resolver) handler { middlewares := []middleware{} + if resolver.hosts != nil { + middlewares = append(middlewares, withHosts(resolver.hosts)) + } + if resolver.FakeIPEnabled() { middlewares = append(middlewares, withFakeIP(resolver.pool)) } diff --git a/dns/resolver.go b/dns/resolver.go index da7f5db811..dd3e2f47b7 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -14,6 +14,7 @@ import ( "github.com/Dreamacro/clash/common/picker" "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/resolver" + "github.com/Dreamacro/clash/component/trie" D "github.com/miekg/dns" "golang.org/x/sync/singleflight" @@ -37,6 +38,7 @@ type Resolver struct { ipv6 bool mapping bool fakeip bool + hosts *trie.DomainTrie pool *fakeip.Pool main []dnsClient fallback []dnsClient @@ -308,6 +310,7 @@ type Config struct { EnhancedMode EnhancedMode FallbackFilter FallbackFilter Pool *fakeip.Pool + Hosts *trie.DomainTrie } func New(config Config) *Resolver { @@ -323,6 +326,7 @@ func New(config Config) *Resolver { mapping: config.EnhancedMode == MAPPING, fakeip: config.EnhancedMode == FAKEIP, pool: config.Pool, + hosts: config.Hosts, } if len(config.Fallback) != 0 { diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 8131ad39c2..d2085a7b44 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -113,6 +113,7 @@ func updateDNS(c *config.DNS) { IPv6: c.IPv6, EnhancedMode: c.EnhancedMode, Pool: c.FakeIPRange, + Hosts: c.Hosts, FallbackFilter: dns.FallbackFilter{ GeoIP: c.FallbackFilter.GeoIP, IPCIDR: c.FallbackFilter.IPCIDR, From 4f61c04519eb66ffadbb5324db70ca60a5583097 Mon Sep 17 00:00:00 2001 From: maddie Date: Tue, 11 Aug 2020 10:35:30 +0800 Subject: [PATCH 458/535] Fix: ssr typo (#887) --- component/ssr/protocol/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/component/ssr/protocol/protocol.go b/component/ssr/protocol/protocol.go index 943303da45..43c98d0da9 100644 --- a/component/ssr/protocol/protocol.go +++ b/component/ssr/protocol/protocol.go @@ -12,7 +12,7 @@ var ( errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac") errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum") - errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt posision is too large") + errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large") errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") From 0b7918de9c26137993f1ae1ff991e78cb939660e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Wed, 12 Aug 2020 13:47:50 +0800 Subject: [PATCH 459/535] Migration: go 1.15 --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 33aaded3a2..e65dd5eaff 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,7 +9,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: 1.14.x + go-version: 1.15.x - name: Check out code into the Go module directory uses: actions/checkout@v2 From 8b7c731fd6299a04891f818f92cf88a0de71850a Mon Sep 17 00:00:00 2001 From: goomadao <39483078+goomadao@users.noreply.github.com> Date: Wed, 12 Aug 2020 05:50:56 -0700 Subject: [PATCH 460/535] Fix: ssr broken (#895) --- component/ssr/protocol/auth_aes128_md5.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/component/ssr/protocol/auth_aes128_md5.go b/component/ssr/protocol/auth_aes128_md5.go index 0740f0b37a..fc0c41bf08 100644 --- a/component/ssr/protocol/auth_aes128_md5.go +++ b/component/ssr/protocol/auth_aes128_md5.go @@ -92,8 +92,8 @@ func (a *authAES128) Decode(b []byte) ([]byte, int, error) { break } - h = a.hmac(key, b[:bSize-4]) - if !bytes.Equal(h[:4], b[bSize-4:]) { + h = a.hmac(key, b[:length-4]) + if !bytes.Equal(h[:4], b[length-4:length]) { return nil, 0, errAuthAES128IncorrectChecksum } From 50d778da3c155af36181b53aa736b21fb3753f24 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Sat, 15 Aug 2020 16:55:55 +0800 Subject: [PATCH 461/535] Chore: cache process name when resolve failed (#900) --- rules/process_darwin.go | 1 - rules/process_freebsd_amd64.go | 1 - 2 files changed, 2 deletions(-) diff --git a/rules/process_darwin.go b/rules/process_darwin.go index 1b18a0062f..0ff8960dc6 100644 --- a/rules/process_darwin.go +++ b/rules/process_darwin.go @@ -36,7 +36,6 @@ func (ps *Process) Match(metadata *C.Metadata) bool { name, err := getExecPathFromAddress(metadata) if err != nil { log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) - return false } processCache.Set(key, name) diff --git a/rules/process_freebsd_amd64.go b/rules/process_freebsd_amd64.go index c719b3a411..40e7a5fcef 100644 --- a/rules/process_freebsd_amd64.go +++ b/rules/process_freebsd_amd64.go @@ -35,7 +35,6 @@ func (ps *Process) Match(metadata *C.Metadata) bool { name, err := getExecPathFromAddress(metadata) if err != nil { log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) - return false } processCache.Set(key, name) From 008743f20b749f6b11539aed8d34f7cec72abdd6 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 16 Aug 2020 11:32:51 +0800 Subject: [PATCH 462/535] Chore: update dependencies --- go.mod | 10 +++++----- go.sum | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index faf471cea3..ff19a68b51 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,14 @@ require ( github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.3.0+incompatible github.com/gorilla/websocket v1.4.2 - github.com/miekg/dns v1.1.29 + github.com/miekg/dns v1.1.31 github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.6.0 github.com/stretchr/testify v1.6.1 - golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 - golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a - golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd + golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de + golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc + golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 + golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 1080e4b38f..bc588d746d 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= -github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= @@ -36,17 +36,18 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -54,6 +55,8 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From c1b4382fe8fff98b3811d05b488c6cacfb3cfb5e Mon Sep 17 00:00:00 2001 From: R3pl4c3r <30682790+R3pl4c3r@users.noreply.github.com> Date: Sun, 16 Aug 2020 13:50:56 +0800 Subject: [PATCH 463/535] Feature: add Windows ARM32 build (#902) Co-authored-by: MarksonHon <50002150+MarksonHon@users.noreply.github.com> --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fe6839f408..16e1cb4c35 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,8 @@ PLATFORM_LIST = \ WINDOWS_ARCH_LIST = \ windows-386 \ - windows-amd64 + windows-amd64 \ + windows-arm32v7 all: linux-amd64 darwin-amd64 windows-amd64 # Most used @@ -82,6 +83,9 @@ windows-386: windows-amd64: GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe + +windows-arm32v7: + GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe gz_releases=$(addsuffix .gz, $(PLATFORM_LIST)) zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST)) From 5805334ccd55cc20e9a7a20a2aec3b756fd62f5d Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 25 Aug 2020 22:19:59 +0800 Subject: [PATCH 464/535] Chore: pass staticcheck --- adapters/outbound/base.go | 4 --- adapters/outbound/parser.go | 10 ++++--- adapters/outboundgroup/parser.go | 5 +++- adapters/outboundgroup/relay.go | 2 +- adapters/outboundgroup/selector.go | 2 +- adapters/provider/provider.go | 6 ++-- common/pool/alloc.go | 2 ++ common/pool/alloc_test.go | 8 +++--- common/singledo/singledo.go | 2 ++ component/dialer/dialer.go | 37 ++++++++++++------------- component/snell/snell.go | 2 +- component/ssr/obfs/tls12_ticket_auth.go | 1 - component/ssr/protocol/auth_chain_a.go | 1 - component/vmess/aead.go | 2 +- component/vmess/chunk.go | 2 +- component/vmess/vmess.go | 9 +----- component/vmess/websocket.go | 4 +-- config/config.go | 18 ++++++------ config/initial.go | 12 ++++---- config/utils.go | 11 +------- dns/middleware.go | 3 -- dns/resolver.go | 4 +-- hub/executor/executor.go | 4 +-- log/log.go | 1 - main.go | 5 ++-- proxy/http/server.go | 3 +- proxy/listener.go | 6 ---- proxy/redir/utils.go | 1 - proxy/socks/utils.go | 1 - rules/base.go | 1 - tunnel/connection.go | 2 +- 31 files changed, 72 insertions(+), 99 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index d51a26ddc8..26fa5d56db 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -13,10 +13,6 @@ import ( C "github.com/Dreamacro/clash/constant" ) -var ( - defaultURLTestTimeout = time.Second * 5 -) - type Base struct { name string addr string diff --git a/adapters/outbound/parser.go b/adapters/outbound/parser.go index 19a779ee01..f0f894da30 100644 --- a/adapters/outbound/parser.go +++ b/adapters/outbound/parser.go @@ -11,11 +11,13 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) proxyType, existType := mapping["type"].(string) if !existType { - return nil, fmt.Errorf("Missing type") + return nil, fmt.Errorf("missing type") } - var proxy C.ProxyAdapter - err := fmt.Errorf("Cannot parse") + var ( + proxy C.ProxyAdapter + err error + ) switch proxyType { case "ss": ssOption := &ShadowSocksOption{} @@ -72,7 +74,7 @@ func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) { } proxy, err = NewTrojan(*trojanOption) default: - return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType) + return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) } if err != nil { diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index 0a33b94ed4..f0fa54c9f0 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -12,7 +12,6 @@ import ( var ( errFormat = errors.New("format error") errType = errors.New("unsupport type") - errMissUse = errors.New("`use` field should not be empty") errMissProxy = errors.New("`use` or `proxies` missing") errMissHealthCheck = errors.New("`url` or `interval` missing") errDuplicateProvider = errors.New("`duplicate provider name") @@ -63,6 +62,10 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providers = append(providers, pd) } else { + if _, ok := providersMap[groupName]; ok { + return nil, errDuplicateProvider + } + // select don't need health check if groupOption.Type == "select" || groupOption.Type == "relay" { hc := provider.NewHealthCheck(ps, "", 0) diff --git a/adapters/outboundgroup/relay.go b/adapters/outboundgroup/relay.go index eb9b8eb8a3..e27df09b69 100644 --- a/adapters/outboundgroup/relay.go +++ b/adapters/outboundgroup/relay.go @@ -22,7 +22,7 @@ type Relay struct { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { proxies := r.proxies(metadata) if len(proxies) == 0 { - return nil, errors.New("Proxy does not exist") + return nil, errors.New("proxy does not exist") } first := proxies[0] last := proxies[len(proxies)-1] diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index 239abcf742..e253cb3e6e 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -64,7 +64,7 @@ func (s *Selector) Set(name string) error { } } - return errors.New("Proxy does not exist") + return errors.New("proxy not exist") } func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index f5321e73a4..09d0543032 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -120,20 +120,20 @@ func proxiesParse(buf []byte) (interface{}, error) { } if schema.Proxies == nil { - return nil, errors.New("File must have a `proxies` field") + return nil, errors.New("file must have a `proxies` field") } proxies := []C.Proxy{} for idx, mapping := range schema.Proxies { proxy, err := outbound.ParseProxy(mapping) if err != nil { - return nil, fmt.Errorf("Proxy %d error: %w", idx, err) + return nil, fmt.Errorf("proxy %d error: %w", idx, err) } proxies = append(proxies, proxy) } if len(proxies) == 0 { - return nil, errors.New("File doesn't have any valid proxy") + return nil, errors.New("file doesn't have any valid proxy") } return proxies, nil diff --git a/common/pool/alloc.go b/common/pool/alloc.go index e9a7d52ce1..4f26c03647 100644 --- a/common/pool/alloc.go +++ b/common/pool/alloc.go @@ -55,6 +55,8 @@ func (alloc *Allocator) Put(buf []byte) error { if cap(buf) == 0 || cap(buf) > 65536 || cap(buf) != 1< maxSize { - return 0, errors.New("Buffer is larger than standard") + return 0, errors.New("buffer is larger than standard") } buf := pool.Get(size) diff --git a/component/vmess/chunk.go b/component/vmess/chunk.go index cd7ea57b11..ab1adb6df2 100644 --- a/component/vmess/chunk.go +++ b/component/vmess/chunk.go @@ -47,7 +47,7 @@ func (cr *chunkReader) Read(b []byte) (int, error) { size := int(binary.BigEndian.Uint16(cr.sizeBuf)) if size > maxSize { - return 0, errors.New("Buffer is larger than standard") + return 0, errors.New("buffer is larger than standard") } if len(b) >= size { diff --git a/component/vmess/vmess.go b/component/vmess/vmess.go index 9b7ef906a5..5b538f7e6c 100644 --- a/component/vmess/vmess.go +++ b/component/vmess/vmess.go @@ -1,12 +1,10 @@ package vmess import ( - "crypto/tls" "fmt" "math/rand" "net" "runtime" - "sync" "github.com/gofrs/uuid" ) @@ -37,11 +35,6 @@ var CipherMapping = map[string]byte{ "chacha20-poly1305": SecurityCHACHA20POLY1305, } -var ( - clientSessionCache tls.ClientSessionCache - once sync.Once -) - // Command types const ( CommandTCP byte = 1 @@ -106,7 +99,7 @@ func NewClient(config Config) (*Client, error) { security = SecurityAES128GCM } default: - return nil, fmt.Errorf("Unknown security type: %s", config.Security) + return nil, fmt.Errorf("unknown security type: %s", config.Security) } return &Client{ diff --git a/component/vmess/websocket.go b/component/vmess/websocket.go index 499f15c8dd..980add13e0 100644 --- a/component/vmess/websocket.go +++ b/component/vmess/websocket.go @@ -73,7 +73,7 @@ func (wsc *websocketConn) Close() error { errors = append(errors, err.Error()) } if len(errors) > 0 { - return fmt.Errorf("Failed to close connection: %s", strings.Join(errors, ",")) + return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ",")) } return nil } @@ -159,7 +159,7 @@ func StreamWebsocketConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { if resp != nil { reason = resp.Status } - return nil, fmt.Errorf("Dial %s error: %s", uri.Host, reason) + return nil, fmt.Errorf("dial %s error: %s", uri.Host, reason) } return &websocketConn{ diff --git a/config/config.go b/config/config.go index c853cbd5cf..cf6a767092 100644 --- a/config/config.go +++ b/config/config.go @@ -264,11 +264,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ for idx, mapping := range proxiesConfig { proxy, err := outbound.ParseProxy(mapping) if err != nil { - return nil, nil, fmt.Errorf("Proxy %d: %w", idx, err) + return nil, nil, fmt.Errorf("proxy %d: %w", idx, err) } if _, exist := proxies[proxy.Name()]; exist { - return nil, nil, fmt.Errorf("Proxy %s is the duplicate name", proxy.Name()) + return nil, nil, fmt.Errorf("proxy %s is the duplicate name", proxy.Name()) } proxies[proxy.Name()] = proxy proxyList = append(proxyList, proxy.Name()) @@ -278,7 +278,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ for idx, mapping := range groupsConfig { groupName, existName := mapping["name"].(string) if !existName { - return nil, nil, fmt.Errorf("ProxyGroup %d: missing name", idx) + return nil, nil, fmt.Errorf("proxy group %d: missing name", idx) } proxyList = append(proxyList, groupName) } @@ -313,12 +313,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ for idx, mapping := range groupsConfig { group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap) if err != nil { - return nil, nil, fmt.Errorf("ProxyGroup[%d]: %w", idx, err) + return nil, nil, fmt.Errorf("proxy group[%d]: %w", idx, err) } groupName := group.Name() if _, exist := proxies[groupName]; exist { - return nil, nil, fmt.Errorf("ProxyGroup %s: the duplicate name", groupName) + return nil, nil, fmt.Errorf("proxy group %s: the duplicate name", groupName) } proxies[groupName] = outbound.NewProxy(group) @@ -373,11 +373,11 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { target = rule[2] params = rule[3:] default: - return nil, fmt.Errorf("Rules[%d] [%s] error: format invalid", idx, line) + return nil, fmt.Errorf("rules[%d] [%s] error: format invalid", idx, line) } if _, ok := proxies[target]; !ok { - return nil, fmt.Errorf("Rules[%d] [%s] error: proxy [%s] not found", idx, line, target) + return nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target) } rule = trimArr(rule) @@ -389,7 +389,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line) continue } - return nil, fmt.Errorf("Rules[%d] [%s] error: %s", idx, line, parseErr.Error()) + return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } rules = append(rules, parsed) @@ -499,7 +499,7 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) { func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { if cfg.Enable && len(cfg.NameServer) == 0 { - return nil, fmt.Errorf("If DNS configuration is turned on, NameServer cannot be empty") + return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty") } dnsCfg := &DNS{ diff --git a/config/initial.go b/config/initial.go index 26421f681d..7c876162b4 100644 --- a/config/initial.go +++ b/config/initial.go @@ -32,18 +32,18 @@ func initMMDB() error { if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { log.Infoln("Can't find MMDB, start download") if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("Can't download MMDB: %s", err.Error()) + return fmt.Errorf("can't download MMDB: %s", err.Error()) } } if !mmdb.Verify() { log.Warnln("MMDB invalid, remove and download") if err := os.Remove(C.Path.MMDB()); err != nil { - return fmt.Errorf("Can't remove invalid MMDB: %s", err.Error()) + return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) } if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("Can't download MMDB: %s", err.Error()) + return fmt.Errorf("can't download MMDB: %s", err.Error()) } } @@ -55,7 +55,7 @@ func Init(dir string) error { // initial homedir if _, err := os.Stat(dir); os.IsNotExist(err) { if err := os.MkdirAll(dir, 0777); err != nil { - return fmt.Errorf("Can't create config directory %s: %s", dir, err.Error()) + return fmt.Errorf("can't create config directory %s: %s", dir, err.Error()) } } @@ -64,7 +64,7 @@ func Init(dir string) error { log.Infoln("Can't find config, create a initial config file") f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return fmt.Errorf("Can't create file %s: %s", C.Path.Config(), err.Error()) + return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error()) } f.Write([]byte(`port: 7890`)) f.Close() @@ -72,7 +72,7 @@ func Init(dir string) error { // initial mmdb if err := initMMDB(); err != nil { - return fmt.Errorf("Can't initial MMDB: %w", err) + return fmt.Errorf("can't initial MMDB: %w", err) } return nil } diff --git a/config/utils.go b/config/utils.go index 79fae6c91e..74af4a6490 100644 --- a/config/utils.go +++ b/config/utils.go @@ -15,15 +15,6 @@ func trimArr(arr []string) (r []string) { return } -func or(pointers ...*int) *int { - for _, p := range pointers { - if p != nil { - return p - } - } - return pointers[len(pointers)-1] -} - // Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order. // Meanwhile, record the original index in the config file. // If loop is detected, return an error with location of loop. @@ -153,5 +144,5 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { loopElements = append(loopElements, name) delete(graph, name) } - return fmt.Errorf("Loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements) + return fmt.Errorf("loop is detected in ProxyGroup, please check following ProxyGroups: %v", loopElements) } diff --git a/dns/middleware.go b/dns/middleware.go index ad04b85697..cc58c001e5 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -55,7 +55,6 @@ func withHosts(hosts *trie.DomainTrie) middleware { msg.RecursionAvailable = true w.WriteMsg(msg) - return } } } @@ -99,7 +98,6 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { msg.RecursionAvailable = true w.WriteMsg(msg) - return } } } @@ -130,7 +128,6 @@ func withResolver(resolver *Resolver) handler { msg.SetRcode(r, msg.Rcode) msg.Authoritative = true w.WriteMsg(msg) - return } } diff --git a/dns/resolver.go b/dns/resolver.go index dd3e2f47b7..04ea41bbe1 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -106,7 +106,7 @@ func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) { setMsgTTL(msg, uint32(1)) // Continue fetch go r.exchangeWithoutCache(m) } else { - setMsgTTL(msg, uint32(expireTime.Sub(time.Now()).Seconds())) + setMsgTTL(msg, uint32(time.Until(expireTime).Seconds())) } return } @@ -203,7 +203,7 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err elm := fast.Wait() if elm == nil { - err := errors.New("All DNS requests failed") + err := errors.New("all DNS requests failed") if fErr := fast.Error(); fErr != nil { err = fmt.Errorf("%w, first error: %s", err, fErr.Error()) } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index d2085a7b44..3bb29f5742 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -34,7 +34,7 @@ func readConfig(path string) ([]byte, error) { } if len(data) == 0 { - return nil, fmt.Errorf("Configuration file %s is empty", path) + return nil, fmt.Errorf("configuration file %s is empty", path) } return data, err @@ -101,7 +101,7 @@ func GetGeneral() *config.General { func updateExperimental(c *config.Config) {} func updateDNS(c *config.DNS) { - if c.Enable == false { + if !c.Enable { resolver.DefaultResolver = nil tunnel.SetResolver(nil) dns.ReCreateServer("", nil) diff --git a/log/log.go b/log/log.go index 7190cc8989..73ff0b9eeb 100644 --- a/log/log.go +++ b/log/log.go @@ -64,7 +64,6 @@ func Subscribe() observable.Subscription { func UnSubscribe(sub observable.Subscription) { source.UnSubscribe(sub) - return } func Level() LogLevel { diff --git a/main.go b/main.go index 28229bfb1c..d612fcb4df 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,6 @@ import ( "syscall" "github.com/Dreamacro/clash/config" - "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/hub/executor" @@ -76,10 +75,10 @@ func main() { if testConfig { if _, err := executor.Parse(); err != nil { log.Errorln(err.Error()) - fmt.Printf("configuration file %s test failed\n", constant.Path.Config()) + fmt.Printf("configuration file %s test failed\n", C.Path.Config()) os.Exit(1) } - fmt.Printf("configuration file %s test is successful\n", constant.Path.Config()) + fmt.Printf("configuration file %s test is successful\n", C.Path.Config()) return } diff --git a/proxy/http/server.go b/proxy/http/server.go index 1791f62ede..8eecf1e7c9 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -60,6 +60,7 @@ func (l *HttpListener) Address() string { func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache.Cache) (ret bool) { if result := cache.Get(loginStr); result != nil { ret = result.(bool) + return } loginData, err := base64.StdEncoding.DecodeString(loginStr) login := strings.Split(string(loginData), ":") @@ -80,7 +81,7 @@ func HandleConn(conn net.Conn, cache *cache.Cache) { authenticator := authStore.Authenticator() if authenticator != nil { if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 { - _, err = conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")) + conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")) conn.Close() return } else if !canActivate(authStrings[1], authenticator, cache) { diff --git a/proxy/listener.go b/proxy/listener.go index 88d0f522d6..d1414b5a69 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -30,14 +30,8 @@ var ( httpMux sync.Mutex redirMux sync.Mutex mixedMux sync.Mutex - tunMux sync.Mutex ) -type listener interface { - Close() - Address() string -} - type Ports struct { Port int `json:"port"` SocksPort int `json:"socks-port"` diff --git a/proxy/redir/utils.go b/proxy/redir/utils.go index e78563f4ea..46e39b8abd 100644 --- a/proxy/redir/utils.go +++ b/proxy/redir/utils.go @@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr { func (c *packet) Drop() { pool.Put(c.buf) - return } diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index 6b77a40f92..d3ab3b7819 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -34,5 +34,4 @@ func (c *packet) LocalAddr() net.Addr { func (c *packet) Drop() { pool.Put(c.bufRef) - return } diff --git a/rules/base.go b/rules/base.go index 9fa5e6ea9b..2db3558c1e 100644 --- a/rules/base.go +++ b/rules/base.go @@ -6,7 +6,6 @@ import ( var ( errPayload = errors.New("payload error") - errParams = errors.New("params error") ErrPlatformNotSupport = errors.New("not support on this platform") ErrInvalidNetwork = errors.New("invalid network") diff --git a/tunnel/connection.go b/tunnel/connection.go index bdba863a6e..13f7875af1 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -121,7 +121,7 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n from = fAddr } - n, err = packet.WriteBack(buf[:n], from) + _, err = packet.WriteBack(buf[:n], from) if err != nil { return } From b70882f01acb60bd305c4303322b68902412b272 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 25 Aug 2020 22:32:23 +0800 Subject: [PATCH 465/535] Chore: add static check --- .github/workflows/go.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e65dd5eaff..aed4f6ce43 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -22,9 +22,12 @@ jobs: restore-keys: | ${{ runner.os }}-go- - - name: Get dependencies and run test + - name: Get dependencies, run test and static check run: | go test ./... + go vet ./... + go get -u honnef.co/go/tools/cmd/staticcheck + staticcheck -- $(go list ./...) - name: Build if: startsWith(github.ref, 'refs/tags/') From c9735ef75b428876c2a7c7a561a7a178edded73e Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Tue, 25 Aug 2020 22:36:38 +0800 Subject: [PATCH 466/535] Fix: static check --- rules/process_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/process_linux.go b/rules/process_linux.go index ff83875058..d883b2959d 100644 --- a/rules/process_linux.go +++ b/rules/process_linux.go @@ -257,7 +257,7 @@ func resolveProcessNameByProcSearch(inode, uid int) (string, error) { continue } - if bytes.Compare(buffer[:n], socket) == 0 { + if bytes.Equal(buffer[:n], socket) { cmdline, err := ioutil.ReadFile(path.Join(processPath, "cmdline")) if err != nil { return "", err From ad18064e6b4cd3e95e07167fb3e189a535a3d9cb Mon Sep 17 00:00:00 2001 From: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Date: Sun, 30 Aug 2020 19:53:00 +0800 Subject: [PATCH 467/535] Chore: code style (#933) --- adapters/outboundgroup/urltest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index 7073b5ec86..c20340ecd7 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -78,7 +78,7 @@ func (u *URLTest) fast() C.Proxy { } // tolerance - if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay() + u.tolerance { + if u.fastNode == nil || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance { u.fastNode = fast } From 687c2a21cfdc7b99eafc2df2db36646f43fd9d24 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 30 Aug 2020 22:49:55 +0800 Subject: [PATCH 468/535] Fix: vmess UDP option should be effect --- adapters/outbound/vmess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 8eca97dff2..20309c2e39 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -158,7 +158,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { name: option.Name, addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Vmess, - udp: true, + udp: option.UDP, }, client: client, option: &option, From b8ed73823804bab31d1b10593dbabff6ac630043 Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Sun, 30 Aug 2020 23:06:21 +0800 Subject: [PATCH 469/535] Chore: update actions version --- .github/workflows/docker.yml | 5 ++--- .github/workflows/stale.yml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 02dedf1162..b6212dfc30 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,10 +19,9 @@ jobs: - name: Set up docker buildx id: buildx - uses: crazy-max/ghaction-docker-buildx@v2 + uses: crazy-max/ghaction-docker-buildx@v3 with: buildx-version: latest - skip-cache: false qemu-version: latest - name: Docker login @@ -39,7 +38,7 @@ jobs: - name: Replace tag without `v` if: startsWith(github.ref, 'refs/tags/') - uses: actions/github-script@v1 + uses: actions/github-script@v3 id: version with: script: | diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 075218595b..17ca678936 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v1 + - uses: actions/stale@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' From a32ee13fc93e79a2869e7e333e7a5a3d717987fa Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Mon, 31 Aug 2020 00:32:18 +0800 Subject: [PATCH 470/535] Feature: reuse dns resolver cache when hot reload --- common/cache/lrucache.go | 17 +++++++++++++++++ common/cache/lrucache_test.go | 18 ++++++++++++++++++ dns/resolver.go | 5 +++++ hub/executor/executor.go | 8 ++++++++ 4 files changed, 48 insertions(+) diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 1b1e492acc..4246d841ab 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -146,6 +146,23 @@ func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires tim c.maybeDeleteOldest() } +// CloneTo clone and overwrite elements to another LruCache +func (c *LruCache) CloneTo(n *LruCache) { + c.mu.Lock() + defer c.mu.Unlock() + + n.mu.Lock() + defer n.mu.Unlock() + + n.lru = list.New() + n.cache = make(map[interface{}]*list.Element) + + for e := c.lru.Front(); e != nil; e = e.Next() { + elm := e.Value.(*entry) + n.cache[elm.key] = n.lru.PushBack(elm) + } +} + func (c *LruCache) get(key interface{}) *entry { c.mu.Lock() defer c.mu.Unlock() diff --git a/common/cache/lrucache_test.go b/common/cache/lrucache_test.go index c3c629d905..13675bff38 100644 --- a/common/cache/lrucache_test.go +++ b/common/cache/lrucache_test.go @@ -164,3 +164,21 @@ func TestStale(t *testing.T) { assert.Equal(t, tenSecBefore, expires) assert.Equal(t, true, exist) } + +func TestCloneTo(t *testing.T) { + o := NewLRUCache(WithSize(10)) + o.Set("1", 1) + o.Set("2", 2) + + n := NewLRUCache(WithSize(2)) + n.Set("3", 3) + n.Set("4", 4) + + o.CloneTo(n) + + assert.False(t, n.Exist("3")) + assert.True(t, n.Exist("1")) + + n.Set("5", 5) + assert.False(t, n.Exist("1")) +} diff --git a/dns/resolver.go b/dns/resolver.go index 04ea41bbe1..21ebb00a80 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -186,6 +186,11 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool { return false } +// PatchCache overwrite lruCache to the new resolver +func (r *Resolver) PatchCache(n *Resolver) { + r.lruCache.CloneTo(n.lruCache) +} + func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { fast, ctx := picker.WithTimeout(context.Background(), time.Second*5) for _, client := range clients { diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 3bb29f5742..f0f4c9b698 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -120,6 +120,14 @@ func updateDNS(c *config.DNS) { }, Default: c.DefaultNameserver, }) + + // reuse cache of old resolver + if resolver.DefaultResolver != nil { + if o, ok := resolver.DefaultResolver.(*dns.Resolver); ok { + o.PatchCache(r) + } + } + resolver.DefaultResolver = r tunnel.SetResolver(r) if err := dns.ReCreateServer(c.Listen, r); err != nil { From 7631bcc99ea82ed982999fb9db7a6605ce74f7d3 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Wed, 2 Sep 2020 16:34:12 +0800 Subject: [PATCH 471/535] Improve: use atomic for connection statistic (#938) --- tunnel/manager.go | 43 +++++++++++++++++++------------------------ tunnel/tracker.go | 8 ++++---- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/tunnel/manager.go b/tunnel/manager.go index 50c2e11683..aef8b617c1 100644 --- a/tunnel/manager.go +++ b/tunnel/manager.go @@ -2,23 +2,19 @@ package tunnel import ( "sync" + "sync/atomic" "time" ) var DefaultManager *Manager func init() { - DefaultManager = &Manager{ - upload: make(chan int64), - download: make(chan int64), - } + DefaultManager = &Manager{} DefaultManager.handle() } type Manager struct { connections sync.Map - upload chan int64 - download chan int64 uploadTemp int64 downloadTemp int64 uploadBlip int64 @@ -35,16 +31,18 @@ func (m *Manager) Leave(c tracker) { m.connections.Delete(c.ID()) } -func (m *Manager) Upload() chan<- int64 { - return m.upload +func (m *Manager) PushUploaded(size int64) { + atomic.AddInt64(&m.uploadTemp, size) + atomic.AddInt64(&m.uploadTotal, size) } -func (m *Manager) Download() chan<- int64 { - return m.download +func (m *Manager) PushDownloaded(size int64) { + atomic.AddInt64(&m.downloadTemp, size) + atomic.AddInt64(&m.downloadTotal, size) } func (m *Manager) Now() (up int64, down int64) { - return m.uploadBlip, m.downloadBlip + return atomic.LoadInt64(&m.uploadBlip), atomic.LoadInt64(&m.downloadBlip) } func (m *Manager) Snapshot() *Snapshot { @@ -55,8 +53,8 @@ func (m *Manager) Snapshot() *Snapshot { }) return &Snapshot{ - UploadTotal: m.uploadTotal, - DownloadTotal: m.downloadTotal, + UploadTotal: atomic.LoadInt64(&m.uploadTotal), + DownloadTotal: atomic.LoadInt64(&m.downloadTotal), Connections: connections, } } @@ -71,21 +69,18 @@ func (m *Manager) ResetStatistic() { } func (m *Manager) handle() { - go m.handleCh(m.upload, &m.uploadTemp, &m.uploadBlip, &m.uploadTotal) - go m.handleCh(m.download, &m.downloadTemp, &m.downloadBlip, &m.downloadTotal) + go m.handleCh(&m.uploadTemp, &m.uploadBlip) + go m.handleCh(&m.downloadTemp, &m.downloadBlip) } -func (m *Manager) handleCh(ch <-chan int64, temp *int64, blip *int64, total *int64) { +func (m *Manager) handleCh(temp *int64, blip *int64) { ticker := time.NewTicker(time.Second) + for { - select { - case n := <-ch: - *temp += n - *total += n - case <-ticker.C: - *blip = *temp - *temp = 0 - } + <-ticker.C + + atomic.StoreInt64(blip, atomic.LoadInt64(temp)) + atomic.StoreInt64(temp, 0) } } diff --git a/tunnel/tracker.go b/tunnel/tracker.go index 142b7110ec..e39caec777 100644 --- a/tunnel/tracker.go +++ b/tunnel/tracker.go @@ -37,7 +37,7 @@ func (tt *tcpTracker) ID() string { func (tt *tcpTracker) Read(b []byte) (int, error) { n, err := tt.Conn.Read(b) download := int64(n) - tt.manager.Download() <- download + tt.manager.PushDownloaded(download) tt.DownloadTotal += download return n, err } @@ -45,7 +45,7 @@ func (tt *tcpTracker) Read(b []byte) (int, error) { func (tt *tcpTracker) Write(b []byte) (int, error) { n, err := tt.Conn.Write(b) upload := int64(n) - tt.manager.Upload() <- upload + tt.manager.PushUploaded(upload) tt.UploadTotal += upload return n, err } @@ -92,7 +92,7 @@ func (ut *udpTracker) ID() string { func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { n, addr, err := ut.PacketConn.ReadFrom(b) download := int64(n) - ut.manager.Download() <- download + ut.manager.PushDownloaded(download) ut.DownloadTotal += download return n, addr, err } @@ -100,7 +100,7 @@ func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { n, err := ut.PacketConn.WriteTo(b, addr) upload := int64(n) - ut.manager.Upload() <- upload + ut.manager.PushUploaded(upload) ut.UploadTotal += upload return n, err } From 02d9169b5d1e45a47f0f0355a32bff045838d621 Mon Sep 17 00:00:00 2001 From: icpz Date: Thu, 3 Sep 2020 10:27:20 +0800 Subject: [PATCH 472/535] Fix: potential PCB buffer overflow on bsd systems (#941) --- rules/process_darwin.go | 4 ++-- rules/process_freebsd_amd64.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rules/process_darwin.go b/rules/process_darwin.go index 0ff8960dc6..1af828a9fd 100644 --- a/rules/process_darwin.go +++ b/rules/process_darwin.go @@ -127,8 +127,8 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { // rup8(sizeof(xtcpcb_n)) itemSize += 208 } - // skip the first and last xinpgen(24 bytes) block - for i := 24; i < len(buf)-24; i += itemSize { + // skip the first xinpgen(24 bytes) block + for i := 24; i+itemSize <= len(buf); i += itemSize { // offset of xinpcb_n and xsocket_n inp, so := i, i+104 diff --git a/rules/process_freebsd_amd64.go b/rules/process_freebsd_amd64.go index 40e7a5fcef..05ab7c3fa3 100644 --- a/rules/process_freebsd_amd64.go +++ b/rules/process_freebsd_amd64.go @@ -95,7 +95,7 @@ func searchSocketPid(socket uint64) (uint32, error) { // struct xfile itemSize := 128 - for i := 0; i < len(buf); i += itemSize { + for i := 0; i+itemSize <= len(buf); i += itemSize { // xfile.xf_data data := binary.BigEndian.Uint64(buf[i+56 : i+64]) if data == socket { @@ -141,8 +141,8 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { buf := []byte(value) - // skip the first and last xinpgen(64 bytes) block - for i := 64; i < len(buf)-64; i += itemSize { + // skip the first xinpgen(64 bytes) block + for i := 64; i+itemSize <= len(buf); i += itemSize { inp := i + inpOffset srcPort := binary.BigEndian.Uint16(buf[inp+254 : inp+256]) From 13275b1aa6b764bfd00369ed0b039dd528ef53c5 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Thu, 3 Sep 2020 10:30:18 +0800 Subject: [PATCH 473/535] Chore: use only one goroutine to handle statistic (#940) --- tunnel/manager.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tunnel/manager.go b/tunnel/manager.go index aef8b617c1..1493bc000c 100644 --- a/tunnel/manager.go +++ b/tunnel/manager.go @@ -10,7 +10,8 @@ var DefaultManager *Manager func init() { DefaultManager = &Manager{} - DefaultManager.handle() + + go DefaultManager.handle() } type Manager struct { @@ -69,18 +70,13 @@ func (m *Manager) ResetStatistic() { } func (m *Manager) handle() { - go m.handleCh(&m.uploadTemp, &m.uploadBlip) - go m.handleCh(&m.downloadTemp, &m.downloadBlip) -} - -func (m *Manager) handleCh(temp *int64, blip *int64) { ticker := time.NewTicker(time.Second) - for { - <-ticker.C - - atomic.StoreInt64(blip, atomic.LoadInt64(temp)) - atomic.StoreInt64(temp, 0) + for range ticker.C { + atomic.StoreInt64(&m.uploadBlip, atomic.LoadInt64(&m.uploadTemp)) + atomic.StoreInt64(&m.uploadTemp, 0) + atomic.StoreInt64(&m.downloadBlip, atomic.LoadInt64(&m.downloadTemp)) + atomic.StoreInt64(&m.downloadTemp, 0) } } From 314ce1c249e042f75297c353e2b11e2f7fb9270f Mon Sep 17 00:00:00 2001 From: Dreamacro <305009791@qq.com> Date: Fri, 4 Sep 2020 21:27:19 +0800 Subject: [PATCH 474/535] Feature: vmess network http support TLS (https) --- adapters/outbound/vmess.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 20309c2e39..7999093a0f 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -71,6 +71,25 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } c, err = vmess.StreamWebsocketConn(c, wsOpts) case "http": + // readability first, so just copy default TLS logic + if v.option.TLS { + host, _, _ := net.SplitHostPort(v.addr) + tlsOpts := &vmess.TLSConfig{ + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + SessionCache: getClientSessionCache(), + } + + if v.option.ServerName != "" { + tlsOpts.Host = v.option.ServerName + } + + c, err = vmess.StreamTLSConn(c, tlsOpts) + if err != nil { + return nil, err + } + } + host, _, _ := net.SplitHostPort(v.addr) httpOpts := &vmess.HTTPConfig{ Host: host, From e773f95f219ece9c382ac372273a9f3a7d60eb1a Mon Sep 17 00:00:00 2001 From: icpz Date: Mon, 7 Sep 2020 17:43:34 +0800 Subject: [PATCH 475/535] Fix: PROCESS-NAME on FreeBSD 11.x (#947) --- rules/process_freebsd_amd64.go | 205 +++++++++++++++++++++++++-------- 1 file changed, 156 insertions(+), 49 deletions(-) diff --git a/rules/process_freebsd_amd64.go b/rules/process_freebsd_amd64.go index 05ab7c3fa3..24c416a27b 100644 --- a/rules/process_freebsd_amd64.go +++ b/rules/process_freebsd_amd64.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "syscall" "unsafe" @@ -17,7 +18,15 @@ import ( ) // store process name for when dealing with multiple PROCESS-NAME rules -var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) +var ( + processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) + errNotFound = errors.New("process not found") + matchMeta = func(p *Process, m *C.Metadata) bool { return false } + + defaultSearcher *searcher + + once sync.Once +) type Process struct { adapter string @@ -28,7 +37,7 @@ func (ps *Process) RuleType() C.RuleType { return C.Process } -func (ps *Process) Match(metadata *C.Metadata) bool { +func match(ps *Process, metadata *C.Metadata) bool { key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) cached, hit := processCache.Get(key) if !hit { @@ -45,6 +54,10 @@ func (ps *Process) Match(metadata *C.Metadata) bool { return strings.EqualFold(cached.(string), ps.process) } +func (ps *Process) Match(metadata *C.Metadata) bool { + return matchMeta(ps, metadata) +} + func (p *Process) Adapter() string { return p.adapter } @@ -58,6 +71,15 @@ func (p *Process) ShouldResolveIP() bool { } func NewProcess(process string, adapter string) (*Process, error) { + once.Do(func() { + err := initSearcher() + if err != nil { + log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) + log.Warnln("All PROCESS-NAME rules will be skipped") + return + } + matchMeta = match + }) return &Process{ adapter: adapter, process: process, @@ -85,28 +107,6 @@ func getExecPathFromPID(pid uint32) (string, error) { return filepath.Base(string(buf[:size-1])), nil } -func searchSocketPid(socket uint64) (uint32, error) { - value, err := syscall.Sysctl("kern.file") - if err != nil { - return 0, err - } - - buf := []byte(value) - - // struct xfile - itemSize := 128 - for i := 0; i+itemSize <= len(buf); i += itemSize { - // xfile.xf_data - data := binary.BigEndian.Uint64(buf[i+56 : i+64]) - if data == socket { - // xfile.xf_pid - pid := readNativeUint32(buf[i+8 : i+12]) - return pid, nil - } - } - return 0, errors.New("pid not found") -} - func getExecPathFromAddress(metadata *C.Metadata) (string, error) { ip := metadata.SrcIP port, err := strconv.Atoi(metadata.SrcPort) @@ -115,25 +115,18 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { } var spath string - var itemSize int - var inpOffset int + var isTCP bool switch metadata.NetWork { case C.TCP: spath = "net.inet.tcp.pcblist" - // struct xtcpcb - itemSize = 744 - inpOffset = 8 + isTCP = true case C.UDP: spath = "net.inet.udp.pcblist" - // struct xinpcb - itemSize = 400 - inpOffset = 0 + isTCP = false default: return "", ErrInvalidNetwork } - isIPv4 := ip.To4() != nil - value, err := syscall.Sysctl(spath) if err != nil { return "", err @@ -141,27 +134,73 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { buf := []byte(value) - // skip the first xinpgen(64 bytes) block - for i := 64; i+itemSize <= len(buf); i += itemSize { + pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP) + if err != nil { + return "", err + } + + return getExecPathFromPID(pid) +} + +func readNativeUint32(b []byte) uint32 { + return *(*uint32)(unsafe.Pointer(&b[0])) +} + +type searcher struct { + // sizeof(struct xinpgen) + headSize int + // sizeof(struct xtcpcb) + tcpItemSize int + // sizeof(struct xinpcb) + udpItemSize int + udpInpOffset int + port int + ip int + vflag int + socket int + + // sizeof(struct xfile) + fileItemSize int + data int + pid int +} + +func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint32, error) { + var itemSize int + var inpOffset int + + if isTCP { + // struct xtcpcb + itemSize = s.tcpItemSize + inpOffset = 8 + } else { + // struct xinpcb + itemSize = s.udpItemSize + inpOffset = s.udpInpOffset + } + + isIPv4 := ip.To4() != nil + // skip the first xinpgen block + for i := s.headSize; i+itemSize <= len(buf); i += itemSize { inp := i + inpOffset - srcPort := binary.BigEndian.Uint16(buf[inp+254 : inp+256]) + srcPort := binary.BigEndian.Uint16(buf[inp+s.port : inp+s.port+2]) - if uint16(port) != srcPort { + if port != srcPort { continue } // xinpcb.inp_vflag - flag := buf[inp+392] + flag := buf[inp+s.vflag] var srcIP net.IP switch { case flag&0x1 > 0 && isIPv4: // ipv4 - srcIP = net.IP(buf[inp+284 : inp+288]) + srcIP = net.IP(buf[inp+s.ip : inp+s.ip+4]) case flag&0x2 > 0 && !isIPv4: // ipv6 - srcIP = net.IP(buf[inp+272 : inp+288]) + srcIP = net.IP(buf[inp+s.ip-12 : inp+s.ip+4]) default: continue } @@ -171,17 +210,85 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { } // xsocket.xso_so, interpreted as big endian anyway since it's only used for comparison - socket := binary.BigEndian.Uint64(buf[inp+16 : inp+24]) - pid, err := searchSocketPid(socket) - if err != nil { - return "", err + socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8]) + return s.searchSocketPid(socket) + } + return 0, errNotFound +} + +func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { + value, err := syscall.Sysctl("kern.file") + if err != nil { + return 0, err + } + + buf := []byte(value) + + // struct xfile + itemSize := s.fileItemSize + for i := 0; i+itemSize <= len(buf); i += itemSize { + // xfile.xf_data + data := binary.BigEndian.Uint64(buf[i+s.data : i+s.data+8]) + if data == socket { + // xfile.xf_pid + pid := readNativeUint32(buf[i+s.pid : i+s.pid+4]) + return pid, nil } - return getExecPathFromPID(pid) } + return 0, errNotFound +} - return "", errors.New("process not found") +func newSearcher(major int) *searcher { + var s *searcher = nil + switch major { + case 11: + s = &searcher{ + headSize: 32, + tcpItemSize: 1304, + udpItemSize: 632, + port: 198, + ip: 228, + vflag: 116, + socket: 88, + fileItemSize: 80, + data: 56, + pid: 8, + udpInpOffset: 8, + } + case 12: + s = &searcher{ + headSize: 64, + tcpItemSize: 744, + udpItemSize: 400, + port: 254, + ip: 284, + vflag: 392, + socket: 16, + fileItemSize: 128, + data: 56, + pid: 8, + } + } + return s } -func readNativeUint32(b []byte) uint32 { - return *(*uint32)(unsafe.Pointer(&b[0])) +func initSearcher() error { + osRelease, err := syscall.Sysctl("kern.osrelease") + if err != nil { + return err + } + + dot := strings.Index(osRelease, ".") + if dot != -1 { + osRelease = osRelease[:dot] + } + major, err := strconv.Atoi(osRelease) + if err != nil { + return err + } + defaultSearcher = newSearcher(major) + if defaultSearcher == nil { + return fmt.Errorf("unsupported freebsd version %d", major) + } + return nil } From 558ac6b96576e1c06d53219e022a5392048a99c5 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Thu, 17 Sep 2020 10:48:42 +0800 Subject: [PATCH 476/535] Chore: split enhanced mode instance (#936) Co-authored-by: Dreamacro <305009791@qq.com> --- component/fakeip/pool.go | 5 ++ component/resolver/enhancer.go | 46 +++++++++++++++++ dns/enhancer.go | 76 ++++++++++++++++++++++++++++ dns/middleware.go | 90 ++++++++++++++++++++++++---------- dns/resolver.go | 54 +------------------- dns/server.go | 14 ++++-- hub/executor/executor.go | 25 +++++----- tunnel/tunnel.go | 31 +++++------- 8 files changed, 228 insertions(+), 113 deletions(-) create mode 100644 component/resolver/enhancer.go create mode 100644 dns/enhancer.go diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index be7bdd8d1c..dd8749a611 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -89,6 +89,11 @@ func (p *Pool) Gateway() net.IP { return uintToIP(p.gateway) } +// PatchFrom clone cache from old pool +func (p *Pool) PatchFrom(o *Pool) { + o.cache.CloneTo(p.cache) +} + func (p *Pool) get(host string) net.IP { current := p.offset for { diff --git a/component/resolver/enhancer.go b/component/resolver/enhancer.go new file mode 100644 index 0000000000..48b49bea6a --- /dev/null +++ b/component/resolver/enhancer.go @@ -0,0 +1,46 @@ +package resolver + +import ( + "net" +) + +var DefaultHostMapper Enhancer + +type Enhancer interface { + FakeIPEnabled() bool + MappingEnabled() bool + IsFakeIP(net.IP) bool + FindHostByIP(net.IP) (string, bool) +} + +func FakeIPEnabled() bool { + if mapper := DefaultHostMapper; mapper != nil { + return mapper.FakeIPEnabled() + } + + return false +} + +func MappingEnabled() bool { + if mapper := DefaultHostMapper; mapper != nil { + return mapper.MappingEnabled() + } + + return false +} + +func IsFakeIP(ip net.IP) bool { + if mapper := DefaultHostMapper; mapper != nil { + return mapper.IsFakeIP(ip) + } + + return false +} + +func FindHostByIP(ip net.IP) (string, bool) { + if mapper := DefaultHostMapper; mapper != nil { + return mapper.FindHostByIP(ip) + } + + return "", false +} diff --git a/dns/enhancer.go b/dns/enhancer.go new file mode 100644 index 0000000000..5018affa16 --- /dev/null +++ b/dns/enhancer.go @@ -0,0 +1,76 @@ +package dns + +import ( + "net" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/fakeip" +) + +type ResolverEnhancer struct { + mode EnhancedMode + fakePool *fakeip.Pool + mapping *cache.LruCache +} + +func (h *ResolverEnhancer) FakeIPEnabled() bool { + return h.mode == FAKEIP +} + +func (h *ResolverEnhancer) MappingEnabled() bool { + return h.mode == FAKEIP || h.mode == MAPPING +} + +func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { + if !h.FakeIPEnabled() { + return false + } + + if pool := h.fakePool; pool != nil { + return pool.Exist(ip) + } + + return false +} + +func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { + if pool := h.fakePool; pool != nil { + if host, existed := pool.LookBack(ip); existed { + return host, true + } + } + + if mapping := h.mapping; mapping != nil { + if host, existed := h.mapping.Get(ip.String()); existed { + return host.(string), true + } + } + + return "", false +} + +func (h *ResolverEnhancer) PatchFrom(o *ResolverEnhancer) { + if h.mapping != nil && o.mapping != nil { + o.mapping.CloneTo(h.mapping) + } + + if h.fakePool != nil && o.fakePool != nil { + h.fakePool.PatchFrom(o.fakePool) + } +} + +func NewEnhancer(cfg Config) *ResolverEnhancer { + var fakePool *fakeip.Pool + var mapping *cache.LruCache + + if cfg.EnhancedMode != NORMAL { + fakePool = cfg.Pool + mapping = cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)) + } + + return &ResolverEnhancer{ + mode: cfg.EnhancedMode, + fakePool: fakePool, + mapping: mapping, + } +} diff --git a/dns/middleware.go b/dns/middleware.go index cc58c001e5..8aff0647ff 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -3,7 +3,9 @@ package dns import ( "net" "strings" + "time" + "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/log" @@ -11,23 +13,21 @@ import ( D "github.com/miekg/dns" ) -type handler func(w D.ResponseWriter, r *D.Msg) +type handler func(r *D.Msg) (*D.Msg, error) type middleware func(next handler) handler func withHosts(hosts *trie.DomainTrie) middleware { return func(next handler) handler { - return func(w D.ResponseWriter, r *D.Msg) { + return func(r *D.Msg) (*D.Msg, error) { q := r.Question[0] if !isIPRequest(q) { - next(w, r) - return + return next(r) } record := hosts.Search(strings.TrimRight(q.Name, ".")) if record == nil { - next(w, r) - return + return next(r) } ip := record.Data.(net.IP) @@ -46,22 +46,60 @@ func withHosts(hosts *trie.DomainTrie) middleware { msg.Answer = []D.RR{rr} } else { - next(w, r) - return + return next(r) } msg.SetRcode(r, D.RcodeSuccess) msg.Authoritative = true msg.RecursionAvailable = true - w.WriteMsg(msg) + return msg, nil + } + } +} + +func withMapping(mapping *cache.LruCache) middleware { + return func(next handler) handler { + return func(r *D.Msg) (*D.Msg, error) { + q := r.Question[0] + + if !isIPRequest(q) { + return next(r) + } + + msg, err := next(r) + if err != nil { + return nil, err + } + + host := strings.TrimRight(q.Name, ".") + + for _, ans := range msg.Answer { + var ip net.IP + var ttl uint32 + + switch a := ans.(type) { + case *D.A: + ip = a.A + ttl = a.Hdr.Ttl + case *D.AAAA: + ip = a.AAAA + ttl = a.Hdr.Ttl + default: + continue + } + + mapping.SetWithExpire(ip.String(), host, time.Now().Add(time.Second*time.Duration(ttl))) + } + + return msg, nil } } } func withFakeIP(fakePool *fakeip.Pool) middleware { return func(next handler) handler { - return func(w D.ResponseWriter, r *D.Msg) { + return func(r *D.Msg) (*D.Msg, error) { q := r.Question[0] if q.Qtype == D.TypeAAAA { @@ -72,17 +110,14 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { msg.Authoritative = true msg.RecursionAvailable = true - w.WriteMsg(msg) - return + return msg, nil } else if q.Qtype != D.TypeA { - next(w, r) - return + return next(r) } host := strings.TrimRight(q.Name, ".") if fakePool.LookupHost(host) { - next(w, r) - return + return next(r) } rr := &D.A{} @@ -97,13 +132,13 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { msg.Authoritative = true msg.RecursionAvailable = true - w.WriteMsg(msg) + return msg, nil } } } func withResolver(resolver *Resolver) handler { - return func(w D.ResponseWriter, r *D.Msg) { + return func(r *D.Msg) (*D.Msg, error) { q := r.Question[0] // return a empty AAAA msg when ipv6 disabled @@ -115,19 +150,18 @@ func withResolver(resolver *Resolver) handler { msg.Authoritative = true msg.RecursionAvailable = true - w.WriteMsg(msg) - return + return msg, nil } msg, err := resolver.Exchange(r) if err != nil { log.Debugln("[DNS Server] Exchange %s failed: %v", q.String(), err) - D.HandleFailed(w, r) - return + return msg, err } msg.SetRcode(r, msg.Rcode) msg.Authoritative = true - w.WriteMsg(msg) + + return msg, nil } } @@ -142,15 +176,19 @@ func compose(middlewares []middleware, endpoint handler) handler { return h } -func newHandler(resolver *Resolver) handler { +func newHandler(resolver *Resolver, mapper *ResolverEnhancer) handler { middlewares := []middleware{} if resolver.hosts != nil { middlewares = append(middlewares, withHosts(resolver.hosts)) } - if resolver.FakeIPEnabled() { - middlewares = append(middlewares, withFakeIP(resolver.pool)) + if mapper.mode == FAKEIP { + middlewares = append(middlewares, withFakeIP(mapper.fakePool)) + } + + if mapper.mode != NORMAL { + middlewares = append(middlewares, withMapping(mapper.mapping)) } return compose(middlewares, withResolver(resolver)) diff --git a/dns/resolver.go b/dns/resolver.go index 21ebb00a80..b7d20b5c61 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -7,7 +7,6 @@ import ( "fmt" "math/rand" "net" - "strings" "time" "github.com/Dreamacro/clash/common/cache" @@ -36,10 +35,7 @@ type result struct { type Resolver struct { ipv6 bool - mapping bool - fakeip bool hosts *trie.DomainTrie - pool *fakeip.Pool main []dnsClient fallback []dnsClient fallbackFilters []fallbackFilter @@ -126,12 +122,6 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { msg := result.(*D.Msg) putMsgToCache(r.lruCache, q.String(), msg) - if r.mapping || r.fakeip { - ips := r.msgToIP(msg) - for _, ip := range ips { - putMsgToCache(r.lruCache, ip.String(), msg) - } - } }() isIPReq := isIPRequest(q) @@ -152,45 +142,6 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { return } -// IPToHost return fake-ip or redir-host mapping host -func (r *Resolver) IPToHost(ip net.IP) (string, bool) { - if r.fakeip { - record, existed := r.pool.LookBack(ip) - if existed { - return record, true - } - } - - cache, _ := r.lruCache.Get(ip.String()) - if cache == nil { - return "", false - } - fqdn := cache.(*D.Msg).Question[0].Name - return strings.TrimRight(fqdn, "."), true -} - -func (r *Resolver) IsMapping() bool { - return r.mapping -} - -// FakeIPEnabled returns if fake-ip is enabled -func (r *Resolver) FakeIPEnabled() bool { - return r.fakeip -} - -// IsFakeIP determine if given ip is a fake-ip -func (r *Resolver) IsFakeIP(ip net.IP) bool { - if r.FakeIPEnabled() { - return r.pool.Exist(ip) - } - return false -} - -// PatchCache overwrite lruCache to the new resolver -func (r *Resolver) PatchCache(n *Resolver) { - r.lruCache.CloneTo(n.lruCache) -} - func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { fast, ctx := picker.WithTimeout(context.Background(), time.Second*5) for _, client := range clients { @@ -318,7 +269,7 @@ type Config struct { Hosts *trie.DomainTrie } -func New(config Config) *Resolver { +func NewResolver(config Config) *Resolver { defaultResolver := &Resolver{ main: transform(config.Default, nil), lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)), @@ -328,9 +279,6 @@ func New(config Config) *Resolver { ipv6: config.IPv6, main: transform(config.Main, defaultResolver), lruCache: cache.NewLRUCache(cache.WithSize(4096), cache.WithStale(true)), - mapping: config.EnhancedMode == MAPPING, - fakeip: config.EnhancedMode == FAKEIP, - pool: config.Pool, hosts: config.Hosts, } diff --git a/dns/server.go b/dns/server.go index 6e6a02b69e..23718c4f0d 100644 --- a/dns/server.go +++ b/dns/server.go @@ -27,16 +27,22 @@ func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { return } - s.handler(w, r) + msg, err := s.handler(r) + if err != nil { + D.HandleFailed(w, r) + return + } + + w.WriteMsg(msg) } func (s *Server) setHandler(handler handler) { s.handler = handler } -func ReCreateServer(addr string, resolver *Resolver) error { +func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) error { if addr == address && resolver != nil { - handler := newHandler(resolver) + handler := newHandler(resolver, mapper) server.setHandler(handler) return nil } @@ -68,7 +74,7 @@ func ReCreateServer(addr string, resolver *Resolver) error { } address = addr - handler := newHandler(resolver) + handler := newHandler(resolver, mapper) server = &Server{handler: handler} server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server} diff --git a/hub/executor/executor.go b/hub/executor/executor.go index f0f4c9b698..c24d6123f6 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -103,11 +103,12 @@ func updateExperimental(c *config.Config) {} func updateDNS(c *config.DNS) { if !c.Enable { resolver.DefaultResolver = nil - tunnel.SetResolver(nil) - dns.ReCreateServer("", nil) + resolver.DefaultHostMapper = nil + dns.ReCreateServer("", nil, nil) return } - r := dns.New(dns.Config{ + + cfg := dns.Config{ Main: c.NameServer, Fallback: c.Fallback, IPv6: c.IPv6, @@ -119,18 +120,20 @@ func updateDNS(c *config.DNS) { IPCIDR: c.FallbackFilter.IPCIDR, }, Default: c.DefaultNameserver, - }) + } - // reuse cache of old resolver - if resolver.DefaultResolver != nil { - if o, ok := resolver.DefaultResolver.(*dns.Resolver); ok { - o.PatchCache(r) - } + r := dns.NewResolver(cfg) + m := dns.NewEnhancer(cfg) + + // reuse cache of old host mapper + if old := resolver.DefaultHostMapper; old != nil { + m.PatchFrom(old.(*dns.ResolverEnhancer)) } resolver.DefaultResolver = r - tunnel.SetResolver(r) - if err := dns.ReCreateServer(c.Listen, r); err != nil { + resolver.DefaultHostMapper = m + + if err := dns.ReCreateServer(c.Listen, r, m); err != nil { log.Errorln("Start DNS server error: %s", err.Error()) return } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 64911bc7de..b44411160e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -12,21 +12,19 @@ import ( "github.com/Dreamacro/clash/component/nat" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/dns" "github.com/Dreamacro/clash/log" channels "gopkg.in/eapache/channels.v1" ) var ( - tcpQueue = channels.NewInfiniteChannel() - udpQueue = channels.NewInfiniteChannel() - natTable = nat.New() - rules []C.Rule - proxies = make(map[string]C.Proxy) - providers map[string]provider.ProxyProvider - configMux sync.RWMutex - enhancedMode *dns.Resolver + tcpQueue = channels.NewInfiniteChannel() + udpQueue = channels.NewInfiniteChannel() + natTable = nat.New() + rules []C.Rule + proxies = make(map[string]C.Proxy) + providers map[string]provider.ProxyProvider + configMux sync.RWMutex // Outbound Rule mode = Rule @@ -89,11 +87,6 @@ func SetMode(m TunnelMode) { mode = m } -// SetResolver set custom dns resolver for enhanced mode -func SetResolver(r *dns.Resolver) { - enhancedMode = r -} - // processUDP starts a loop to handle udp packet func processUDP() { queue := udpQueue.Out() @@ -120,7 +113,7 @@ func process() { } func needLookupIP(metadata *C.Metadata) bool { - return enhancedMode != nil && (enhancedMode.IsMapping() || enhancedMode.FakeIPEnabled()) && metadata.Host == "" && metadata.DstIP != nil + return resolver.MappingEnabled() && metadata.Host == "" && metadata.DstIP != nil } func preHandleMetadata(metadata *C.Metadata) error { @@ -131,17 +124,17 @@ func preHandleMetadata(metadata *C.Metadata) error { // preprocess enhanced-mode metadata if needLookupIP(metadata) { - host, exist := enhancedMode.IPToHost(metadata.DstIP) + host, exist := resolver.FindHostByIP(metadata.DstIP) if exist { metadata.Host = host metadata.AddrType = C.AtypDomainName - if enhancedMode.FakeIPEnabled() { + if resolver.FakeIPEnabled() { metadata.DstIP = nil } else if node := resolver.DefaultHosts.Search(host); node != nil { // redir-host should lookup the hosts metadata.DstIP = node.Data.(net.IP) } - } else if enhancedMode.IsFakeIP(metadata.DstIP) { + } else if resolver.IsFakeIP(metadata.DstIP) { return fmt.Errorf("fake DNS record %s missing", metadata.DstIP) } } @@ -177,7 +170,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { // make a fAddr if requset ip is fakeip var fAddr net.Addr - if enhancedMode != nil && enhancedMode.IsFakeIP(metadata.DstIP) { + if resolver.IsFakeIP(metadata.DstIP) { fAddr = metadata.UDPAddr() } From 68dd0622b87418a55ab429003b700ac828b78622 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 20 Sep 2020 15:53:27 +0800 Subject: [PATCH 477/535] Chore: code style --- component/ssr/obfs/tls12_ticket_auth.go | 4 ++-- config/config.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/component/ssr/obfs/tls12_ticket_auth.go b/component/ssr/obfs/tls12_ticket_auth.go index b9977b79c6..b4c1089aa4 100644 --- a/component/ssr/obfs/tls12_ticket_auth.go +++ b/component/ssr/obfs/tls12_ticket_auth.go @@ -6,13 +6,13 @@ import ( "encoding/binary" "fmt" "io" - "log" "math/rand" "strings" "time" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/log" ) type tlsAuthData struct { @@ -63,7 +63,7 @@ func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) { var h [5]byte t.recvBuffer.Read(h[:]) if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) { - log.Println("incorrect magic number", h[:3], ", 0x170303 is expected") + log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3]) return nil, false, errTLS12TicketAuthIncorrectMagicNumber } size := int(binary.BigEndian.Uint16(h[3:5])) diff --git a/config/config.go b/config/config.go index cf6a767092..b9e42dd426 100644 --- a/config/config.go +++ b/config/config.go @@ -403,7 +403,7 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie, error) { // add default hosts if err := tree.Insert("localhost", net.IP{127, 0, 0, 1}); err != nil { - println(err.Error()) + log.Errorln("insert localhost to host error: %s", err.Error()) } if len(cfg.Hosts) != 0 { From 96a8259c42797d3b2548c95b0790d1e9a4cbd266 Mon Sep 17 00:00:00 2001 From: Kaming Chan Date: Mon, 21 Sep 2020 00:33:13 +0800 Subject: [PATCH 478/535] Feature: support snell v2 (#952) Co-authored-by: Dreamacro <8615343+Dreamacro@users.noreply.github.com> --- adapters/outbound/snell.go | 65 +++++++++++++++++--- component/pool/pool.go | 114 ++++++++++++++++++++++++++++++++++++ component/pool/pool_test.go | 96 ++++++++++++++++++++++++++++++ component/snell/cipher.go | 39 +++++++++++- component/snell/pool.go | 80 +++++++++++++++++++++++++ component/snell/snell.go | 43 +++++++++++--- go.mod | 2 +- go.sum | 6 +- 8 files changed, 421 insertions(+), 24 deletions(-) create mode 100644 component/pool/pool.go create mode 100644 component/pool/pool_test.go create mode 100644 component/snell/pool.go diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go index 61c103b85e..edc0ddd750 100644 --- a/adapters/outbound/snell.go +++ b/adapters/outbound/snell.go @@ -16,7 +16,9 @@ import ( type Snell struct { *Base psk []byte + pool *snell.Pool obfsOption *simpleObfsOption + version int } type SnellOption struct { @@ -24,24 +26,47 @@ type SnellOption struct { Server string `proxy:"server"` Port int `proxy:"port"` Psk string `proxy:"psk"` + Version int `proxy:"version,omitempty"` ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"` } -func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { - switch s.obfsOption.Mode { +type streamOption struct { + psk []byte + version int + addr string + obfsOption *simpleObfsOption +} + +func streamConn(c net.Conn, option streamOption) *snell.Snell { + switch option.obfsOption.Mode { case "tls": - c = obfs.NewTLSObfs(c, s.obfsOption.Host) + c = obfs.NewTLSObfs(c, option.obfsOption.Host) case "http": - _, port, _ := net.SplitHostPort(s.addr) - c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port) + _, port, _ := net.SplitHostPort(option.addr) + c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port) } - c = snell.StreamConn(c, s.psk) + return snell.StreamConn(c, option.psk, option.version) +} + +func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { + c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) port, _ := strconv.Atoi(metadata.DstPort) - err := snell.WriteHeader(c, metadata.String(), uint(port)) + err := snell.WriteHeader(c, metadata.String(), uint(port), s.version) return c, err } func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + if s.version == snell.Version2 { + c, err := s.pool.Get() + if err != nil { + return nil, err + } + + port, _ := strconv.Atoi(metadata.DstPort) + err = snell.WriteHeader(c, metadata.String(), uint(port), s.version) + return NewConn(c, s), err + } + c, err := dialer.DialContext(ctx, "tcp", s.addr) if err != nil { return nil, fmt.Errorf("%s connect error: %w", s.addr, err) @@ -66,7 +91,15 @@ func NewSnell(option SnellOption) (*Snell, error) { return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) } - return &Snell{ + // backward compatible + if option.Version == 0 { + option.Version = snell.DefaultSnellVersion + } + if option.Version != snell.Version1 && option.Version != snell.Version2 { + return nil, fmt.Errorf("snell version error: %d", option.Version) + } + + s := &Snell{ Base: &Base{ name: option.Name, addr: addr, @@ -74,5 +107,19 @@ func NewSnell(option SnellOption) (*Snell, error) { }, psk: psk, obfsOption: obfsOption, - }, nil + version: option.Version, + } + + if option.Version == snell.Version2 { + s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) { + c, err := dialer.DialContext(ctx, "tcp", addr) + if err != nil { + return nil, err + } + + tcpKeepAlive(c) + return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil + }) + } + return s, nil } diff --git a/component/pool/pool.go b/component/pool/pool.go new file mode 100644 index 0000000000..ce40a1298c --- /dev/null +++ b/component/pool/pool.go @@ -0,0 +1,114 @@ +package pool + +import ( + "context" + "runtime" + "time" +) + +type Factory = func(context.Context) (interface{}, error) + +type entry struct { + elm interface{} + time time.Time +} + +type Option func(*pool) + +// WithEvict set the evict callback +func WithEvict(cb func(interface{})) Option { + return func(p *pool) { + p.evict = cb + } +} + +// WithAge defined element max age (millisecond) +func WithAge(maxAge int64) Option { + return func(p *pool) { + p.maxAge = maxAge + } +} + +// WithSize defined max size of Pool +func WithSize(maxSize int) Option { + return func(p *pool) { + p.ch = make(chan interface{}, maxSize) + } +} + +// Pool is for GC, see New for detail +type Pool struct { + *pool +} + +type pool struct { + ch chan interface{} + factory Factory + evict func(interface{}) + maxAge int64 +} + +func (p *pool) GetContext(ctx context.Context) (interface{}, error) { + now := time.Now() + for { + select { + case item := <-p.ch: + elm := item.(*entry) + if p.maxAge != 0 && now.Sub(item.(*entry).time).Milliseconds() > p.maxAge { + if p.evict != nil { + p.evict(elm.elm) + } + continue + } + + return elm.elm, nil + default: + return p.factory(ctx) + } + } +} + +func (p *pool) Get() (interface{}, error) { + return p.GetContext(context.Background()) +} + +func (p *pool) Put(item interface{}) { + e := &entry{ + elm: item, + time: time.Now(), + } + + select { + case p.ch <- e: + return + default: + // pool is full + if p.evict != nil { + p.evict(item) + } + return + } +} + +func recycle(p *Pool) { + for item := range p.pool.ch { + if p.pool.evict != nil { + p.pool.evict(item.(*entry).elm) + } + } +} + +func New(factory Factory, options ...Option) *Pool { + p := &pool{ + ch: make(chan interface{}, 10), + factory: factory, + } + + for _, option := range options { + option(p) + } + + P := &Pool{p} + runtime.SetFinalizer(P, recycle) + return P +} diff --git a/component/pool/pool_test.go b/component/pool/pool_test.go new file mode 100644 index 0000000000..844ef24585 --- /dev/null +++ b/component/pool/pool_test.go @@ -0,0 +1,96 @@ +package pool + +import ( + "context" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func lg() Factory { + initial := -1 + return func(context.Context) (interface{}, error) { + initial++ + return initial, nil + } +} + +func TestPool_Basic(t *testing.T) { + g := lg() + pool := New(g) + + elm, _ := pool.Get() + assert.Equal(t, 0, elm.(int)) + pool.Put(elm) + elm, _ = pool.Get() + assert.Equal(t, 0, elm.(int)) + elm, _ = pool.Get() + assert.Equal(t, 1, elm.(int)) +} + +func TestPool_MaxSize(t *testing.T) { + g := lg() + size := 5 + pool := New(g, WithSize(size)) + + items := []interface{}{} + + for i := 0; i < size; i++ { + item, _ := pool.Get() + items = append(items, item) + } + + extra, _ := pool.Get() + assert.Equal(t, size, extra.(int)) + + for _, item := range items { + pool.Put(item) + } + + pool.Put(extra) + + for _, item := range items { + elm, _ := pool.Get() + assert.Equal(t, item.(int), elm.(int)) + } +} + +func TestPool_MaxAge(t *testing.T) { + g := lg() + pool := New(g, WithAge(20)) + + elm, _ := pool.Get() + pool.Put(elm) + + elm, _ = pool.Get() + assert.Equal(t, 0, elm.(int)) + pool.Put(elm) + + time.Sleep(time.Millisecond * 22) + elm, _ = pool.Get() + assert.Equal(t, 1, elm.(int)) +} + +func TestPool_AutoGC(t *testing.T) { + g := lg() + + sign := make(chan int) + pool := New(g, WithEvict(func(item interface{}) { + sign <- item.(int) + })) + + elm, _ := pool.Get() + assert.Equal(t, 0, elm.(int)) + pool.Put(2) + + runtime.GC() + + select { + case num := <-sign: + assert.Equal(t, 2, num) + case <-time.After(time.Second * 3): + assert.Fail(t, "something wrong") + } +} diff --git a/component/snell/cipher.go b/component/snell/cipher.go index f66f0801ce..f778e64713 100644 --- a/component/snell/cipher.go +++ b/component/snell/cipher.go @@ -1,21 +1,54 @@ package snell import ( + "crypto/aes" "crypto/cipher" + "github.com/Dreamacro/go-shadowsocks2/shadowaead" "golang.org/x/crypto/argon2" + "golang.org/x/crypto/chacha20poly1305" ) type snellCipher struct { psk []byte + keySize int makeAEAD func(key []byte) (cipher.AEAD, error) } -func (sc *snellCipher) KeySize() int { return 32 } +func (sc *snellCipher) KeySize() int { return sc.keySize } func (sc *snellCipher) SaltSize() int { return 16 } func (sc *snellCipher) Encrypter(salt []byte) (cipher.AEAD, error) { - return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) + return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) } func (sc *snellCipher) Decrypter(salt []byte) (cipher.AEAD, error) { - return sc.makeAEAD(argon2.IDKey(sc.psk, salt, 3, 8, 1, uint32(sc.KeySize()))) + return sc.makeAEAD(snellKDF(sc.psk, salt, sc.KeySize())) +} + +func snellKDF(psk, salt []byte, keySize int) []byte { + // snell use a special kdf function + return argon2.IDKey(psk, salt, 3, 8, 1, 32)[:keySize] +} + +func aesGCM(key []byte) (cipher.AEAD, error) { + blk, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + return cipher.NewGCM(blk) +} + +func NewAES128GCM(psk []byte) shadowaead.Cipher { + return &snellCipher{ + psk: psk, + keySize: 16, + makeAEAD: aesGCM, + } +} + +func NewChacha20Poly1305(psk []byte) shadowaead.Cipher { + return &snellCipher{ + psk: psk, + keySize: 32, + makeAEAD: chacha20poly1305.New, + } } diff --git a/component/snell/pool.go b/component/snell/pool.go new file mode 100644 index 0000000000..37b7de14dd --- /dev/null +++ b/component/snell/pool.go @@ -0,0 +1,80 @@ +package snell + +import ( + "context" + "net" + + "github.com/Dreamacro/clash/component/pool" + "github.com/Dreamacro/go-shadowsocks2/shadowaead" +) + +type Pool struct { + pool *pool.Pool +} + +func (p *Pool) Get() (net.Conn, error) { + return p.GetContext(context.Background()) +} + +func (p *Pool) GetContext(ctx context.Context) (net.Conn, error) { + elm, err := p.pool.GetContext(ctx) + if err != nil { + return nil, err + } + + return &PoolConn{elm.(*Snell), p}, nil +} + +func (p *Pool) Put(conn net.Conn) { + if err := HalfClose(conn); err != nil { + conn.Close() + return + } + + p.pool.Put(conn) +} + +type PoolConn struct { + *Snell + pool *Pool +} + +func (pc *PoolConn) Read(b []byte) (int, error) { + // save old status of reply (it mutable by Read) + reply := pc.Snell.reply + + n, err := pc.Snell.Read(b) + if err == shadowaead.ErrZeroChunk { + // if reply is false, it should be client halfclose. + // ignore error and read data again. + if !reply { + pc.Snell.reply = false + return pc.Snell.Read(b) + } + } + return n, err +} + +func (pc *PoolConn) Write(b []byte) (int, error) { + return pc.Snell.Write(b) +} + +func (pc *PoolConn) Close() error { + pc.pool.Put(pc.Snell) + return nil +} + +func NewPool(factory func(context.Context) (*Snell, error)) *Pool { + p := pool.New( + func(ctx context.Context) (interface{}, error) { + return factory(ctx) + }, + pool.WithAge(15000), + pool.WithSize(10), + pool.WithEvict(func(item interface{}) { + item.(*Snell).Close() + }), + ) + + return &Pool{p} +} diff --git a/component/snell/snell.go b/component/snell/snell.go index 920f122554..ecbc90ee8a 100644 --- a/component/snell/snell.go +++ b/component/snell/snell.go @@ -10,14 +10,21 @@ import ( "sync" "github.com/Dreamacro/go-shadowsocks2/shadowaead" - "golang.org/x/crypto/chacha20poly1305" ) const ( - CommandPing byte = 0 - CommandConnect byte = 1 + Version1 = 1 + Version2 = 2 + DefaultSnellVersion = Version1 +) + +const ( + CommandPing byte = 0 + CommandConnect byte = 1 + CommandConnectV2 byte = 5 CommandTunnel byte = 0 + CommandPong byte = 1 CommandError byte = 2 Version byte = 1 @@ -25,6 +32,7 @@ const ( var ( bufferPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} + endSignal = []byte{} ) type Snell struct { @@ -70,12 +78,16 @@ func (s *Snell) Read(b []byte) (int, error) { return 0, fmt.Errorf("server reported code: %d, message: %s", errcode, string(msg)) } -func WriteHeader(conn net.Conn, host string, port uint) error { +func WriteHeader(conn net.Conn, host string, port uint, version int) error { buf := bufferPool.Get().(*bytes.Buffer) buf.Reset() defer bufferPool.Put(buf) buf.WriteByte(Version) - buf.WriteByte(CommandConnect) + if version == Version2 { + buf.WriteByte(CommandConnectV2) + } else { + buf.WriteByte(CommandConnect) + } // clientID length & id buf.WriteByte(0) @@ -92,7 +104,24 @@ func WriteHeader(conn net.Conn, host string, port uint) error { return nil } -func StreamConn(conn net.Conn, psk []byte) net.Conn { - cipher := &snellCipher{psk, chacha20poly1305.New} +// HalfClose works only on version2 +func HalfClose(conn net.Conn) error { + if _, err := conn.Write(endSignal); err != nil { + return err + } + + if s, ok := conn.(*Snell); ok { + s.reply = false + } + return nil +} + +func StreamConn(conn net.Conn, psk []byte, version int) *Snell { + var cipher shadowaead.Cipher + if version == Version2 { + cipher = NewAES128GCM(psk) + } else { + cipher = NewChacha20Poly1305(psk) + } return &Snell{Conn: shadowaead.NewConn(conn, cipher)} } diff --git a/go.mod b/go.mod index ff19a68b51..f1f85e526f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Dreamacro/clash go 1.14 require ( - github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a + github.com/Dreamacro/go-shadowsocks2 v0.1.6 github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.1.1 diff --git a/go.sum b/go.sum index bc588d746d..8611148b39 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a h1:JhQFrFOkCpRB8qsN6PrzHFzjy/8iQpFFk5cbOiplh6s= -github.com/Dreamacro/go-shadowsocks2 v0.1.6-0.20200722122336-8e5c7db4f96a/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= +github.com/Dreamacro/go-shadowsocks2 v0.1.6 h1:PysSf9sLT3Qn8jhlin5v7Rk68gOQG4K5BZFY1nxLGxI= +github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72ItcBT/N2xNC685cYeU= 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= @@ -27,7 +27,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -53,7 +52,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= From 10f9571c9e2d91d1eed28630b98b73637245b440 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 21 Sep 2020 00:44:47 +0800 Subject: [PATCH 479/535] Fix: pool gc test --- component/pool/pool_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/component/pool/pool_test.go b/component/pool/pool_test.go index 844ef24585..6fc8622d02 100644 --- a/component/pool/pool_test.go +++ b/component/pool/pool_test.go @@ -84,6 +84,7 @@ func TestPool_AutoGC(t *testing.T) { elm, _ := pool.Get() assert.Equal(t, 0, elm.(int)) pool.Put(2) + pool = nil runtime.GC() From 8766287e72240809695b1a9b540b2c615591604e Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 21 Sep 2020 22:22:07 +0800 Subject: [PATCH 480/535] Chore: sync necessary changes from premium --- common/cache/lrucache.go | 2 +- component/fakeip/pool.go | 2 +- tunnel/connection.go | 13 ++++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 4246d841ab..a2e0e90350 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -188,7 +188,7 @@ func (c *LruCache) get(key interface{}) *entry { } // Delete removes the value associated with a key. -func (c *LruCache) Delete(key string) { +func (c *LruCache) Delete(key interface{}) { c.mu.Lock() if le, ok := c.cache[key]; ok { diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index dd8749a611..46fe59274b 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -121,7 +121,7 @@ func ipToUint(ip net.IP) uint32 { } func uintToIP(v uint32) net.IP { - return net.IPv4(byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) + return net.IP{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)} } // New return Pool instance diff --git a/tunnel/connection.go b/tunnel/connection.go index 13f7875af1..1e8ee1df5e 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -9,14 +9,13 @@ import ( "strings" "time" - adapters "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" - - "github.com/Dreamacro/clash/common/pool" ) -func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { +func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) { req := request.R host := req.Host @@ -28,7 +27,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { req.Header.Set("Connection", "close") req.RequestURI = "" - adapters.RemoveHopByHopHeaders(req.Header) + inbound.RemoveHopByHopHeaders(req.Header) err := req.Write(outbound) if err != nil { break @@ -39,7 +38,7 @@ func handleHTTP(request *adapters.HTTPAdapter, outbound net.Conn) { if err != nil { break } - adapters.RemoveHopByHopHeaders(resp.Header) + inbound.RemoveHopByHopHeaders(resp.Header) if resp.StatusCode == http.StatusContinue { err = resp.Write(request) @@ -128,7 +127,7 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n } } -func handleSocket(request *adapters.SocketAdapter, outbound net.Conn) { +func handleSocket(request C.ServerAdapter, outbound net.Conn) { relay(request, outbound) } From 5bd189f2d0473a652edbac906fbdf5f12523790a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=82=85Fox?= Date: Sat, 26 Sep 2020 20:33:57 +0800 Subject: [PATCH 481/535] Feature: support VMess HTTP/2 transport (#903) --- adapters/outbound/vmess.go | 33 +++++++++++ component/vmess/h2.go | 111 +++++++++++++++++++++++++++++++++++++ component/vmess/tls.go | 2 + go.sum | 1 + 4 files changed, 147 insertions(+) create mode 100644 component/vmess/h2.go diff --git a/adapters/outbound/vmess.go b/adapters/outbound/vmess.go index 7999093a0f..db1e203992 100644 --- a/adapters/outbound/vmess.go +++ b/adapters/outbound/vmess.go @@ -32,6 +32,7 @@ type VmessOption struct { UDP bool `proxy:"udp,omitempty"` Network string `proxy:"network,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` WSPath string `proxy:"ws-path,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` @@ -44,6 +45,11 @@ type HTTPOptions struct { Headers map[string][]string `proxy:"headers,omitempty"` } +type HTTP2Options struct { + Host []string `proxy:"host,omitempty"` + Path string `proxy:"path,omitempty"` +} + func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { var err error switch v.option.Network { @@ -99,6 +105,30 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { } c = vmess.StreamHTTPConn(c, httpOpts) + case "h2": + host, _, _ := net.SplitHostPort(v.addr) + tlsOpts := vmess.TLSConfig{ + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + SessionCache: getClientSessionCache(), + NextProtos: []string{"h2"}, + } + + if v.option.ServerName != "" { + tlsOpts.Host = v.option.ServerName + } + + c, err = vmess.StreamTLSConn(c, &tlsOpts) + if err != nil { + return nil, err + } + + h2Opts := &vmess.H2Config{ + Hosts: v.option.HTTP2Opts.Host, + Path: v.option.HTTP2Opts.Path, + } + + c, err = vmess.StreamH2Conn(c, h2Opts) default: // handle TLS if v.option.TLS { @@ -171,6 +201,9 @@ func NewVmess(option VmessOption) (*Vmess, error) { if err != nil { return nil, err } + if option.Network == "h2" && !option.TLS { + return nil, fmt.Errorf("TLS must be true with h2 network") + } return &Vmess{ Base: &Base{ diff --git a/component/vmess/h2.go b/component/vmess/h2.go new file mode 100644 index 0000000000..b814ae9b5f --- /dev/null +++ b/component/vmess/h2.go @@ -0,0 +1,111 @@ +package vmess + +import ( + "io" + "math/rand" + "net" + "net/http" + "net/url" + + "golang.org/x/net/http2" +) + +type h2Conn struct { + net.Conn + *http2.ClientConn + pwriter *io.PipeWriter + res *http.Response + cfg *H2Config +} + +type H2Config struct { + Hosts []string + Path string +} + +func (hc *h2Conn) establishConn() error { + preader, pwriter := io.Pipe() + + host := hc.cfg.Hosts[rand.Intn(len(hc.cfg.Hosts))] + path := hc.cfg.Path + // TODO: connect use VMess Host instead of H2 Host + req := http.Request{ + Method: "PUT", + Host: host, + URL: &url.URL{ + Scheme: "https", + Host: host, + Path: path, + }, + Proto: "HTTP/2", + ProtoMajor: 2, + ProtoMinor: 0, + Body: preader, + Header: map[string][]string{ + "Accept-Encoding": {"identity"}, + }, + } + + res, err := hc.ClientConn.RoundTrip(&req) + if err != nil { + return err + } + + hc.pwriter = pwriter + hc.res = res + + return nil +} + +// Read implements net.Conn.Read() +func (hc *h2Conn) Read(b []byte) (int, error) { + if hc.res != nil && !hc.res.Close { + n, err := hc.res.Body.Read(b) + return n, err + } + + if err := hc.establishConn(); err != nil { + return 0, err + } + return hc.res.Body.Read(b) +} + +// Write implements io.Writer. +func (hc *h2Conn) Write(b []byte) (int, error) { + if hc.pwriter != nil { + return hc.pwriter.Write(b) + } + + if err := hc.establishConn(); err != nil { + return 0, err + } + return hc.pwriter.Write(b) +} + +func (hc *h2Conn) Close() error { + if err := hc.pwriter.Close(); err != nil { + return err + } + if err := hc.ClientConn.Shutdown(hc.res.Request.Context()); err != nil { + return err + } + if err := hc.Conn.Close(); err != nil { + return err + } + return nil +} + +func StreamH2Conn(conn net.Conn, cfg *H2Config) (net.Conn, error) { + transport := &http2.Transport{} + + cconn, err := transport.NewClientConn(conn) + if err != nil { + return nil, err + } + + return &h2Conn{ + Conn: conn, + ClientConn: cconn, + cfg: cfg, + }, nil +} diff --git a/component/vmess/tls.go b/component/vmess/tls.go index 8ed1977702..b003a75336 100644 --- a/component/vmess/tls.go +++ b/component/vmess/tls.go @@ -9,6 +9,7 @@ type TLSConfig struct { Host string SkipCertVerify bool SessionCache tls.ClientSessionCache + NextProtos []string } func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { @@ -16,6 +17,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { ServerName: cfg.Host, InsecureSkipVerify: cfg.SkipCertVerify, ClientSessionCache: cfg.SessionCache, + NextProtos: cfg.NextProtos, } tlsConn := tls.Client(conn, tlsConfig) diff --git a/go.sum b/go.sum index 8611148b39..9bded5cee8 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,7 @@ golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From e09931dcf7d70e786e15ab51872667e416788a20 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sat, 26 Sep 2020 20:36:52 +0800 Subject: [PATCH 482/535] Chore: remove broken test temporarily --- component/pool/pool_test.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/component/pool/pool_test.go b/component/pool/pool_test.go index 6fc8622d02..f03a1c9bbc 100644 --- a/component/pool/pool_test.go +++ b/component/pool/pool_test.go @@ -2,7 +2,6 @@ package pool import ( "context" - "runtime" "testing" "time" @@ -72,26 +71,3 @@ func TestPool_MaxAge(t *testing.T) { elm, _ = pool.Get() assert.Equal(t, 1, elm.(int)) } - -func TestPool_AutoGC(t *testing.T) { - g := lg() - - sign := make(chan int) - pool := New(g, WithEvict(func(item interface{}) { - sign <- item.(int) - })) - - elm, _ := pool.Get() - assert.Equal(t, 0, elm.(int)) - pool.Put(2) - pool = nil - - runtime.GC() - - select { - case num := <-sign: - assert.Equal(t, 2, num) - case <-time.After(time.Second * 3): - assert.Fail(t, "something wrong") - } -} From a6444bb449d034fb2e5daeaee193f9baf5787b02 Mon Sep 17 00:00:00 2001 From: Melvin Date: Mon, 28 Sep 2020 22:17:10 +0800 Subject: [PATCH 483/535] Feature: support domain in fallback filter (#964) --- config/config.go | 3 ++ dns/filters.go | 22 ++++++++++- dns/resolver.go | 82 +++++++++++++++++++++++++++++++--------- hub/executor/executor.go | 1 + 4 files changed, 89 insertions(+), 19 deletions(-) diff --git a/config/config.go b/config/config.go index b9e42dd426..9c39b7efd0 100644 --- a/config/config.go +++ b/config/config.go @@ -69,6 +69,7 @@ type DNS struct { type FallbackFilter struct { GeoIP bool `yaml:"geoip"` IPCIDR []*net.IPNet `yaml:"ipcidr"` + Domain []string `yaml:"domain"` } // Experimental config @@ -103,6 +104,7 @@ type RawDNS struct { type RawFallbackFilter struct { GeoIP bool `yaml:"geoip"` IPCIDR []string `yaml:"ipcidr"` + Domain []string `yaml:"domain"` } type RawConfig struct { @@ -561,6 +563,7 @@ func parseDNS(cfg RawDNS, hosts *trie.DomainTrie) (*DNS, error) { if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil { dnsCfg.FallbackFilter.IPCIDR = fallbackip } + dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain if cfg.UseHosts { dnsCfg.Hosts = hosts diff --git a/dns/filters.go b/dns/filters.go index 03089d4c8d..583883fabf 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -4,9 +4,10 @@ import ( "net" "github.com/Dreamacro/clash/component/mmdb" + "github.com/Dreamacro/clash/component/trie" ) -type fallbackFilter interface { +type fallbackIPFilter interface { Match(net.IP) bool } @@ -24,3 +25,22 @@ type ipnetFilter struct { func (inf *ipnetFilter) Match(ip net.IP) bool { return inf.ipnet.Contains(ip) } + +type fallbackDomainFilter interface { + Match(domain string) bool +} +type domainFilter struct { + tree *trie.DomainTrie +} + +func NewDomainFilter(domains []string) *domainFilter { + df := domainFilter{tree: trie.New()} + for _, domain := range domains { + df.tree.Insert(domain, "") + } + return &df +} + +func (df *domainFilter) Match(domain string) bool { + return df.tree.Search(domain) != nil +} diff --git a/dns/resolver.go b/dns/resolver.go index b7d20b5c61..93e3ca6ef0 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "net" + "strings" "time" "github.com/Dreamacro/clash/common/cache" @@ -34,13 +35,14 @@ type result struct { } type Resolver struct { - ipv6 bool - hosts *trie.DomainTrie - main []dnsClient - fallback []dnsClient - fallbackFilters []fallbackFilter - group singleflight.Group - lruCache *cache.LruCache + ipv6 bool + hosts *trie.DomainTrie + main []dnsClient + fallback []dnsClient + fallbackDomainFilters []fallbackDomainFilter + fallbackIPFilters []fallbackIPFilter + group singleflight.Group + lruCache *cache.LruCache } // ResolveIP request with TypeA and TypeAAAA, priority return TypeA @@ -78,8 +80,8 @@ func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) { return r.resolveIP(host, D.TypeAAAA) } -func (r *Resolver) shouldFallback(ip net.IP) bool { - for _, filter := range r.fallbackFilters { +func (r *Resolver) shouldIPFallback(ip net.IP) bool { + for _, filter := range r.fallbackIPFilters { if filter.Match(ip) { return true } @@ -126,7 +128,7 @@ func (r *Resolver) exchangeWithoutCache(m *D.Msg) (msg *D.Msg, err error) { isIPReq := isIPRequest(q) if isIPReq { - return r.fallbackExchange(m) + return r.ipExchange(m) } return r.batchExchange(r.main, m) @@ -170,19 +172,49 @@ func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err return } -func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) { +func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { + if r.fallback == nil || len(r.fallbackDomainFilters) == 0 { + return false + } + + domain := r.msgToDomain(m) + + if domain == "" { + return false + } + + for _, df := range r.fallbackDomainFilters { + if df.Match(domain) { + return true + } + } + + return false +} + +func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { + + onlyFallback := r.shouldOnlyQueryFallback(m) + + if onlyFallback { + res := <-r.asyncExchange(r.fallback, m) + return res.Msg, res.Error + } + msgCh := r.asyncExchange(r.main, m) - if r.fallback == nil { + + if r.fallback == nil { // directly return if no fallback servers are available res := <-msgCh msg, err = res.Msg, res.Error return } + fallbackMsg := r.asyncExchange(r.fallback, m) res := <-msgCh if res.Error == nil { if ips := r.msgToIP(res.Msg); len(ips) != 0 { - if !r.shouldFallback(ips[0]) { - msg = res.Msg + if !r.shouldIPFallback(ips[0]) { + msg = res.Msg // no need to wait for fallback result err = res.Error return msg, err } @@ -240,6 +272,14 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { return ips } +func (r *Resolver) msgToDomain(msg *D.Msg) string { + if len(msg.Question) > 0 { + return strings.TrimRight(msg.Question[0].Name, ".") + } + + return "" +} + func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result { ch := make(chan *result, 1) go func() { @@ -257,6 +297,7 @@ type NameServer struct { type FallbackFilter struct { GeoIP bool IPCIDR []*net.IPNet + Domain []string } type Config struct { @@ -286,14 +327,19 @@ func NewResolver(config Config) *Resolver { r.fallback = transform(config.Fallback, defaultResolver) } - fallbackFilters := []fallbackFilter{} + fallbackIPFilters := []fallbackIPFilter{} if config.FallbackFilter.GeoIP { - fallbackFilters = append(fallbackFilters, &geoipFilter{}) + fallbackIPFilters = append(fallbackIPFilters, &geoipFilter{}) } for _, ipnet := range config.FallbackFilter.IPCIDR { - fallbackFilters = append(fallbackFilters, &ipnetFilter{ipnet: ipnet}) + fallbackIPFilters = append(fallbackIPFilters, &ipnetFilter{ipnet: ipnet}) + } + r.fallbackIPFilters = fallbackIPFilters + + if len(config.FallbackFilter.Domain) != 0 { + fallbackDomainFilters := []fallbackDomainFilter{NewDomainFilter(config.FallbackFilter.Domain)} + r.fallbackDomainFilters = fallbackDomainFilters } - r.fallbackFilters = fallbackFilters return r } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index c24d6123f6..822b43e568 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -118,6 +118,7 @@ func updateDNS(c *config.DNS) { FallbackFilter: dns.FallbackFilter{ GeoIP: c.FallbackFilter.GeoIP, IPCIDR: c.FallbackFilter.IPCIDR, + Domain: c.FallbackFilter.Domain, }, Default: c.DefaultNameserver, } From d65b51c62b27f0358ee437f6e9f6b39852155ea2 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 2 Oct 2020 11:34:40 +0800 Subject: [PATCH 484/535] Feature: http support custom sni --- adapters/outbound/http.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adapters/outbound/http.go b/adapters/outbound/http.go index e005b329b8..840716b85c 100644 --- a/adapters/outbound/http.go +++ b/adapters/outbound/http.go @@ -31,6 +31,7 @@ type HttpOption struct { UserName string `proxy:"username,omitempty"` Password string `proxy:"password,omitempty"` TLS bool `proxy:"tls,omitempty"` + SNI string `proxy:"sni,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` } @@ -114,10 +115,14 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func NewHttp(option HttpOption) *Http { var tlsConfig *tls.Config if option.TLS { + sni := option.Server + if option.SNI != "" { + sni = option.SNI + } tlsConfig = &tls.Config{ InsecureSkipVerify: option.SkipCertVerify, ClientSessionCache: getClientSessionCache(), - ServerName: option.Server, + ServerName: sni, } } From 4859b158b4ee46d0ba3fb08bd7580ddb71f09fc1 Mon Sep 17 00:00:00 2001 From: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Date: Thu, 8 Oct 2020 17:54:38 +0800 Subject: [PATCH 485/535] Chore: make builds reproducible (#1006) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 16e1cb4c35..e1a913f95b 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ VERSION=$(shell git describe --tags || echo "unknown version") BUILDTIME=$(shell date -u) GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \ -X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \ - -w -s' + -w -s -buildid=' PLATFORM_LIST = \ darwin-amd64 \ From d3b14c325f4487f57a3e2e8c7dca73e1a937ccdc Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 9 Oct 2020 00:04:24 +0800 Subject: [PATCH 486/535] Fix: the priority of fake-ip-filter --- dns/middleware.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dns/middleware.go b/dns/middleware.go index 8aff0647ff..e14bd13a33 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -102,6 +102,11 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { return func(r *D.Msg) (*D.Msg, error) { q := r.Question[0] + host := strings.TrimRight(q.Name, ".") + if fakePool.LookupHost(host) { + return next(r) + } + if q.Qtype == D.TypeAAAA { msg := &D.Msg{} msg.Answer = []D.RR{} @@ -115,11 +120,6 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { return next(r) } - host := strings.TrimRight(q.Name, ".") - if fakePool.LookupHost(host) { - return next(r) - } - rr := &D.A{} rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: dnsDefaultTTL} ip := fakePool.Lookup(host) From bc52f8e4fda00b097009825369a8ea29325b6a3c Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 13 Oct 2020 00:15:49 +0800 Subject: [PATCH 487/535] Chore: return empty record in SVCB/HTTPSSVC on fake-ip mode --- dns/middleware.go | 23 ++++++----------------- dns/util.go | 11 +++++++++++ go.mod | 12 ++++++------ go.sum | 33 +++++++++++++++++---------------- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/dns/middleware.go b/dns/middleware.go index e14bd13a33..9a119e7873 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -107,16 +107,12 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { return next(r) } - if q.Qtype == D.TypeAAAA { - msg := &D.Msg{} - msg.Answer = []D.RR{} - - msg.SetRcode(r, D.RcodeSuccess) - msg.Authoritative = true - msg.RecursionAvailable = true + switch q.Qtype { + case D.TypeAAAA, D.TypeSVCB, D.TypeHTTPS: + return handleMsgWithEmptyAnswer(r), nil + } - return msg, nil - } else if q.Qtype != D.TypeA { + if q.Qtype != D.TypeA { return next(r) } @@ -143,14 +139,7 @@ func withResolver(resolver *Resolver) handler { // return a empty AAAA msg when ipv6 disabled if !resolver.ipv6 && q.Qtype == D.TypeAAAA { - msg := &D.Msg{} - msg.Answer = []D.RR{} - - msg.SetRcode(r, D.RcodeSuccess) - msg.Authoritative = true - msg.RecursionAvailable = true - - return msg, nil + return handleMsgWithEmptyAnswer(r), nil } msg, err := resolver.Exchange(r) diff --git a/dns/util.go b/dns/util.go index 23c5b12fcd..55a280dbaf 100644 --- a/dns/util.go +++ b/dns/util.go @@ -142,3 +142,14 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { } return ret } + +func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg { + msg := &D.Msg{} + msg.Answer = []D.RR{} + + msg.SetRcode(r, D.RcodeSuccess) + msg.Authoritative = true + msg.RecursionAvailable = true + + return msg +} diff --git a/go.mod b/go.mod index f1f85e526f..3a3c67f78b 100644 --- a/go.mod +++ b/go.mod @@ -10,14 +10,14 @@ require ( github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.3.0+incompatible github.com/gorilla/websocket v1.4.2 - github.com/miekg/dns v1.1.31 + github.com/miekg/dns v1.1.32 github.com/oschwald/geoip2-golang v1.4.0 - github.com/sirupsen/logrus v1.6.0 + github.com/sirupsen/logrus v1.7.0 github.com/stretchr/testify v1.6.1 - golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de - golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc - golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 - golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed + golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 + golang.org/x/net v0.0.0-20201010224723-4f7140c49acb + golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 + golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 gopkg.in/eapache/channels.v1 v1.1.0 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 9bded5cee8..5c7408a4b3 100644 --- a/go.sum +++ b/go.sum @@ -15,18 +15,16 @@ github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6 github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= -github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.32 h1:MDaYYzWOYscpvDOEgPMT1c1mebCZmIdxZI/J161OdJU= +github.com/miekg/dns v1.1.32/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= 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/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -36,27 +34,30 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc h1:zK/HqS5bZxDptfPJNq8v7vJfXtkU7r9TLIoSr1bXaP4= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 h1:Bx6FllMpG4NWDOfhMBz1VR2QYNp/SAOHPIAsaVmxfPo= +golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 8c3e2a7559fc16203348f5a77b5be9a93646911c Mon Sep 17 00:00:00 2001 From: kongminhao <353213666@qq.com> Date: Wed, 14 Oct 2020 19:56:02 +0800 Subject: [PATCH 488/535] Chore: fix typo (#1017) --- common/cache/lrucache.go | 2 +- common/pool/alloc.go | 2 +- common/singledo/singledo.go | 4 ++-- config/utils.go | 4 ++-- constant/adapters.go | 2 +- hub/route/configs.go | 2 +- proxy/redir/utils.go | 2 +- proxy/socks/utils.go | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index a2e0e90350..4269d86b33 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -121,7 +121,7 @@ func (c *LruCache) Set(key interface{}, value interface{}) { c.SetWithExpire(key, value, time.Unix(expires, 0)) } -// SetWithExpire stores the interface{} representation of a response for a given key and given exires. +// SetWithExpire stores the interface{} representation of a response for a given key and given expires. // The expires time will round to second. func (c *LruCache) SetWithExpire(key interface{}, value interface{}, expires time.Time) { c.mu.Lock() diff --git a/common/pool/alloc.go b/common/pool/alloc.go index 4f26c03647..bf177da886 100644 --- a/common/pool/alloc.go +++ b/common/pool/alloc.go @@ -61,7 +61,7 @@ func (alloc *Allocator) Put(buf []byte) error { return nil } -// msb return the pos of most significiant bit +// msb return the pos of most significant bit func msb(size int) uint16 { return uint16(bits.Len32(uint32(size)) - 1) } diff --git a/common/singledo/singledo.go b/common/singledo/singledo.go index e592343be8..f6123fe4c2 100644 --- a/common/singledo/singledo.go +++ b/common/singledo/singledo.go @@ -24,8 +24,8 @@ type Result struct { Err error } -// Do single.Do likes sync.singleFilght -//lint:ignore ST1008 it likes sync.singleFilght +// Do single.Do likes sync.singleFlight +//lint:ignore ST1008 it likes sync.singleFlight func (s *Single) Do(fn func() (interface{}, error)) (v interface{}, err error, shared bool) { s.mux.Lock() now := time.Now() diff --git a/config/utils.go b/config/utils.go index 74af4a6490..b3d198f7f1 100644 --- a/config/utils.go +++ b/config/utils.go @@ -23,7 +23,7 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { indegree int // topological order topo int - // the origional data in `groupsConfig` + // the original data in `groupsConfig` data map[string]interface{} // `outdegree` and `from` are used in loop locating outdegree int @@ -65,7 +65,7 @@ func proxyGroupsDagSort(groupsConfig []map[string]interface{}) error { index := 0 queue := make([]string, 0) for name, node := range graph { - // in the begning, put nodes that have `node.indegree == 0` into queue. + // in the beginning, put nodes that have `node.indegree == 0` into queue. if node.indegree == 0 { queue = append(queue, name) } diff --git a/constant/adapters.go b/constant/adapters.go index a12975bac2..4ba891deed 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -137,7 +137,7 @@ type UDPPacket interface { // WriteBack writes the payload with source IP/Port equals addr // - variable source IP/Port is important to STUN - // - if addr is not provided, WriteBack will wirte out UDP packet with SourceIP/Prot equals to origional Target, + // - if addr is not provided, WriteBack will write out UDP packet with SourceIP/Port equals to original Target, // this is important when using Fake-IP. WriteBack(b []byte, addr net.Addr) (n int, err error) diff --git a/hub/route/configs.go b/hub/route/configs.go index c551d66729..581f28558d 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -106,7 +106,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } else { if !filepath.IsAbs(req.Path) { render.Status(r, http.StatusBadRequest) - render.JSON(w, r, newError("path is not a absoluted path")) + render.JSON(w, r, newError("path is not a absolute path")) return } diff --git a/proxy/redir/utils.go b/proxy/redir/utils.go index 46e39b8abd..58e30b0ced 100644 --- a/proxy/redir/utils.go +++ b/proxy/redir/utils.go @@ -15,7 +15,7 @@ func (c *packet) Data() []byte { return c.buf } -// WriteBack opens a new socket binding `addr` to wirte UDP packet back +// WriteBack opens a new socket binding `addr` to write UDP packet back func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { tc, err := dialUDP("udp", addr.(*net.UDPAddr), c.lAddr) if err != nil { diff --git a/proxy/socks/utils.go b/proxy/socks/utils.go index d3ab3b7819..797e0a2207 100644 --- a/proxy/socks/utils.go +++ b/proxy/socks/utils.go @@ -18,7 +18,7 @@ func (c *packet) Data() []byte { return c.payload } -// WriteBack wirtes UDP packet with source(ip, port) = `addr` +// WriteBack write UDP packet with source(ip, port) = `addr` func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b) if err != nil { From d3bb4c65a894bbd641ba5c9f0ce87f4281c4aa60 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sat, 17 Oct 2020 12:52:43 +0800 Subject: [PATCH 489/535] Fix: missing fake-ip record should return error --- component/fakeip/pool.go | 7 +++++++ component/resolver/enhancer.go | 9 +++++++++ dns/enhancer.go | 14 +++++++++++++- tunnel/tunnel.go | 2 +- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/component/fakeip/pool.go b/component/fakeip/pool.go index 46fe59274b..97d812ac3f 100644 --- a/component/fakeip/pool.go +++ b/component/fakeip/pool.go @@ -17,6 +17,7 @@ type Pool struct { offset uint32 mux sync.Mutex host *trie.DomainTrie + ipnet *net.IPNet cache *cache.LruCache } @@ -89,6 +90,11 @@ func (p *Pool) Gateway() net.IP { return uintToIP(p.gateway) } +// IPNet return raw ipnet +func (p *Pool) IPNet() *net.IPNet { + return p.ipnet +} + // PatchFrom clone cache from old pool func (p *Pool) PatchFrom(o *Pool) { o.cache.CloneTo(p.cache) @@ -141,6 +147,7 @@ func New(ipnet *net.IPNet, size int, host *trie.DomainTrie) (*Pool, error) { max: max, gateway: min - 1, host: host, + ipnet: ipnet, cache: cache.NewLRUCache(cache.WithSize(size * 2)), }, nil } diff --git a/component/resolver/enhancer.go b/component/resolver/enhancer.go index 48b49bea6a..c096f87a39 100644 --- a/component/resolver/enhancer.go +++ b/component/resolver/enhancer.go @@ -10,6 +10,7 @@ type Enhancer interface { FakeIPEnabled() bool MappingEnabled() bool IsFakeIP(net.IP) bool + IsExistFakeIP(net.IP) bool FindHostByIP(net.IP) (string, bool) } @@ -37,6 +38,14 @@ func IsFakeIP(ip net.IP) bool { return false } +func IsExistFakeIP(ip net.IP) bool { + if mapper := DefaultHostMapper; mapper != nil { + return mapper.IsExistFakeIP(ip) + } + + return false +} + func FindHostByIP(ip net.IP) (string, bool) { if mapper := DefaultHostMapper; mapper != nil { return mapper.FindHostByIP(ip) diff --git a/dns/enhancer.go b/dns/enhancer.go index 5018affa16..0b2570059d 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -21,7 +21,7 @@ func (h *ResolverEnhancer) MappingEnabled() bool { return h.mode == FAKEIP || h.mode == MAPPING } -func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { +func (h *ResolverEnhancer) IsExistFakeIP(ip net.IP) bool { if !h.FakeIPEnabled() { return false } @@ -33,6 +33,18 @@ func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { return false } +func (h *ResolverEnhancer) IsFakeIP(ip net.IP) bool { + if !h.FakeIPEnabled() { + return false + } + + if pool := h.fakePool; pool != nil { + return pool.IPNet().Contains(ip) && !pool.Gateway().Equal(ip) + } + + return false +} + func (h *ResolverEnhancer) FindHostByIP(ip net.IP) (string, bool) { if pool := h.fakePool; pool != nil { if host, existed := pool.LookBack(ip); existed { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index b44411160e..c645ce0827 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -170,7 +170,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { // make a fAddr if requset ip is fakeip var fAddr net.Addr - if resolver.IsFakeIP(metadata.DstIP) { + if resolver.IsExistFakeIP(metadata.DstIP) { fAddr = metadata.UDPAddr() } From baabf21340285747313626201065217e94d4995d Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sat, 17 Oct 2020 13:46:05 +0800 Subject: [PATCH 490/535] Chore: update github workflow --- .github/workflows/docker.yml | 56 ++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b6212dfc30..6c15c03f01 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,36 +17,50 @@ jobs: with: fetch-depth: 0 + - name: Set up QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: all + - name: Set up docker buildx id: buildx - uses: crazy-max/ghaction-docker-buildx@v3 + uses: docker/setup-buildx-action@v1 + with: + version: latest + + - name: Login to DockerHub + uses: docker/login-action@v1 with: - buildx-version: latest - qemu-version: latest - - - name: Docker login - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: | - echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin - - - name: Docker buildx image and push on dev branch + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build dev branch and push if: github.ref == 'refs/heads/dev' - run: | - docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:dev . + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/amd64,linux/arm/v7,linux/arm64 + push: true + tags: 'dreamacro/clash:dev' - - name: Replace tag without `v` + - name: Get all docker tags if: startsWith(github.ref, 'refs/tags/') uses: actions/github-script@v3 - id: version + id: tags with: script: | - return context.payload.ref.replace(/\/?refs\/tags\/v/, '') + const tags = [ + 'dreamacro/clash:latest', + `dreamacro/clash:${context.payload.ref.replace(/\/?refs\/tags\//, '')}` + ] + return tags.join(',') result-encoding: string - - name: Docker buildx image and push on release + - name: Build release and push if: startsWith(github.ref, 'refs/tags/') - run: | - docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:${{steps.version.outputs.result}} . - docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag dreamacro/clash:latest . + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/amd64,linux/arm/v7,linux/arm64 + push: true + tags: ${{steps.tags.outputs.result}} From 2321e9139d28d56cc4009e63e67aa69b621faca9 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 20 Oct 2020 17:44:39 +0800 Subject: [PATCH 491/535] Chore: deprecated eapache/channels --- common/observable/observable_test.go | 31 ++++++++++++++++++++++++++++ common/observable/subscriber.go | 12 +++++------ go.mod | 12 +++++------ go.sum | 20 +++++++----------- tunnel/tunnel.go | 20 +++++++----------- 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index 67e2934131..6dd6ee4221 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -113,3 +113,34 @@ func TestObservable_SubscribeGoroutineLeak(t *testing.T) { _, more := <-list[0] assert.False(t, more) } + +func Benchmark_Observable_1000(b *testing.B) { + ch := make(chan interface{}) + o := NewObservable(ch) + num := 1000 + + subs := []Subscription{} + for i := 0; i < num; i++ { + sub, _ := o.Subscribe() + subs = append(subs, sub) + } + + wg := sync.WaitGroup{} + wg.Add(num) + + b.ResetTimer() + for _, sub := range subs { + go func(s Subscription) { + for range s { + } + wg.Done() + }(sub) + } + + for i := 0; i < b.N; i++ { + ch <- i + } + + close(ch) + wg.Wait() +} diff --git a/common/observable/subscriber.go b/common/observable/subscriber.go index 3fb1e587d2..cb2a70f42e 100644 --- a/common/observable/subscriber.go +++ b/common/observable/subscriber.go @@ -2,34 +2,32 @@ package observable import ( "sync" - - "gopkg.in/eapache/channels.v1" ) type Subscription <-chan interface{} type Subscriber struct { - buffer *channels.InfiniteChannel + buffer chan interface{} once sync.Once } func (s *Subscriber) Emit(item interface{}) { - s.buffer.In() <- item + s.buffer <- item } func (s *Subscriber) Out() Subscription { - return s.buffer.Out() + return s.buffer } func (s *Subscriber) Close() { s.once.Do(func() { - s.buffer.Close() + close(s.buffer) }) } func newSubscriber() *Subscriber { sub := &Subscriber{ - buffer: channels.NewInfiniteChannel(), + buffer: make(chan interface{}, 200), } return sub } diff --git a/go.mod b/go.mod index 3a3c67f78b..0b62577de2 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,21 @@ module github.com/Dreamacro/clash -go 1.14 +go 1.15 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.6 - github.com/eapache/queue v1.1.0 // indirect github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.1.1 github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.3.0+incompatible github.com/gorilla/websocket v1.4.2 - github.com/miekg/dns v1.1.32 + github.com/miekg/dns v1.1.34 github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.7.0 github.com/stretchr/testify v1.6.1 - golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 - golang.org/x/net v0.0.0-20201010224723-4f7140c49acb + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 + golang.org/x/net v0.0.0-20201020065357-d65d470038a5 golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 - golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 - gopkg.in/eapache/channels.v1 v1.1.0 + golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 5c7408a4b3..1495863c62 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,6 @@ github.com/Dreamacro/go-shadowsocks2 v0.1.6/go.mod h1:LSXCjyHesPY3pLjhwff1mQX72I 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/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= @@ -15,8 +13,8 @@ github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6 github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/miekg/dns v1.1.32 h1:MDaYYzWOYscpvDOEgPMT1c1mebCZmIdxZI/J161OdJU= -github.com/miekg/dns v1.1.32/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.34 h1:SgTzfkN+oLoIHF1bgUP+C71mzuDl3AhLApHzCCIAMWM= +github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= @@ -34,14 +32,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201020065357-d65d470038a5 h1:KrxvpY64uUzANd9wKWr6ZAsufiii93XnvXaeikyCJ2g= +golang.org/x/net v0.0.0-20201020065357-d65d470038a5/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 h1:Bx6FllMpG4NWDOfhMBz1VR2QYNp/SAOHPIAsaVmxfPo= golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -51,8 +49,8 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 h1:5jaG59Zhd+8ZXe8C+lgiAGqkOaZBruqrWclLkgAww34= +golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= @@ -62,8 +60,6 @@ golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapK golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/eapache/channels.v1 v1.1.0 h1:5bGAyKKvyCTWjSj7mhefG6Lc68VyN4MH1v8/7OoeeB4= -gopkg.in/eapache/channels.v1 v1.1.0/go.mod h1:BHIBujSvu9yMTrTYbTCjDD43gUhtmaOtTWDe7sTv1js= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index c645ce0827..19d9f8b258 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -13,13 +13,11 @@ import ( "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" - - channels "gopkg.in/eapache/channels.v1" ) var ( - tcpQueue = channels.NewInfiniteChannel() - udpQueue = channels.NewInfiniteChannel() + tcpQueue = make(chan C.ServerAdapter, 200) + udpQueue = make(chan *inbound.PacketAdapter, 200) natTable = nat.New() rules []C.Rule proxies = make(map[string]C.Proxy) @@ -39,12 +37,12 @@ func init() { // Add request to queue func Add(req C.ServerAdapter) { - tcpQueue.In() <- req + tcpQueue <- req } // AddPacket add udp Packet to queue func AddPacket(packet *inbound.PacketAdapter) { - udpQueue.In() <- packet + udpQueue <- packet } // Rules return all rules @@ -89,9 +87,8 @@ func SetMode(m TunnelMode) { // processUDP starts a loop to handle udp packet func processUDP() { - queue := udpQueue.Out() - for elm := range queue { - conn := elm.(*inbound.PacketAdapter) + queue := udpQueue + for conn := range queue { handleUDPConn(conn) } } @@ -105,9 +102,8 @@ func process() { go processUDP() } - queue := tcpQueue.Out() - for elm := range queue { - conn := elm.(C.ServerAdapter) + queue := tcpQueue + for conn := range queue { go handleTCPConn(conn) } } From 50b3d497f6ff89ac05aa7d648ced801c1d6b8a8e Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 22 Oct 2020 00:11:49 +0800 Subject: [PATCH 492/535] Feature: use native syscall to bind interface on Linux and macOS --- component/dialer/bind.go | 104 ++++++++++++++++++++++++++ component/dialer/bind_darwin.go | 46 ++++++++++++ component/dialer/bind_linux.go | 26 +++++++ component/dialer/bind_others.go | 13 ++++ component/dialer/dialer.go | 26 ++----- component/dialer/hook.go | 125 +++----------------------------- 6 files changed, 205 insertions(+), 135 deletions(-) create mode 100644 component/dialer/bind.go create mode 100644 component/dialer/bind_darwin.go create mode 100644 component/dialer/bind_linux.go create mode 100644 component/dialer/bind_others.go diff --git a/component/dialer/bind.go b/component/dialer/bind.go new file mode 100644 index 0000000000..cb24a8b625 --- /dev/null +++ b/component/dialer/bind.go @@ -0,0 +1,104 @@ +package dialer + +import ( + "errors" + "net" +) + +var ( + errPlatformNotSupport = errors.New("unsupport platform") +) + +func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) { + ipv4 := ip.To4() != nil + + for _, elm := range addrs { + addr, ok := elm.(*net.IPNet) + if !ok { + continue + } + + addrV4 := addr.IP.To4() != nil + + if addrV4 && ipv4 { + return &net.TCPAddr{IP: addr.IP, Port: 0}, nil + } else if !addrV4 && !ipv4 { + return &net.TCPAddr{IP: addr.IP, Port: 0}, nil + } + } + + return nil, ErrAddrNotFound +} + +func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { + ipv4 := ip.To4() != nil + + for _, elm := range addrs { + addr, ok := elm.(*net.IPNet) + if !ok { + continue + } + + addrV4 := addr.IP.To4() != nil + + if addrV4 && ipv4 { + return &net.UDPAddr{IP: addr.IP, Port: 0}, nil + } else if !addrV4 && !ipv4 { + return &net.UDPAddr{IP: addr.IP, Port: 0}, nil + } + } + + return nil, ErrAddrNotFound +} + +func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + + addrs, err := iface.Addrs() + if err != nil { + return err + } + + switch network { + case "tcp", "tcp4", "tcp6": + if addr, err := lookupTCPAddr(ip, addrs); err == nil { + dialer.LocalAddr = addr + } else { + return err + } + case "udp", "udp4", "udp6": + if addr, err := lookupUDPAddr(ip, addrs); err == nil { + dialer.LocalAddr = addr + } else { + return err + } + } + + return nil +} + +func fallbackBindToListenConfig(name string) (string, error) { + iface, err := net.InterfaceByName(name) + if err != nil { + return "", err + } + + addrs, err := iface.Addrs() + if err != nil { + return "", err + } + + for _, elm := range addrs { + addr, ok := elm.(*net.IPNet) + if !ok || addr.IP.To4() == nil { + continue + } + + return net.JoinHostPort(addr.IP.String(), "0"), nil + } + + return "", ErrAddrNotFound +} diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go new file mode 100644 index 0000000000..d46c6735b2 --- /dev/null +++ b/component/dialer/bind_darwin.go @@ -0,0 +1,46 @@ +package dialer + +import ( + "net" + "syscall" +) + +func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return err + } + + dialer.Control = func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + switch network { + case "tcp4", "udp4": + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, iface.Index) + case "tcp6", "udp6": + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, iface.Index) + } + }) + } + + return nil +} + +func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return err + } + + lc.Control = func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + switch network { + case "tcp4", "udp4": + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, iface.Index) + case "tcp6", "udp6": + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, iface.Index) + } + }) + } + + return nil +} diff --git a/component/dialer/bind_linux.go b/component/dialer/bind_linux.go new file mode 100644 index 0000000000..8afa3d382f --- /dev/null +++ b/component/dialer/bind_linux.go @@ -0,0 +1,26 @@ +package dialer + +import ( + "net" + "syscall" +) + +func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { + dialer.Control = func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + syscall.BindToDevice(int(fd), ifaceName) + }) + } + + return nil +} + +func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { + lc.Control = func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + syscall.BindToDevice(int(fd), ifaceName) + }) + } + + return nil +} diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go new file mode 100644 index 0000000000..87bb47dff9 --- /dev/null +++ b/component/dialer/bind_others.go @@ -0,0 +1,13 @@ +// +build !linux,!darwin + +package dialer + +import "net" + +func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { + return errNotSupport +} + +func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { + return errNotSupport +} diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index b0be7a6d1a..be26681a7c 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -19,17 +19,6 @@ func Dialer() (*net.Dialer, error) { return dialer, nil } -func ListenConfig() (*net.ListenConfig, error) { - cfg := &net.ListenConfig{} - if ListenConfigHook != nil { - if err := ListenConfigHook(cfg); err != nil { - return nil, err - } - } - - return cfg, nil -} - func Dial(network, address string) (net.Conn, error) { return DialContext(context.Background(), network, address) } @@ -73,19 +62,16 @@ func DialContext(ctx context.Context, network, address string) (net.Conn, error) } func ListenPacket(network, address string) (net.PacketConn, error) { - lc, err := ListenConfig() - if err != nil { - return nil, err - } - - if ListenPacketHook != nil && address == "" { - ip, err := ListenPacketHook() + cfg := &net.ListenConfig{} + if ListenPacketHook != nil { + var err error + address, err = ListenPacketHook(cfg, address) if err != nil { return nil, err } - address = net.JoinHostPort(ip.String(), "0") } - return lc.ListenPacket(context.Background(), network, address) + + return cfg.ListenPacket(context.Background(), network, address) } func dualStackDialContext(ctx context.Context, network, address string) (net.Conn, error) { diff --git a/component/dialer/hook.go b/component/dialer/hook.go index d4c955ab80..356e4b25f7 100644 --- a/component/dialer/hook.go +++ b/component/dialer/hook.go @@ -3,20 +3,15 @@ package dialer import ( "errors" "net" - "time" - - "github.com/Dreamacro/clash/common/singledo" ) type DialerHookFunc = func(dialer *net.Dialer) error type DialHookFunc = func(dialer *net.Dialer, network string, ip net.IP) error -type ListenConfigHookFunc = func(*net.ListenConfig) error -type ListenPacketHookFunc = func() (net.IP, error) +type ListenPacketHookFunc = func(lc *net.ListenConfig, address string) (string, error) var ( DialerHook DialerHookFunc DialHook DialHookFunc - ListenConfigHook ListenConfigHookFunc ListenPacketHook ListenPacketHookFunc ) @@ -25,124 +20,24 @@ var ( ErrNetworkNotSupport = errors.New("network not support") ) -func lookupTCPAddr(ip net.IP, addrs []net.Addr) (*net.TCPAddr, error) { - ipv4 := ip.To4() != nil - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok { - continue - } - - addrV4 := addr.IP.To4() != nil - - if addrV4 && ipv4 { - return &net.TCPAddr{IP: addr.IP, Port: 0}, nil - } else if !addrV4 && !ipv4 { - return &net.TCPAddr{IP: addr.IP, Port: 0}, nil - } - } - - return nil, ErrAddrNotFound -} - -func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { - ipv4 := ip.To4() != nil - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok { - continue - } - - addrV4 := addr.IP.To4() != nil - - if addrV4 && ipv4 { - return &net.UDPAddr{IP: addr.IP, Port: 0}, nil - } else if !addrV4 && !ipv4 { - return &net.UDPAddr{IP: addr.IP, Port: 0}, nil - } - } - - return nil, ErrAddrNotFound -} - func ListenPacketWithInterface(name string) ListenPacketHookFunc { - single := singledo.NewSingle(5 * time.Second) - - return func() (net.IP, error) { - elm, err, _ := single.Do(func() (interface{}, error) { - iface, err := net.InterfaceByName(name) - if err != nil { - return nil, err - } - - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - return addrs, nil - }) - - if err != nil { - return nil, err + return func(lc *net.ListenConfig, address string) (string, error) { + err := bindIfaceToListenConfig(lc, name) + if err == errPlatformNotSupport { + address, err = fallbackBindToListenConfig(name) } - addrs := elm.([]net.Addr) - - for _, elm := range addrs { - addr, ok := elm.(*net.IPNet) - if !ok || addr.IP.To4() == nil { - continue - } - - return addr.IP, nil - } - - return nil, ErrAddrNotFound + return address, err } } func DialerWithInterface(name string) DialHookFunc { - single := singledo.NewSingle(5 * time.Second) - return func(dialer *net.Dialer, network string, ip net.IP) error { - elm, err, _ := single.Do(func() (interface{}, error) { - iface, err := net.InterfaceByName(name) - if err != nil { - return nil, err - } - - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - return addrs, nil - }) - - if err != nil { - return err - } - - addrs := elm.([]net.Addr) - - switch network { - case "tcp", "tcp4", "tcp6": - if addr, err := lookupTCPAddr(ip, addrs); err == nil { - dialer.LocalAddr = addr - } else { - return err - } - case "udp", "udp4", "udp6": - if addr, err := lookupUDPAddr(ip, addrs); err == nil { - dialer.LocalAddr = addr - } else { - return err - } + err := bindIfaceToDialer(dialer, name) + if err == errPlatformNotSupport { + err = fallbackBindToDialer(dialer, network, ip, name) } - return nil + return err } } From 2db4ce57ef237f917181b2afccc6b97547129e42 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 23 Oct 2020 00:30:17 +0800 Subject: [PATCH 493/535] Chore: make stale time into 60 days --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 17ca678936..c94c5e9ef4 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -15,5 +15,5 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' - days-before-stale: 120 + days-before-stale: 60 days-before-close: 5 From 76c9820065a2cda16dd3988736f9b882eaae47c0 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 23 Oct 2020 17:49:34 +0800 Subject: [PATCH 494/535] Fix: undefined variable --- component/dialer/bind_others.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go index 87bb47dff9..be30bae848 100644 --- a/component/dialer/bind_others.go +++ b/component/dialer/bind_others.go @@ -5,9 +5,9 @@ package dialer import "net" func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - return errNotSupport + return errPlatformNotSupport } func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - return errNotSupport + return errPlatformNotSupport } From b1795b1e3d13a2c8898fc457b7b8b4147b41f4ee Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 25 Oct 2020 11:53:03 +0800 Subject: [PATCH 495/535] Fix: stale typo --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index c94c5e9ef4..7581fc4033 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -14,6 +14,6 @@ jobs: - uses: actions/stale@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' + stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' days-before-stale: 60 days-before-close: 5 From ba060bd0eee01c5a537311591aca7dea5c7f1fca Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 25 Oct 2020 20:31:01 +0800 Subject: [PATCH 496/535] Fix: should not bind interface on local address --- component/dialer/bind_darwin.go | 41 ++++++++++++++++++--------------- component/dialer/bind_linux.go | 24 +++++++++++++------ 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go index d46c6735b2..469054ca1c 100644 --- a/component/dialer/bind_darwin.go +++ b/component/dialer/bind_darwin.go @@ -5,22 +5,36 @@ import ( "syscall" ) -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - iface, err := net.InterfaceByName(ifaceName) - if err != nil { - return err - } +type controlFn = func(network, address string, c syscall.RawConn) error + +func bindControl(ifaceIdx int) controlFn { + return func(network, address string, c syscall.RawConn) error { + ipStr, _, err := net.SplitHostPort(address) + if err == nil { + ip := net.ParseIP(ipStr) + if ip != nil && !ip.IsGlobalUnicast() { + return nil + } + } - dialer.Control = func(network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { switch network { case "tcp4", "udp4": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, iface.Index) + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx) case "tcp6", "udp6": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, iface.Index) + syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx) } }) } +} + +func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + return err + } + + dialer.Control = bindControl(iface.Index) return nil } @@ -31,16 +45,7 @@ func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { return err } - lc.Control = func(network, address string, c syscall.RawConn) error { - return c.Control(func(fd uintptr) { - switch network { - case "tcp4", "udp4": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, iface.Index) - case "tcp6", "udp6": - syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, iface.Index) - } - }) - } + lc.Control = bindControl(iface.Index) return nil } diff --git a/component/dialer/bind_linux.go b/component/dialer/bind_linux.go index 8afa3d382f..7e9d308ddd 100644 --- a/component/dialer/bind_linux.go +++ b/component/dialer/bind_linux.go @@ -5,22 +5,32 @@ import ( "syscall" ) -func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - dialer.Control = func(network, address string, c syscall.RawConn) error { +type controlFn = func(network, address string, c syscall.RawConn) error + +func bindControl(ifaceName string) controlFn { + return func(network, address string, c syscall.RawConn) error { + ipStr, _, err := net.SplitHostPort(address) + if err == nil { + ip := net.ParseIP(ipStr) + if ip != nil && !ip.IsGlobalUnicast() { + return nil + } + } + return c.Control(func(fd uintptr) { syscall.BindToDevice(int(fd), ifaceName) }) } +} + +func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { + dialer.Control = bindControl(ifaceName) return nil } func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - lc.Control = func(network, address string, c syscall.RawConn) error { - return c.Control(func(fd uintptr) { - syscall.BindToDevice(int(fd), ifaceName) - }) - } + lc.Control = bindControl(ifaceName) return nil } From 2cd1b890ce211ab7bd54857099a123c2ba840ab5 Mon Sep 17 00:00:00 2001 From: Jason Lyu Date: Wed, 28 Oct 2020 21:26:50 +0800 Subject: [PATCH 497/535] Fix: tunnel UDP race condition (#1043) --- component/nat/table.go | 6 +-- tunnel/tunnel.go | 90 +++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/component/nat/table.go b/component/nat/table.go index 9a8696b737..fbb16deca2 100644 --- a/component/nat/table.go +++ b/component/nat/table.go @@ -22,9 +22,9 @@ func (t *Table) Get(key string) C.PacketConn { return item.(C.PacketConn) } -func (t *Table) GetOrCreateLock(key string) (*sync.WaitGroup, bool) { - item, loaded := t.mapping.LoadOrStore(key, &sync.WaitGroup{}) - return item.(*sync.WaitGroup), loaded +func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { + item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{})) + return item.(*sync.Cond), loaded } func (t *Table) Delete(key string) { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 19d9f8b258..2e7b96967b 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -164,7 +164,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { return } - // make a fAddr if requset ip is fakeip + // make a fAddr if request ip is fakeip var fAddr net.Addr if resolver.IsExistFakeIP(metadata.DstIP) { fAddr = metadata.UDPAddr() @@ -176,57 +176,65 @@ func handleUDPConn(packet *inbound.PacketAdapter) { } key := packet.LocalAddr().String() - pc := natTable.Get(key) - if pc != nil { - handleUDPToRemote(packet, pc, metadata) + + handle := func() bool { + pc := natTable.Get(key) + if pc != nil { + handleUDPToRemote(packet, pc, metadata) + return true + } + return false + } + + if handle() { return } lockKey := key + "-lock" - wg, loaded := natTable.GetOrCreateLock(lockKey) + cond, loaded := natTable.GetOrCreateLock(lockKey) go func() { - if !loaded { - wg.Add(1) - proxy, rule, err := resolveMetadata(metadata) - if err != nil { - log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) - natTable.Delete(lockKey) - wg.Done() - return - } - - rawPc, err := proxy.DialUDP(metadata) - if err != nil { - log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error()) - natTable.Delete(lockKey) - wg.Done() - return - } - pc = newUDPTracker(rawPc, DefaultManager, metadata, rule) - - switch true { - case rule != nil: - log.Infoln("[UDP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) - case mode == Global: - log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) - case mode == Direct: - log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) - default: - log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) - } + if loaded { + cond.L.Lock() + cond.Wait() + handle() + cond.L.Unlock() + return + } - natTable.Set(key, pc) + defer func() { natTable.Delete(lockKey) - wg.Done() - go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) + cond.Broadcast() + }() + + proxy, rule, err := resolveMetadata(metadata) + if err != nil { + log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) + return } - wg.Wait() - pc := natTable.Get(key) - if pc != nil { - handleUDPToRemote(packet, pc, metadata) + rawPc, err := proxy.DialUDP(metadata) + if err != nil { + log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error()) + return + } + pc := newUDPTracker(rawPc, DefaultManager, metadata, rule) + + switch true { + case rule != nil: + log.Infoln("[UDP] %s --> %v match %s(%s) using %s", metadata.SourceAddress(), metadata.String(), rule.RuleType().String(), rule.Payload(), rawPc.Chains().String()) + case mode == Global: + log.Infoln("[UDP] %s --> %v using GLOBAL", metadata.SourceAddress(), metadata.String()) + case mode == Direct: + log.Infoln("[UDP] %s --> %v using DIRECT", metadata.SourceAddress(), metadata.String()) + default: + log.Infoln("[UDP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) } + + go handleUDPToLocal(packet.UDPPacket, pc, key, fAddr) + + natTable.Set(key, pc) + handle() }() } From 9a62b1081d79cb00562ce25ebe73cb1c8dbca407 Mon Sep 17 00:00:00 2001 From: uchuhimo Date: Wed, 28 Oct 2020 22:35:02 +0800 Subject: [PATCH 498/535] Feature: support round-robin strategy for load-balance group (#1044) --- adapters/outboundgroup/loadbalance.go | 85 +++++++++++++++++++++------ adapters/outboundgroup/parser.go | 3 +- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/adapters/outboundgroup/loadbalance.go b/adapters/outboundgroup/loadbalance.go index 8a7fe974c1..de6349679c 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -3,6 +3,8 @@ package outboundgroup import ( "context" "encoding/json" + "errors" + "fmt" "net" "github.com/Dreamacro/clash/adapters/outbound" @@ -14,11 +16,24 @@ import ( "golang.org/x/net/publicsuffix" ) +type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy + type LoadBalance struct { *outbound.Base - single *singledo.Single - maxRetry int - providers []provider.ProxyProvider + single *singledo.Single + providers []provider.ProxyProvider + strategyFn strategyFn +} + +var errStrategy = errors.New("unsupported strategy") + +func parseStrategy(config map[string]interface{}) string { + if elm, ok := config["strategy"]; ok { + if strategy, ok := elm.(string); ok { + return strategy + } + } + return "consistent-hashing" } func getKey(metadata *C.Metadata) string { @@ -81,19 +96,42 @@ func (lb *LoadBalance) SupportUDP() bool { return true } -func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { - key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) - proxies := lb.proxies() - buckets := int32(len(proxies)) - for i := 0; i < lb.maxRetry; i, key = i+1, key+1 { - idx := jumpHash(key, buckets) - proxy := proxies[idx] - if proxy.Alive() { - return proxy +func strategyRoundRobin() strategyFn { + idx := 0 + return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy { + length := len(proxies) + for i := 0; i < length; i++ { + idx = (idx + 1) % length + proxy := proxies[idx] + if proxy.Alive() { + return proxy + } } + + return proxies[0] } +} - return proxies[0] +func strategyConsistentHashing() strategyFn { + maxRetry := 5 + return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy { + key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) + buckets := int32(len(proxies)) + for i := 0; i < maxRetry; i, key = i+1, key+1 { + idx := jumpHash(key, buckets) + proxy := proxies[idx] + if proxy.Alive() { + return proxy + } + } + + return proxies[0] + } +} + +func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { + proxies := lb.proxies() + return lb.strategyFn(proxies, metadata) } func (lb *LoadBalance) proxies() []C.Proxy { @@ -115,11 +153,20 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { }) } -func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance { - return &LoadBalance{ - Base: outbound.NewBase(name, "", C.LoadBalance, false), - single: singledo.NewSingle(defaultGetProxiesDuration), - maxRetry: 3, - providers: providers, +func NewLoadBalance(name string, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) { + var strategyFn strategyFn + switch strategy { + case "consistent-hashing": + strategyFn = strategyConsistentHashing() + case "round-robin": + strategyFn = strategyRoundRobin() + default: + return nil, fmt.Errorf("%w: %s", errStrategy, strategy) } + return &LoadBalance{ + Base: outbound.NewBase(name, "", C.LoadBalance, false), + single: singledo.NewSingle(defaultGetProxiesDuration), + providers: providers, + strategyFn: strategyFn, + }, nil } diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index f0fa54c9f0..9a4a168109 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -111,7 +111,8 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, case "fallback": group = NewFallback(groupName, providers) case "load-balance": - group = NewLoadBalance(groupName, providers) + strategy := parseStrategy(config) + return NewLoadBalance(groupName, providers, strategy) case "relay": group = NewRelay(groupName, providers) default: From b98e9ea202b81189546da0fb6528d36d2cfd2c13 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 29 Oct 2020 00:32:31 +0800 Subject: [PATCH 499/535] Improve: #1038 and #1041 --- tunnel/tracker.go | 9 +++++---- tunnel/tunnel.go | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tunnel/tracker.go b/tunnel/tracker.go index e39caec777..19e7911042 100644 --- a/tunnel/tracker.go +++ b/tunnel/tracker.go @@ -2,6 +2,7 @@ package tunnel import ( "net" + "sync/atomic" "time" C "github.com/Dreamacro/clash/constant" @@ -38,7 +39,7 @@ func (tt *tcpTracker) Read(b []byte) (int, error) { n, err := tt.Conn.Read(b) download := int64(n) tt.manager.PushDownloaded(download) - tt.DownloadTotal += download + atomic.AddInt64(&tt.DownloadTotal, download) return n, err } @@ -46,7 +47,7 @@ func (tt *tcpTracker) Write(b []byte) (int, error) { n, err := tt.Conn.Write(b) upload := int64(n) tt.manager.PushUploaded(upload) - tt.UploadTotal += upload + atomic.AddInt64(&tt.UploadTotal, upload) return n, err } @@ -93,7 +94,7 @@ func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { n, addr, err := ut.PacketConn.ReadFrom(b) download := int64(n) ut.manager.PushDownloaded(download) - ut.DownloadTotal += download + atomic.AddInt64(&ut.DownloadTotal, download) return n, addr, err } @@ -101,7 +102,7 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { n, err := ut.PacketConn.WriteTo(b, addr) upload := int64(n) ut.manager.PushUploaded(upload) - ut.UploadTotal += upload + atomic.AddInt64(&ut.UploadTotal, upload) return n, err } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 2e7b96967b..e6a274b0a9 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -42,7 +42,10 @@ func Add(req C.ServerAdapter) { // AddPacket add udp Packet to queue func AddPacket(packet *inbound.PacketAdapter) { - udpQueue <- packet + select { + case udpQueue <- packet: + default: + } } // Rules return all rules From 87e4d9429098fcf2421713641426b88256d1decc Mon Sep 17 00:00:00 2001 From: Jason Lyu Date: Thu, 29 Oct 2020 17:51:14 +0800 Subject: [PATCH 500/535] Fix: tunnel manager & tracker race condition (#1048) --- adapters/outbound/base.go | 19 ++++----- common/observable/observable_test.go | 10 ++--- common/singledo/singledo_test.go | 8 ++-- go.mod | 1 + go.sum | 3 ++ tunnel/manager.go | 58 ++++++++++++++++------------ tunnel/tracker.go | 51 +++++++++++++----------- 7 files changed, 82 insertions(+), 68 deletions(-) diff --git a/adapters/outbound/base.go b/adapters/outbound/base.go index 26fa5d56db..f4da21ce05 100644 --- a/adapters/outbound/base.go +++ b/adapters/outbound/base.go @@ -6,11 +6,12 @@ import ( "errors" "net" "net/http" - "sync/atomic" "time" "github.com/Dreamacro/clash/common/queue" C "github.com/Dreamacro/clash/constant" + + "go.uber.org/atomic" ) type Base struct { @@ -95,11 +96,11 @@ func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { type Proxy struct { C.ProxyAdapter history *queue.Queue - alive uint32 + alive *atomic.Bool } func (p *Proxy) Alive() bool { - return atomic.LoadUint32(&p.alive) > 0 + return p.alive.Load() } func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { @@ -111,7 +112,7 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { conn, err := p.ProxyAdapter.DialContext(ctx, metadata) if err != nil { - atomic.StoreUint32(&p.alive, 0) + p.alive.Store(false) } return conn, err } @@ -128,7 +129,7 @@ func (p *Proxy) DelayHistory() []C.DelayHistory { // LastDelay return last history record. if proxy is not alive, return the max value of uint16. func (p *Proxy) LastDelay() (delay uint16) { var max uint16 = 0xffff - if atomic.LoadUint32(&p.alive) == 0 { + if !p.alive.Load() { return max } @@ -159,11 +160,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { // URLTest get the delay for the specified URL func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { defer func() { - if err == nil { - atomic.StoreUint32(&p.alive, 1) - } else { - atomic.StoreUint32(&p.alive, 0) - } + p.alive.Store(err == nil) record := C.DelayHistory{Time: time.Now()} if err == nil { record.Delay = t @@ -219,5 +216,5 @@ func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) { } func NewProxy(adapter C.ProxyAdapter) *Proxy { - return &Proxy{adapter, queue.New(10), 1} + return &Proxy{adapter, queue.New(10), atomic.NewBool(true)} } diff --git a/common/observable/observable_test.go b/common/observable/observable_test.go index 6dd6ee4221..cb16ad3995 100644 --- a/common/observable/observable_test.go +++ b/common/observable/observable_test.go @@ -2,11 +2,11 @@ package observable import ( "sync" - "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" + "go.uber.org/atomic" ) func iterator(item []interface{}) chan interface{} { @@ -33,25 +33,25 @@ func TestObservable(t *testing.T) { assert.Equal(t, count, 5) } -func TestObservable_MutilSubscribe(t *testing.T) { +func TestObservable_MultiSubscribe(t *testing.T) { iter := iterator([]interface{}{1, 2, 3, 4, 5}) src := NewObservable(iter) ch1, _ := src.Subscribe() ch2, _ := src.Subscribe() - var count int32 + var count = atomic.NewInt32(0) var wg sync.WaitGroup wg.Add(2) waitCh := func(ch <-chan interface{}) { for range ch { - atomic.AddInt32(&count, 1) + count.Inc() } wg.Done() } go waitCh(ch1) go waitCh(ch2) wg.Wait() - assert.Equal(t, int32(10), count) + assert.Equal(t, int32(10), count.Load()) } func TestObservable_UnSubscribe(t *testing.T) { diff --git a/common/singledo/singledo_test.go b/common/singledo/singledo_test.go index c9c58e587e..2b0d5988bb 100644 --- a/common/singledo/singledo_test.go +++ b/common/singledo/singledo_test.go @@ -2,17 +2,17 @@ package singledo import ( "sync" - "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" + "go.uber.org/atomic" ) func TestBasic(t *testing.T) { single := NewSingle(time.Millisecond * 30) foo := 0 - var shardCount int32 = 0 + var shardCount = atomic.NewInt32(0) call := func() (interface{}, error) { foo++ time.Sleep(time.Millisecond * 5) @@ -26,7 +26,7 @@ func TestBasic(t *testing.T) { go func() { _, _, shard := single.Do(call) if shard { - atomic.AddInt32(&shardCount, 1) + shardCount.Inc() } wg.Done() }() @@ -34,7 +34,7 @@ func TestBasic(t *testing.T) { wg.Wait() assert.Equal(t, 1, foo) - assert.Equal(t, int32(4), shardCount) + assert.Equal(t, int32(4), shardCount.Load()) } func TestTimer(t *testing.T) { diff --git a/go.mod b/go.mod index 0b62577de2..455ca0ecc2 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.7.0 github.com/stretchr/testify v1.6.1 + go.uber.org/atomic v1.7.0 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/net v0.0.0-20201020065357-d65d470038a5 golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 diff --git a/go.sum b/go.sum index 1495863c62..f99949d59c 100644 --- a/go.sum +++ b/go.sum @@ -25,9 +25,12 @@ github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/tunnel/manager.go b/tunnel/manager.go index 1493bc000c..784d57d9b9 100644 --- a/tunnel/manager.go +++ b/tunnel/manager.go @@ -2,26 +2,34 @@ package tunnel import ( "sync" - "sync/atomic" "time" + + "go.uber.org/atomic" ) var DefaultManager *Manager func init() { - DefaultManager = &Manager{} + DefaultManager = &Manager{ + uploadTemp: atomic.NewInt64(0), + downloadTemp: atomic.NewInt64(0), + uploadBlip: atomic.NewInt64(0), + downloadBlip: atomic.NewInt64(0), + uploadTotal: atomic.NewInt64(0), + downloadTotal: atomic.NewInt64(0), + } go DefaultManager.handle() } type Manager struct { connections sync.Map - uploadTemp int64 - downloadTemp int64 - uploadBlip int64 - downloadBlip int64 - uploadTotal int64 - downloadTotal int64 + uploadTemp *atomic.Int64 + downloadTemp *atomic.Int64 + uploadBlip *atomic.Int64 + downloadBlip *atomic.Int64 + uploadTotal *atomic.Int64 + downloadTotal *atomic.Int64 } func (m *Manager) Join(c tracker) { @@ -33,17 +41,17 @@ func (m *Manager) Leave(c tracker) { } func (m *Manager) PushUploaded(size int64) { - atomic.AddInt64(&m.uploadTemp, size) - atomic.AddInt64(&m.uploadTotal, size) + m.uploadTemp.Add(size) + m.uploadTotal.Add(size) } func (m *Manager) PushDownloaded(size int64) { - atomic.AddInt64(&m.downloadTemp, size) - atomic.AddInt64(&m.downloadTotal, size) + m.downloadTemp.Add(size) + m.downloadTotal.Add(size) } func (m *Manager) Now() (up int64, down int64) { - return atomic.LoadInt64(&m.uploadBlip), atomic.LoadInt64(&m.downloadBlip) + return m.uploadBlip.Load(), m.downloadBlip.Load() } func (m *Manager) Snapshot() *Snapshot { @@ -54,29 +62,29 @@ func (m *Manager) Snapshot() *Snapshot { }) return &Snapshot{ - UploadTotal: atomic.LoadInt64(&m.uploadTotal), - DownloadTotal: atomic.LoadInt64(&m.downloadTotal), + UploadTotal: m.uploadTotal.Load(), + DownloadTotal: m.downloadTotal.Load(), Connections: connections, } } func (m *Manager) ResetStatistic() { - m.uploadTemp = 0 - m.uploadBlip = 0 - m.uploadTotal = 0 - m.downloadTemp = 0 - m.downloadBlip = 0 - m.downloadTotal = 0 + m.uploadTemp.Store(0) + m.uploadBlip.Store(0) + m.uploadTotal.Store(0) + m.downloadTemp.Store(0) + m.downloadBlip.Store(0) + m.downloadTotal.Store(0) } func (m *Manager) handle() { ticker := time.NewTicker(time.Second) for range ticker.C { - atomic.StoreInt64(&m.uploadBlip, atomic.LoadInt64(&m.uploadTemp)) - atomic.StoreInt64(&m.uploadTemp, 0) - atomic.StoreInt64(&m.downloadBlip, atomic.LoadInt64(&m.downloadTemp)) - atomic.StoreInt64(&m.downloadTemp, 0) + m.uploadBlip.Store(m.uploadTemp.Load()) + m.uploadTemp.Store(0) + m.downloadBlip.Store(m.downloadTemp.Load()) + m.downloadTemp.Store(0) } } diff --git a/tunnel/tracker.go b/tunnel/tracker.go index 19e7911042..dcb81e7f9f 100644 --- a/tunnel/tracker.go +++ b/tunnel/tracker.go @@ -2,11 +2,12 @@ package tunnel import ( "net" - "sync/atomic" "time" C "github.com/Dreamacro/clash/constant" + "github.com/gofrs/uuid" + "go.uber.org/atomic" ) type tracker interface { @@ -15,14 +16,14 @@ type tracker interface { } type trackerInfo struct { - UUID uuid.UUID `json:"id"` - Metadata *C.Metadata `json:"metadata"` - UploadTotal int64 `json:"upload"` - DownloadTotal int64 `json:"download"` - Start time.Time `json:"start"` - Chain C.Chain `json:"chains"` - Rule string `json:"rule"` - RulePayload string `json:"rulePayload"` + UUID uuid.UUID `json:"id"` + Metadata *C.Metadata `json:"metadata"` + UploadTotal *atomic.Int64 `json:"upload"` + DownloadTotal *atomic.Int64 `json:"download"` + Start time.Time `json:"start"` + Chain C.Chain `json:"chains"` + Rule string `json:"rule"` + RulePayload string `json:"rulePayload"` } type tcpTracker struct { @@ -39,7 +40,7 @@ func (tt *tcpTracker) Read(b []byte) (int, error) { n, err := tt.Conn.Read(b) download := int64(n) tt.manager.PushDownloaded(download) - atomic.AddInt64(&tt.DownloadTotal, download) + tt.DownloadTotal.Add(download) return n, err } @@ -47,7 +48,7 @@ func (tt *tcpTracker) Write(b []byte) (int, error) { n, err := tt.Conn.Write(b) upload := int64(n) tt.manager.PushUploaded(upload) - atomic.AddInt64(&tt.UploadTotal, upload) + tt.UploadTotal.Add(upload) return n, err } @@ -63,11 +64,13 @@ func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R Conn: conn, manager: manager, trackerInfo: &trackerInfo{ - UUID: uuid, - Start: time.Now(), - Metadata: metadata, - Chain: conn.Chains(), - Rule: "", + UUID: uuid, + Start: time.Now(), + Metadata: metadata, + Chain: conn.Chains(), + Rule: "", + UploadTotal: atomic.NewInt64(0), + DownloadTotal: atomic.NewInt64(0), }, } @@ -94,7 +97,7 @@ func (ut *udpTracker) ReadFrom(b []byte) (int, net.Addr, error) { n, addr, err := ut.PacketConn.ReadFrom(b) download := int64(n) ut.manager.PushDownloaded(download) - atomic.AddInt64(&ut.DownloadTotal, download) + ut.DownloadTotal.Add(download) return n, addr, err } @@ -102,7 +105,7 @@ func (ut *udpTracker) WriteTo(b []byte, addr net.Addr) (int, error) { n, err := ut.PacketConn.WriteTo(b, addr) upload := int64(n) ut.manager.PushUploaded(upload) - atomic.AddInt64(&ut.UploadTotal, upload) + ut.UploadTotal.Add(upload) return n, err } @@ -118,11 +121,13 @@ func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru PacketConn: conn, manager: manager, trackerInfo: &trackerInfo{ - UUID: uuid, - Start: time.Now(), - Metadata: metadata, - Chain: conn.Chains(), - Rule: "", + UUID: uuid, + Start: time.Now(), + Metadata: metadata, + Chain: conn.Chains(), + Rule: "", + UploadTotal: atomic.NewInt64(0), + DownloadTotal: atomic.NewInt64(0), }, } From 83efe2ae57ccae2b4b8626c2f88bfb5a391f73f2 Mon Sep 17 00:00:00 2001 From: maskedeken <52683904+maskedeken@users.noreply.github.com> Date: Mon, 9 Nov 2020 10:46:10 +0800 Subject: [PATCH 501/535] Feature: add TCP TPROXY support (#1049) --- config/config.go | 3 ++ constant/metadata.go | 3 ++ hub/executor/executor.go | 5 +++ hub/route/configs.go | 2 + proxy/listener.go | 80 ++++++++++++++++++++++++++++++------- proxy/redir/tproxy.go | 71 ++++++++++++++++++++++++++++++++ proxy/redir/tproxy_linux.go | 40 +++++++++++++++++++ proxy/redir/tproxy_other.go | 12 ++++++ proxy/redir/udp.go | 7 +++- proxy/redir/udp_linux.go | 37 ----------------- proxy/redir/udp_other.go | 4 -- 11 files changed, 207 insertions(+), 57 deletions(-) create mode 100644 proxy/redir/tproxy.go create mode 100644 proxy/redir/tproxy_linux.go create mode 100644 proxy/redir/tproxy_other.go diff --git a/config/config.go b/config/config.go index 9c39b7efd0..6840134625 100644 --- a/config/config.go +++ b/config/config.go @@ -38,6 +38,7 @@ type Inbound struct { Port int `json:"port"` SocksPort int `json:"socks-port"` RedirPort int `json:"redir-port"` + TProxyPort int `json:"tproxy-port"` MixedPort int `json:"mixed-port"` Authentication []string `json:"authentication"` AllowLan bool `json:"allow-lan"` @@ -111,6 +112,7 @@ type RawConfig struct { Port int `yaml:"port"` SocksPort int `yaml:"socks-port"` RedirPort int `yaml:"redir-port"` + TProxyPort int `yaml:"tproxy-port"` MixedPort int `yaml:"mixed-port"` Authentication []string `yaml:"authentication"` AllowLan bool `yaml:"allow-lan"` @@ -234,6 +236,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Port: cfg.Port, SocksPort: cfg.SocksPort, RedirPort: cfg.RedirPort, + TProxyPort: cfg.TProxyPort, MixedPort: cfg.MixedPort, AllowLan: cfg.AllowLan, BindAddress: cfg.BindAddress, diff --git a/constant/metadata.go b/constant/metadata.go index 647b332d0e..93ef406d2d 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -19,6 +19,7 @@ const ( HTTPCONNECT SOCKS REDIR + TPROXY ) type NetWork int @@ -46,6 +47,8 @@ func (t Type) String() string { return "Socks5" case REDIR: return "Redir" + case TPROXY: + return "TProxy" default: return "Unknown" } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 822b43e568..d5b96a2776 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -86,6 +86,7 @@ func GetGeneral() *config.General { Port: ports.Port, SocksPort: ports.SocksPort, RedirPort: ports.RedirPort, + TProxyPort: ports.TProxyPort, MixedPort: ports.MixedPort, Authentication: authenticator, AllowLan: P.AllowLan(), @@ -191,6 +192,10 @@ func updateGeneral(general *config.General, force bool) { log.Errorln("Start Redir server error: %s", err.Error()) } + if err := P.ReCreateTProxy(general.TProxyPort); err != nil { + log.Errorln("Start TProxy server error: %s", err.Error()) + } + if err := P.ReCreateMixed(general.MixedPort); err != nil { log.Errorln("Start Mixed(http and socks5) server error: %s", err.Error()) } diff --git a/hub/route/configs.go b/hub/route/configs.go index 581f28558d..a0e183712b 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -26,6 +26,7 @@ type configSchema struct { Port *int `json:"port"` SocksPort *int `json:"socks-port"` RedirPort *int `json:"redir-port"` + TProxyPort *int `json:"tproxy-port"` MixedPort *int `json:"mixed-port"` AllowLan *bool `json:"allow-lan"` BindAddress *string `json:"bind-address"` @@ -66,6 +67,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port)) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort)) P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort)) + P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort)) P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort)) if general.Mode != nil { diff --git a/proxy/listener.go b/proxy/listener.go index d1414b5a69..3ccfbe967e 100644 --- a/proxy/listener.go +++ b/proxy/listener.go @@ -17,26 +17,30 @@ var ( allowLan = false bindAddress = "*" - socksListener *socks.SockListener - socksUDPListener *socks.SockUDPListener - httpListener *http.HttpListener - redirListener *redir.RedirListener - redirUDPListener *redir.RedirUDPListener - mixedListener *mixed.MixedListener - mixedUDPLister *socks.SockUDPListener + socksListener *socks.SockListener + socksUDPListener *socks.SockUDPListener + httpListener *http.HttpListener + redirListener *redir.RedirListener + redirUDPListener *redir.RedirUDPListener + tproxyListener *redir.TProxyListener + tproxyUDPListener *redir.RedirUDPListener + mixedListener *mixed.MixedListener + mixedUDPLister *socks.SockUDPListener // lock for recreate function - socksMux sync.Mutex - httpMux sync.Mutex - redirMux sync.Mutex - mixedMux sync.Mutex + socksMux sync.Mutex + httpMux sync.Mutex + redirMux sync.Mutex + tproxyMux sync.Mutex + mixedMux sync.Mutex ) type Ports struct { - Port int `json:"port"` - SocksPort int `json:"socks-port"` - RedirPort int `json:"redir-port"` - MixedPort int `json:"mixed-port"` + Port int `json:"port"` + SocksPort int `json:"socks-port"` + RedirPort int `json:"redir-port"` + TProxyPort int `json:"tproxy-port"` + MixedPort int `json:"mixed-port"` } func AllowLan() bool { @@ -174,6 +178,46 @@ func ReCreateRedir(port int) error { return nil } +func ReCreateTProxy(port int) error { + tproxyMux.Lock() + defer tproxyMux.Unlock() + + addr := genAddr(bindAddress, port, allowLan) + + if tproxyListener != nil { + if tproxyListener.Address() == addr { + return nil + } + tproxyListener.Close() + tproxyListener = nil + } + + if tproxyUDPListener != nil { + if tproxyUDPListener.Address() == addr { + return nil + } + tproxyUDPListener.Close() + tproxyUDPListener = nil + } + + if portIsZero(addr) { + return nil + } + + var err error + tproxyListener, err = redir.NewTProxy(addr) + if err != nil { + return err + } + + tproxyUDPListener, err = redir.NewRedirUDPProxy(addr) + if err != nil { + log.Warnln("Failed to start TProxy UDP Listener: %s", err) + } + + return nil +} + func ReCreateMixed(port int) error { mixedMux.Lock() defer mixedMux.Unlock() @@ -245,6 +289,12 @@ func GetPorts() *Ports { ports.RedirPort = port } + if tproxyListener != nil { + _, portStr, _ := net.SplitHostPort(tproxyListener.Address()) + port, _ := strconv.Atoi(portStr) + ports.TProxyPort = port + } + if mixedListener != nil { _, portStr, _ := net.SplitHostPort(mixedListener.Address()) port, _ := strconv.Atoi(portStr) diff --git a/proxy/redir/tproxy.go b/proxy/redir/tproxy.go new file mode 100644 index 0000000000..27c7b56dc8 --- /dev/null +++ b/proxy/redir/tproxy.go @@ -0,0 +1,71 @@ +package redir + +import ( + "net" + + "github.com/Dreamacro/clash/adapters/inbound" + "github.com/Dreamacro/clash/component/socks5" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/tunnel" +) + +type TProxyListener struct { + net.Listener + address string + closed bool +} + +func NewTProxy(addr string) (*TProxyListener, error) { + l, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + tl := l.(*net.TCPListener) + rc, err := tl.SyscallConn() + if err != nil { + return nil, err + } + + err = setsockopt(rc, addr) + if err != nil { + return nil, err + } + + rl := &TProxyListener{ + Listener: l, + address: addr, + } + + go func() { + log.Infoln("TProxy server listening at: %s", addr) + for { + c, err := l.Accept() + if err != nil { + if rl.closed { + break + } + continue + } + go rl.handleRedir(c) + } + }() + + return rl, nil +} + +func (l *TProxyListener) Close() { + l.closed = true + l.Listener.Close() +} + +func (l *TProxyListener) Address() string { + return l.address +} + +func (l *TProxyListener) handleRedir(conn net.Conn) { + target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) + conn.(*net.TCPConn).SetKeepAlive(true) + tunnel.Add(inbound.NewSocket(target, conn, C.TPROXY)) +} diff --git a/proxy/redir/tproxy_linux.go b/proxy/redir/tproxy_linux.go new file mode 100644 index 0000000000..22f8b46b21 --- /dev/null +++ b/proxy/redir/tproxy_linux.go @@ -0,0 +1,40 @@ +// +build linux + +package redir + +import ( + "net" + "syscall" +) + +func setsockopt(rc syscall.RawConn, addr string) error { + isIPv6 := true + host, _, err := net.SplitHostPort(addr) + if err != nil { + return err + } + ip := net.ParseIP(host) + if ip != nil && ip.To4() != nil { + isIPv6 = false + } + + rc.Control(func(fd uintptr) { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) + } + if err == nil && isIPv6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) + } + + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) + } + if err == nil && isIPv6 { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) + } + }) + + return err +} diff --git a/proxy/redir/tproxy_other.go b/proxy/redir/tproxy_other.go new file mode 100644 index 0000000000..88e4de50bb --- /dev/null +++ b/proxy/redir/tproxy_other.go @@ -0,0 +1,12 @@ +// +build !linux + +package redir + +import ( + "errors" + "syscall" +) + +func setsockopt(rc syscall.RawConn, addr string) error { + return errors.New("Not supported on current platform") +} diff --git a/proxy/redir/udp.go b/proxy/redir/udp.go index bcba6ffd1a..74b47570b5 100644 --- a/proxy/redir/udp.go +++ b/proxy/redir/udp.go @@ -26,7 +26,12 @@ func NewRedirUDPProxy(addr string) (*RedirUDPListener, error) { c := l.(*net.UDPConn) - err = setsockopt(c, addr) + rc, err := c.SyscallConn() + if err != nil { + return nil, err + } + + err = setsockopt(rc, addr) if err != nil { return nil, err } diff --git a/proxy/redir/udp_linux.go b/proxy/redir/udp_linux.go index 228daca456..e5a53e4da3 100644 --- a/proxy/redir/udp_linux.go +++ b/proxy/redir/udp_linux.go @@ -14,43 +14,6 @@ const ( IPV6_RECVORIGDSTADDR = 0x4a ) -func setsockopt(c *net.UDPConn, addr string) error { - isIPv6 := true - host, _, err := net.SplitHostPort(addr) - if err != nil { - return err - } - ip := net.ParseIP(host) - if ip != nil && ip.To4() != nil { - isIPv6 = false - } - - rc, err := c.SyscallConn() - if err != nil { - return err - } - - rc.Control(func(fd uintptr) { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) - - if err == nil { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) - } - if err == nil && isIPv6 { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_TRANSPARENT, 1) - } - - if err == nil { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1) - } - if err == nil && isIPv6 { - err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) - } - }) - - return err -} - func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { msgs, err := syscall.ParseSocketControlMessage(oob[:oobn]) if err != nil { diff --git a/proxy/redir/udp_other.go b/proxy/redir/udp_other.go index d9afdb6829..e50ec5b979 100644 --- a/proxy/redir/udp_other.go +++ b/proxy/redir/udp_other.go @@ -7,10 +7,6 @@ import ( "net" ) -func setsockopt(c *net.UDPConn, addr string) error { - return errors.New("UDP redir not supported on current platform") -} - func getOrigDst(oob []byte, oobn int) (*net.UDPAddr, error) { return nil, errors.New("UDP redir not supported on current platform") } From 16ae107e70004b3e19f6d8d486b023ab218223fd Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Tue, 10 Nov 2020 15:19:12 +0800 Subject: [PATCH 502/535] Chore: push image to github docker registry --- .github/workflows/docker.yml | 14 ++++++++++++-- Dockerfile | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6c15c03f01..002a9cb356 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,6 +33,13 @@ jobs: with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to Github Package + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: Dreamacro + password: ${{ secrets.PACKAGE_TOKEN }} - name: Build dev branch and push if: github.ref == 'refs/heads/dev' @@ -41,7 +48,7 @@ jobs: context: . platforms: linux/amd64,linux/arm/v7,linux/arm64 push: true - tags: 'dreamacro/clash:dev' + tags: 'dreamacro/clash:dev,ghcr.io/dreamacro/clash:dev' - name: Get all docker tags if: startsWith(github.ref, 'refs/tags/') @@ -49,9 +56,12 @@ jobs: id: tags with: script: | + const ref = `${context.payload.ref.replace(/\/?refs\/tags\//, '')}` const tags = [ 'dreamacro/clash:latest', - `dreamacro/clash:${context.payload.ref.replace(/\/?refs\/tags\//, '')}` + `dreamacro/clash:${ref}`, + 'ghcr.io/dreamacro/clash:latest', + `ghcr.io/dreamacro/clash:${ref}` ] return tags.join(',') result-encoding: string diff --git a/Dockerfile b/Dockerfile index e94a0d0856..653d08fccd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,7 @@ RUN go mod download && \ mv ./bin/clash-docker /clash FROM alpine:latest +LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash RUN apk add --no-cache ca-certificates COPY --from=builder /Country.mmdb /root/.config/clash/ From 4735f61fd1f6a5c65773eecf20600dfaeaa21568 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 13 Nov 2020 21:48:52 +0800 Subject: [PATCH 503/535] Feature: add `disable-udp` option for all proxy group --- adapters/outboundgroup/fallback.go | 18 ++++++++++++------ adapters/outboundgroup/loadbalance.go | 8 +++++--- adapters/outboundgroup/parser.go | 23 ++++++++++++----------- adapters/outboundgroup/relay.go | 4 ++-- adapters/outboundgroup/selector.go | 22 ++++++++++++++-------- adapters/outboundgroup/urltest.go | 10 ++++++++-- config/config.go | 7 ++++++- 7 files changed, 59 insertions(+), 33 deletions(-) diff --git a/adapters/outboundgroup/fallback.go b/adapters/outboundgroup/fallback.go index acf577e1f7..8fb31c8e6f 100644 --- a/adapters/outboundgroup/fallback.go +++ b/adapters/outboundgroup/fallback.go @@ -12,8 +12,9 @@ import ( type Fallback struct { *outbound.Base - single *singledo.Single - providers []provider.ProxyProvider + disableUDP bool + single *singledo.Single + providers []provider.ProxyProvider } func (f *Fallback) Now() string { @@ -40,6 +41,10 @@ func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } func (f *Fallback) SupportUDP() bool { + if f.disableUDP { + return false + } + proxy := f.findAliveProxy() return proxy.SupportUDP() } @@ -80,10 +85,11 @@ func (f *Fallback) findAliveProxy() C.Proxy { return f.proxies()[0] } -func NewFallback(name string, providers []provider.ProxyProvider) *Fallback { +func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { return &Fallback{ - Base: outbound.NewBase(name, "", C.Fallback, false), - single: singledo.NewSingle(defaultGetProxiesDuration), - providers: providers, + Base: outbound.NewBase(options.Name, "", C.Fallback, false), + single: singledo.NewSingle(defaultGetProxiesDuration), + providers: providers, + disableUDP: options.DisableUDP, } } diff --git a/adapters/outboundgroup/loadbalance.go b/adapters/outboundgroup/loadbalance.go index de6349679c..992b2c4358 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -20,6 +20,7 @@ type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy type LoadBalance struct { *outbound.Base + disableUDP bool single *singledo.Single providers []provider.ProxyProvider strategyFn strategyFn @@ -93,7 +94,7 @@ func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, err error } func (lb *LoadBalance) SupportUDP() bool { - return true + return !lb.disableUDP } func strategyRoundRobin() strategyFn { @@ -153,7 +154,7 @@ func (lb *LoadBalance) MarshalJSON() ([]byte, error) { }) } -func NewLoadBalance(name string, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) { +func NewLoadBalance(options *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) { var strategyFn strategyFn switch strategy { case "consistent-hashing": @@ -164,9 +165,10 @@ func NewLoadBalance(name string, providers []provider.ProxyProvider, strategy st return nil, fmt.Errorf("%w: %s", errStrategy, strategy) } return &LoadBalance{ - Base: outbound.NewBase(name, "", C.LoadBalance, false), + Base: outbound.NewBase(options.Name, "", C.LoadBalance, false), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, strategyFn: strategyFn, + disableUDP: options.DisableUDP, }, nil } diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index 9a4a168109..a6c2c84813 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -18,12 +18,13 @@ var ( ) type GroupCommonOption struct { - Name string `group:"name"` - Type string `group:"type"` - Proxies []string `group:"proxies,omitempty"` - Use []string `group:"use,omitempty"` - URL string `group:"url,omitempty"` - Interval int `group:"interval,omitempty"` + Name string `group:"name"` + Type string `group:"type"` + Proxies []string `group:"proxies,omitempty"` + Use []string `group:"use,omitempty"` + URL string `group:"url,omitempty"` + Interval int `group:"interval,omitempty"` + DisableUDP bool `group:"disable-udp,omitempty"` } func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) { @@ -105,16 +106,16 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, switch groupOption.Type { case "url-test": opts := parseURLTestOption(config) - group = NewURLTest(groupName, providers, opts...) + group = NewURLTest(groupOption, providers, opts...) case "select": - group = NewSelector(groupName, providers) + group = NewSelector(groupOption, providers) case "fallback": - group = NewFallback(groupName, providers) + group = NewFallback(groupOption, providers) case "load-balance": strategy := parseStrategy(config) - return NewLoadBalance(groupName, providers, strategy) + return NewLoadBalance(groupOption, providers, strategy) case "relay": - group = NewRelay(groupName, providers) + group = NewRelay(groupOption, providers) default: return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) } diff --git a/adapters/outboundgroup/relay.go b/adapters/outboundgroup/relay.go index e27df09b69..adc28881de 100644 --- a/adapters/outboundgroup/relay.go +++ b/adapters/outboundgroup/relay.go @@ -89,9 +89,9 @@ func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy { return proxies } -func NewRelay(name string, providers []provider.ProxyProvider) *Relay { +func NewRelay(options *GroupCommonOption, providers []provider.ProxyProvider) *Relay { return &Relay{ - Base: outbound.NewBase(name, "", C.Relay, false), + Base: outbound.NewBase(options.Name, "", C.Relay, false), single: singledo.NewSingle(defaultGetProxiesDuration), providers: providers, } diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index e253cb3e6e..e4f11b2f28 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -13,9 +13,10 @@ import ( type Selector struct { *outbound.Base - single *singledo.Single - selected string - providers []provider.ProxyProvider + disableUDP bool + single *singledo.Single + selected string + providers []provider.ProxyProvider } func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { @@ -35,6 +36,10 @@ func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } func (s *Selector) SupportUDP() bool { + if s.disableUDP { + return false + } + return s.selectedProxy().SupportUDP() } @@ -86,12 +91,13 @@ func (s *Selector) selectedProxy() C.Proxy { return elm.(C.Proxy) } -func NewSelector(name string, providers []provider.ProxyProvider) *Selector { +func NewSelector(options *GroupCommonOption, providers []provider.ProxyProvider) *Selector { selected := providers[0].Proxies()[0].Name() return &Selector{ - Base: outbound.NewBase(name, "", C.Selector, false), - single: singledo.NewSingle(defaultGetProxiesDuration), - providers: providers, - selected: selected, + Base: outbound.NewBase(options.Name, "", C.Selector, false), + single: singledo.NewSingle(defaultGetProxiesDuration), + providers: providers, + selected: selected, + disableUDP: options.DisableUDP, } } diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index c20340ecd7..1074a42a03 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -22,6 +22,7 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption { type URLTest struct { *outbound.Base tolerance uint16 + disableUDP bool fastNode C.Proxy single *singledo.Single fastSingle *singledo.Single @@ -89,6 +90,10 @@ func (u *URLTest) fast() C.Proxy { } func (u *URLTest) SupportUDP() bool { + if u.disableUDP { + return false + } + return u.fast().SupportUDP() } @@ -117,12 +122,13 @@ func parseURLTestOption(config map[string]interface{}) []urlTestOption { return opts } -func NewURLTest(name string, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { +func NewURLTest(commonOptions *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { urlTest := &URLTest{ - Base: outbound.NewBase(name, "", C.URLTest, false), + Base: outbound.NewBase(commonOptions.Name, "", C.URLTest, false), single: singledo.NewSingle(defaultGetProxiesDuration), fastSingle: singledo.NewSingle(time.Second * 10), providers: providers, + disableUDP: commonOptions.DisableUDP, } for _, option := range options { diff --git a/config/config.go b/config/config.go index 6840134625..99d48b92a0 100644 --- a/config/config.go +++ b/config/config.go @@ -349,7 +349,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) providersMap[provider.ReservedName] = pd - global := outboundgroup.NewSelector("GLOBAL", []provider.ProxyProvider{pd}) + global := outboundgroup.NewSelector( + &outboundgroup.GroupCommonOption{ + Name: "GLOBAL", + }, + []provider.ProxyProvider{pd}, + ) proxies["GLOBAL"] = outbound.NewProxy(global) return proxies, providersMap, nil } From 0402878daa904cc4c47cb459ff5b65168b864efc Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 19 Nov 2020 00:53:22 +0800 Subject: [PATCH 504/535] Feature: add `lazy` for proxy group and provider --- adapters/outboundgroup/common.go | 8 +++++-- adapters/outboundgroup/fallback.go | 22 +++++++++--------- adapters/outboundgroup/loadbalance.go | 8 +++---- adapters/outboundgroup/parser.go | 11 +++++---- adapters/outboundgroup/relay.go | 12 +++++----- adapters/outboundgroup/selector.go | 18 +++++++-------- adapters/outboundgroup/urltest.go | 20 ++++++++-------- adapters/provider/healthcheck.go | 33 +++++++++++++++++++-------- adapters/provider/parser.go | 9 ++++++-- adapters/provider/provider.go | 13 +++++++++++ config/config.go | 2 +- 11 files changed, 97 insertions(+), 59 deletions(-) diff --git a/adapters/outboundgroup/common.go b/adapters/outboundgroup/common.go index ce40072ce9..c5c719f298 100644 --- a/adapters/outboundgroup/common.go +++ b/adapters/outboundgroup/common.go @@ -11,10 +11,14 @@ const ( defaultGetProxiesDuration = time.Second * 5 ) -func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy { +func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { proxies := []C.Proxy{} for _, provider := range providers { - proxies = append(proxies, provider.Proxies()...) + if touch { + proxies = append(proxies, provider.ProxiesWithTouch()...) + } else { + proxies = append(proxies, provider.Proxies()...) + } } return proxies } diff --git a/adapters/outboundgroup/fallback.go b/adapters/outboundgroup/fallback.go index 8fb31c8e6f..756c4e8173 100644 --- a/adapters/outboundgroup/fallback.go +++ b/adapters/outboundgroup/fallback.go @@ -18,12 +18,12 @@ type Fallback struct { } func (f *Fallback) Now() string { - proxy := f.findAliveProxy() + proxy := f.findAliveProxy(false) return proxy.Name() } func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - proxy := f.findAliveProxy() + proxy := f.findAliveProxy(true) c, err := proxy.DialContext(ctx, metadata) if err == nil { c.AppendToChains(f) @@ -32,7 +32,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con } func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - proxy := f.findAliveProxy() + proxy := f.findAliveProxy(true) pc, err := proxy.DialUDP(metadata) if err == nil { pc.AppendToChains(f) @@ -45,13 +45,13 @@ func (f *Fallback) SupportUDP() bool { return false } - proxy := f.findAliveProxy() + proxy := f.findAliveProxy(false) return proxy.SupportUDP() } func (f *Fallback) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range f.proxies() { + for _, proxy := range f.proxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]interface{}{ @@ -62,27 +62,27 @@ func (f *Fallback) MarshalJSON() ([]byte, error) { } func (f *Fallback) Unwrap(metadata *C.Metadata) C.Proxy { - proxy := f.findAliveProxy() + proxy := f.findAliveProxy(true) return proxy } -func (f *Fallback) proxies() []C.Proxy { +func (f *Fallback) proxies(touch bool) []C.Proxy { elm, _, _ := f.single.Do(func() (interface{}, error) { - return getProvidersProxies(f.providers), nil + return getProvidersProxies(f.providers, touch), nil }) return elm.([]C.Proxy) } -func (f *Fallback) findAliveProxy() C.Proxy { - proxies := f.proxies() +func (f *Fallback) findAliveProxy(touch bool) C.Proxy { + proxies := f.proxies(touch) for _, proxy := range proxies { if proxy.Alive() { return proxy } } - return f.proxies()[0] + return proxies[0] } func NewFallback(options *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { diff --git a/adapters/outboundgroup/loadbalance.go b/adapters/outboundgroup/loadbalance.go index 992b2c4358..289d0b46e5 100644 --- a/adapters/outboundgroup/loadbalance.go +++ b/adapters/outboundgroup/loadbalance.go @@ -131,13 +131,13 @@ func strategyConsistentHashing() strategyFn { } func (lb *LoadBalance) Unwrap(metadata *C.Metadata) C.Proxy { - proxies := lb.proxies() + proxies := lb.proxies(true) return lb.strategyFn(proxies, metadata) } -func (lb *LoadBalance) proxies() []C.Proxy { +func (lb *LoadBalance) proxies(touch bool) []C.Proxy { elm, _, _ := lb.single.Do(func() (interface{}, error) { - return getProvidersProxies(lb.providers), nil + return getProvidersProxies(lb.providers, touch), nil }) return elm.([]C.Proxy) @@ -145,7 +145,7 @@ func (lb *LoadBalance) proxies() []C.Proxy { func (lb *LoadBalance) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range lb.proxies() { + for _, proxy := range lb.proxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]interface{}{ diff --git a/adapters/outboundgroup/parser.go b/adapters/outboundgroup/parser.go index a6c2c84813..82ceaa7989 100644 --- a/adapters/outboundgroup/parser.go +++ b/adapters/outboundgroup/parser.go @@ -24,13 +24,16 @@ type GroupCommonOption struct { Use []string `group:"use,omitempty"` URL string `group:"url,omitempty"` Interval int `group:"interval,omitempty"` + Lazy bool `group:"lazy,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"` } func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) { decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) - groupOption := &GroupCommonOption{} + groupOption := &GroupCommonOption{ + Lazy: true, + } if err := decoder.Decode(config, groupOption); err != nil { return nil, errFormat } @@ -55,7 +58,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, // if Use not empty, drop health check options if len(groupOption.Use) != 0 { - hc := provider.NewHealthCheck(ps, "", 0) + hc := provider.NewHealthCheck(ps, "", 0, true) pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, err @@ -69,7 +72,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, // select don't need health check if groupOption.Type == "select" || groupOption.Type == "relay" { - hc := provider.NewHealthCheck(ps, "", 0) + hc := provider.NewHealthCheck(ps, "", 0, true) pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, err @@ -82,7 +85,7 @@ func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, return nil, errMissHealthCheck } - hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval)) + hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy) pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { return nil, err diff --git a/adapters/outboundgroup/relay.go b/adapters/outboundgroup/relay.go index adc28881de..d2eeba6623 100644 --- a/adapters/outboundgroup/relay.go +++ b/adapters/outboundgroup/relay.go @@ -20,7 +20,7 @@ type Relay struct { } func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - proxies := r.proxies(metadata) + proxies := r.proxies(metadata, true) if len(proxies) == 0 { return nil, errors.New("proxy does not exist") } @@ -58,7 +58,7 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, func (r *Relay) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range r.rawProxies() { + for _, proxy := range r.rawProxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]interface{}{ @@ -67,16 +67,16 @@ func (r *Relay) MarshalJSON() ([]byte, error) { }) } -func (r *Relay) rawProxies() []C.Proxy { +func (r *Relay) rawProxies(touch bool) []C.Proxy { elm, _, _ := r.single.Do(func() (interface{}, error) { - return getProvidersProxies(r.providers), nil + return getProvidersProxies(r.providers, touch), nil }) return elm.([]C.Proxy) } -func (r *Relay) proxies(metadata *C.Metadata) []C.Proxy { - proxies := r.rawProxies() +func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy { + proxies := r.rawProxies(touch) for n, proxy := range proxies { subproxy := proxy.Unwrap(metadata) diff --git a/adapters/outboundgroup/selector.go b/adapters/outboundgroup/selector.go index e4f11b2f28..996407457a 100644 --- a/adapters/outboundgroup/selector.go +++ b/adapters/outboundgroup/selector.go @@ -20,7 +20,7 @@ type Selector struct { } func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { - c, err := s.selectedProxy().DialContext(ctx, metadata) + c, err := s.selectedProxy(true).DialContext(ctx, metadata) if err == nil { c.AppendToChains(s) } @@ -28,7 +28,7 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Con } func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := s.selectedProxy().DialUDP(metadata) + pc, err := s.selectedProxy(true).DialUDP(metadata) if err == nil { pc.AppendToChains(s) } @@ -40,12 +40,12 @@ func (s *Selector) SupportUDP() bool { return false } - return s.selectedProxy().SupportUDP() + return s.selectedProxy(false).SupportUDP() } func (s *Selector) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range getProvidersProxies(s.providers) { + for _, proxy := range getProvidersProxies(s.providers, false) { all = append(all, proxy.Name()) } @@ -57,11 +57,11 @@ func (s *Selector) MarshalJSON() ([]byte, error) { } func (s *Selector) Now() string { - return s.selectedProxy().Name() + return s.selectedProxy(false).Name() } func (s *Selector) Set(name string) error { - for _, proxy := range getProvidersProxies(s.providers) { + for _, proxy := range getProvidersProxies(s.providers, false) { if proxy.Name() == name { s.selected = name s.single.Reset() @@ -73,12 +73,12 @@ func (s *Selector) Set(name string) error { } func (s *Selector) Unwrap(metadata *C.Metadata) C.Proxy { - return s.selectedProxy() + return s.selectedProxy(true) } -func (s *Selector) selectedProxy() C.Proxy { +func (s *Selector) selectedProxy(touch bool) C.Proxy { elm, _, _ := s.single.Do(func() (interface{}, error) { - proxies := getProvidersProxies(s.providers) + proxies := getProvidersProxies(s.providers, touch) for _, proxy := range proxies { if proxy.Name() == s.selected { return proxy, nil diff --git a/adapters/outboundgroup/urltest.go b/adapters/outboundgroup/urltest.go index 1074a42a03..4b9539f1f6 100644 --- a/adapters/outboundgroup/urltest.go +++ b/adapters/outboundgroup/urltest.go @@ -30,11 +30,11 @@ type URLTest struct { } func (u *URLTest) Now() string { - return u.fast().Name() + return u.fast(false).Name() } func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { - c, err = u.fast().DialContext(ctx, metadata) + c, err = u.fast(true).DialContext(ctx, metadata) if err == nil { c.AppendToChains(u) } @@ -42,7 +42,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Co } func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { - pc, err := u.fast().DialUDP(metadata) + pc, err := u.fast(true).DialUDP(metadata) if err == nil { pc.AppendToChains(u) } @@ -50,20 +50,20 @@ func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy { - return u.fast() + return u.fast(true) } -func (u *URLTest) proxies() []C.Proxy { +func (u *URLTest) proxies(touch bool) []C.Proxy { elm, _, _ := u.single.Do(func() (interface{}, error) { - return getProvidersProxies(u.providers), nil + return getProvidersProxies(u.providers, touch), nil }) return elm.([]C.Proxy) } -func (u *URLTest) fast() C.Proxy { +func (u *URLTest) fast(touch bool) C.Proxy { elm, _, _ := u.fastSingle.Do(func() (interface{}, error) { - proxies := u.proxies() + proxies := u.proxies(touch) fast := proxies[0] min := fast.LastDelay() for _, proxy := range proxies[1:] { @@ -94,12 +94,12 @@ func (u *URLTest) SupportUDP() bool { return false } - return u.fast().SupportUDP() + return u.fast(false).SupportUDP() } func (u *URLTest) MarshalJSON() ([]byte, error) { var all []string - for _, proxy := range u.proxies() { + for _, proxy := range u.proxies(false) { all = append(all, proxy.Name()) } return json.Marshal(map[string]interface{}{ diff --git a/adapters/provider/healthcheck.go b/adapters/provider/healthcheck.go index 61f343b1b4..d741ed7f3c 100644 --- a/adapters/provider/healthcheck.go +++ b/adapters/provider/healthcheck.go @@ -5,6 +5,8 @@ import ( "time" C "github.com/Dreamacro/clash/constant" + + "go.uber.org/atomic" ) const ( @@ -17,10 +19,12 @@ type HealthCheckOption struct { } type HealthCheck struct { - url string - proxies []C.Proxy - interval uint - done chan struct{} + url string + proxies []C.Proxy + interval uint + lazy bool + lastTouch *atomic.Int64 + done chan struct{} } func (hc *HealthCheck) process() { @@ -30,7 +34,10 @@ func (hc *HealthCheck) process() { for { select { case <-ticker.C: - hc.check() + now := time.Now().Unix() + if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) { + hc.check() + } case <-hc.done: ticker.Stop() return @@ -46,6 +53,10 @@ func (hc *HealthCheck) auto() bool { return hc.interval != 0 } +func (hc *HealthCheck) touch() { + hc.lastTouch.Store(time.Now().Unix()) +} + func (hc *HealthCheck) check() { ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) for _, proxy := range hc.proxies { @@ -60,11 +71,13 @@ func (hc *HealthCheck) close() { hc.done <- struct{}{} } -func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck { +func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *HealthCheck { return &HealthCheck{ - proxies: proxies, - url: url, - interval: interval, - done: make(chan struct{}, 1), + proxies: proxies, + url: url, + interval: interval, + lazy: lazy, + lastTouch: atomic.NewInt64(0), + done: make(chan struct{}, 1), } } diff --git a/adapters/provider/parser.go b/adapters/provider/parser.go index e662b67964..1c9b2e6871 100644 --- a/adapters/provider/parser.go +++ b/adapters/provider/parser.go @@ -17,6 +17,7 @@ type healthCheckSchema struct { Enable bool `provider:"enable"` URL string `provider:"url"` Interval int `provider:"interval"` + Lazy bool `provider:"lazy,omitempty"` } type proxyProviderSchema struct { @@ -30,7 +31,11 @@ type proxyProviderSchema struct { func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) { decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) - schema := &proxyProviderSchema{} + schema := &proxyProviderSchema{ + HealthCheck: healthCheckSchema{ + Lazy: true, + }, + } if err := decoder.Decode(mapping, schema); err != nil { return nil, err } @@ -39,7 +44,7 @@ func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvi if schema.HealthCheck.Enable { hcInterval = uint(schema.HealthCheck.Interval) } - hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval) + hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy) path := C.Path.Resolve(schema.Path) diff --git a/adapters/provider/provider.go b/adapters/provider/provider.go index 09d0543032..756a89d12e 100644 --- a/adapters/provider/provider.go +++ b/adapters/provider/provider.go @@ -50,6 +50,9 @@ type Provider interface { type ProxyProvider interface { Provider Proxies() []C.Proxy + // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. + // Commonly used in Dial and DialUDP + ProxiesWithTouch() []C.Proxy HealthCheck() } @@ -112,6 +115,11 @@ func (pp *proxySetProvider) Proxies() []C.Proxy { return pp.proxies } +func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy { + pp.healthCheck.touch() + return pp.Proxies() +} + func proxiesParse(buf []byte) (interface{}, error) { schema := &ProxySchema{} @@ -223,6 +231,11 @@ func (cp *compatibleProvider) Proxies() []C.Proxy { return cp.proxies } +func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy { + cp.healthCheck.touch() + return cp.Proxies() +} + func stopCompatibleProvider(pd *CompatibleProvider) { pd.healthCheck.close() } diff --git a/config/config.go b/config/config.go index 99d48b92a0..38a55915c9 100644 --- a/config/config.go +++ b/config/config.go @@ -345,7 +345,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ for _, v := range proxyList { ps = append(ps, proxies[v]) } - hc := provider.NewHealthCheck(ps, "", 0) + hc := provider.NewHealthCheck(ps, "", 0, true) pd, _ := provider.NewCompatibleProvider(provider.ReservedName, ps, hc) providersMap[provider.ReservedName] = pd From 97581148b5312da1368fa7c9e9ae1904f72adcad Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 19 Nov 2020 00:56:36 +0800 Subject: [PATCH 505/535] Fix: static check --- proxy/redir/tproxy_other.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/redir/tproxy_other.go b/proxy/redir/tproxy_other.go index 88e4de50bb..c20ea7d728 100644 --- a/proxy/redir/tproxy_other.go +++ b/proxy/redir/tproxy_other.go @@ -8,5 +8,5 @@ import ( ) func setsockopt(rc syscall.RawConn, addr string) error { - return errors.New("Not supported on current platform") + return errors.New("not supported on current platform") } From 34febc45798c9d977f9c052b4c604d98995a8324 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 20 Nov 2020 00:27:37 +0800 Subject: [PATCH 506/535] Chore: more detailed error when dial failed --- tunnel/tunnel.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index e6a274b0a9..8678d47c7a 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -218,7 +218,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { rawPc, err := proxy.DialUDP(metadata) if err != nil { - log.Warnln("[UDP] dial %s error: %s", proxy.Name(), err.Error()) + log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) return } pc := newUDPTracker(rawPc, DefaultManager, metadata, rule) @@ -257,13 +257,13 @@ func handleTCPConn(localConn C.ServerAdapter) { proxy, rule, err := resolveMetadata(metadata) if err != nil { - log.Warnln("Parse metadata failed: %v", err) + log.Warnln("[Metadata] parse failed: %s", err.Error()) return } remoteConn, err := proxy.Dial(metadata) if err != nil { - log.Warnln("dial %s error: %s", proxy.Name(), err.Error()) + log.Warnln("dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) return } remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule) From 1e5593f1a9e11714fbeff1d0adecfe68f48a697d Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 20 Nov 2020 20:36:20 +0800 Subject: [PATCH 507/535] Chore: update dependencies --- go.mod | 10 +++++----- go.sum | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 455ca0ecc2..f9365e6434 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,14 @@ require ( github.com/go-chi/render v1.0.1 github.com/gofrs/uuid v3.3.0+incompatible github.com/gorilla/websocket v1.4.2 - github.com/miekg/dns v1.1.34 + github.com/miekg/dns v1.1.35 github.com/oschwald/geoip2-golang v1.4.0 github.com/sirupsen/logrus v1.7.0 github.com/stretchr/testify v1.6.1 go.uber.org/atomic v1.7.0 - golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 - golang.org/x/net v0.0.0-20201020065357-d65d470038a5 - golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 - golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 + golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index f99949d59c..889ba928d1 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6 github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/miekg/dns v1.1.34 h1:SgTzfkN+oLoIHF1bgUP+C71mzuDl3AhLApHzCCIAMWM= -github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= @@ -35,25 +35,26 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201020065357-d65d470038a5 h1:KrxvpY64uUzANd9wKWr6ZAsufiii93XnvXaeikyCJ2g= -golang.org/x/net v0.0.0-20201020065357-d65d470038a5/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 h1:Bx6FllMpG4NWDOfhMBz1VR2QYNp/SAOHPIAsaVmxfPo= -golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13 h1:5jaG59Zhd+8ZXe8C+lgiAGqkOaZBruqrWclLkgAww34= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= From bea2ee8bf23412bbbb7b19386059fcbb1db87e27 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 22 Nov 2020 19:12:36 +0800 Subject: [PATCH 508/535] Chore: log rule msg on dial error --- tunnel/tunnel.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 8678d47c7a..9844e8f6f5 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -218,7 +218,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) { rawPc, err := proxy.DialUDP(metadata) if err != nil { - log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) + log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) return } pc := newUDPTracker(rawPc, DefaultManager, metadata, rule) @@ -263,7 +263,7 @@ func handleTCPConn(localConn C.ServerAdapter) { remoteConn, err := proxy.Dial(metadata) if err != nil { - log.Warnln("dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) + log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) return } remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule) From 994cbff215622172e7a17dfc51ccfed4f635d128 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 22 Nov 2020 23:38:12 +0800 Subject: [PATCH 509/535] Fix: should not log rule when rule = nil --- tunnel/tunnel.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 9844e8f6f5..1339701a50 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -218,7 +218,11 @@ func handleUDPConn(packet *inbound.PacketAdapter) { rawPc, err := proxy.DialUDP(metadata) if err != nil { - log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) + if rule == nil { + log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) + } else { + log.Warnln("[UDP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) + } return } pc := newUDPTracker(rawPc, DefaultManager, metadata, rule) @@ -263,7 +267,11 @@ func handleTCPConn(localConn C.ServerAdapter) { remoteConn, err := proxy.Dial(metadata) if err != nil { - log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) + if rule == nil { + log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.String(), err.Error()) + } else { + log.Warnln("[TCP] dial %s (match %s/%s) to %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.String(), err.Error()) + } return } remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule) From 0d33dc3eb9bce2d89e54406e8dea275adb99e4b0 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Tue, 24 Nov 2020 22:52:23 +0800 Subject: [PATCH 510/535] Chore: health checks return immediately if completed (#1097) --- adapters/provider/healthcheck.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/adapters/provider/healthcheck.go b/adapters/provider/healthcheck.go index d741ed7f3c..98f934e42a 100644 --- a/adapters/provider/healthcheck.go +++ b/adapters/provider/healthcheck.go @@ -2,6 +2,7 @@ package provider import ( "context" + "sync" "time" C "github.com/Dreamacro/clash/constant" @@ -59,11 +60,18 @@ func (hc *HealthCheck) touch() { func (hc *HealthCheck) check() { ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout) + wg := &sync.WaitGroup{} + for _, proxy := range hc.proxies { - go proxy.URLTest(ctx, hc.url) + wg.Add(1) + + go func(p C.Proxy) { + p.URLTest(ctx, hc.url) + wg.Done() + }(proxy) } - <-ctx.Done() + wg.Wait() cancel() } From 4b1b494164b4b87af4ae44db1c890e64ca3fedd0 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 17 Dec 2020 22:17:27 +0800 Subject: [PATCH 511/535] Chore: move find process name to a single part --- component/process/process.go | 21 +++ .../process}/process_darwin.go | 118 ++++------------- .../process}/process_freebsd_amd64.go | 124 ++++-------------- {rules => component/process}/process_linux.go | 77 ++--------- component/process/process_other.go | 10 ++ .../process}/process_windows.go | 77 +---------- config/config.go | 4 - rules/base.go | 4 +- rules/process.go | 65 +++++++++ rules/process_other.go | 12 -- 10 files changed, 171 insertions(+), 341 deletions(-) create mode 100644 component/process/process.go rename {rules => component/process}/process_darwin.go (56%) rename {rules => component/process}/process_freebsd_amd64.go (70%) rename {rules => component/process}/process_linux.go (73%) create mode 100644 component/process/process_other.go rename {rules => component/process}/process_windows.go (75%) create mode 100644 rules/process.go delete mode 100644 rules/process_other.go diff --git a/component/process/process.go b/component/process/process.go new file mode 100644 index 0000000000..67a5df6675 --- /dev/null +++ b/component/process/process.go @@ -0,0 +1,21 @@ +package process + +import ( + "errors" + "net" +) + +var ( + ErrInvalidNetwork = errors.New("invalid network") + ErrPlatformNotSupport = errors.New("not support on this platform") + ErrNotFound = errors.New("process not found") +) + +const ( + TCP = "tcp" + UDP = "udp" +) + +func FindProcessName(network string, srcIP net.IP, srcPort int) (string, error) { + return findProcessName(network, srcIP, srcPort) +} diff --git a/rules/process_darwin.go b/component/process/process_darwin.go similarity index 56% rename from rules/process_darwin.go rename to component/process/process_darwin.go index 1af828a9fd..5157b2099f 100644 --- a/rules/process_darwin.go +++ b/component/process/process_darwin.go @@ -1,109 +1,26 @@ -package rules +package process import ( "bytes" "encoding/binary" - "errors" - "fmt" "net" "path/filepath" - "strconv" - "strings" "syscall" "unsafe" - - "github.com/Dreamacro/clash/common/cache" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" ) -// store process name for when dealing with multiple PROCESS-NAME rules -var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - -type Process struct { - adapter string - process string -} - -func (ps *Process) RuleType() C.RuleType { - return C.Process -} - -func (ps *Process) Match(metadata *C.Metadata) bool { - key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) - cached, hit := processCache.Get(key) - if !hit { - name, err := getExecPathFromAddress(metadata) - if err != nil { - log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) - } - - processCache.Set(key, name) - - cached = name - } - - return strings.EqualFold(cached.(string), ps.process) -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func NewProcess(process string, adapter string) (*Process, error) { - return &Process{ - adapter: adapter, - process: process, - }, nil -} - const ( procpidpathinfo = 0xb procpidpathinfosize = 1024 proccallnumpidinfo = 0x2 ) -func getExecPathFromPID(pid uint32) (string, error) { - buf := make([]byte, procpidpathinfosize) - _, _, errno := syscall.Syscall6( - syscall.SYS_PROC_INFO, - proccallnumpidinfo, - uintptr(pid), - procpidpathinfo, - 0, - uintptr(unsafe.Pointer(&buf[0])), - procpidpathinfosize) - if errno != 0 { - return "", errno - } - firstZero := bytes.IndexByte(buf, 0) - if firstZero <= 0 { - return "", nil - } - - return filepath.Base(string(buf[:firstZero])), nil -} - -func getExecPathFromAddress(metadata *C.Metadata) (string, error) { - ip := metadata.SrcIP - port, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return "", err - } - +func findProcessName(network string, ip net.IP, port int) (string, error) { var spath string - switch metadata.NetWork { - case C.TCP: + switch network { + case TCP: spath = "net.inet.tcp.pcblist_n" - case C.UDP: + case UDP: spath = "net.inet.udp.pcblist_n" default: return "", ErrInvalidNetwork @@ -123,7 +40,7 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { // rup8(sizeof(xinpcb_n)) + rup8(sizeof(xsocket_n)) + // 2 * rup8(sizeof(xsockbuf_n)) + rup8(sizeof(xsockstat_n)) itemSize := 384 - if metadata.NetWork == C.TCP { + if network == TCP { // rup8(sizeof(xtcpcb_n)) itemSize += 208 } @@ -161,7 +78,28 @@ func getExecPathFromAddress(metadata *C.Metadata) (string, error) { return getExecPathFromPID(pid) } - return "", errors.New("process not found") + return "", ErrNotFound +} + +func getExecPathFromPID(pid uint32) (string, error) { + buf := make([]byte, procpidpathinfosize) + _, _, errno := syscall.Syscall6( + syscall.SYS_PROC_INFO, + proccallnumpidinfo, + uintptr(pid), + procpidpathinfo, + 0, + uintptr(unsafe.Pointer(&buf[0])), + procpidpathinfosize) + if errno != 0 { + return "", errno + } + firstZero := bytes.IndexByte(buf, 0) + if firstZero <= 0 { + return "", nil + } + + return filepath.Base(string(buf[:firstZero])), nil } func readNativeUint32(b []byte) uint32 { diff --git a/rules/process_freebsd_amd64.go b/component/process/process_freebsd_amd64.go similarity index 70% rename from rules/process_freebsd_amd64.go rename to component/process/process_freebsd_amd64.go index 24c416a27b..5a80670ef9 100644 --- a/rules/process_freebsd_amd64.go +++ b/component/process/process_freebsd_amd64.go @@ -1,8 +1,7 @@ -package rules +package process import ( "encoding/binary" - "errors" "fmt" "net" "path/filepath" @@ -12,78 +11,48 @@ import ( "syscall" "unsafe" - "github.com/Dreamacro/clash/common/cache" - C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) // store process name for when dealing with multiple PROCESS-NAME rules var ( - processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - errNotFound = errors.New("process not found") - matchMeta = func(p *Process, m *C.Metadata) bool { return false } - defaultSearcher *searcher once sync.Once ) -type Process struct { - adapter string - process string -} - -func (ps *Process) RuleType() C.RuleType { - return C.Process -} - -func match(ps *Process, metadata *C.Metadata) bool { - key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) - cached, hit := processCache.Get(key) - if !hit { - name, err := getExecPathFromAddress(metadata) - if err != nil { - log.Debugln("[%s] getExecPathFromAddress error: %s", C.Process.String(), err.Error()) - } - - processCache.Set(key, name) - - cached = name - } - - return strings.EqualFold(cached.(string), ps.process) -} - -func (ps *Process) Match(metadata *C.Metadata) bool { - return matchMeta(ps, metadata) -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func NewProcess(process string, adapter string) (*Process, error) { +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { once.Do(func() { - err := initSearcher() - if err != nil { + if err := initSearcher(); err != nil { log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Warnln("All PROCESS-NAME rules will be skipped") return } - matchMeta = match }) - return &Process{ - adapter: adapter, - process: process, - }, nil + + var spath string + isTCP := network == TCP + switch network { + case TCP: + spath = "net.inet.tcp.pcblist" + case UDP: + spath = "net.inet.udp.pcblist" + default: + return "", ErrInvalidNetwork + } + + value, err := syscall.Sysctl(spath) + if err != nil { + return "", err + } + + buf := []byte(value) + pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP) + if err != nil { + return "", err + } + + return getExecPathFromPID(pid) } func getExecPathFromPID(pid uint32) (string, error) { @@ -107,41 +76,6 @@ func getExecPathFromPID(pid uint32) (string, error) { return filepath.Base(string(buf[:size-1])), nil } -func getExecPathFromAddress(metadata *C.Metadata) (string, error) { - ip := metadata.SrcIP - port, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return "", err - } - - var spath string - var isTCP bool - switch metadata.NetWork { - case C.TCP: - spath = "net.inet.tcp.pcblist" - isTCP = true - case C.UDP: - spath = "net.inet.udp.pcblist" - isTCP = false - default: - return "", ErrInvalidNetwork - } - - value, err := syscall.Sysctl(spath) - if err != nil { - return "", err - } - - buf := []byte(value) - - pid, err := defaultSearcher.Search(buf, ip, uint16(port), isTCP) - if err != nil { - return "", err - } - - return getExecPathFromPID(pid) -} - func readNativeUint32(b []byte) uint32 { return *(*uint32)(unsafe.Pointer(&b[0])) } @@ -213,7 +147,7 @@ func (s *searcher) Search(buf []byte, ip net.IP, port uint16, isTCP bool) (uint3 socket := binary.BigEndian.Uint64(buf[inp+s.socket : inp+s.socket+8]) return s.searchSocketPid(socket) } - return 0, errNotFound + return 0, ErrNotFound } func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { @@ -235,7 +169,7 @@ func (s *searcher) searchSocketPid(socket uint64) (uint32, error) { return pid, nil } } - return 0, errNotFound + return 0, ErrNotFound } func newSearcher(major int) *searcher { diff --git a/rules/process_linux.go b/component/process/process_linux.go similarity index 73% rename from rules/process_linux.go rename to component/process/process_linux.go index d883b2959d..1f651cd48a 100644 --- a/rules/process_linux.go +++ b/component/process/process_linux.go @@ -1,4 +1,4 @@ -package rules +package process import ( "bytes" @@ -9,15 +9,10 @@ import ( "net" "path" "path/filepath" - "strconv" - "strings" "syscall" "unsafe" - "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/pool" - C "github.com/Dreamacro/clash/constant" - "github.com/Dreamacro/clash/log" ) // from https://github.com/vishvananda/netlink/blob/bca67dfc8220b44ef582c9da4e9172bf1c9ec973/nl/nl_linux.go#L52-L62 @@ -30,7 +25,7 @@ func init() { } } -type SocketResolver func(metadata *C.Metadata) (inode, uid int, err error) +type SocketResolver func(network string, ip net.IP, srcPort int) (inode, uid int, err error) type ProcessNameResolver func(inode, uid int) (name string, err error) // export for android @@ -39,51 +34,6 @@ var ( DefaultProcessNameResolver ProcessNameResolver = resolveProcessNameByProcSearch ) -type Process struct { - adapter string - process string -} - -func (p *Process) RuleType() C.RuleType { - return C.Process -} - -func (p *Process) Match(metadata *C.Metadata) bool { - key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) - cached, hit := processCache.Get(key) - if !hit { - processName, err := resolveProcessName(metadata) - if err != nil { - log.Debugln("[%s] Resolve process of %s failure: %s", C.Process.String(), key, err.Error()) - } - - processCache.Set(key, processName) - - cached = processName - } - - return strings.EqualFold(cached.(string), p.process) -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func NewProcess(process string, adapter string) (*Process, error) { - return &Process{ - adapter: adapter, - process: process, - }, nil -} - const ( sizeOfSocketDiagRequest = syscall.SizeofNlMsghdr + 8 + 48 socketDiagByFamily = 20 @@ -92,10 +42,8 @@ const ( var nativeEndian binary.ByteOrder = binary.LittleEndian -var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - -func resolveProcessName(metadata *C.Metadata) (string, error) { - inode, uid, err := DefaultSocketResolver(metadata) +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { + inode, uid, err := DefaultSocketResolver(network, ip, srcPort) if err != nil { return "", err } @@ -103,31 +51,26 @@ func resolveProcessName(metadata *C.Metadata) (string, error) { return DefaultProcessNameResolver(inode, uid) } -func resolveSocketByNetlink(metadata *C.Metadata) (int, int, error) { +func resolveSocketByNetlink(network string, ip net.IP, srcPort int) (int, int, error) { var family byte var protocol byte - switch metadata.NetWork { - case C.TCP: + switch network { + case TCP: protocol = syscall.IPPROTO_TCP - case C.UDP: + case UDP: protocol = syscall.IPPROTO_UDP default: return 0, 0, ErrInvalidNetwork } - if metadata.SrcIP.To4() != nil { + if ip.To4() != nil { family = syscall.AF_INET } else { family = syscall.AF_INET6 } - srcPort, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return 0, 0, err - } - - req := packSocketDiagRequest(family, protocol, metadata.SrcIP, uint16(srcPort)) + req := packSocketDiagRequest(family, protocol, ip, uint16(srcPort)) socket, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_DGRAM, syscall.NETLINK_INET_DIAG) if err != nil { diff --git a/component/process/process_other.go b/component/process/process_other.go new file mode 100644 index 0000000000..1e0bd4478d --- /dev/null +++ b/component/process/process_other.go @@ -0,0 +1,10 @@ +// +build !darwin,!linux,!windows +// +build !freebsd !amd64 + +package process + +import "net" + +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { + return "", ErrPlatformNotSupport +} diff --git a/rules/process_windows.go b/component/process/process_windows.go similarity index 75% rename from rules/process_windows.go rename to component/process/process_windows.go index b1e4b93172..39953111da 100644 --- a/rules/process_windows.go +++ b/component/process/process_windows.go @@ -1,18 +1,13 @@ -package rules +package process import ( - "errors" "fmt" "net" "path/filepath" - "strconv" - "strings" "sync" "syscall" "unsafe" - "github.com/Dreamacro/clash/common/cache" - C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "golang.org/x/sys/windows" @@ -27,10 +22,6 @@ const ( ) var ( - processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) - errNotFound = errors.New("process not found") - matchMeta = func(p *Process, m *C.Metadata) bool { return false } - getExTcpTable uintptr getExUdpTable uintptr queryProcName uintptr @@ -67,47 +58,7 @@ func initWin32API() error { return nil } -type Process struct { - adapter string - process string -} - -func (p *Process) RuleType() C.RuleType { - return C.Process -} - -func (p *Process) Adapter() string { - return p.adapter -} - -func (p *Process) Payload() string { - return p.process -} - -func (p *Process) ShouldResolveIP() bool { - return false -} - -func match(p *Process, metadata *C.Metadata) bool { - key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) - cached, hit := processCache.Get(key) - if !hit { - processName, err := resolveProcessName(metadata) - if err != nil { - log.Debugln("[%s] Resolve process of %s failed: %s", C.Process.String(), key, err.Error()) - } - - processCache.Set(key, processName) - cached = processName - } - return strings.EqualFold(cached.(string), p.process) -} - -func (p *Process) Match(metadata *C.Metadata) bool { - return matchMeta(p, metadata) -} - -func NewProcess(process string, adapter string) (*Process, error) { +func findProcessName(network string, ip net.IP, srcPort int) (string, error) { once.Do(func() { err := initWin32API() if err != nil { @@ -115,16 +66,7 @@ func NewProcess(process string, adapter string) (*Process, error) { log.Warnln("All PROCESS-NAMES rules will be skiped") return } - matchMeta = match }) - return &Process{ - adapter: adapter, - process: process, - }, nil -} - -func resolveProcessName(metadata *C.Metadata) (string, error) { - ip := metadata.SrcIP family := windows.AF_INET if ip.To4() == nil { family = windows.AF_INET6 @@ -132,28 +74,23 @@ func resolveProcessName(metadata *C.Metadata) (string, error) { var class int var fn uintptr - switch metadata.NetWork { - case C.TCP: + switch network { + case TCP: fn = getExTcpTable class = tcpTablePidConn - case C.UDP: + case UDP: fn = getExUdpTable class = udpTablePid default: return "", ErrInvalidNetwork } - srcPort, err := strconv.Atoi(metadata.SrcPort) - if err != nil { - return "", err - } - buf, err := getTransportTable(fn, family, class) if err != nil { return "", err } - s := newSearcher(family == windows.AF_INET, metadata.NetWork == C.TCP) + s := newSearcher(family == windows.AF_INET, network == TCP) pid, err := s.Search(buf, ip, uint16(srcPort)) if err != nil { @@ -203,7 +140,7 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) { pid := readNativeUint32(row[s.pid : s.pid+4]) return pid, nil } - return 0, errNotFound + return 0, ErrNotFound } func newSearcher(isV4, isTCP bool) *searcher { diff --git a/config/config.go b/config/config.go index 38a55915c9..f89fe433fd 100644 --- a/config/config.go +++ b/config/config.go @@ -395,10 +395,6 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) { parsed, parseErr := R.ParseRule(rule[0], payload, target, params) if parseErr != nil { - if parseErr == R.ErrPlatformNotSupport { - log.Warnln("Rules[%d] [%s] don't support current OS, skip", idx, line) - continue - } return nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } diff --git a/rules/base.go b/rules/base.go index 2db3558c1e..0f2d9f29b8 100644 --- a/rules/base.go +++ b/rules/base.go @@ -5,9 +5,7 @@ import ( ) var ( - errPayload = errors.New("payload error") - ErrPlatformNotSupport = errors.New("not support on this platform") - ErrInvalidNetwork = errors.New("invalid network") + errPayload = errors.New("payload error") noResolve = "no-resolve" ) diff --git a/rules/process.go b/rules/process.go new file mode 100644 index 0000000000..3f502e7f76 --- /dev/null +++ b/rules/process.go @@ -0,0 +1,65 @@ +package rules + +import ( + "fmt" + "strconv" + "strings" + + "github.com/Dreamacro/clash/common/cache" + "github.com/Dreamacro/clash/component/process" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" +) + +var processCache = cache.NewLRUCache(cache.WithAge(2), cache.WithSize(64)) + +type Process struct { + adapter string + process string +} + +func (ps *Process) RuleType() C.RuleType { + return C.Process +} + +func (ps *Process) Match(metadata *C.Metadata) bool { + key := fmt.Sprintf("%s:%s:%s", metadata.NetWork.String(), metadata.SrcIP.String(), metadata.SrcPort) + cached, hit := processCache.Get(key) + if !hit { + srcPort, err := strconv.Atoi(metadata.SrcPort) + if err != nil { + processCache.Set(key, "") + return false + } + + name, err := process.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, srcPort) + if err != nil { + log.Debugln("[Rule] find process name %s error: %s", C.Process.String(), err.Error()) + } + + processCache.Set(key, name) + + cached = name + } + + return strings.EqualFold(cached.(string), ps.process) +} + +func (p *Process) Adapter() string { + return p.adapter +} + +func (p *Process) Payload() string { + return p.process +} + +func (p *Process) ShouldResolveIP() bool { + return false +} + +func NewProcess(process string, adapter string) (*Process, error) { + return &Process{ + adapter: adapter, + process: process, + }, nil +} diff --git a/rules/process_other.go b/rules/process_other.go deleted file mode 100644 index a392250b71..0000000000 --- a/rules/process_other.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !darwin,!linux,!windows -// +build !freebsd !amd64 - -package rules - -import ( - C "github.com/Dreamacro/clash/constant" -) - -func NewProcess(process string, adapter string) (C.Rule, error) { - return nil, ErrPlatformNotSupport -} From 532396d25c6a4b54a94d5893d55453f9bef9cca0 Mon Sep 17 00:00:00 2001 From: icpz Date: Tue, 22 Dec 2020 15:13:44 +0800 Subject: [PATCH 512/535] Fix: PROCESS-NAME rule for UDP sessions on Windows (#1140) --- component/process/process_windows.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/component/process/process_windows.go b/component/process/process_windows.go index 39953111da..ba96c7e4bd 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -133,7 +133,8 @@ func (s *searcher) Search(b []byte, ip net.IP, port uint16) (uint32, error) { } srcIP := net.IP(row[s.ip : s.ip+s.ipSize]) - if !ip.Equal(srcIP) { + // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto + if !ip.Equal(srcIP) && (!srcIP.IsUnspecified() || s.tcpState != -1) { continue } From ed27898a3391afc6f1b85e8aaf16c3e5247ebd7c Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 24 Dec 2020 13:47:56 +0800 Subject: [PATCH 513/535] Fix: snell should support the config without obfs --- adapters/outbound/shadowsocks.go | 2 +- adapters/outbound/snell.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/adapters/outbound/shadowsocks.go b/adapters/outbound/shadowsocks.go index 68c907e2cd..2028df7495 100644 --- a/adapters/outbound/shadowsocks.go +++ b/adapters/outbound/shadowsocks.go @@ -40,7 +40,7 @@ type ShadowSocksOption struct { } type simpleObfsOption struct { - Mode string `obfs:"mode"` + Mode string `obfs:"mode,omitempty"` Host string `obfs:"host,omitempty"` } diff --git a/adapters/outbound/snell.go b/adapters/outbound/snell.go index edc0ddd750..8cd2cdb141 100644 --- a/adapters/outbound/snell.go +++ b/adapters/outbound/snell.go @@ -87,7 +87,10 @@ func NewSnell(option SnellOption) (*Snell, error) { return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err) } - if obfsOption.Mode != "tls" && obfsOption.Mode != "http" { + switch obfsOption.Mode { + case "tls", "http", "": + break + default: return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode) } From 5dfe7f8561da277f5dafa3b7e086700b7d824fec Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 24 Dec 2020 14:54:48 +0800 Subject: [PATCH 514/535] Fix: handle keep alive on http connect proxy --- proxy/http/server.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/proxy/http/server.go b/proxy/http/server.go index 8eecf1e7c9..7cf2b1c18c 100644 --- a/proxy/http/server.go +++ b/proxy/http/server.go @@ -72,21 +72,29 @@ func canActivate(loginStr string, authenticator auth.Authenticator, cache *cache func HandleConn(conn net.Conn, cache *cache.Cache) { br := bufio.NewReader(conn) + +keepAlive: request, err := http.ReadRequest(br) if err != nil || request.URL.Host == "" { conn.Close() return } + keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" authenticator := authStore.Authenticator() if authenticator != nil { if authStrings := strings.Split(request.Header.Get("Proxy-Authorization"), " "); len(authStrings) != 2 { conn.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic\r\n\r\n")) - conn.Close() + if keepAlive { + goto keepAlive + } return } else if !canActivate(authStrings[1], authenticator, cache) { conn.Write([]byte("HTTP/1.1 403 Forbidden\r\n\r\n")) log.Infoln("Auth failed from %s", conn.RemoteAddr().String()) + if keepAlive { + goto keepAlive + } conn.Close() return } @@ -95,6 +103,7 @@ func HandleConn(conn net.Conn, cache *cache.Cache) { if request.Method == http.MethodConnect { _, err := conn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) if err != nil { + conn.Close() return } tunnel.Add(adapters.NewHTTPS(request, conn)) From de7656a78719797a89f76992614a5355d28fde8d Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 27 Dec 2020 00:14:24 +0800 Subject: [PATCH 515/535] Chore: update premium README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 38b0716d3e..da789f2af6 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,18 @@ - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Comprehensive HTTP RESTful API controller +## Premium Features + +- TUN mode on macOS, Linux and Windows. [Doc](https://github.com/Dreamacro/clash/wiki/premium-core-features#tun-device) +- Match your tunnel by [Script](https://github.com/Dreamacro/clash/wiki/premium-core-features#script) +- [Rule Provider](https://github.com/Dreamacro/clash/wiki/premium-core-features#rule-providers) + ## Getting Started Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). +## Premium Release +[Release](https://github.com/Dreamacro/clash/releases/tag/premium) + ## Credits * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) From 3600077f3b6997c7b9e7a42a69afde6b17a5e5b1 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 27 Dec 2020 18:59:59 +0800 Subject: [PATCH 516/535] Chore: update dependencies --- go.mod | 10 +++++----- go.sum | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index f9365e6434..240aee601a 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,9 @@ require ( github.com/sirupsen/logrus v1.7.0 github.com/stretchr/testify v1.6.1 go.uber.org/atomic v1.7.0 - golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b - golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 - golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 - gopkg.in/yaml.v2 v2.3.0 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad + golang.org/x/net v0.0.0-20201224014010-6772e930b67b + golang.org/x/sync v0.0.0-20201207232520-09787c993a3a + golang.org/x/sys v0.0.0-20201223074533-0d417f636930 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 889ba928d1..24cbd209a7 100644 --- a/go.sum +++ b/go.sum @@ -34,27 +34,28 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= +golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= @@ -65,7 +66,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 09c28e0355c2d5be5328e76fe2e0e81d64291269 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 28 Dec 2020 22:24:58 +0800 Subject: [PATCH 517/535] Fix: fallback bind fn should not bind global unicast --- component/dialer/bind.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/component/dialer/bind.go b/component/dialer/bind.go index cb24a8b625..55e5f1634c 100644 --- a/component/dialer/bind.go +++ b/component/dialer/bind.go @@ -52,6 +52,10 @@ func lookupUDPAddr(ip net.IP, addrs []net.Addr) (*net.UDPAddr, error) { } func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name string) error { + if !ip.IsGlobalUnicast() { + return nil + } + iface, err := net.InterfaceByName(name) if err != nil { return err From 02d029dd2d7ad75ff493ca754c806d70abcda1f3 Mon Sep 17 00:00:00 2001 From: Keyi Xie Date: Tue, 29 Dec 2020 11:28:22 +0800 Subject: [PATCH 518/535] Fix: close http Response body on provider (#1154) --- adapters/provider/vehicle.go | 1 + component/vmess/h2.go | 1 + tunnel/connection.go | 2 ++ 3 files changed, 4 insertions(+) diff --git a/adapters/provider/vehicle.go b/adapters/provider/vehicle.go index 175f50cead..13d898fc8d 100644 --- a/adapters/provider/vehicle.go +++ b/adapters/provider/vehicle.go @@ -107,6 +107,7 @@ func (h *HTTPVehicle) Read() ([]byte, error) { if err != nil { return nil, err } + defer resp.Body.Close() buf, err := ioutil.ReadAll(resp.Body) if err != nil { diff --git a/component/vmess/h2.go b/component/vmess/h2.go index b814ae9b5f..2c4c47fa28 100644 --- a/component/vmess/h2.go +++ b/component/vmess/h2.go @@ -46,6 +46,7 @@ func (hc *h2Conn) establishConn() error { }, } + // it will be close at : `func (hc *h2Conn) Close() error` res, err := hc.ClientConn.RoundTrip(&req) if err != nil { return err diff --git a/tunnel/connection.go b/tunnel/connection.go index 1e8ee1df5e..15a6255546 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -34,6 +34,8 @@ func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) { } handleResponse: + // resp will be closed after we call resp.Write() + // see https://golang.org/pkg/net/http/#Response.Write resp, err := http.ReadResponse(outboundReader, req) if err != nil { break From 9619c3fb20f29a12f7069f323febca92c6ace30e Mon Sep 17 00:00:00 2001 From: Jason Lyu Date: Thu, 31 Dec 2020 18:58:03 +0800 Subject: [PATCH 519/535] Fix: support unspecified UDP bind address (#1159) --- adapters/outbound/socks5.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/adapters/outbound/socks5.go b/adapters/outbound/socks5.go index 78a5946882..eb65d51315 100644 --- a/adapters/outbound/socks5.go +++ b/adapters/outbound/socks5.go @@ -122,7 +122,21 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) { pc.Close() }() - return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindAddr.UDPAddr(), tcpConn: c}, ss), nil + // Support unspecified UDP bind address. + bindUDPAddr := bindAddr.UDPAddr() + if bindUDPAddr == nil { + err = errors.New("invalid UDP bind address") + return + } else if bindUDPAddr.IP.IsUnspecified() { + serverAddr, err := resolveUDPAddr("udp", ss.Addr()) + if err != nil { + return nil, err + } + + bindUDPAddr.IP = serverAddr.IP + } + + return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil } func NewSocks5(option Socks5Option) *Socks5 { From 6fedd7ec844e58a5cc95f4146134cccdf45201bc Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Mon, 4 Jan 2021 00:51:53 +0800 Subject: [PATCH 520/535] Fix: dns client should not bind local address --- dns/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns/client.go b/dns/client.go index a40888dddf..151bee24a0 100644 --- a/dns/client.go +++ b/dns/client.go @@ -39,7 +39,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err return nil, err } - if dialer.DialHook != nil { + if ip != nil && ip.IsGlobalUnicast() && dialer.DialHook != nil { network := "udp" if strings.HasPrefix(c.Client.Net, "tcp") { network = "tcp" From b25009cde7d948dc581de67443d84f1b89241d08 Mon Sep 17 00:00:00 2001 From: Student414 <805447391@qq.com> Date: Wed, 6 Jan 2021 14:20:15 +0800 Subject: [PATCH 521/535] Fix: unnecessary write operation on provider (#1170) --- adapters/provider/fetcher.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adapters/provider/fetcher.go b/adapters/provider/fetcher.go index c0f18c0128..1e2551186d 100644 --- a/adapters/provider/fetcher.go +++ b/adapters/provider/fetcher.go @@ -108,8 +108,10 @@ func (f *fetcher) Update() (interface{}, bool, error) { return nil, false, err } - if err := safeWrite(f.vehicle.Path(), buf); err != nil { - return nil, false, err + if f.vehicle.Type() != File { + if err := safeWrite(f.vehicle.Path(), buf); err != nil { + return nil, false, err + } } f.updatedAt = &now From b6ee47a541c364a1487233a06be3cc20e2233048 Mon Sep 17 00:00:00 2001 From: Fndroid Date: Thu, 7 Jan 2021 13:59:39 +0800 Subject: [PATCH 522/535] Fix: get general should return correct result (#1172) --- config/config.go | 2 +- hub/executor/executor.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index f89fe433fd..8408fb52d5 100644 --- a/config/config.go +++ b/config/config.go @@ -30,7 +30,7 @@ type General struct { Mode T.TunnelMode `json:"mode"` LogLevel log.LogLevel `json:"log-level"` IPv6 bool `json:"ipv6"` - Interface string `json:"interface-name"` + Interface string `json:"-"` } // Inbound diff --git a/hub/executor/executor.go b/hub/executor/executor.go index d5b96a2776..4bf40f1711 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -94,6 +94,7 @@ func GetGeneral() *config.General { }, Mode: tunnel.Mode(), LogLevel: log.Level(), + IPv6: !resolver.DisableIPv6, } return general From e4cdea2111d3bfa8608b36ab741d9610ad10f949 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Wed, 13 Jan 2021 17:30:54 +0800 Subject: [PATCH 523/535] chore: use singleDo to get interface info --- component/dialer/bind.go | 18 ++++++++++++++---- component/dialer/bind_darwin.go | 14 ++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/component/dialer/bind.go b/component/dialer/bind.go index 55e5f1634c..0ad520683e 100644 --- a/component/dialer/bind.go +++ b/component/dialer/bind.go @@ -3,8 +3,14 @@ package dialer import ( "errors" "net" + "time" + + "github.com/Dreamacro/clash/common/singledo" ) +// In some OS, such as Windows, it takes a little longer to get interface information +var ifaceSingle = singledo.NewSingle(time.Second * 20) + var ( errPlatformNotSupport = errors.New("unsupport platform") ) @@ -56,12 +62,14 @@ func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name st return nil } - iface, err := net.InterfaceByName(name) + iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { + return net.InterfaceByName(name) + }) if err != nil { return err } - addrs, err := iface.Addrs() + addrs, err := iface.(*net.Interface).Addrs() if err != nil { return err } @@ -85,12 +93,14 @@ func fallbackBindToDialer(dialer *net.Dialer, network string, ip net.IP, name st } func fallbackBindToListenConfig(name string) (string, error) { - iface, err := net.InterfaceByName(name) + iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { + return net.InterfaceByName(name) + }) if err != nil { return "", err } - addrs, err := iface.Addrs() + addrs, err := iface.(*net.Interface).Addrs() if err != nil { return "", err } diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go index 469054ca1c..0ce5054128 100644 --- a/component/dialer/bind_darwin.go +++ b/component/dialer/bind_darwin.go @@ -29,23 +29,25 @@ func bindControl(ifaceIdx int) controlFn { } func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error { - iface, err := net.InterfaceByName(ifaceName) + iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { + return net.InterfaceByName(ifaceName) + }) if err != nil { return err } - dialer.Control = bindControl(iface.Index) - + dialer.Control = bindControl(iface.(*net.Interface).Index) return nil } func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error { - iface, err := net.InterfaceByName(ifaceName) + iface, err, _ := ifaceSingle.Do(func() (interface{}, error) { + return net.InterfaceByName(ifaceName) + }) if err != nil { return err } - lc.Control = bindControl(iface.Index) - + lc.Control = bindControl(iface.(*net.Interface).Index) return nil } From ff430df8454a1662f78c4d0935bde66066eb224e Mon Sep 17 00:00:00 2001 From: goomadao <39483078+goomadao@users.noreply.github.com> Date: Wed, 13 Jan 2021 23:35:41 +0800 Subject: [PATCH 524/535] Fix: connectivity of ssr auth_chain_(ab) protocol (#1180) --- component/ssr/protocol/auth_chain_a.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/component/ssr/protocol/auth_chain_a.go b/component/ssr/protocol/auth_chain_a.go index d14fc817e0..2028ba7987 100644 --- a/component/ssr/protocol/auth_chain_a.go +++ b/component/ssr/protocol/auth_chain_a.go @@ -230,7 +230,7 @@ func (a *authChain) initUserKeyAndID() { if len(params) >= 2 { if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) - a.userKey = []byte(params[1])[:len(a.userKey)] + a.userKey = []byte(params[1]) } } @@ -292,6 +292,10 @@ func (a *authChain) packAuthData(data []byte) (outData []byte) { defer a.mutex.Unlock() a.connectionID++ if a.connectionID > 0xFF000000 { + a.clientID = nil + } + if len(a.clientID) == 0 { + a.clientID = make([]byte, 4) rand.Read(a.clientID) b := make([]byte, 4) rand.Read(b) @@ -351,7 +355,7 @@ func (a *authChain) packAuthData(data []byte) (outData []byte) { // data chunkLength, randLength := a.packedDataLen(data) - if chunkLength <= 1500 { + if chunkLength+authHeadLength <= cap(outData) { outData = outData[:authHeadLength+chunkLength] } else { newOutData := make([]byte, authHeadLength+chunkLength) From 35925cb3da21751e3df21c70b11c5387a7e2be10 Mon Sep 17 00:00:00 2001 From: Junjie Yuan Date: Wed, 20 Jan 2021 16:08:24 +0800 Subject: [PATCH 525/535] Chore: standardized Dockerfile label (#1191) Signed-off-by: Junjie Yuan --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 653d08fccd..90a651b986 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN go mod download && \ mv ./bin/clash-docker /clash FROM alpine:latest -LABEL org.opencontainers.image.source https://github.com/Dreamacro/clash +LABEL org.opencontainers.image.source="https://github.com/Dreamacro/clash" RUN apk add --no-cache ca-certificates COPY --from=builder /Country.mmdb /root/.config/clash/ From f4de055aa174000024d439e00a1301b99d476478 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sat, 23 Jan 2021 14:49:46 +0800 Subject: [PATCH 526/535] Refactor: make inbound request contextual --- adapters/inbound/http.go | 23 +++------------ adapters/inbound/https.go | 10 +++---- adapters/inbound/socket.go | 21 +++----------- constant/adapters.go | 14 +++++---- constant/context.go | 23 +++++++++++++++ context/conn.go | 39 +++++++++++++++++++++++++ context/dns.go | 41 +++++++++++++++++++++++++++ context/http.go | 47 +++++++++++++++++++++++++++++++ context/packetconn.go | 43 ++++++++++++++++++++++++++++ dns/middleware.go | 28 ++++++++++-------- dns/resolver.go | 19 ++----------- dns/server.go | 18 ++++++++---- dns/util.go | 15 ++++++++++ hub/route/connections.go | 10 +++---- hub/route/server.go | 4 +-- tunnel/connection.go | 18 ++++++------ tunnel/{ => statistic}/manager.go | 2 +- tunnel/{ => statistic}/tracker.go | 6 ++-- tunnel/tunnel.go | 44 ++++++++++++++--------------- 19 files changed, 301 insertions(+), 124 deletions(-) create mode 100644 constant/context.go create mode 100644 context/conn.go create mode 100644 context/dns.go create mode 100644 context/http.go create mode 100644 context/packetconn.go rename tunnel/{ => statistic}/manager.go (99%) rename tunnel/{ => statistic}/tracker.go (95%) diff --git a/adapters/inbound/http.go b/adapters/inbound/http.go index 867dab22b2..ee26ac7c0e 100644 --- a/adapters/inbound/http.go +++ b/adapters/inbound/http.go @@ -6,33 +6,18 @@ import ( "strings" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/context" ) -// HTTPAdapter is a adapter for HTTP connection -type HTTPAdapter struct { - net.Conn - metadata *C.Metadata - R *http.Request -} - -// Metadata return destination metadata -func (h *HTTPAdapter) Metadata() *C.Metadata { - return h.metadata -} - -// NewHTTP is HTTPAdapter generator -func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter { +// NewHTTP recieve normal http request and return HTTPContext +func NewHTTP(request *http.Request, conn net.Conn) *context.HTTPContext { metadata := parseHTTPAddr(request) metadata.Type = C.HTTP if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { metadata.SrcIP = ip metadata.SrcPort = port } - return &HTTPAdapter{ - metadata: metadata, - R: request, - Conn: conn, - } + return context.NewHTTPContext(conn, request, metadata) } // RemoveHopByHopHeaders remove hop-by-hop header diff --git a/adapters/inbound/https.go b/adapters/inbound/https.go index 7a2199801b..bb2bd97d77 100644 --- a/adapters/inbound/https.go +++ b/adapters/inbound/https.go @@ -5,18 +5,16 @@ import ( "net/http" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/context" ) -// NewHTTPS is HTTPAdapter generator -func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter { +// NewHTTPS recieve CONNECT request and return ConnContext +func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext { metadata := parseHTTPAddr(request) metadata.Type = C.HTTPCONNECT if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil { metadata.SrcIP = ip metadata.SrcPort = port } - return &SocketAdapter{ - metadata: metadata, - Conn: conn, - } + return context.NewConnContext(conn, metadata) } diff --git a/adapters/inbound/socket.go b/adapters/inbound/socket.go index 134be489f4..1370b701d8 100644 --- a/adapters/inbound/socket.go +++ b/adapters/inbound/socket.go @@ -5,21 +5,11 @@ import ( "github.com/Dreamacro/clash/component/socks5" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/context" ) -// SocketAdapter is a adapter for socks and redir connection -type SocketAdapter struct { - net.Conn - metadata *C.Metadata -} - -// Metadata return destination metadata -func (s *SocketAdapter) Metadata() *C.Metadata { - return s.metadata -} - -// NewSocket is SocketAdapter generator -func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter { +// NewSocket recieve TCP inbound and return ConnContext +func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnContext { metadata := parseSocksAddr(target) metadata.NetWork = C.TCP metadata.Type = source @@ -28,8 +18,5 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *SocketAdapter metadata.SrcPort = port } - return &SocketAdapter{ - Conn: conn, - metadata: metadata, - } + return context.NewConnContext(conn, metadata) } diff --git a/constant/adapters.go b/constant/adapters.go index 4ba891deed..7456c304ab 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -27,11 +27,6 @@ const ( LoadBalance ) -type ServerAdapter interface { - net.Conn - Metadata() *Metadata -} - type Connection interface { Chains() Chain AppendToChains(adapter ProxyAdapter) @@ -50,6 +45,15 @@ func (c Chain) String() string { } } +func (c Chain) Last() string { + switch len(c) { + case 0: + return "" + default: + return c[0] + } +} + type Conn interface { net.Conn Connection diff --git a/constant/context.go b/constant/context.go new file mode 100644 index 0000000000..e641ed1485 --- /dev/null +++ b/constant/context.go @@ -0,0 +1,23 @@ +package constant + +import ( + "net" + + "github.com/gofrs/uuid" +) + +type PlainContext interface { + ID() uuid.UUID +} + +type ConnContext interface { + PlainContext + Metadata() *Metadata + Conn() net.Conn +} + +type PacketConnContext interface { + PlainContext + Metadata() *Metadata + PacketConn() net.PacketConn +} diff --git a/context/conn.go b/context/conn.go new file mode 100644 index 0000000000..ee0f3a9da2 --- /dev/null +++ b/context/conn.go @@ -0,0 +1,39 @@ +package context + +import ( + "net" + + C "github.com/Dreamacro/clash/constant" + + "github.com/gofrs/uuid" +) + +type ConnContext struct { + id uuid.UUID + metadata *C.Metadata + conn net.Conn +} + +func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext { + id, _ := uuid.NewV4() + return &ConnContext{ + id: id, + metadata: metadata, + conn: conn, + } +} + +// ID implement C.ConnContext ID +func (c *ConnContext) ID() uuid.UUID { + return c.id +} + +// Metadata implement C.ConnContext Metadata +func (c *ConnContext) Metadata() *C.Metadata { + return c.metadata +} + +// Conn implement C.ConnContext Conn +func (c *ConnContext) Conn() net.Conn { + return c.conn +} diff --git a/context/dns.go b/context/dns.go new file mode 100644 index 0000000000..0be4a1fc2f --- /dev/null +++ b/context/dns.go @@ -0,0 +1,41 @@ +package context + +import ( + "github.com/gofrs/uuid" + "github.com/miekg/dns" +) + +const ( + DNSTypeHost = "host" + DNSTypeFakeIP = "fakeip" + DNSTypeRaw = "raw" +) + +type DNSContext struct { + id uuid.UUID + msg *dns.Msg + tp string +} + +func NewDNSContext(msg *dns.Msg) *DNSContext { + id, _ := uuid.NewV4() + return &DNSContext{ + id: id, + msg: msg, + } +} + +// ID implement C.PlainContext ID +func (c *DNSContext) ID() uuid.UUID { + return c.id +} + +// SetType set type of response +func (c *DNSContext) SetType(tp string) { + c.tp = tp +} + +// Type return type of response +func (c *DNSContext) Type() string { + return c.tp +} diff --git a/context/http.go b/context/http.go new file mode 100644 index 0000000000..292f7d9702 --- /dev/null +++ b/context/http.go @@ -0,0 +1,47 @@ +package context + +import ( + "net" + "net/http" + + C "github.com/Dreamacro/clash/constant" + + "github.com/gofrs/uuid" +) + +type HTTPContext struct { + id uuid.UUID + metadata *C.Metadata + conn net.Conn + req *http.Request +} + +func NewHTTPContext(conn net.Conn, req *http.Request, metadata *C.Metadata) *HTTPContext { + id, _ := uuid.NewV4() + return &HTTPContext{ + id: id, + metadata: metadata, + conn: conn, + req: req, + } +} + +// ID implement C.ConnContext ID +func (hc *HTTPContext) ID() uuid.UUID { + return hc.id +} + +// Metadata implement C.ConnContext Metadata +func (hc *HTTPContext) Metadata() *C.Metadata { + return hc.metadata +} + +// Conn implement C.ConnContext Conn +func (hc *HTTPContext) Conn() net.Conn { + return hc.conn +} + +// Request return the http request struct +func (hc *HTTPContext) Request() *http.Request { + return hc.req +} diff --git a/context/packetconn.go b/context/packetconn.go new file mode 100644 index 0000000000..3b0051415d --- /dev/null +++ b/context/packetconn.go @@ -0,0 +1,43 @@ +package context + +import ( + "net" + + C "github.com/Dreamacro/clash/constant" + + "github.com/gofrs/uuid" +) + +type PacketConnContext struct { + id uuid.UUID + metadata *C.Metadata + packetConn net.PacketConn +} + +func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext { + id, _ := uuid.NewV4() + return &PacketConnContext{ + id: id, + metadata: metadata, + } +} + +// ID implement C.PacketConnContext ID +func (pc *PacketConnContext) ID() uuid.UUID { + return pc.id +} + +// Metadata implement C.PacketConnContext Metadata +func (pc *PacketConnContext) Metadata() *C.Metadata { + return pc.metadata +} + +// PacketConn implement C.PacketConnContext PacketConn +func (pc *PacketConnContext) PacketConn() net.PacketConn { + return pc.packetConn +} + +// InjectPacketConn injectPacketConn manually +func (pc *PacketConnContext) InjectPacketConn(pconn C.PacketConn) { + pc.packetConn = pconn +} diff --git a/dns/middleware.go b/dns/middleware.go index 9a119e7873..782c0ef078 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -8,26 +8,27 @@ import ( "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/trie" + "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" ) -type handler func(r *D.Msg) (*D.Msg, error) +type handler func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) type middleware func(next handler) handler func withHosts(hosts *trie.DomainTrie) middleware { return func(next handler) handler { - return func(r *D.Msg) (*D.Msg, error) { + return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { q := r.Question[0] if !isIPRequest(q) { - return next(r) + return next(ctx, r) } record := hosts.Search(strings.TrimRight(q.Name, ".")) if record == nil { - return next(r) + return next(ctx, r) } ip := record.Data.(net.IP) @@ -46,9 +47,10 @@ func withHosts(hosts *trie.DomainTrie) middleware { msg.Answer = []D.RR{rr} } else { - return next(r) + return next(ctx, r) } + ctx.SetType(context.DNSTypeHost) msg.SetRcode(r, D.RcodeSuccess) msg.Authoritative = true msg.RecursionAvailable = true @@ -60,14 +62,14 @@ func withHosts(hosts *trie.DomainTrie) middleware { func withMapping(mapping *cache.LruCache) middleware { return func(next handler) handler { - return func(r *D.Msg) (*D.Msg, error) { + return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { q := r.Question[0] if !isIPRequest(q) { - return next(r) + return next(ctx, r) } - msg, err := next(r) + msg, err := next(ctx, r) if err != nil { return nil, err } @@ -99,12 +101,12 @@ func withMapping(mapping *cache.LruCache) middleware { func withFakeIP(fakePool *fakeip.Pool) middleware { return func(next handler) handler { - return func(r *D.Msg) (*D.Msg, error) { + return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { q := r.Question[0] host := strings.TrimRight(q.Name, ".") if fakePool.LookupHost(host) { - return next(r) + return next(ctx, r) } switch q.Qtype { @@ -113,7 +115,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { } if q.Qtype != D.TypeA { - return next(r) + return next(ctx, r) } rr := &D.A{} @@ -123,6 +125,7 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { msg := r.Copy() msg.Answer = []D.RR{rr} + ctx.SetType(context.DNSTypeFakeIP) setMsgTTL(msg, 1) msg.SetRcode(r, D.RcodeSuccess) msg.Authoritative = true @@ -134,7 +137,8 @@ func withFakeIP(fakePool *fakeip.Pool) middleware { } func withResolver(resolver *Resolver) handler { - return func(r *D.Msg) (*D.Msg, error) { + return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) { + ctx.SetType(context.DNSTypeRaw) q := r.Question[0] // return a empty AAAA msg when ipv6 disabled diff --git a/dns/resolver.go b/dns/resolver.go index 93e3ca6ef0..d110aa3456 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -212,7 +212,7 @@ func (r *Resolver) ipExchange(m *D.Msg) (msg *D.Msg, err error) { fallbackMsg := r.asyncExchange(r.fallback, m) res := <-msgCh if res.Error == nil { - if ips := r.msgToIP(res.Msg); len(ips) != 0 { + if ips := msgToIP(res.Msg); len(ips) != 0 { if !r.shouldIPFallback(ips[0]) { msg = res.Msg // no need to wait for fallback result err = res.Error @@ -247,7 +247,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) return nil, err } - ips := r.msgToIP(msg) + ips := msgToIP(msg) ipLength := len(ips) if ipLength == 0 { return nil, resolver.ErrIPNotFound @@ -257,21 +257,6 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) return } -func (r *Resolver) msgToIP(msg *D.Msg) []net.IP { - ips := []net.IP{} - - for _, answer := range msg.Answer { - switch ans := answer.(type) { - case *D.AAAA: - ips = append(ips, ans.AAAA) - case *D.A: - ips = append(ips, ans.A) - } - } - - return ips -} - func (r *Resolver) msgToDomain(msg *D.Msg) string { if len(msg.Question) > 0 { return strings.TrimRight(msg.Question[0].Name, ".") diff --git a/dns/server.go b/dns/server.go index 23718c4f0d..84ff0bac47 100644 --- a/dns/server.go +++ b/dns/server.go @@ -1,9 +1,11 @@ package dns import ( + "errors" "net" "github.com/Dreamacro/clash/common/sockopt" + "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" @@ -21,19 +23,23 @@ type Server struct { handler handler } +// ServeDNS implement D.Handler ServeDNS func (s *Server) ServeDNS(w D.ResponseWriter, r *D.Msg) { - if len(r.Question) == 0 { + msg, err := handlerWithContext(s.handler, r) + if err != nil { D.HandleFailed(w, r) return } + w.WriteMsg(msg) +} - msg, err := s.handler(r) - if err != nil { - D.HandleFailed(w, r) - return +func handlerWithContext(handler handler, msg *D.Msg) (*D.Msg, error) { + if len(msg.Question) == 0 { + return nil, errors.New("at least one question is required") } - w.WriteMsg(msg) + ctx := context.NewDNSContext(msg) + return handler(ctx, msg) } func (s *Server) setHandler(handler handler) { diff --git a/dns/util.go b/dns/util.go index 55a280dbaf..c2bb11d868 100644 --- a/dns/util.go +++ b/dns/util.go @@ -153,3 +153,18 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg { return msg } + +func msgToIP(msg *D.Msg) []net.IP { + ips := []net.IP{} + + for _, answer := range msg.Answer { + switch ans := answer.(type) { + case *D.AAAA: + ips = append(ips, ans.AAAA) + case *D.A: + ips = append(ips, ans.A) + } + } + + return ips +} diff --git a/hub/route/connections.go b/hub/route/connections.go index 2179262527..edec6d6ace 100644 --- a/hub/route/connections.go +++ b/hub/route/connections.go @@ -7,7 +7,7 @@ import ( "strconv" "time" - T "github.com/Dreamacro/clash/tunnel" + "github.com/Dreamacro/clash/tunnel/statistic" "github.com/gorilla/websocket" "github.com/go-chi/chi" @@ -24,7 +24,7 @@ func connectionRouter() http.Handler { func getConnections(w http.ResponseWriter, r *http.Request) { if !websocket.IsWebSocketUpgrade(r) { - snapshot := T.DefaultManager.Snapshot() + snapshot := statistic.DefaultManager.Snapshot() render.JSON(w, r, snapshot) return } @@ -50,7 +50,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) { buf := &bytes.Buffer{} sendSnapshot := func() error { buf.Reset() - snapshot := T.DefaultManager.Snapshot() + snapshot := statistic.DefaultManager.Snapshot() if err := json.NewEncoder(buf).Encode(snapshot); err != nil { return err } @@ -73,7 +73,7 @@ func getConnections(w http.ResponseWriter, r *http.Request) { func closeConnection(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") - snapshot := T.DefaultManager.Snapshot() + snapshot := statistic.DefaultManager.Snapshot() for _, c := range snapshot.Connections { if id == c.ID() { c.Close() @@ -84,7 +84,7 @@ func closeConnection(w http.ResponseWriter, r *http.Request) { } func closeAllConnections(w http.ResponseWriter, r *http.Request) { - snapshot := T.DefaultManager.Snapshot() + snapshot := statistic.DefaultManager.Snapshot() for _, c := range snapshot.Connections { c.Close() } diff --git a/hub/route/server.go b/hub/route/server.go index 5948f12190..ed0133cb33 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -9,7 +9,7 @@ import ( C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" - T "github.com/Dreamacro/clash/tunnel" + "github.com/Dreamacro/clash/tunnel/statistic" "github.com/go-chi/chi" "github.com/go-chi/cors" @@ -143,7 +143,7 @@ func traffic(w http.ResponseWriter, r *http.Request) { tick := time.NewTicker(time.Second) defer tick.Stop() - t := T.DefaultManager + t := statistic.DefaultManager buf := &bytes.Buffer{} var err error for range tick.C { diff --git a/tunnel/connection.go b/tunnel/connection.go index 15a6255546..77671a4bf7 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -13,13 +13,15 @@ import ( "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/context" ) -func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) { - req := request.R +func handleHTTP(ctx *context.HTTPContext, outbound net.Conn) { + req := ctx.Request() + conn := ctx.Conn() host := req.Host - inboundReader := bufio.NewReader(request) + inboundReader := bufio.NewReader(conn) outboundReader := bufio.NewReader(outbound) for { @@ -43,7 +45,7 @@ func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) { inbound.RemoveHopByHopHeaders(resp.Header) if resp.StatusCode == http.StatusContinue { - err = resp.Write(request) + err = resp.Write(conn) if err != nil { break } @@ -58,14 +60,14 @@ func handleHTTP(request *inbound.HTTPAdapter, outbound net.Conn) { } else { resp.Close = true } - err = resp.Write(request) + err = resp.Write(conn) if err != nil || resp.Close { break } // even if resp.Write write body to the connection, but some http request have to Copy to close it buf := pool.Get(pool.RelayBufferSize) - _, err = io.CopyBuffer(request, resp.Body, buf) + _, err = io.CopyBuffer(conn, resp.Body, buf) pool.Put(buf) if err != nil && err != io.EOF { break @@ -129,8 +131,8 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n } } -func handleSocket(request C.ServerAdapter, outbound net.Conn) { - relay(request, outbound) +func handleSocket(ctx C.ConnContext, outbound net.Conn) { + relay(ctx.Conn(), outbound) } // relay copies between left and right bidirectionally. diff --git a/tunnel/manager.go b/tunnel/statistic/manager.go similarity index 99% rename from tunnel/manager.go rename to tunnel/statistic/manager.go index 784d57d9b9..462da674e7 100644 --- a/tunnel/manager.go +++ b/tunnel/statistic/manager.go @@ -1,4 +1,4 @@ -package tunnel +package statistic import ( "sync" diff --git a/tunnel/tracker.go b/tunnel/statistic/tracker.go similarity index 95% rename from tunnel/tracker.go rename to tunnel/statistic/tracker.go index dcb81e7f9f..1f5f1f9ccb 100644 --- a/tunnel/tracker.go +++ b/tunnel/statistic/tracker.go @@ -1,4 +1,4 @@ -package tunnel +package statistic import ( "net" @@ -57,7 +57,7 @@ func (tt *tcpTracker) Close() error { return tt.Conn.Close() } -func newTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { +func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { uuid, _ := uuid.NewV4() t := &tcpTracker{ @@ -114,7 +114,7 @@ func (ut *udpTracker) Close() error { return ut.PacketConn.Close() } -func newUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { +func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker { uuid, _ := uuid.NewV4() ut := &udpTracker{ diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 1339701a50..d7ca4c807e 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -12,11 +12,13 @@ import ( "github.com/Dreamacro/clash/component/nat" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/context" "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/tunnel/statistic" ) var ( - tcpQueue = make(chan C.ServerAdapter, 200) + tcpQueue = make(chan C.ConnContext, 200) udpQueue = make(chan *inbound.PacketAdapter, 200) natTable = nat.New() rules []C.Rule @@ -36,8 +38,8 @@ func init() { } // Add request to queue -func Add(req C.ServerAdapter) { - tcpQueue <- req +func Add(ctx C.ConnContext) { + tcpQueue <- ctx } // AddPacket add udp Packet to queue @@ -141,9 +143,7 @@ func preHandleMetadata(metadata *C.Metadata) error { return nil } -func resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { - var proxy C.Proxy - var rule C.Rule +func resolveMetadata(ctx C.PlainContext, metadata *C.Metadata) (proxy C.Proxy, rule C.Rule, err error) { switch mode { case Direct: proxy = proxies["DIRECT"] @@ -151,13 +151,9 @@ func resolveMetadata(metadata *C.Metadata) (C.Proxy, C.Rule, error) { proxy = proxies["GLOBAL"] // Rule default: - var err error proxy, rule, err = match(metadata) - if err != nil { - return nil, nil, err - } } - return proxy, rule, nil + return } func handleUDPConn(packet *inbound.PacketAdapter) { @@ -210,7 +206,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) { cond.Broadcast() }() - proxy, rule, err := resolveMetadata(metadata) + ctx := context.NewPacketConnContext(metadata) + proxy, rule, err := resolveMetadata(ctx, metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) return @@ -225,7 +222,8 @@ func handleUDPConn(packet *inbound.PacketAdapter) { } return } - pc := newUDPTracker(rawPc, DefaultManager, metadata, rule) + ctx.InjectPacketConn(rawPc) + pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule) switch true { case rule != nil: @@ -245,10 +243,10 @@ func handleUDPConn(packet *inbound.PacketAdapter) { }() } -func handleTCPConn(localConn C.ServerAdapter) { - defer localConn.Close() +func handleTCPConn(ctx C.ConnContext) { + defer ctx.Conn().Close() - metadata := localConn.Metadata() + metadata := ctx.Metadata() if !metadata.Valid() { log.Warnln("[Metadata] not valid: %#v", metadata) return @@ -259,7 +257,7 @@ func handleTCPConn(localConn C.ServerAdapter) { return } - proxy, rule, err := resolveMetadata(metadata) + proxy, rule, err := resolveMetadata(ctx, metadata) if err != nil { log.Warnln("[Metadata] parse failed: %s", err.Error()) return @@ -274,7 +272,7 @@ func handleTCPConn(localConn C.ServerAdapter) { } return } - remoteConn = newTCPTracker(remoteConn, DefaultManager, metadata, rule) + remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule) defer remoteConn.Close() switch true { @@ -288,11 +286,11 @@ func handleTCPConn(localConn C.ServerAdapter) { log.Infoln("[TCP] %s --> %v doesn't match any rule using DIRECT", metadata.SourceAddress(), metadata.String()) } - switch adapter := localConn.(type) { - case *inbound.HTTPAdapter: - handleHTTP(adapter, remoteConn) - case *inbound.SocketAdapter: - handleSocket(adapter, remoteConn) + switch c := ctx.(type) { + case *context.HTTPContext: + handleHTTP(c, remoteConn) + default: + handleSocket(ctx, remoteConn) } } From fcc594ae266767b41b86e94b8376297684d4f282 Mon Sep 17 00:00:00 2001 From: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Date: Sat, 30 Jan 2021 00:40:35 +0800 Subject: [PATCH 527/535] Chore: use jsdelivr CDN for Country.mmdb (#1057) --- config/initial.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initial.go b/config/initial.go index 7c876162b4..a2da9384b2 100644 --- a/config/initial.go +++ b/config/initial.go @@ -12,7 +12,7 @@ import ( ) func downloadMMDB(path string) (err error) { - resp, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb") + resp, err := http.Get("https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb") if err != nil { return } From cd48f69b1fda17ab69cab6192095dd4c7189eba5 Mon Sep 17 00:00:00 2001 From: Kr328 Date: Mon, 1 Feb 2021 20:06:45 +0800 Subject: [PATCH 528/535] Fix: wrap net.Conn to avoid using *net.TCPConn.(ReadFrom) (#1209) --- common/net/io.go | 11 +++++++++++ tunnel/connection.go | 7 +++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 common/net/io.go diff --git a/common/net/io.go b/common/net/io.go new file mode 100644 index 0000000000..5bb4f0023b --- /dev/null +++ b/common/net/io.go @@ -0,0 +1,11 @@ +package net + +import "io" + +type ReadOnlyReader struct { + io.Reader +} + +type WriteOnlyWriter struct { + io.Writer +} diff --git a/tunnel/connection.go b/tunnel/connection.go index 77671a4bf7..b3554a47fd 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -10,6 +10,7 @@ import ( "time" "github.com/Dreamacro/clash/adapters/inbound" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" @@ -141,14 +142,16 @@ func relay(leftConn, rightConn net.Conn) { go func() { buf := pool.Get(pool.RelayBufferSize) - _, err := io.CopyBuffer(leftConn, rightConn, buf) + // Wrapping to avoid using *net.TCPConn.(ReadFrom) + // See also https://github.com/Dreamacro/clash/pull/1209 + _, err := io.CopyBuffer(N.WriteOnlyWriter{Writer: leftConn}, N.ReadOnlyReader{Reader: rightConn}, buf) pool.Put(buf) leftConn.SetReadDeadline(time.Now()) ch <- err }() buf := pool.Get(pool.RelayBufferSize) - io.CopyBuffer(rightConn, leftConn, buf) + io.CopyBuffer(N.WriteOnlyWriter{Writer: rightConn}, N.ReadOnlyReader{Reader: leftConn}, buf) pool.Put(buf) rightConn.SetReadDeadline(time.Now()) <-ch From 6036fb63ba4f4cad5d821a27043f0fed239b8d6f Mon Sep 17 00:00:00 2001 From: Kr328 Date: Tue, 2 Feb 2021 17:52:46 +0800 Subject: [PATCH 529/535] Chore: avoid provider unnecessary write file operations (#1210) --- adapters/provider/fetcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/provider/fetcher.go b/adapters/provider/fetcher.go index 1e2551186d..3a351e9a6e 100644 --- a/adapters/provider/fetcher.go +++ b/adapters/provider/fetcher.go @@ -74,7 +74,7 @@ func (f *fetcher) Initial() (interface{}, error) { } } - if f.vehicle.Type() != File { + if f.vehicle.Type() != File && !isLocal { if err := safeWrite(f.vehicle.Path(), buf); err != nil { return nil, err } From d48cfecf60b928425bfe2c179b3e83d7c747129e Mon Sep 17 00:00:00 2001 From: Fndroid Date: Fri, 5 Feb 2021 16:43:42 +0800 Subject: [PATCH 530/535] Chore: API support patch ipv6 config (#1217) --- hub/route/configs.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hub/route/configs.go b/hub/route/configs.go index a0e183712b..5908f51618 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -4,6 +4,7 @@ import ( "net/http" "path/filepath" + "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" @@ -32,6 +33,7 @@ type configSchema struct { BindAddress *string `json:"bind-address"` Mode *tunnel.TunnelMode `json:"mode"` LogLevel *log.LogLevel `json:"log-level"` + IPv6 *bool `json:"ipv6"` } func getConfigs(w http.ResponseWriter, r *http.Request) { @@ -78,6 +80,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { log.SetLevel(*general.LogLevel) } + if general.IPv6 != nil { + resolver.DisableIPv6 = !*general.IPv6 + } + render.NoContent(w, r) } From 9eb98e399d3163497e9f341ed0bd008fd3153211 Mon Sep 17 00:00:00 2001 From: goomadao <39483078+goomadao@users.noreply.github.com> Date: Mon, 15 Feb 2021 14:32:03 +0800 Subject: [PATCH 531/535] Improve: refactor ssr and fix #995 (#1189) Co-authored-by: goomada --- adapters/outbound/shadowsocksr.go | 68 +- component/ssr/obfs/base.go | 12 +- component/ssr/obfs/http_post.go | 2 +- component/ssr/obfs/http_simple.go | 723 +++++++++--------- component/ssr/obfs/obfs.go | 33 +- component/ssr/obfs/plain.go | 18 +- component/ssr/obfs/random_head.go | 96 ++- component/ssr/obfs/stream.go | 72 -- component/ssr/obfs/tls1.2_ticket_auth.go | 231 ++++++ component/ssr/obfs/tls12_ticket_auth.go | 290 ------- component/ssr/protocol/auth_aes128_md5.go | 312 +------- component/ssr/protocol/auth_aes128_sha1.go | 269 ++++++- component/ssr/protocol/auth_chain_a.go | 552 ++++++------- component/ssr/protocol/auth_chain_b.go | 99 ++- component/ssr/protocol/auth_sha1_v4.go | 317 +++----- component/ssr/protocol/base.go | 73 +- component/ssr/protocol/origin.go | 41 +- component/ssr/protocol/packet.go | 24 +- component/ssr/protocol/protocol.go | 95 ++- component/ssr/protocol/stream.go | 46 +- component/ssr/tools/bufPool.go | 18 + component/ssr/tools/{encrypt.go => crypto.go} | 4 +- component/ssr/tools/random.go | 57 ++ 23 files changed, 1627 insertions(+), 1825 deletions(-) delete mode 100644 component/ssr/obfs/stream.go create mode 100644 component/ssr/obfs/tls1.2_ticket_auth.go delete mode 100644 component/ssr/obfs/tls12_ticket_auth.go create mode 100644 component/ssr/tools/bufPool.go rename component/ssr/tools/{encrypt.go => crypto.go} (88%) create mode 100644 component/ssr/tools/random.go diff --git a/adapters/outbound/shadowsocksr.go b/adapters/outbound/shadowsocksr.go index 1a422cdf5f..155ec83769 100644 --- a/adapters/outbound/shadowsocksr.go +++ b/adapters/outbound/shadowsocksr.go @@ -12,12 +12,13 @@ import ( "github.com/Dreamacro/clash/component/ssr/protocol" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/go-shadowsocks2/core" + "github.com/Dreamacro/go-shadowsocks2/shadowaead" "github.com/Dreamacro/go-shadowsocks2/shadowstream" ) type ShadowSocksR struct { *Base - cipher *core.StreamCipher + cipher core.Cipher obfs obfs.Obfs protocol protocol.Protocol } @@ -36,17 +37,22 @@ type ShadowSocksROption struct { } func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { - c = obfs.NewConn(c, ssr.obfs) + c = ssr.obfs.StreamConn(c) c = ssr.cipher.StreamConn(c) - conn, ok := c.(*shadowstream.Conn) - if !ok { + var ( + iv []byte + err error + ) + switch conn := c.(type) { + case *shadowstream.Conn: + iv, err = conn.ObtainWriteIV() + if err != nil { + return nil, err + } + case *shadowaead.Conn: return nil, fmt.Errorf("invalid connection type") } - iv, err := conn.ObtainWriteIV() - if err != nil { - return nil, err - } - c = protocol.NewConn(c, ssr.protocol, iv) + c = ssr.protocol.StreamConn(c, iv) _, err = c.Write(serializesSocksAddr(metadata)) return c, err } @@ -74,7 +80,7 @@ func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } pc = ssr.cipher.PacketConn(pc) - pc = protocol.NewPacketConn(pc, ssr.protocol) + pc = ssr.protocol.PacketConn(pc) return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil } @@ -90,35 +96,43 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { password := option.Password coreCiph, err := core.PickCipher(cipher, nil, password) if err != nil { - return nil, fmt.Errorf("ssr %s initialize cipher error: %w", addr, err) + return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err) } - ciph, ok := coreCiph.(*core.StreamCipher) - if !ok { - return nil, fmt.Errorf("%s is not a supported stream cipher in ssr", cipher) + var ( + ivSize int + key []byte + ) + if option.Cipher == "dummy" { + ivSize = 0 + key = core.Kdf(option.Password, 16) + } else { + ciph, ok := coreCiph.(*core.StreamCipher) + if !ok { + return nil, fmt.Errorf("%s is not dummy or a supported stream cipher in ssr", cipher) + } + ivSize = ciph.IVSize() + key = ciph.Key } - obfs, err := obfs.PickObfs(option.Obfs, &obfs.Base{ - IVSize: ciph.IVSize(), - Key: ciph.Key, - HeadLen: 30, - Host: option.Server, - Port: option.Port, - Param: option.ObfsParam, + obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{ + Host: option.Server, + Port: option.Port, + Key: key, + IVSize: ivSize, + Param: option.ObfsParam, }) if err != nil { return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err) } protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{ - IV: nil, - Key: ciph.Key, - TCPMss: 1460, - Param: option.ProtocolParam, + Key: key, + Overhead: obfsOverhead, + Param: option.ProtocolParam, }) if err != nil { return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err) } - protocol.SetOverhead(obfs.GetObfsOverhead() + protocol.GetProtocolOverhead()) return &ShadowSocksR{ Base: &Base{ @@ -127,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { tp: C.ShadowsocksR, udp: option.UDP, }, - cipher: ciph, + cipher: coreCiph, obfs: obfs, protocol: protocol, }, nil diff --git a/component/ssr/obfs/base.go b/component/ssr/obfs/base.go index 0f58118e03..7fd1b84cf7 100644 --- a/component/ssr/obfs/base.go +++ b/component/ssr/obfs/base.go @@ -1,11 +1,9 @@ package obfs -// Base information for obfs type Base struct { - IVSize int - Key []byte - HeadLen int - Host string - Port int - Param string + Host string + Port int + Key []byte + IVSize int + Param string } diff --git a/component/ssr/obfs/http_post.go b/component/ssr/obfs/http_post.go index e50ece1c1e..4be6cbe8b1 100644 --- a/component/ssr/obfs/http_post.go +++ b/component/ssr/obfs/http_post.go @@ -1,7 +1,7 @@ package obfs func init() { - register("http_post", newHTTPPost) + register("http_post", newHTTPPost, 0) } func newHTTPPost(b *Base) Obfs { diff --git a/component/ssr/obfs/http_simple.go b/component/ssr/obfs/http_simple.go index c485a66141..86dea94753 100644 --- a/component/ssr/obfs/http_simple.go +++ b/component/ssr/obfs/http_simple.go @@ -3,400 +3,405 @@ package obfs import ( "bytes" "encoding/hex" - "fmt" "io" "math/rand" + "net" + "strconv" "strings" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" ) -type httpObfs struct { - *Base - firstRequest bool - firstResponse bool - post bool +func init() { + register("http_simple", newHTTPSimple, 0) } -func init() { - register("http_simple", newHTTPSimple) +type httpObfs struct { + *Base + post bool } func newHTTPSimple(b *Base) Obfs { return &httpObfs{Base: b} } -func (h *httpObfs) initForConn() Obfs { - return &httpObfs{ - Base: h.Base, - firstRequest: true, - firstResponse: true, - post: h.post, - } +type httpConn struct { + net.Conn + *httpObfs + hasSentHeader bool + hasRecvHeader bool + buf []byte } -func (h *httpObfs) GetObfsOverhead() int { - return 0 +func (h *httpObfs) StreamConn(c net.Conn) net.Conn { + return &httpConn{Conn: c, httpObfs: h} } -func (h *httpObfs) Decode(b []byte) ([]byte, bool, error) { - if h.firstResponse { - idx := bytes.Index(b, []byte("\r\n\r\n")) - if idx == -1 { - return nil, false, io.EOF +func (c *httpConn) Read(b []byte) (int, error) { + if c.buf != nil { + n := copy(b, c.buf) + if n == len(c.buf) { + c.buf = nil + } else { + c.buf = c.buf[n:] } - h.firstResponse = false - return b[idx+4:], false, nil + return n, nil } - return b, false, nil -} -func (h *httpObfs) Encode(b []byte) ([]byte, error) { - if h.firstRequest { - bSize := len(b) - var headData []byte + if c.hasRecvHeader { + return c.Conn.Read(b) + } - if headSize := h.IVSize + h.HeadLen; bSize-headSize > 64 { - headData = make([]byte, headSize+rand.Intn(64)) - } else { - headData = make([]byte, bSize) - } - copy(headData, b[:len(headData)]) - host := h.Host - var customHead string + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + n, err := c.Conn.Read(buf) + if err != nil { + return 0, err + } + pos := bytes.Index(buf[:n], []byte("\r\n\r\n")) + if pos == -1 { + return 0, io.EOF + } + c.hasRecvHeader = true + dataLength := n - pos - 4 + n = copy(b, buf[4+pos:n]) + if dataLength > n { + c.buf = append(c.buf, buf[4+pos+n:4+pos+dataLength]...) + } + return n, nil +} - if len(h.Param) > 0 { - customHeads := strings.Split(h.Param, "#") - if len(customHeads) > 2 { - customHeads = customHeads[:2] - } - customHosts := h.Param - if len(customHeads) > 1 { - customHosts = customHeads[0] - customHead = customHeads[1] - } - hosts := strings.Split(customHosts, ",") - if len(hosts) > 0 { - host = strings.TrimSpace(hosts[rand.Intn(len(hosts))]) - } - } +func (c *httpConn) Write(b []byte) (int, error) { + if c.hasSentHeader { + return c.Conn.Write(b) + } + // 30: head length + headLength := c.IVSize + 30 - method := "GET /" - if h.post { - method = "POST /" - } - requestPathIndex := rand.Intn(len(requestPath)/2) * 2 - httpBuf := fmt.Sprintf("%s%s%s%s HTTP/1.1\r\nHost: %s:%d\r\n", - method, - requestPath[requestPathIndex], - data2URLEncode(headData), - requestPath[requestPathIndex+1], - host, h.Port) - if len(customHead) > 0 { - httpBuf = httpBuf + strings.Replace(customHead, "\\n", "\r\n", -1) + "\r\n\r\n" - } else { - var contentType string - if h.post { - contentType = "Content-Type: multipart/form-data; boundary=" + boundary() + "\r\n" - } - httpBuf = httpBuf + "User-agent: " + requestUserAgent[rand.Intn(len(requestUserAgent))] + "\r\n" + - "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + - "Accept-Language: en-US,en;q=0.8\r\n" + - "Accept-Encoding: gzip, deflate\r\n" + - contentType + - "DNT: 1\r\n" + - "Connection: keep-alive\r\n" + - "\r\n" - } + bLength := len(b) + headDataLength := bLength + if bLength-headLength > 64 { + headDataLength = headLength + rand.Intn(65) + } + headData := b[:headDataLength] + b = b[headDataLength:] - var encoded []byte - if len(headData) < bSize { - encoded = make([]byte, len(httpBuf)+(bSize-len(headData))) - copy(encoded, []byte(httpBuf)) - copy(encoded[len(httpBuf):], b[len(headData):]) + var body string + host := c.Host + if len(c.Param) > 0 { + pos := strings.Index(c.Param, "#") + if pos != -1 { + body = strings.ReplaceAll(c.Param[pos+1:], "\n", "\r\n") + body = strings.ReplaceAll(body, "\\n", "\r\n") + host = c.Param[:pos] } else { - encoded = []byte(httpBuf) + host = c.Param } - h.firstRequest = false - return encoded, nil } + hosts := strings.Split(host, ",") + host = hosts[rand.Intn(len(hosts))] - return b, nil + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + if c.post { + buf.WriteString("POST /") + } else { + buf.WriteString("GET /") + } + packURLEncodedHeadData(buf, headData) + buf.WriteString(" HTTP/1.1\r\nHost: " + host) + if c.Port != 80 { + buf.WriteString(":" + strconv.Itoa(c.Port)) + } + buf.WriteString("\r\n") + if len(body) > 0 { + buf.WriteString(body + "\r\n\r\n") + } else { + buf.WriteString("User-Agent: ") + buf.WriteString(userAgent[rand.Intn(len(userAgent))]) + buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n") + if c.post { + packBoundary(buf) + } + buf.WriteString("DNT: 1\r\nConnection: keep-alive\r\n\r\n") + } + buf.Write(b) + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, nil + } + c.hasSentHeader = true + return bLength, nil } -func data2URLEncode(data []byte) (ret string) { - for i := 0; i < len(data); i++ { - ret = fmt.Sprintf("%s%%%s", ret, hex.EncodeToString([]byte{data[i]})) +func packURLEncodedHeadData(buf *bytes.Buffer, data []byte) { + dataLength := len(data) + for i := 0; i < dataLength; i++ { + buf.WriteRune('%') + buf.WriteString(hex.EncodeToString(data[i : i+1])) } - return } -func boundary() (ret string) { +func packBoundary(buf *bytes.Buffer) { + buf.WriteString("Content-Type: multipart/form-data; boundary=") set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" for i := 0; i < 32; i++ { - ret = fmt.Sprintf("%s%c", ret, set[rand.Intn(len(set))]) + buf.WriteByte(set[rand.Intn(62)]) } - return + buf.WriteString("\r\n") } -var ( - requestPath = []string{ - "", "", - "login.php?redir=", "", - "register.php?code=", "", - "?keyword=", "", - "search?src=typd&q=", "&lang=en", - "s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=", "&rn=", - "post.php?id=", "&goto=view.php", - } - requestUserAgent = []string{ - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", - "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36", - "Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0", - "Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", - "Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", - "Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", - "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1", - "Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", - "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", - "Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", - "Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", - "Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", - "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", - } -) +var userAgent = []string{ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto C Build/NRD90M.059) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J120M Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Moto G (5) Build/NPPS25.137-93-14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L03 Build/HUAWEICAM-L03) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.63 Safari/534.3", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/534.10", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36", + "Mozilla/5.0 (X11; Datanyze; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; SM-J111M Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J700M Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; WAS-LX3 Build/HUAWEIWAS-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.1805 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX3 Build/HUAWEITRT-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1610 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; de-de; SAMSUNG GT-I9195 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.5 Chrome/28.0.1500.94 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; ANE-LX3 Build/HUAWEIANE-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (X11; U; Linux i586; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.1 Safari/533.2", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-J500M Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.44 Safari/534.7", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.104 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1606 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; vivo 1716 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G570M Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; MYA-L22 Build/HUAWEIMYA-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A1601 Build/LMY47I) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; TRT-LX2 Build/HUAWEITRT-LX2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Linux; Android 6.0; CAM-L21 Build/HUAWEICAM-L21; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi 4X Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Linux; Android 4.4.2; SM-G7102 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; HUAWEI CUN-L22 Build/HUAWEICUN-L22; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1.1; A37fw Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J730GM Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-G610F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.2; Redmi Note 5A Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36", + "Mozilla/5.0 (Unknown; Linux) AppleWebKit/538.1 (KHTML, like Gecko) Chrome/v1.0.0 Safari/538.1", + "Mozilla/5.0 (Linux; Android 7.0; BLL-L22 Build/HUAWEIBLL-L22) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.0; SM-J710F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1.1; CPH1723 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.98 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Linux; Android 8.0.0; FIG-LX3 Build/HUAWEIFIG-LX3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.63 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.65 Safari/537.36", + "Mozilla/5.0 (Linux; Android 7.1; Mi A1 Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.99 Safari/533.4", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 MVisionPlayer/1.0.0.0", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Linux; Android 5.1; A37f Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; CPH1607 Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/63.0.3239.111 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532M Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; Redmi 4A Build/MMB29M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.116 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.64 Safari/537.31", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0.1; SM-G532G Build/MMB29T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.83 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36", + "Mozilla/5.0 (Linux; Android 6.0; vivo 1713 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36", +} diff --git a/component/ssr/obfs/obfs.go b/component/ssr/obfs/obfs.go index 1e8c43349c..c56acc8adb 100644 --- a/component/ssr/obfs/obfs.go +++ b/component/ssr/obfs/obfs.go @@ -3,7 +3,7 @@ package obfs import ( "errors" "fmt" - "strings" + "net" ) var ( @@ -12,26 +12,31 @@ var ( errTLS12TicketAuthHMACError = errors.New("tls1.2_ticket_auth hmac verifying failed") ) -// Obfs provides methods for decoding and encoding +type authData struct { + clientID [32]byte +} + type Obfs interface { - initForConn() Obfs - GetObfsOverhead() int - Decode(b []byte) ([]byte, bool, error) - Encode(b []byte) ([]byte, error) + StreamConn(net.Conn) net.Conn } type obfsCreator func(b *Base) Obfs -var obfsList = make(map[string]obfsCreator) +var obfsList = make(map[string]struct { + overhead int + new obfsCreator +}) -func register(name string, c obfsCreator) { - obfsList[name] = c +func register(name string, c obfsCreator, o int) { + obfsList[name] = struct { + overhead int + new obfsCreator + }{overhead: o, new: c} } -// PickObfs returns an obfs of the given name -func PickObfs(name string, b *Base) (Obfs, error) { - if obfsCreator, ok := obfsList[strings.ToLower(name)]; ok { - return obfsCreator(b), nil +func PickObfs(name string, b *Base) (Obfs, int, error) { + if choice, ok := obfsList[name]; ok { + return choice.new(b), choice.overhead, nil } - return nil, fmt.Errorf("Obfs %s not supported", name) + return nil, 0, fmt.Errorf("Obfs %s not supported", name) } diff --git a/component/ssr/obfs/plain.go b/component/ssr/obfs/plain.go index 372feff2d1..eb998a47b4 100644 --- a/component/ssr/obfs/plain.go +++ b/component/ssr/obfs/plain.go @@ -1,25 +1,15 @@ package obfs +import "net" + type plain struct{} func init() { - register("plain", newPlain) + register("plain", newPlain, 0) } func newPlain(b *Base) Obfs { return &plain{} } -func (p *plain) initForConn() Obfs { return &plain{} } - -func (p *plain) GetObfsOverhead() int { - return 0 -} - -func (p *plain) Encode(b []byte) ([]byte, error) { - return b, nil -} - -func (p *plain) Decode(b []byte) ([]byte, bool, error) { - return b, false, nil -} +func (p *plain) StreamConn(c net.Conn) net.Conn { return c } diff --git a/component/ssr/obfs/random_head.go b/component/ssr/obfs/random_head.go index deaab2bdc0..b10b01c56b 100644 --- a/component/ssr/obfs/random_head.go +++ b/component/ssr/obfs/random_head.go @@ -4,72 +4,68 @@ import ( "encoding/binary" "hash/crc32" "math/rand" + "net" + + "github.com/Dreamacro/clash/common/pool" ) -type randomHead struct { - *Base - firstRequest bool - firstResponse bool - headerSent bool - buffer []byte +func init() { + register("random_head", newRandomHead, 0) } -func init() { - register("random_head", newRandomHead) +type randomHead struct { + *Base } func newRandomHead(b *Base) Obfs { return &randomHead{Base: b} } -func (r *randomHead) initForConn() Obfs { - return &randomHead{ - Base: r.Base, - firstRequest: true, - firstResponse: true, - } +type randomHeadConn struct { + net.Conn + *randomHead + hasSentHeader bool + rawTransSent bool + rawTransRecv bool + buf []byte } -func (r *randomHead) GetObfsOverhead() int { - return 0 +func (r *randomHead) StreamConn(c net.Conn) net.Conn { + return &randomHeadConn{Conn: c, randomHead: r} } -func (r *randomHead) Encode(b []byte) (encoded []byte, err error) { - if !r.firstRequest { - return b, nil - } - - bSize := len(b) - if r.headerSent { - if bSize > 0 { - d := make([]byte, len(r.buffer)+bSize) - copy(d, r.buffer) - copy(d[len(r.buffer):], b) - r.buffer = d - } else { - encoded = r.buffer - r.buffer = nil - r.firstRequest = false - } - } else { - size := rand.Intn(96) + 8 - encoded = make([]byte, size) - rand.Read(encoded) - crc := (0xFFFFFFFF - crc32.ChecksumIEEE(encoded[:size-4])) & 0xFFFFFFFF - binary.LittleEndian.PutUint32(encoded[size-4:], crc) - - d := make([]byte, bSize) - copy(d, b) - r.buffer = d +func (c *randomHeadConn) Read(b []byte) (int, error) { + if c.rawTransRecv { + return c.Conn.Read(b) } - r.headerSent = true - return encoded, nil + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + c.Conn.Read(buf) + c.rawTransRecv = true + c.Write(nil) + return 0, nil } -func (r *randomHead) Decode(b []byte) ([]byte, bool, error) { - if r.firstResponse { - r.firstResponse = false - return b, true, nil +func (c *randomHeadConn) Write(b []byte) (int, error) { + if c.rawTransSent { + return c.Conn.Write(b) + } + c.buf = append(c.buf, b...) + if !c.hasSentHeader { + c.hasSentHeader = true + dataLength := rand.Intn(96) + 4 + buf := pool.Get(dataLength + 4) + defer pool.Put(buf) + rand.Read(buf[:dataLength]) + binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength])) + _, err := c.Conn.Write(buf) + return len(b), err + } + if c.rawTransRecv { + _, err := c.Conn.Write(c.buf) + c.buf = nil + c.rawTransSent = true + return len(b), err } - return b, false, nil + return len(b), nil } diff --git a/component/ssr/obfs/stream.go b/component/ssr/obfs/stream.go deleted file mode 100644 index f94cf85c84..0000000000 --- a/component/ssr/obfs/stream.go +++ /dev/null @@ -1,72 +0,0 @@ -package obfs - -import ( - "net" - - "github.com/Dreamacro/clash/common/pool" -) - -// NewConn wraps a stream-oriented net.Conn with obfs decoding/encoding -func NewConn(c net.Conn, o Obfs) net.Conn { - return &Conn{Conn: c, Obfs: o.initForConn()} -} - -// Conn represents an obfs connection -type Conn struct { - net.Conn - Obfs - buf []byte - offset int -} - -func (c *Conn) Read(b []byte) (int, error) { - if c.buf != nil { - n := copy(b, c.buf[c.offset:]) - c.offset += n - if c.offset == len(c.buf) { - pool.Put(c.buf) - c.buf = nil - } - return n, nil - } - - buf := pool.Get(pool.RelayBufferSize) - defer pool.Put(buf) - n, err := c.Conn.Read(buf) - if err != nil { - return 0, err - } - decoded, sendback, err := c.Decode(buf[:n]) - // decoded may be part of buf - decodedData := pool.Get(len(decoded)) - copy(decodedData, decoded) - if err != nil { - pool.Put(decodedData) - return 0, err - } - if sendback { - c.Write(nil) - pool.Put(decodedData) - return 0, nil - } - n = copy(b, decodedData) - if len(decodedData) > len(b) { - c.buf = decodedData - c.offset = n - } else { - pool.Put(decodedData) - } - return n, err -} - -func (c *Conn) Write(b []byte) (int, error) { - encoded, err := c.Encode(b) - if err != nil { - return 0, err - } - _, err = c.Conn.Write(encoded) - if err != nil { - return 0, err - } - return len(b), nil -} diff --git a/component/ssr/obfs/tls1.2_ticket_auth.go b/component/ssr/obfs/tls1.2_ticket_auth.go new file mode 100644 index 0000000000..f3c5b456d6 --- /dev/null +++ b/component/ssr/obfs/tls1.2_ticket_auth.go @@ -0,0 +1,231 @@ +package obfs + +import ( + "bytes" + "crypto/hmac" + "encoding/binary" + "math/rand" + "net" + "strings" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" +) + +func init() { + register("tls1.2_ticket_auth", newTLS12Ticket, 5) + register("tls1.2_ticket_fastauth", newTLS12Ticket, 5) +} + +type tls12Ticket struct { + *Base + *authData +} + +func newTLS12Ticket(b *Base) Obfs { + r := &tls12Ticket{Base: b, authData: &authData{}} + rand.Read(r.clientID[:]) + return r +} + +type tls12TicketConn struct { + net.Conn + *tls12Ticket + handshakeStatus int + decoded bytes.Buffer + underDecoded bytes.Buffer + sendBuf bytes.Buffer +} + +func (t *tls12Ticket) StreamConn(c net.Conn) net.Conn { + return &tls12TicketConn{Conn: c, tls12Ticket: t} +} + +func (c *tls12TicketConn) Read(b []byte) (int, error) { + if c.decoded.Len() > 0 { + return c.decoded.Read(b) + } + + buf := pool.Get(pool.RelayBufferSize) + defer pool.Put(buf) + n, err := c.Conn.Read(buf) + if err != nil { + return 0, err + } + + if c.handshakeStatus == 8 { + c.underDecoded.Write(buf[:n]) + for c.underDecoded.Len() > 5 { + if !bytes.Equal(c.underDecoded.Bytes()[:3], []byte{0x17, 3, 3}) { + c.underDecoded.Reset() + return 0, errTLS12TicketAuthIncorrectMagicNumber + } + size := int(binary.BigEndian.Uint16(c.underDecoded.Bytes()[3:5])) + if c.underDecoded.Len() < 5+size { + break + } + c.underDecoded.Next(5) + c.decoded.Write(c.underDecoded.Next(size)) + } + n, _ = c.decoded.Read(b) + return n, nil + } + + if n < 11+32+1+32 { + return 0, errTLS12TicketAuthTooShortData + } + + if !hmac.Equal(buf[33:43], c.hmacSHA1(buf[11:33])[:10]) || !hmac.Equal(buf[n-10:n], c.hmacSHA1(buf[:n-10])[:10]) { + return 0, errTLS12TicketAuthHMACError + } + + c.Write(nil) + return 0, nil +} + +func (c *tls12TicketConn) Write(b []byte) (int, error) { + length := len(b) + if c.handshakeStatus == 8 { + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + for len(b) > 2048 { + size := rand.Intn(4096) + 100 + if len(b) < size { + size = len(b) + } + packData(buf, b[:size]) + b = b[size:] + } + if len(b) > 0 { + packData(buf, b) + } + _, err := c.Conn.Write(buf.Bytes()) + if err != nil { + return 0, err + } + return length, nil + } + + if len(b) > 0 { + packData(&c.sendBuf, b) + } + + if c.handshakeStatus == 0 { + c.handshakeStatus = 1 + + data := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(data) + defer data.Reset() + + data.Write([]byte{3, 3}) + c.packAuthData(data) + data.WriteByte(0x20) + data.Write(c.clientID[:]) + data.Write([]byte{0x00, 0x1c, 0xc0, 0x2b, 0xc0, 0x2f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0x14, 0xcc, 0x13, 0xc0, 0x0a, 0xc0, 0x14, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x9c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0x0a}) + data.Write([]byte{0x1, 0x0}) + + ext := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(ext) + defer ext.Reset() + + host := c.getHost() + ext.Write([]byte{0xff, 0x01, 0x00, 0x01, 0x00}) + packSNIData(ext, host) + ext.Write([]byte{0, 0x17, 0, 0}) + c.packTicketBuf(ext, host) + ext.Write([]byte{0x00, 0x0d, 0x00, 0x16, 0x00, 0x14, 0x06, 0x01, 0x06, 0x03, 0x05, 0x01, 0x05, 0x03, 0x04, 0x01, 0x04, 0x03, 0x03, 0x01, 0x03, 0x03, 0x02, 0x01, 0x02, 0x03}) + ext.Write([]byte{0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00}) + ext.Write([]byte{0x00, 0x12, 0x00, 0x00}) + ext.Write([]byte{0x75, 0x50, 0x00, 0x00}) + ext.Write([]byte{0x00, 0x0b, 0x00, 0x02, 0x01, 0x00}) + ext.Write([]byte{0x00, 0x0a, 0x00, 0x06, 0x00, 0x04, 0x00, 0x17, 0x00, 0x18}) + + binary.Write(data, binary.BigEndian, uint16(ext.Len())) + data.ReadFrom(ext) + + ret := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(ret) + defer ret.Reset() + + ret.Write([]byte{0x16, 3, 1}) + binary.Write(ret, binary.BigEndian, uint16(data.Len()+4)) + ret.Write([]byte{1, 0}) + binary.Write(ret, binary.BigEndian, uint16(data.Len())) + ret.ReadFrom(data) + + _, err := c.Conn.Write(ret.Bytes()) + if err != nil { + return 0, err + } + return length, nil + } else if c.handshakeStatus == 1 && len(b) == 0 { + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + + buf.Write([]byte{0x14, 3, 3, 0, 1, 1, 0x16, 3, 3, 0, 0x20}) + tools.AppendRandBytes(buf, 22) + buf.Write(c.hmacSHA1(buf.Bytes())[:10]) + buf.ReadFrom(&c.sendBuf) + + c.handshakeStatus = 8 + + _, err := c.Conn.Write(buf.Bytes()) + return 0, err + } + return length, nil +} + +func packData(buf *bytes.Buffer, data []byte) { + buf.Write([]byte{0x17, 3, 3}) + binary.Write(buf, binary.BigEndian, uint16(len(data))) + buf.Write(data) +} + +func (t *tls12Ticket) packAuthData(buf *bytes.Buffer) { + binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) + tools.AppendRandBytes(buf, 18) + buf.Write(t.hmacSHA1(buf.Bytes()[buf.Len()-22:])[:10]) +} + +func packSNIData(buf *bytes.Buffer, u string) { + len := uint16(len(u)) + buf.Write([]byte{0, 0}) + binary.Write(buf, binary.BigEndian, len+5) + binary.Write(buf, binary.BigEndian, len+3) + buf.WriteByte(0) + binary.Write(buf, binary.BigEndian, len) + buf.WriteString(u) +} + +func (c *tls12TicketConn) packTicketBuf(buf *bytes.Buffer, u string) { + length := 16 * (rand.Intn(17) + 8) + buf.Write([]byte{0, 0x23}) + binary.Write(buf, binary.BigEndian, uint16(length)) + tools.AppendRandBytes(buf, length) +} + +func (t *tls12Ticket) hmacSHA1(data []byte) []byte { + key := pool.Get(len(t.Key) + 32) + defer pool.Put(key) + copy(key, t.Key) + copy(key[len(t.Key):], t.clientID[:]) + + sha1Data := tools.HmacSHA1(key, data) + return sha1Data[:10] +} + +func (t *tls12Ticket) getHost() string { + host := t.Param + if len(host) == 0 { + host = t.Host + } + if len(host) > 0 && host[len(host)-1] >= '0' && host[len(host)-1] <= '9' { + host = "" + } + hosts := strings.Split(host, ",") + host = hosts[rand.Intn(len(hosts))] + return host +} diff --git a/component/ssr/obfs/tls12_ticket_auth.go b/component/ssr/obfs/tls12_ticket_auth.go deleted file mode 100644 index b4c1089aa4..0000000000 --- a/component/ssr/obfs/tls12_ticket_auth.go +++ /dev/null @@ -1,290 +0,0 @@ -package obfs - -import ( - "bytes" - "crypto/hmac" - "encoding/binary" - "fmt" - "io" - "math/rand" - "strings" - "time" - - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" - "github.com/Dreamacro/clash/log" -) - -type tlsAuthData struct { - localClientID [32]byte -} - -type tls12Ticket struct { - *Base - *tlsAuthData - handshakeStatus int - sendSaver bytes.Buffer - recvBuffer bytes.Buffer - buffer bytes.Buffer -} - -func init() { - register("tls1.2_ticket_auth", newTLS12Ticket) - register("tls1.2_ticket_fastauth", newTLS12Ticket) -} - -func newTLS12Ticket(b *Base) Obfs { - return &tls12Ticket{ - Base: b, - } -} - -func (t *tls12Ticket) initForConn() Obfs { - r := &tls12Ticket{ - Base: t.Base, - tlsAuthData: &tlsAuthData{}, - } - rand.Read(r.localClientID[:]) - return r -} - -func (t *tls12Ticket) GetObfsOverhead() int { - return 5 -} - -func (t *tls12Ticket) Decode(b []byte) ([]byte, bool, error) { - if t.handshakeStatus == -1 { - return b, false, nil - } - t.buffer.Reset() - if t.handshakeStatus == 8 { - t.recvBuffer.Write(b) - for t.recvBuffer.Len() > 5 { - var h [5]byte - t.recvBuffer.Read(h[:]) - if !bytes.Equal(h[:3], []byte{0x17, 0x3, 0x3}) { - log.Warnln("incorrect magic number %x, 0x170303 is expected", h[:3]) - return nil, false, errTLS12TicketAuthIncorrectMagicNumber - } - size := int(binary.BigEndian.Uint16(h[3:5])) - if t.recvBuffer.Len() < size { - // 不够读,下回再读吧 - unread := t.recvBuffer.Bytes() - t.recvBuffer.Reset() - t.recvBuffer.Write(h[:]) - t.recvBuffer.Write(unread) - break - } - d := pool.Get(size) - t.recvBuffer.Read(d) - t.buffer.Write(d) - pool.Put(d) - } - return t.buffer.Bytes(), false, nil - } - - if len(b) < 11+32+1+32 { - return nil, false, errTLS12TicketAuthTooShortData - } - - hash := t.hmacSHA1(b[11 : 11+22]) - - if !hmac.Equal(b[33:33+tools.HmacSHA1Len], hash) { - return nil, false, errTLS12TicketAuthHMACError - } - return nil, true, nil -} - -func (t *tls12Ticket) Encode(b []byte) ([]byte, error) { - t.buffer.Reset() - switch t.handshakeStatus { - case 8: - if len(b) < 1024 { - d := []byte{0x17, 0x3, 0x3, 0, 0} - binary.BigEndian.PutUint16(d[3:5], uint16(len(b)&0xFFFF)) - t.buffer.Write(d) - t.buffer.Write(b) - return t.buffer.Bytes(), nil - } - start := 0 - var l int - for len(b)-start > 2048 { - l = rand.Intn(4096) + 100 - if l > len(b)-start { - l = len(b) - start - } - packData(&t.buffer, b[start:start+l]) - start += l - } - if len(b)-start > 0 { - l = len(b) - start - packData(&t.buffer, b[start:start+l]) - } - return t.buffer.Bytes(), nil - case 1: - if len(b) > 0 { - if len(b) < 1024 { - packData(&t.sendSaver, b) - } else { - start := 0 - var l int - for len(b)-start > 2048 { - l = rand.Intn(4096) + 100 - if l > len(b)-start { - l = len(b) - start - } - packData(&t.buffer, b[start:start+l]) - start += l - } - if len(b)-start > 0 { - l = len(b) - start - packData(&t.buffer, b[start:start+l]) - } - io.Copy(&t.sendSaver, &t.buffer) - } - return []byte{}, nil - } - hmacData := make([]byte, 43) - handshakeFinish := []byte("\x14\x03\x03\x00\x01\x01\x16\x03\x03\x00\x20") - copy(hmacData, handshakeFinish) - rand.Read(hmacData[11:33]) - h := t.hmacSHA1(hmacData[:33]) - copy(hmacData[33:], h) - t.buffer.Write(hmacData) - io.Copy(&t.buffer, &t.sendSaver) - t.handshakeStatus = 8 - return t.buffer.Bytes(), nil - case 0: - tlsData0 := []byte("\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00") - tlsData1 := []byte("\xff\x01\x00\x01\x00") - tlsData2 := []byte("\x00\x17\x00\x00\x00\x23\x00\xd0") - // tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18\x00\x15\x00\x66\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - tlsData3 := []byte("\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18") - - var tlsData [2048]byte - tlsDataLen := 0 - copy(tlsData[0:], tlsData1) - tlsDataLen += len(tlsData1) - sni := t.sni(t.getHost()) - copy(tlsData[tlsDataLen:], sni) - tlsDataLen += len(sni) - copy(tlsData[tlsDataLen:], tlsData2) - tlsDataLen += len(tlsData2) - ticketLen := rand.Intn(164)*2 + 64 - tlsData[tlsDataLen-1] = uint8(ticketLen & 0xff) - tlsData[tlsDataLen-2] = uint8(ticketLen >> 8) - //ticketLen := 208 - rand.Read(tlsData[tlsDataLen : tlsDataLen+ticketLen]) - tlsDataLen += ticketLen - copy(tlsData[tlsDataLen:], tlsData3) - tlsDataLen += len(tlsData3) - - length := 11 + 32 + 1 + 32 + len(tlsData0) + 2 + tlsDataLen - encodedData := make([]byte, length) - pdata := length - tlsDataLen - l := tlsDataLen - copy(encodedData[pdata:], tlsData[:tlsDataLen]) - encodedData[pdata-1] = uint8(tlsDataLen) - encodedData[pdata-2] = uint8(tlsDataLen >> 8) - pdata -= 2 - l += 2 - copy(encodedData[pdata-len(tlsData0):], tlsData0) - pdata -= len(tlsData0) - l += len(tlsData0) - copy(encodedData[pdata-32:], t.localClientID[:]) - pdata -= 32 - l += 32 - encodedData[pdata-1] = 0x20 - pdata-- - l++ - copy(encodedData[pdata-32:], t.packAuthData()) - pdata -= 32 - l += 32 - encodedData[pdata-1] = 0x3 - encodedData[pdata-2] = 0x3 // tls version - pdata -= 2 - l += 2 - encodedData[pdata-1] = uint8(l) - encodedData[pdata-2] = uint8(l >> 8) - encodedData[pdata-3] = 0 - encodedData[pdata-4] = 1 - pdata -= 4 - l += 4 - encodedData[pdata-1] = uint8(l) - encodedData[pdata-2] = uint8(l >> 8) - pdata -= 2 - l += 2 - encodedData[pdata-1] = 0x1 - encodedData[pdata-2] = 0x3 // tls version - pdata -= 2 - l += 2 - encodedData[pdata-1] = 0x16 // tls handshake - pdata-- - l++ - packData(&t.sendSaver, b) - t.handshakeStatus = 1 - return encodedData, nil - default: - return nil, fmt.Errorf("unexpected handshake status: %d", t.handshakeStatus) - } -} - -func (t *tls12Ticket) hmacSHA1(data []byte) []byte { - key := make([]byte, len(t.Key)+32) - copy(key, t.Key) - copy(key[len(t.Key):], t.localClientID[:]) - - sha1Data := tools.HmacSHA1(key, data) - return sha1Data[:tools.HmacSHA1Len] -} - -func (t *tls12Ticket) sni(u string) []byte { - bURL := []byte(u) - length := len(bURL) - ret := make([]byte, length+9) - copy(ret[9:9+length], bURL) - binary.BigEndian.PutUint16(ret[7:], uint16(length&0xFFFF)) - length += 3 - binary.BigEndian.PutUint16(ret[4:], uint16(length&0xFFFF)) - length += 2 - binary.BigEndian.PutUint16(ret[2:], uint16(length&0xFFFF)) - return ret -} - -func (t *tls12Ticket) getHost() string { - host := t.Host - if len(t.Param) > 0 { - hosts := strings.Split(t.Param, ",") - if len(hosts) > 0 { - - host = hosts[rand.Intn(len(hosts))] - host = strings.TrimSpace(host) - } - } - if len(host) > 0 && host[len(host)-1] >= byte('0') && host[len(host)-1] <= byte('9') && len(t.Param) == 0 { - host = "" - } - return host -} - -func (t *tls12Ticket) packAuthData() (ret []byte) { - retSize := 32 - ret = make([]byte, retSize) - - now := time.Now().Unix() - binary.BigEndian.PutUint32(ret[:4], uint32(now)) - - rand.Read(ret[4 : 4+18]) - - hash := t.hmacSHA1(ret[:retSize-tools.HmacSHA1Len]) - copy(ret[retSize-tools.HmacSHA1Len:], hash) - - return -} - -func packData(buffer *bytes.Buffer, suffix []byte) { - d := []byte{0x17, 0x3, 0x3, 0, 0} - binary.BigEndian.PutUint16(d[3:5], uint16(len(suffix)&0xFFFF)) - buffer.Write(d) - buffer.Write(suffix) -} diff --git a/component/ssr/protocol/auth_aes128_md5.go b/component/ssr/protocol/auth_aes128_md5.go index fc0c41bf08..08e350c46f 100644 --- a/component/ssr/protocol/auth_aes128_md5.go +++ b/component/ssr/protocol/auth_aes128_md5.go @@ -1,310 +1,18 @@ package protocol -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "encoding/base64" - "encoding/binary" - "math/rand" - "strconv" - "strings" - "time" - - "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/component/ssr/tools" - "github.com/Dreamacro/go-shadowsocks2/core" -) - -type authAES128 struct { - *Base - *recvInfo - *authData - hasSentHeader bool - packID uint32 - userKey []byte - uid [4]byte - salt string - hmac hmacMethod - hashDigest hashDigestMethod -} +import "github.com/Dreamacro/clash/component/ssr/tools" func init() { - register("auth_aes128_md5", newAuthAES128MD5) + register("auth_aes128_md5", newAuthAES128MD5, 9) } func newAuthAES128MD5(b *Base) Protocol { - return &authAES128{ - Base: b, - authData: &authData{}, - salt: "auth_aes128_md5", - hmac: tools.HmacMD5, - hashDigest: tools.MD5Sum, - } -} - -func (a *authAES128) initForConn(iv []byte) Protocol { - return &authAES128{ - Base: &Base{ - IV: iv, - Key: a.Key, - TCPMss: a.TCPMss, - Overhead: a.Overhead, - Param: a.Param, - }, - recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)}, - authData: a.authData, - packID: 1, - salt: a.salt, - hmac: a.hmac, - hashDigest: a.hashDigest, - } -} - -func (a *authAES128) GetProtocolOverhead() int { - return 9 -} - -func (a *authAES128) SetOverhead(overhead int) { - a.Overhead = overhead -} - -func (a *authAES128) Decode(b []byte) ([]byte, int, error) { - a.buffer.Reset() - bSize := len(b) - readSize := 0 - key := pool.Get(len(a.userKey) + 4) - defer pool.Put(key) - copy(key, a.userKey) - for bSize > 4 { - binary.LittleEndian.PutUint32(key[len(key)-4:], a.recvID) - - h := a.hmac(key, b[:2]) - if !bytes.Equal(h[:2], b[2:4]) { - return nil, 0, errAuthAES128IncorrectMAC - } - - length := int(binary.LittleEndian.Uint16(b[:2])) - if length >= 8192 || length < 8 { - return nil, 0, errAuthAES128DataLengthError - } - if length > bSize { - break - } - - h = a.hmac(key, b[:length-4]) - if !bytes.Equal(h[:4], b[length-4:length]) { - return nil, 0, errAuthAES128IncorrectChecksum - } - - a.recvID++ - pos := int(b[4]) - if pos < 255 { - pos += 4 - } else { - pos = int(binary.LittleEndian.Uint16(b[5:7])) + 4 - } - - if pos > length-4 { - return nil, 0, errAuthAES128PositionTooLarge - } - a.buffer.Write(b[pos : length-4]) - b = b[length:] - bSize -= length - readSize += length - } - return a.buffer.Bytes(), readSize, nil -} - -func (a *authAES128) Encode(b []byte) ([]byte, error) { - a.buffer.Reset() - bSize := len(b) - offset := 0 - if bSize > 0 && !a.hasSentHeader { - authSize := bSize - if authSize > 1200 { - authSize = 1200 - } - a.hasSentHeader = true - a.buffer.Write(a.packAuthData(b[:authSize])) - bSize -= authSize - offset += authSize - } - const blockSize = 4096 - for bSize > blockSize { - packSize, randSize := a.packedDataSize(b[offset : offset+blockSize]) - pack := pool.Get(packSize) - a.packData(b[offset:offset+blockSize], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) - bSize -= blockSize - offset += blockSize - } - if bSize > 0 { - packSize, randSize := a.packedDataSize(b[offset:]) - pack := pool.Get(packSize) - a.packData(b[offset:], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) - } - return a.buffer.Bytes(), nil -} - -func (a *authAES128) DecodePacket(b []byte) ([]byte, int, error) { - bSize := len(b) - h := a.hmac(a.Key, b[:bSize-4]) - if !bytes.Equal(h[:4], b[bSize-4:]) { - return nil, 0, errAuthAES128IncorrectMAC - } - return b[:bSize-4], bSize - 4, nil -} - -func (a *authAES128) EncodePacket(b []byte) ([]byte, error) { - a.initUserKeyAndID() - - var buf bytes.Buffer - buf.Write(b) - buf.Write(a.uid[:]) - h := a.hmac(a.userKey, buf.Bytes()) - buf.Write(h[:4]) - return buf.Bytes(), nil -} - -func (a *authAES128) initUserKeyAndID() { - if a.userKey == nil { - params := strings.Split(a.Param, ":") - if len(params) >= 2 { - if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { - binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) - a.userKey = a.hashDigest([]byte(params[1])) - } - } - - if a.userKey == nil { - rand.Read(a.uid[:]) - a.userKey = make([]byte, len(a.Key)) - copy(a.userKey, a.Key) - } - } -} - -func (a *authAES128) packedDataSize(data []byte) (packSize, randSize int) { - dataSize := len(data) - randSize = 1 - if dataSize <= 1200 { - if a.packID > 4 { - randSize += rand.Intn(32) - } else { - if dataSize > 900 { - randSize += rand.Intn(128) - } else { - randSize += rand.Intn(512) - } - } - } - packSize = randSize + dataSize + 8 - return -} - -func (a *authAES128) packData(data, ret []byte, randSize int) { - dataSize := len(data) - retSize := len(ret) - // 0~1, ret_size - binary.LittleEndian.PutUint16(ret[0:], uint16(retSize&0xFFFF)) - // 2~3, hmac - key := pool.Get(len(a.userKey) + 4) - defer pool.Put(key) - copy(key, a.userKey) - binary.LittleEndian.PutUint32(key[len(key)-4:], a.packID) - h := a.hmac(key, ret[:2]) - copy(ret[2:4], h[:2]) - // 4~rand_size+4, rand number - rand.Read(ret[4 : 4+randSize]) - // 4, rand_size - if randSize < 128 { - ret[4] = byte(randSize & 0xFF) - } else { - // 4, magic number 0xFF - ret[4] = 0xFF - // 5~6, rand_size - binary.LittleEndian.PutUint16(ret[5:], uint16(randSize&0xFFFF)) - } - // rand_size+4~ret_size-4, data - if dataSize > 0 { - copy(ret[randSize+4:], data) - } - a.packID++ - h = a.hmac(key, ret[:retSize-4]) - copy(ret[retSize-4:], h[:4]) -} - -func (a *authAES128) packAuthData(data []byte) (ret []byte) { - dataSize := len(data) - var randSize int - - if dataSize > 400 { - randSize = rand.Intn(512) - } else { - randSize = rand.Intn(1024) - } - - dataOffset := randSize + 16 + 4 + 4 + 7 - retSize := dataOffset + dataSize + 4 - ret = make([]byte, retSize) - encrypt := make([]byte, 24) - key := make([]byte, len(a.IV)+len(a.Key)) - copy(key, a.IV) - copy(key[len(a.IV):], a.Key) - - rand.Read(ret[dataOffset-randSize:]) - a.mutex.Lock() - defer a.mutex.Unlock() - a.connectionID++ - if a.connectionID > 0xFF000000 { - a.clientID = nil - } - if len(a.clientID) == 0 { - a.clientID = make([]byte, 8) - rand.Read(a.clientID) - b := make([]byte, 4) - rand.Read(b) - a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - copy(encrypt[4:], a.clientID) - binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) - - now := time.Now().Unix() - binary.LittleEndian.PutUint32(encrypt[:4], uint32(now)) - - binary.LittleEndian.PutUint16(encrypt[12:], uint16(retSize&0xFFFF)) - binary.LittleEndian.PutUint16(encrypt[14:], uint16(randSize&0xFFFF)) - - a.initUserKeyAndID() - - aesCipherKey := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+a.salt, 16) - block, err := aes.NewCipher(aesCipherKey) - if err != nil { - return nil - } - encryptData := make([]byte, 16) - iv := make([]byte, aes.BlockSize) - cbc := cipher.NewCBCEncrypter(block, iv) - cbc.CryptBlocks(encryptData, encrypt[:16]) - copy(encrypt[:4], a.uid[:]) - copy(encrypt[4:4+16], encryptData) - - h := a.hmac(key, encrypt[:20]) - copy(encrypt[20:], h[:4]) - - rand.Read(ret[:1]) - h = a.hmac(key, ret[:1]) - copy(ret[1:], h[:7-1]) - - copy(ret[7:], encrypt) - copy(ret[dataOffset:], data) - - h = a.hmac(a.userKey, ret[:retSize-4]) - copy(ret[retSize-4:], h[:4]) - - return + a := &authAES128{ + Base: b, + authData: &authData{}, + authAES128Function: &authAES128Function{salt: "auth_aes128_md5", hmac: tools.HmacMD5, hashDigest: tools.MD5Sum}, + userData: &userData{}, + } + a.initUserData() + return a } diff --git a/component/ssr/protocol/auth_aes128_sha1.go b/component/ssr/protocol/auth_aes128_sha1.go index d549b58805..5038345626 100644 --- a/component/ssr/protocol/auth_aes128_sha1.go +++ b/component/ssr/protocol/auth_aes128_sha1.go @@ -2,21 +2,274 @@ package protocol import ( "bytes" + "encoding/binary" + "math" + "math/rand" + "net" + "strconv" + "strings" + "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/log" ) +type hmacMethod func(key, data []byte) []byte +type hashDigestMethod func([]byte) []byte + func init() { - register("auth_aes128_sha1", newAuthAES128SHA1) + register("auth_aes128_sha1", newAuthAES128SHA1, 9) +} + +type authAES128Function struct { + salt string + hmac hmacMethod + hashDigest hashDigestMethod +} + +type authAES128 struct { + *Base + *authData + *authAES128Function + *userData + iv []byte + hasSentHeader bool + rawTrans bool + packID uint32 + recvID uint32 } func newAuthAES128SHA1(b *Base) Protocol { - return &authAES128{ - Base: b, - recvInfo: &recvInfo{buffer: new(bytes.Buffer)}, - authData: &authData{}, - salt: "auth_aes128_sha1", - hmac: tools.HmacSHA1, - hashDigest: tools.SHA1Sum, + a := &authAES128{ + Base: b, + authData: &authData{}, + authAES128Function: &authAES128Function{salt: "auth_aes128_sha1", hmac: tools.HmacSHA1, hashDigest: tools.SHA1Sum}, + userData: &userData{}, + } + a.initUserData() + return a +} + +func (a *authAES128) initUserData() { + params := strings.Split(a.Param, ":") + if len(params) > 1 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) + a.userKey = a.hashDigest([]byte(params[1])) + } else { + log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt) + } + } + if len(a.userKey) == 0 { + a.userKey = a.Key + rand.Read(a.userID[:]) + } +} + +func (a *authAES128) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authAES128{ + Base: a.Base, + authData: a.next(), + authAES128Function: a.authAES128Function, + userData: a.userData, + packID: 1, + recvID: 1, + } + p.iv = iv + return &Conn{Conn: c, Protocol: p} +} + +func (a *authAES128) PacketConn(c net.PacketConn) net.PacketConn { + p := &authAES128{ + Base: a.Base, + authAES128Function: a.authAES128Function, + userData: a.userData, + } + return &PacketConn{PacketConn: c, Protocol: p} +} + +func (a *authAES128) Decode(dst, src *bytes.Buffer) error { + if a.rawTrans { + dst.ReadFrom(src) + return nil + } + for src.Len() > 4 { + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID) + if !bytes.Equal(a.hmac(macKey, src.Bytes()[:2])[:2], src.Bytes()[2:4]) { + src.Reset() + return errAuthAES128MACError + } + + length := int(binary.LittleEndian.Uint16(src.Bytes()[:2])) + if length >= 8192 || length < 7 { + a.rawTrans = true + src.Reset() + return errAuthAES128LengthError + } + if length > src.Len() { + break + } + + if !bytes.Equal(a.hmac(macKey, src.Bytes()[:length-4])[:4], src.Bytes()[length-4:length]) { + a.rawTrans = true + src.Reset() + return errAuthAES128ChksumError + } + + a.recvID++ + + pos := int(src.Bytes()[4]) + if pos < 255 { + pos += 4 + } else { + pos = int(binary.LittleEndian.Uint16(src.Bytes()[5:7])) + 4 + } + dst.Write(src.Bytes()[pos : length-4]) + src.Next(length) + } + return nil +} + +func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error { + fullDataLength := len(b) + if !a.hasSentHeader { + dataLength := getDataLength(b) + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + a.hasSentHeader = true + } + for len(b) > 8100 { + a.packData(buf, b[:8100], fullDataLength) + b = b[8100:] + } + if len(b) > 0 { + a.packData(buf, b, fullDataLength) + } + return nil +} + +func (a *authAES128) DecodePacket(b []byte) ([]byte, error) { + if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) { + return nil, errAuthAES128ChksumError + } + return b[:len(b)-4], nil +} + +func (a *authAES128) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + buf.Write(a.userID[:]) + buf.Write(a.hmac(a.userKey, buf.Bytes())[:4]) + return nil +} + +func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength int) { + dataLength := len(data) + randDataLength := a.getRandDataLengthForPackData(dataLength, fullDataLength) + /* + 2: uint16 LittleEndian packedDataLength + 2: hmac of packedDataLength + 3: maxRandDataLengthPrefix (min:1) + 4: hmac of packedData except the last 4 bytes + */ + packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 + if randDataLength < 128 { + packedDataLength -= 2 + } + + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) + a.packID++ + + binary.Write(poolBuf, binary.LittleEndian, uint16(packedDataLength)) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-2:])[:2]) + a.packRandData(poolBuf, randDataLength) + poolBuf.Write(data) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])[:4]) +} + +func trapezoidRandom(max int, d float64) int { + base := rand.Float64() + if d-0 > 1e-6 { + a := 1 - d + base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d) + } + return int(base * float64(max)) +} + +func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int) int { + if fullDataLength >= 32*1024-a.Overhead { + return 0 + } + // 1460: tcp_mss + revLength := 1460 - dataLength - 9 + if revLength == 0 { + return 0 + } + if revLength < 0 { + if revLength > -1460 { + return trapezoidRandom(revLength+1460, -0.3) + } + return rand.Intn(32) + } + if dataLength > 900 { + return rand.Intn(revLength) + } + return trapezoidRandom(revLength, -0.3) +} + +func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) { + if len(data) == 0 { + return + } + dataLength := len(data) + randDataLength := a.getRandDataLengthForPackAuthData(dataLength) + /* + 7: checkHead(1) and hmac of checkHead(6) + 4: userID + 16: encrypted data of authdata(12), uint16 BigEndian packedDataLength(2) and uint16 BigEndian randDataLength(2) + 4: hmac of userID and encrypted data + 4: hmac of packedAuthData except the last 4 bytes + */ + packedAuthDataLength := 7 + 4 + 16 + 4 + randDataLength + dataLength + 4 + + macKey := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(macKey) + copy(macKey, a.iv) + copy(macKey[len(a.iv):], a.Key) + + poolBuf.WriteByte(byte(rand.Intn(256))) + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6]) + poolBuf.Write(a.userID[:]) + err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt) + if err != nil { + poolBuf.Reset() + return + } + poolBuf.Write(a.hmac(macKey, poolBuf.Bytes()[7:])[:4]) + tools.AppendRandBytes(poolBuf, randDataLength) + poolBuf.Write(data) + poolBuf.Write(a.hmac(a.userKey, poolBuf.Bytes())[:4]) +} + +func (a *authAES128) getRandDataLengthForPackAuthData(size int) int { + if size > 400 { + return rand.Intn(512) + } + return rand.Intn(1024) +} + +func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) { + if size < 128 { + poolBuf.WriteByte(byte(size + 1)) + tools.AppendRandBytes(poolBuf, size) + return } + poolBuf.WriteByte(255) + binary.Write(poolBuf, binary.LittleEndian, uint16(size+3)) + tools.AppendRandBytes(poolBuf, size) } diff --git a/component/ssr/protocol/auth_chain_a.go b/component/ssr/protocol/auth_chain_a.go index 2028ba7987..fdad8afea3 100644 --- a/component/ssr/protocol/auth_chain_a.go +++ b/component/ssr/protocol/auth_chain_a.go @@ -2,430 +2,308 @@ package protocol import ( "bytes" - "crypto/aes" "crypto/cipher" + "crypto/rand" "crypto/rc4" "encoding/base64" "encoding/binary" - "math/rand" + "net" "strconv" "strings" - "time" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/ssr/tools" + "github.com/Dreamacro/clash/log" "github.com/Dreamacro/go-shadowsocks2/core" ) -type authChain struct { +func init() { + register("auth_chain_a", newAuthChainA, 4) +} + +type randDataLengthMethod func(int, []byte, *tools.XorShift128Plus) int + +type authChainA struct { *Base - *recvInfo *authData - randomClient shift128PlusContext - randomServer shift128PlusContext - enc cipher.Stream - dec cipher.Stream - headerSent bool + *userData + iv []byte + salt string + hasSentHeader bool + rawTrans bool lastClientHash []byte lastServerHash []byte - userKey []byte - uid [4]byte - salt string - hmac hmacMethod - hashDigest hashDigestMethod - rnd rndMethod - dataSizeList []int - dataSizeList2 []int - chunkID uint32 -} - -func init() { - register("auth_chain_a", newAuthChainA) + encrypter cipher.Stream + decrypter cipher.Stream + randomClient tools.XorShift128Plus + randomServer tools.XorShift128Plus + randDataLength randDataLengthMethod + packID uint32 + recvID uint32 } func newAuthChainA(b *Base) Protocol { - return &authChain{ - Base: b, - authData: &authData{}, - salt: "auth_chain_a", - hmac: tools.HmacMD5, - hashDigest: tools.SHA1Sum, - rnd: authChainAGetRandLen, + a := &authChainA{ + Base: b, + authData: &authData{}, + userData: &userData{}, + salt: "auth_chain_a", } + a.initUserData() + return a } -func (a *authChain) initForConn(iv []byte) Protocol { - r := &authChain{ - Base: &Base{ - IV: iv, - Key: a.Key, - TCPMss: a.TCPMss, - Overhead: a.Overhead, - Param: a.Param, - }, - recvInfo: &recvInfo{recvID: 1, buffer: new(bytes.Buffer)}, - authData: a.authData, - salt: a.salt, - hmac: a.hmac, - hashDigest: a.hashDigest, - rnd: a.rnd, +func (a *authChainA) initUserData() { + params := strings.Split(a.Param, ":") + if len(params) > 1 { + if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { + binary.LittleEndian.PutUint32(a.userID[:], uint32(userID)) + a.userKey = []byte(params[1]) + } else { + log.Warnln("Wrong protocol-param for %s, only digits are expected before ':'", a.salt) + } } - if r.salt == "auth_chain_b" { - initDataSize(r) + if len(a.userKey) == 0 { + a.userKey = a.Key + rand.Read(a.userID[:]) } - return r } -func (a *authChain) GetProtocolOverhead() int { - return 4 +func (a *authChainA) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authChainA{ + Base: a.Base, + authData: a.next(), + userData: a.userData, + salt: a.salt, + packID: 1, + recvID: 1, + } + p.iv = iv + p.randDataLength = p.getRandLength + return &Conn{Conn: c, Protocol: p} } -func (a *authChain) SetOverhead(overhead int) { - a.Overhead = overhead +func (a *authChainA) PacketConn(c net.PacketConn) net.PacketConn { + p := &authChainA{ + Base: a.Base, + salt: a.salt, + userData: a.userData, + } + return &PacketConn{PacketConn: c, Protocol: p} } -func (a *authChain) Decode(b []byte) ([]byte, int, error) { - a.buffer.Reset() - key := pool.Get(len(a.userKey) + 4) - defer pool.Put(key) - readSize := 0 - copy(key, a.userKey) - for len(b) > 4 { - binary.LittleEndian.PutUint32(key[len(a.userKey):], a.recvID) - dataLen := (int)((uint(b[1]^a.lastServerHash[15]) << 8) + uint(b[0]^a.lastServerHash[14])) - randLen := a.getServerRandLen(dataLen, a.Overhead) - length := randLen + dataLen +func (a *authChainA) Decode(dst, src *bytes.Buffer) error { + if a.rawTrans { + dst.ReadFrom(src) + return nil + } + for src.Len() > 4 { + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.recvID) + + dataLength := int(binary.LittleEndian.Uint16(src.Bytes()[:2]) ^ binary.LittleEndian.Uint16(a.lastServerHash[14:16])) + randDataLength := a.randDataLength(dataLength, a.lastServerHash, &a.randomServer) + length := dataLength + randDataLength + if length >= 4096 { - return nil, 0, errAuthChainDataLengthError + a.rawTrans = true + src.Reset() + return errAuthChainLengthError } - length += 4 - if length > len(b) { + + if 4+length > src.Len() { break } - hash := a.hmac(key, b[:length-2]) - if !bytes.Equal(hash[:2], b[length-2:length]) { - return nil, 0, errAuthChainHMACError + serverHash := tools.HmacMD5(macKey, src.Bytes()[:length+2]) + if !bytes.Equal(serverHash[:2], src.Bytes()[length+2:length+4]) { + a.rawTrans = true + src.Reset() + return errAuthChainChksumError } - var dataPos int - if dataLen > 0 && randLen > 0 { - dataPos = 2 + getRandStartPos(&a.randomServer, randLen) - } else { - dataPos = 2 + a.lastServerHash = serverHash + + pos := 2 + if dataLength > 0 && randDataLength > 0 { + pos += getRandStartPos(randDataLength, &a.randomServer) } - d := pool.Get(dataLen) - a.dec.XORKeyStream(d, b[dataPos:dataPos+dataLen]) - a.buffer.Write(d) - pool.Put(d) + wantedData := src.Bytes()[pos : pos+dataLength] + a.decrypter.XORKeyStream(wantedData, wantedData) if a.recvID == 1 { - a.TCPMss = int(binary.LittleEndian.Uint16(a.buffer.Next(2))) + dst.Write(wantedData[2:]) + } else { + dst.Write(wantedData) } - a.lastServerHash = hash a.recvID++ - b = b[length:] - readSize += length + src.Next(length + 4) } - return a.buffer.Bytes(), readSize, nil + return nil } -func (a *authChain) Encode(b []byte) ([]byte, error) { - a.buffer.Reset() - bSize := len(b) - offset := 0 - if bSize > 0 && !a.headerSent { - headSize := 1200 - if headSize > bSize { - headSize = bSize - } - a.buffer.Write(a.packAuthData(b[:headSize])) - offset += headSize - bSize -= headSize - a.headerSent = true +func (a *authChainA) Encode(buf *bytes.Buffer, b []byte) error { + if !a.hasSentHeader { + dataLength := getDataLength(b) + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + a.hasSentHeader = true } - var unitSize = a.TCPMss - a.Overhead - for bSize > unitSize { - dataLen, randLength := a.packedDataLen(b[offset : offset+unitSize]) - d := pool.Get(dataLen) - a.packData(d, b[offset:offset+unitSize], randLength) - a.buffer.Write(d) - pool.Put(d) - bSize -= unitSize - offset += unitSize + for len(b) > 2800 { + a.packData(buf, b[:2800]) + b = b[2800:] } - if bSize > 0 { - dataLen, randLength := a.packedDataLen(b[offset:]) - d := pool.Get(dataLen) - a.packData(d, b[offset:], randLength) - a.buffer.Write(d) - pool.Put(d) + if len(b) > 0 { + a.packData(buf, b) } - return a.buffer.Bytes(), nil + return nil } -func (a *authChain) DecodePacket(b []byte) ([]byte, int, error) { - bSize := len(b) - if bSize < 9 { - return nil, 0, errAuthChainDataLengthError +func (a *authChainA) DecodePacket(b []byte) ([]byte, error) { + if len(b) < 9 { + return nil, errAuthChainLengthError } - h := a.hmac(a.userKey, b[:bSize-1]) - if h[0] != b[bSize-1] { - return nil, 0, errAuthChainHMACError + if !bytes.Equal(tools.HmacMD5(a.userKey, b[:len(b)-1])[:1], b[len(b)-1:]) { + return nil, errAuthChainChksumError } - hash := a.hmac(a.Key, b[bSize-8:bSize-1]) - cipherKey := a.getRC4CipherKey(hash) - dec, _ := rc4.NewCipher(cipherKey) - randLength := udpGetRandLen(&a.randomServer, hash) - bSize -= 8 + randLength - dec.XORKeyStream(b, b[:bSize]) - return b, bSize, nil + md5Data := tools.HmacMD5(a.Key, b[len(b)-8:len(b)-1]) + + randDataLength := udpGetRandLength(md5Data, &a.randomServer) + + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) + rc4Cipher, err := rc4.NewCipher(key) + if err != nil { + return nil, err + } + wantedData := b[:len(b)-8-randDataLength] + rc4Cipher.XORKeyStream(wantedData, wantedData) + return wantedData, nil } -func (a *authChain) EncodePacket(b []byte) ([]byte, error) { - a.initUserKeyAndID() +func (a *authChainA) EncodePacket(buf *bytes.Buffer, b []byte) error { authData := pool.Get(3) defer pool.Put(authData) rand.Read(authData) - hash := a.hmac(a.Key, authData) - uid := pool.Get(4) - defer pool.Put(uid) - for i := 0; i < 4; i++ { - uid[i] = a.uid[i] ^ hash[i] - } - cipherKey := a.getRC4CipherKey(hash) - enc, _ := rc4.NewCipher(cipherKey) - var buf bytes.Buffer - enc.XORKeyStream(b, b) - buf.Write(b) - - randLength := udpGetRandLen(&a.randomClient, hash) - randBytes := pool.Get(randLength) - defer pool.Put(randBytes) - buf.Write(randBytes) - - buf.Write(authData) - buf.Write(uid) + md5Data := tools.HmacMD5(a.Key, authData) - h := a.hmac(a.userKey, buf.Bytes()) - buf.Write(h[:1]) - return buf.Bytes(), nil -} + randDataLength := udpGetRandLength(md5Data, &a.randomClient) -func (a *authChain) getRC4CipherKey(hash []byte) []byte { - base64UserKey := base64.StdEncoding.EncodeToString(a.userKey) - return a.calcRC4CipherKey(hash, base64UserKey) -} + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(md5Data), 16) + rc4Cipher, err := rc4.NewCipher(key) + if err != nil { + return err + } + rc4Cipher.XORKeyStream(b, b) -func (a *authChain) calcRC4CipherKey(hash []byte, base64UserKey string) []byte { - password := pool.Get(len(base64UserKey) + base64.StdEncoding.EncodedLen(16)) - defer pool.Put(password) - copy(password, base64UserKey) - base64.StdEncoding.Encode(password[len(base64UserKey):], hash[:16]) - return core.Kdf(string(password), 16) + buf.Write(b) + tools.AppendRandBytes(buf, randDataLength) + buf.Write(authData) + binary.Write(buf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(md5Data[:4])) + buf.Write(tools.HmacMD5(a.userKey, buf.Bytes())[:1]) + return nil } -func (a *authChain) initUserKeyAndID() { - if a.userKey == nil { - params := strings.Split(a.Param, ":") - if len(params) >= 2 { - if userID, err := strconv.ParseUint(params[0], 10, 32); err == nil { - binary.LittleEndian.PutUint32(a.uid[:], uint32(userID)) - a.userKey = []byte(params[1]) - } - } - - if a.userKey == nil { - rand.Read(a.uid[:]) - a.userKey = make([]byte, len(a.Key)) - copy(a.userKey, a.Key) - } +func (a *authChainA) packAuthData(poolBuf *bytes.Buffer, data []byte) { + /* + dataLength := len(data) + 12: checkHead(4) and hmac of checkHead(8) + 4: uint32 LittleEndian uid (uid = userID ^ last client hash) + 16: encrypted data of authdata(12), uint16 LittleEndian overhead(2) and uint16 LittleEndian number zero(2) + 4: last server hash(4) + packedAuthDataLength := 12 + 4 + 16 + 4 + dataLength + */ + + macKey := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(macKey) + copy(macKey, a.iv) + copy(macKey[len(a.iv):], a.Key) + + // check head + tools.AppendRandBytes(poolBuf, 4) + a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()) + a.initRC4Cipher() + poolBuf.Write(a.lastClientHash[:8]) + // uid + binary.Write(poolBuf, binary.LittleEndian, binary.LittleEndian.Uint32(a.userID[:])^binary.LittleEndian.Uint32(a.lastClientHash[8:12])) + // encrypted data + err := a.putEncryptedData(poolBuf, a.userKey, [2]int{a.Overhead, 0}, a.salt) + if err != nil { + poolBuf.Reset() + return } + // last server hash + a.lastServerHash = tools.HmacMD5(a.userKey, poolBuf.Bytes()[12:]) + poolBuf.Write(a.lastServerHash[:4]) + // packed data + a.packData(poolBuf, data) } -func (a *authChain) getClientRandLen(dataLength int, overhead int) int { - return a.rnd(dataLength, &a.randomClient, a.lastClientHash, a.dataSizeList, a.dataSizeList2, overhead) -} +func (a *authChainA) packData(poolBuf *bytes.Buffer, data []byte) { + a.encrypter.XORKeyStream(data, data) -func (a *authChain) getServerRandLen(dataLength int, overhead int) int { - return a.rnd(dataLength, &a.randomServer, a.lastServerHash, a.dataSizeList, a.dataSizeList2, overhead) -} + macKey := pool.Get(len(a.userKey) + 4) + defer pool.Put(macKey) + copy(macKey, a.userKey) + binary.LittleEndian.PutUint32(macKey[len(a.userKey):], a.packID) + a.packID++ -func (a *authChain) packedDataLen(data []byte) (chunkLength, randLength int) { - dataLength := len(data) - randLength = a.getClientRandLen(dataLength, a.Overhead) - chunkLength = randLength + dataLength + 2 + 2 - return -} + length := uint16(len(data)) ^ binary.LittleEndian.Uint16(a.lastClientHash[14:16]) -func (a *authChain) packData(outData []byte, data []byte, randLength int) { - dataLength := len(data) - outLength := randLength + dataLength + 2 - outData[0] = byte(dataLength) ^ a.lastClientHash[14] - outData[1] = byte(dataLength>>8) ^ a.lastClientHash[15] - - { - if dataLength > 0 { - randPart1Length := getRandStartPos(&a.randomClient, randLength) - rand.Read(outData[2 : 2+randPart1Length]) - a.enc.XORKeyStream(outData[2+randPart1Length:], data) - rand.Read(outData[2+randPart1Length+dataLength : outLength]) - } else { - rand.Read(outData[2 : 2+randLength]) - } - } - - userKeyLen := uint8(len(a.userKey)) - key := pool.Get(int(userKeyLen + 4)) - defer pool.Put(key) - copy(key, a.userKey) - a.chunkID++ - binary.LittleEndian.PutUint32(key[userKeyLen:], a.chunkID) - a.lastClientHash = a.hmac(key, outData[:outLength]) - copy(outData[outLength:], a.lastClientHash[:2]) + originalLength := poolBuf.Len() + binary.Write(poolBuf, binary.LittleEndian, length) + a.putMixedRandDataAndData(poolBuf, data) + a.lastClientHash = tools.HmacMD5(macKey, poolBuf.Bytes()[originalLength:]) + poolBuf.Write(a.lastClientHash[:2]) } -const authHeadLength = 4 + 8 + 4 + 16 + 4 - -func (a *authChain) packAuthData(data []byte) (outData []byte) { - outData = make([]byte, authHeadLength, authHeadLength+1500) - a.mutex.Lock() - defer a.mutex.Unlock() - a.connectionID++ - if a.connectionID > 0xFF000000 { - a.clientID = nil - } - if len(a.clientID) == 0 { - a.clientID = make([]byte, 4) - rand.Read(a.clientID) - b := make([]byte, 4) - rand.Read(b) - a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF +func (a *authChainA) putMixedRandDataAndData(poolBuf *bytes.Buffer, data []byte) { + randDataLength := a.randDataLength(len(data), a.lastClientHash, &a.randomClient) + if len(data) == 0 { + tools.AppendRandBytes(poolBuf, randDataLength) + return } - var key = make([]byte, len(a.IV)+len(a.Key)) - copy(key, a.IV) - copy(key[len(a.IV):], a.Key) - - encrypt := make([]byte, 20) - t := time.Now().Unix() - binary.LittleEndian.PutUint32(encrypt[:4], uint32(t)) - copy(encrypt[4:8], a.clientID) - binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) - binary.LittleEndian.PutUint16(encrypt[12:], uint16(a.Overhead)) - binary.LittleEndian.PutUint16(encrypt[14:], 0) - - // first 12 bytes - { - rand.Read(outData[:4]) - a.lastClientHash = a.hmac(key, outData[:4]) - copy(outData[4:], a.lastClientHash[:8]) + if randDataLength > 0 { + startPos := getRandStartPos(randDataLength, &a.randomClient) + tools.AppendRandBytes(poolBuf, startPos) + poolBuf.Write(data) + tools.AppendRandBytes(poolBuf, randDataLength-startPos) + return } - var base64UserKey string - // uid & 16 bytes auth data - { - a.initUserKeyAndID() - uid := make([]byte, 4) - for i := 0; i < 4; i++ { - uid[i] = a.uid[i] ^ a.lastClientHash[8+i] - } - base64UserKey = base64.StdEncoding.EncodeToString(a.userKey) - aesCipherKey := core.Kdf(base64UserKey+a.salt, 16) - block, err := aes.NewCipher(aesCipherKey) - if err != nil { - return - } - encryptData := make([]byte, 16) - iv := make([]byte, aes.BlockSize) - cbc := cipher.NewCBCEncrypter(block, iv) - cbc.CryptBlocks(encryptData, encrypt[:16]) - copy(encrypt[:4], uid[:]) - copy(encrypt[4:4+16], encryptData) - } - // final HMAC - { - a.lastServerHash = a.hmac(a.userKey, encrypt[:20]) - - copy(outData[12:], encrypt) - copy(outData[12+20:], a.lastServerHash[:4]) - } - - // init cipher - cipherKey := a.calcRC4CipherKey(a.lastClientHash, base64UserKey) - a.enc, _ = rc4.NewCipher(cipherKey) - a.dec, _ = rc4.NewCipher(cipherKey) - - // data - chunkLength, randLength := a.packedDataLen(data) - if chunkLength+authHeadLength <= cap(outData) { - outData = outData[:authHeadLength+chunkLength] - } else { - newOutData := make([]byte, authHeadLength+chunkLength) - copy(newOutData, outData[:authHeadLength]) - outData = newOutData - } - a.packData(outData[authHeadLength:], data, randLength) - return + poolBuf.Write(data) } -func getRandStartPos(random *shift128PlusContext, randLength int) int { - if randLength > 0 { - return int(random.Next() % 8589934609 % uint64(randLength)) +func getRandStartPos(length int, random *tools.XorShift128Plus) int { + if length == 0 { + return 0 } - return 0 + return int(random.Next()%8589934609) % length } -func authChainAGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { - if dataLength > 1440 { +func (a *authChainA) getRandLength(length int, lastHash []byte, random *tools.XorShift128Plus) int { + if length > 1440 { return 0 } - random.InitFromBinDatalen(lastHash[:16], dataLength) - if dataLength > 1300 { + random.InitFromBinAndLength(lastHash, length) + if length > 1300 { return int(random.Next() % 31) } - if dataLength > 900 { + if length > 900 { return int(random.Next() % 127) } - if dataLength > 400 { + if length > 400 { return int(random.Next() % 521) } return int(random.Next() % 1021) } -func udpGetRandLen(random *shift128PlusContext, lastHash []byte) int { - random.InitFromBin(lastHash[:16]) - return int(random.Next() % 127) -} - -type shift128PlusContext struct { - v [2]uint64 -} - -func (ctx *shift128PlusContext) InitFromBin(bin []byte) { - var fillBin [16]byte - copy(fillBin[:], bin) - - ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) - ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) -} - -func (ctx *shift128PlusContext) InitFromBinDatalen(bin []byte, datalen int) { - var fillBin [16]byte - copy(fillBin[:], bin) - binary.LittleEndian.PutUint16(fillBin[:2], uint16(datalen)) - - ctx.v[0] = binary.LittleEndian.Uint64(fillBin[:8]) - ctx.v[1] = binary.LittleEndian.Uint64(fillBin[8:]) - - for i := 0; i < 4; i++ { - ctx.Next() - } +func (a *authChainA) initRC4Cipher() { + key := core.Kdf(base64.StdEncoding.EncodeToString(a.userKey)+base64.StdEncoding.EncodeToString(a.lastClientHash), 16) + a.encrypter, _ = rc4.NewCipher(key) + a.decrypter, _ = rc4.NewCipher(key) } -func (ctx *shift128PlusContext) Next() uint64 { - x := ctx.v[0] - y := ctx.v[1] - ctx.v[0] = y - x ^= x << 23 - x ^= y ^ (x >> 17) ^ (y >> 26) - ctx.v[1] = x - return x + y +func udpGetRandLength(lastHash []byte, random *tools.XorShift128Plus) int { + random.InitFromBin(lastHash) + return int(random.Next() % 127) } diff --git a/component/ssr/protocol/auth_chain_b.go b/component/ssr/protocol/auth_chain_b.go index 4f01392931..2f3f8f17ae 100644 --- a/component/ssr/protocol/auth_chain_b.go +++ b/component/ssr/protocol/auth_chain_b.go @@ -1,71 +1,96 @@ package protocol import ( + "net" "sort" "github.com/Dreamacro/clash/component/ssr/tools" ) func init() { - register("auth_chain_b", newAuthChainB) + register("auth_chain_b", newAuthChainB, 4) +} + +type authChainB struct { + *authChainA + dataSizeList []int + dataSizeList2 []int } func newAuthChainB(b *Base) Protocol { - return &authChain{ - Base: b, - authData: &authData{}, - salt: "auth_chain_b", - hmac: tools.HmacMD5, - hashDigest: tools.SHA1Sum, - rnd: authChainBGetRandLen, + a := &authChainB{ + authChainA: &authChainA{ + Base: b, + authData: &authData{}, + userData: &userData{}, + salt: "auth_chain_b", + }, } + a.initUserData() + return a } -func initDataSize(r *authChain) { - random := &r.randomServer - random.InitFromBin(r.Key) - len := random.Next()%8 + 4 - r.dataSizeList = make([]int, len) - for i := 0; i < int(len); i++ { - r.dataSizeList[i] = int(random.Next() % 2340 % 2040 % 1440) +func (a *authChainB) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authChainB{ + authChainA: &authChainA{ + Base: a.Base, + authData: a.next(), + userData: a.userData, + salt: a.salt, + packID: 1, + recvID: 1, + }, } - sort.Ints(r.dataSizeList) + p.iv = iv + p.randDataLength = p.getRandLength + p.initDataSize() + return &Conn{Conn: c, Protocol: p} +} + +func (a *authChainB) initDataSize() { + a.dataSizeList = a.dataSizeList[:0] + a.dataSizeList2 = a.dataSizeList2[:0] - len = random.Next()%16 + 8 - r.dataSizeList2 = make([]int, len) - for i := 0; i < int(len); i++ { - r.dataSizeList2[i] = int(random.Next() % 2340 % 2040 % 1440) + a.randomServer.InitFromBin(a.Key) + length := a.randomServer.Next()%8 + 4 + for ; length > 0; length-- { + a.dataSizeList = append(a.dataSizeList, int(a.randomServer.Next()%2340%2040%1440)) } - sort.Ints(r.dataSizeList2) + sort.Ints(a.dataSizeList) + + length = a.randomServer.Next()%16 + 8 + for ; length > 0; length-- { + a.dataSizeList2 = append(a.dataSizeList2, int(a.randomServer.Next()%2340%2040%1440)) + } + sort.Ints(a.dataSizeList2) } -func authChainBGetRandLen(dataLength int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int { - if dataLength > 1440 { +func (a *authChainB) getRandLength(length int, lashHash []byte, random *tools.XorShift128Plus) int { + if length >= 1440 { return 0 } - random.InitFromBinDatalen(lastHash[:16], dataLength) - pos := sort.Search(len(dataSizeList), func(i int) bool { return dataSizeList[i] > dataLength+overhead }) - finalPos := uint64(pos) + random.Next()%uint64(len(dataSizeList)) - if finalPos < uint64(len(dataSizeList)) { - return dataSizeList[finalPos] - dataLength - overhead + random.InitFromBinAndLength(lashHash, length) + pos := sort.Search(len(a.dataSizeList), func(i int) bool { return a.dataSizeList[i] >= length+a.Overhead }) + finalPos := pos + int(random.Next()%uint64(len(a.dataSizeList))) + if finalPos < len(a.dataSizeList) { + return a.dataSizeList[finalPos] - length - a.Overhead } - pos = sort.Search(len(dataSizeList2), func(i int) bool { return dataSizeList2[i] > dataLength+overhead }) - finalPos = uint64(pos) + random.Next()%uint64(len(dataSizeList2)) - if finalPos < uint64(len(dataSizeList2)) { - return dataSizeList2[finalPos] - dataLength - overhead + pos = sort.Search(len(a.dataSizeList2), func(i int) bool { return a.dataSizeList2[i] >= length+a.Overhead }) + finalPos = pos + int(random.Next()%uint64(len(a.dataSizeList2))) + if finalPos < len(a.dataSizeList2) { + return a.dataSizeList2[finalPos] - length - a.Overhead } - if finalPos < uint64(pos+len(dataSizeList2)-1) { + if finalPos < pos+len(a.dataSizeList2)-1 { return 0 } - - if dataLength > 1300 { + if length > 1300 { return int(random.Next() % 31) } - if dataLength > 900 { + if length > 900 { return int(random.Next() % 127) } - if dataLength > 400 { + if length > 400 { return int(random.Next() % 521) } return int(random.Next() % 1021) diff --git a/component/ssr/protocol/auth_sha1_v4.go b/component/ssr/protocol/auth_sha1_v4.go index 0cff1c86f0..0f24c360c9 100644 --- a/component/ssr/protocol/auth_sha1_v4.go +++ b/component/ssr/protocol/auth_sha1_v4.go @@ -6,248 +6,177 @@ import ( "hash/adler32" "hash/crc32" "math/rand" - "time" + "net" "github.com/Dreamacro/clash/common/pool" "github.com/Dreamacro/clash/component/ssr/tools" ) +func init() { + register("auth_sha1_v4", newAuthSHA1V4, 7) +} + type authSHA1V4 struct { *Base *authData - headerSent bool - buffer bytes.Buffer -} - -func init() { - register("auth_sha1_v4", newAuthSHA1V4) + iv []byte + hasSentHeader bool + rawTrans bool } func newAuthSHA1V4(b *Base) Protocol { return &authSHA1V4{Base: b, authData: &authData{}} } -func (a *authSHA1V4) initForConn(iv []byte) Protocol { - return &authSHA1V4{ - Base: &Base{ - IV: iv, - Key: a.Key, - TCPMss: a.TCPMss, - Overhead: a.Overhead, - Param: a.Param, - }, - authData: a.authData, - } -} - -func (a *authSHA1V4) GetProtocolOverhead() int { - return 7 +func (a *authSHA1V4) StreamConn(c net.Conn, iv []byte) net.Conn { + p := &authSHA1V4{Base: a.Base, authData: a.next()} + p.iv = iv + return &Conn{Conn: c, Protocol: p} } -func (a *authSHA1V4) SetOverhead(overhead int) { - a.Overhead = overhead +func (a *authSHA1V4) PacketConn(c net.PacketConn) net.PacketConn { + return c } -func (a *authSHA1V4) Decode(b []byte) ([]byte, int, error) { - a.buffer.Reset() - bSize := len(b) - originalSize := bSize - for bSize > 4 { - crc := crc32.ChecksumIEEE(b[:2]) & 0xFFFF - if binary.LittleEndian.Uint16(b[2:4]) != uint16(crc) { - return nil, 0, errAuthSHA1v4CRC32Error +func (a *authSHA1V4) Decode(dst, src *bytes.Buffer) error { + if a.rawTrans { + dst.ReadFrom(src) + return nil + } + for src.Len() > 4 { + if uint16(crc32.ChecksumIEEE(src.Bytes()[:2])&0xffff) != binary.LittleEndian.Uint16(src.Bytes()[2:4]) { + src.Reset() + return errAuthSHA1V4CRC32Error } - length := int(binary.BigEndian.Uint16(b[:2])) - if length >= 8192 || length < 8 { - return nil, 0, errAuthSHA1v4DataLengthError + + length := int(binary.BigEndian.Uint16(src.Bytes()[:2])) + if length >= 8192 || length < 7 { + a.rawTrans = true + src.Reset() + return errAuthSHA1V4LengthError } - if length > bSize { + if length > src.Len() { break } - if adler32.Checksum(b[:length-4]) == binary.LittleEndian.Uint32(b[length-4:]) { - pos := int(b[4]) - if pos != 0xFF { - pos += 4 - } else { - pos = int(binary.BigEndian.Uint16(b[5:5+2])) + 4 - } - retSize := length - pos - 4 - a.buffer.Write(b[pos : pos+retSize]) - bSize -= length - b = b[length:] + if adler32.Checksum(src.Bytes()[:length-4]) != binary.LittleEndian.Uint32(src.Bytes()[length-4:length]) { + a.rawTrans = true + src.Reset() + return errAuthSHA1V4Adler32Error + } + + pos := int(src.Bytes()[4]) + if pos < 255 { + pos += 4 } else { - return nil, 0, errAuthSHA1v4IncorrectChecksum + pos = int(binary.BigEndian.Uint16(src.Bytes()[5:7])) + 4 } + dst.Write(src.Bytes()[pos : length-4]) + src.Next(length) } - return a.buffer.Bytes(), originalSize - bSize, nil + return nil } -func (a *authSHA1V4) Encode(b []byte) ([]byte, error) { - a.buffer.Reset() - bSize := len(b) - offset := 0 - if !a.headerSent && bSize > 0 { - headSize := getHeadSize(b, 30) - if headSize > bSize { - headSize = bSize - } - a.buffer.Write(a.packAuthData(b[:headSize])) - offset += headSize - bSize -= headSize - a.headerSent = true +func (a *authSHA1V4) Encode(buf *bytes.Buffer, b []byte) error { + if !a.hasSentHeader { + dataLength := getDataLength(b) + + a.packAuthData(buf, b[:dataLength]) + b = b[dataLength:] + + a.hasSentHeader = true } - const blockSize = 4096 - for bSize > blockSize { - packSize, randSize := a.packedDataSize(b[offset : offset+blockSize]) - pack := pool.Get(packSize) - a.packData(b[offset:offset+blockSize], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) - offset += blockSize - bSize -= blockSize + for len(b) > 8100 { + a.packData(buf, b[:8100]) + b = b[8100:] } - if bSize > 0 { - packSize, randSize := a.packedDataSize(b[offset:]) - pack := pool.Get(packSize) - a.packData(b[offset:], pack, randSize) - a.buffer.Write(pack) - pool.Put(pack) + if len(b) > 0 { + a.packData(buf, b) } - return a.buffer.Bytes(), nil -} -func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, int, error) { - return b, len(b), nil + return nil } -func (a *authSHA1V4) EncodePacket(b []byte) ([]byte, error) { - return b, nil -} +func (a *authSHA1V4) DecodePacket(b []byte) ([]byte, error) { return b, nil } -func (a *authSHA1V4) packedDataSize(data []byte) (packSize, randSize int) { - dataSize := len(data) - randSize = 1 - if dataSize <= 1300 { - if dataSize > 400 { - randSize += rand.Intn(128) - } else { - randSize += rand.Intn(1024) - } - } - packSize = randSize + dataSize + 8 - return +func (a *authSHA1V4) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil } -func (a *authSHA1V4) packData(data, ret []byte, randSize int) { - dataSize := len(data) - retSize := len(ret) - // 0~1, ret size - binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF)) - // 2~3, crc of ret size - crc := crc32.ChecksumIEEE(ret[:2]) & 0xFFFF - binary.LittleEndian.PutUint16(ret[2:4], uint16(crc)) - // 4, rand size - if randSize < 128 { - ret[4] = uint8(randSize & 0xFF) - } else { - ret[4] = uint8(0xFF) - binary.BigEndian.PutUint16(ret[5:7], uint16(randSize&0xFFFF)) - } - // (rand size+4)~(ret size-4), data - if dataSize > 0 { - copy(ret[randSize+4:], data) +func (a *authSHA1V4) packData(poolBuf *bytes.Buffer, data []byte) { + dataLength := len(data) + randDataLength := a.getRandDataLength(dataLength) + /* + 2: uint16 BigEndian packedDataLength + 2: uint16 LittleEndian crc32Data & 0xffff + 3: maxRandDataLengthPrefix (min:1) + 4: adler32Data + */ + packedDataLength := 2 + 2 + 3 + randDataLength + dataLength + 4 + if randDataLength < 128 { + packedDataLength -= 2 } - // (ret size-4)~end, adler32 of full data - adler := adler32.Checksum(ret[:retSize-4]) - binary.LittleEndian.PutUint32(ret[retSize-4:], adler) + + binary.Write(poolBuf, binary.BigEndian, uint16(packedDataLength)) + binary.Write(poolBuf, binary.LittleEndian, uint16(crc32.ChecksumIEEE(poolBuf.Bytes()[poolBuf.Len()-2:])&0xffff)) + a.packRandData(poolBuf, randDataLength) + poolBuf.Write(data) + binary.Write(poolBuf, binary.LittleEndian, adler32.Checksum(poolBuf.Bytes()[poolBuf.Len()-packedDataLength+4:])) } -func (a *authSHA1V4) packAuthData(data []byte) (ret []byte) { - dataSize := len(data) - randSize := 1 - if dataSize <= 1300 { - if dataSize > 400 { - randSize += rand.Intn(128) - } else { - randSize += rand.Intn(1024) - } - } - dataOffset := randSize + 4 + 2 - retSize := dataOffset + dataSize + 12 + tools.HmacSHA1Len - ret = make([]byte, retSize) - a.mutex.Lock() - defer a.mutex.Unlock() - a.connectionID++ - if a.connectionID > 0xFF000000 { - a.clientID = nil +func (a *authSHA1V4) packAuthData(poolBuf *bytes.Buffer, data []byte) { + dataLength := len(data) + randDataLength := a.getRandDataLength(12 + dataLength) + /* + 2: uint16 BigEndian packedAuthDataLength + 4: uint32 LittleEndian crc32Data + 3: maxRandDataLengthPrefix (min: 1) + 12: authDataLength + 10: hmacSHA1DataLength + */ + packedAuthDataLength := 2 + 4 + 3 + randDataLength + 12 + dataLength + 10 + if randDataLength < 128 { + packedAuthDataLength -= 2 } - if len(a.clientID) == 0 { - a.clientID = make([]byte, 8) - rand.Read(a.clientID) - b := make([]byte, 4) - rand.Read(b) - a.connectionID = binary.LittleEndian.Uint32(b) & 0xFFFFFF - } - // 0~1, ret size - binary.BigEndian.PutUint16(ret[:2], uint16(retSize&0xFFFF)) - // 2~6, crc of (ret size+salt+key) salt := []byte("auth_sha1_v4") - crcData := make([]byte, len(salt)+len(a.Key)+2) - copy(crcData[:2], ret[:2]) + crcData := pool.Get(len(salt) + len(a.Key) + 2) + defer pool.Put(crcData) + binary.BigEndian.PutUint16(crcData, uint16(packedAuthDataLength)) copy(crcData[2:], salt) copy(crcData[2+len(salt):], a.Key) - crc := crc32.ChecksumIEEE(crcData) & 0xFFFFFFFF - // 2~6, crc of (ret size+salt+key) - binary.LittleEndian.PutUint32(ret[2:], crc) - // 6~(rand size+6), rand numbers - rand.Read(ret[dataOffset-randSize : dataOffset]) - // 6, rand size - if randSize < 128 { - ret[6] = byte(randSize & 0xFF) - } else { - // 6, magic number 0xFF - ret[6] = 0xFF - // 7~8, rand size - binary.BigEndian.PutUint16(ret[7:9], uint16(randSize&0xFFFF)) + + key := pool.Get(len(a.iv) + len(a.Key)) + defer pool.Put(key) + copy(key, a.iv) + copy(key[len(a.iv):], a.Key) + + poolBuf.Write(crcData[:2]) + binary.Write(poolBuf, binary.LittleEndian, crc32.ChecksumIEEE(crcData)) + a.packRandData(poolBuf, randDataLength) + a.putAuthData(poolBuf) + poolBuf.Write(data) + poolBuf.Write(tools.HmacSHA1(key, poolBuf.Bytes()[poolBuf.Len()-packedAuthDataLength+10:])[:10]) +} + +func (a *authSHA1V4) packRandData(poolBuf *bytes.Buffer, size int) { + if size < 128 { + poolBuf.WriteByte(byte(size + 1)) + tools.AppendRandBytes(poolBuf, size) + return } - // rand size+6~(rand size+10), time stamp - now := time.Now().Unix() - binary.LittleEndian.PutUint32(ret[dataOffset:dataOffset+4], uint32(now)) - // rand size+10~(rand size+14), client ID - copy(ret[dataOffset+4:dataOffset+4+4], a.clientID[:4]) - // rand size+14~(rand size+18), connection ID - binary.LittleEndian.PutUint32(ret[dataOffset+8:dataOffset+8+4], a.connectionID) - // rand size+18~(rand size+18)+data length, data - copy(ret[dataOffset+12:], data) - - key := make([]byte, len(a.IV)+len(a.Key)) - copy(key, a.IV) - copy(key[len(a.IV):], a.Key) - - h := tools.HmacSHA1(key, ret[:retSize-tools.HmacSHA1Len]) - // (ret size-10)~(ret size)/(rand size)+18+data length~end, hmac - copy(ret[retSize-tools.HmacSHA1Len:], h[:tools.HmacSHA1Len]) - return ret + poolBuf.WriteByte(255) + binary.Write(poolBuf, binary.BigEndian, uint16(size+3)) + tools.AppendRandBytes(poolBuf, size) } -func getHeadSize(data []byte, defaultValue int) int { - if data == nil || len(data) < 2 { - return defaultValue +func (a *authSHA1V4) getRandDataLength(size int) int { + if size > 1200 { + return 0 } - headType := data[0] & 0x07 - switch headType { - case 1: - // IPv4 1+4+2 - return 7 - case 4: - // IPv6 1+16+2 - return 19 - case 3: - // domain name, variant length - return 4 + int(data[1]) + if size > 400 { + return rand.Intn(256) } - - return defaultValue + return rand.Intn(512) } diff --git a/component/ssr/protocol/base.go b/component/ssr/protocol/base.go index ec7aad5d32..ed8ac18fd1 100644 --- a/component/ssr/protocol/base.go +++ b/component/ssr/protocol/base.go @@ -1,10 +1,77 @@ package protocol -// Base information for protocol +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/binary" + "math/rand" + "sync" + "time" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/go-shadowsocks2/core" +) + type Base struct { - IV []byte Key []byte - TCPMss int Overhead int Param string } + +type userData struct { + userKey []byte + userID [4]byte +} + +type authData struct { + clientID [4]byte + connectionID uint32 + mutex sync.Mutex +} + +func (a *authData) next() *authData { + r := &authData{} + a.mutex.Lock() + defer a.mutex.Unlock() + if a.connectionID > 0xff000000 || a.connectionID == 0 { + rand.Read(a.clientID[:]) + a.connectionID = rand.Uint32() & 0xffffff + } + a.connectionID++ + copy(r.clientID[:], a.clientID[:]) + r.connectionID = a.connectionID + return r +} + +func (a *authData) putAuthData(buf *bytes.Buffer) { + binary.Write(buf, binary.LittleEndian, uint32(time.Now().Unix())) + buf.Write(a.clientID[:]) + binary.Write(buf, binary.LittleEndian, a.connectionID) +} + +func (a *authData) putEncryptedData(b *bytes.Buffer, userKey []byte, paddings [2]int, salt string) error { + encrypt := pool.Get(16) + defer pool.Put(encrypt) + binary.LittleEndian.PutUint32(encrypt, uint32(time.Now().Unix())) + copy(encrypt[4:], a.clientID[:]) + binary.LittleEndian.PutUint32(encrypt[8:], a.connectionID) + binary.LittleEndian.PutUint16(encrypt[12:], uint16(paddings[0])) + binary.LittleEndian.PutUint16(encrypt[14:], uint16(paddings[1])) + + cipherKey := core.Kdf(base64.StdEncoding.EncodeToString(userKey)+salt, 16) + block, err := aes.NewCipher(cipherKey) + if err != nil { + log.Warnln("New cipher error: %s", err.Error()) + return err + } + iv := bytes.Repeat([]byte{0}, 16) + cbcCipher := cipher.NewCBCEncrypter(block, iv) + + cbcCipher.CryptBlocks(encrypt, encrypt) + + b.Write(encrypt) + return nil +} diff --git a/component/ssr/protocol/origin.go b/component/ssr/protocol/origin.go index 0d1fe217f3..80fdfa9a1b 100644 --- a/component/ssr/protocol/origin.go +++ b/component/ssr/protocol/origin.go @@ -1,36 +1,33 @@ package protocol -type origin struct{ *Base } +import ( + "bytes" + "net" +) -func init() { - register("origin", newOrigin) -} +type origin struct{} -func newOrigin(b *Base) Protocol { - return &origin{} -} +func init() { register("origin", newOrigin, 0) } -func (o *origin) initForConn(iv []byte) Protocol { return &origin{} } +func newOrigin(b *Base) Protocol { return &origin{} } -func (o *origin) GetProtocolOverhead() int { - return 0 -} +func (o *origin) StreamConn(c net.Conn, iv []byte) net.Conn { return c } -func (o *origin) SetOverhead(overhead int) { -} +func (o *origin) PacketConn(c net.PacketConn) net.PacketConn { return c } -func (o *origin) Decode(b []byte) ([]byte, int, error) { - return b, len(b), nil +func (o *origin) Decode(dst, src *bytes.Buffer) error { + dst.ReadFrom(src) + return nil } -func (o *origin) Encode(b []byte) ([]byte, error) { - return b, nil +func (o *origin) Encode(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil } -func (o *origin) DecodePacket(b []byte) ([]byte, int, error) { - return b, len(b), nil -} +func (o *origin) DecodePacket(b []byte) ([]byte, error) { return b, nil } -func (o *origin) EncodePacket(b []byte) ([]byte, error) { - return b, nil +func (o *origin) EncodePacket(buf *bytes.Buffer, b []byte) error { + buf.Write(b) + return nil } diff --git a/component/ssr/protocol/packet.go b/component/ssr/protocol/packet.go index c3e5c702fd..1577c7885e 100644 --- a/component/ssr/protocol/packet.go +++ b/component/ssr/protocol/packet.go @@ -1,30 +1,26 @@ package protocol import ( + "bytes" "net" - "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" ) -// NewPacketConn returns a net.NewPacketConn with protocol decoding/encoding -func NewPacketConn(pc net.PacketConn, p Protocol) net.PacketConn { - return &PacketConn{PacketConn: pc, Protocol: p.initForConn(nil)} -} - -// PacketConn represents a protocol packet connection type PacketConn struct { net.PacketConn Protocol } func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { - buf := pool.Get(pool.RelayBufferSize) - defer pool.Put(buf) - buf, err := c.EncodePacket(b) + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + err := c.EncodePacket(buf, b) if err != nil { return 0, err } - _, err = c.PacketConn.WriteTo(buf, addr) + _, err = c.PacketConn.WriteTo(buf.Bytes(), addr) return len(b), err } @@ -33,10 +29,10 @@ func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { if err != nil { return n, addr, err } - bb, length, err := c.DecodePacket(b[:n]) + decoded, err := c.DecodePacket(b[:n]) if err != nil { return n, addr, err } - copy(b, bb) - return length, addr, err + copy(b, decoded) + return len(decoded), addr, nil } diff --git a/component/ssr/protocol/protocol.go b/component/ssr/protocol/protocol.go index 43c98d0da9..41bd984c8b 100644 --- a/component/ssr/protocol/protocol.go +++ b/component/ssr/protocol/protocol.go @@ -4,60 +4,73 @@ import ( "bytes" "errors" "fmt" - "strings" - "sync" + "math/rand" + "net" ) var ( - errAuthAES128IncorrectMAC = errors.New("auth_aes128_* post decrypt incorrect mac") - errAuthAES128DataLengthError = errors.New("auth_aes128_* post decrypt length mismatch") - errAuthAES128IncorrectChecksum = errors.New("auth_aes128_* post decrypt incorrect checksum") - errAuthAES128PositionTooLarge = errors.New("auth_aes128_* post decrypt position is too large") - errAuthSHA1v4CRC32Error = errors.New("auth_sha1_v4 post decrypt data crc32 error") - errAuthSHA1v4DataLengthError = errors.New("auth_sha1_v4 post decrypt data length error") - errAuthSHA1v4IncorrectChecksum = errors.New("auth_sha1_v4 post decrypt incorrect checksum") - errAuthChainDataLengthError = errors.New("auth_chain_* post decrypt length mismatch") - errAuthChainHMACError = errors.New("auth_chain_* post decrypt hmac error") + errAuthSHA1V4CRC32Error = errors.New("auth_sha1_v4 decode data wrong crc32") + errAuthSHA1V4LengthError = errors.New("auth_sha1_v4 decode data wrong length") + errAuthSHA1V4Adler32Error = errors.New("auth_sha1_v4 decode data wrong adler32") + errAuthAES128MACError = errors.New("auth_aes128 decode data wrong mac") + errAuthAES128LengthError = errors.New("auth_aes128 decode data wrong length") + errAuthAES128ChksumError = errors.New("auth_aes128 decode data wrong checksum") + errAuthChainLengthError = errors.New("auth_chain decode data wrong length") + errAuthChainChksumError = errors.New("auth_chain decode data wrong checksum") ) -type authData struct { - clientID []byte - connectionID uint32 - mutex sync.Mutex -} - -type recvInfo struct { - recvID uint32 - buffer *bytes.Buffer -} - -type hmacMethod func(key []byte, data []byte) []byte -type hashDigestMethod func(data []byte) []byte -type rndMethod func(dataSize int, random *shift128PlusContext, lastHash []byte, dataSizeList, dataSizeList2 []int, overhead int) int - -// Protocol provides methods for decoding, encoding and iv setting type Protocol interface { - initForConn(iv []byte) Protocol - GetProtocolOverhead() int - SetOverhead(int) - Decode([]byte) ([]byte, int, error) - Encode([]byte) ([]byte, error) - DecodePacket([]byte) ([]byte, int, error) - EncodePacket([]byte) ([]byte, error) + StreamConn(net.Conn, []byte) net.Conn + PacketConn(net.PacketConn) net.PacketConn + Decode(dst, src *bytes.Buffer) error + Encode(buf *bytes.Buffer, b []byte) error + DecodePacket([]byte) ([]byte, error) + EncodePacket(buf *bytes.Buffer, b []byte) error } type protocolCreator func(b *Base) Protocol -var protocolList = make(map[string]protocolCreator) +var protocolList = make(map[string]struct { + overhead int + new protocolCreator +}) -func register(name string, c protocolCreator) { - protocolList[name] = c +func register(name string, c protocolCreator, o int) { + protocolList[name] = struct { + overhead int + new protocolCreator + }{overhead: o, new: c} } -// PickProtocol returns a protocol of the given name func PickProtocol(name string, b *Base) (Protocol, error) { - if protocolCreator, ok := protocolList[strings.ToLower(name)]; ok { - return protocolCreator(b), nil + if choice, ok := protocolList[name]; ok { + b.Overhead += choice.overhead + return choice.new(b), nil + } + return nil, fmt.Errorf("protocol %s not supported", name) +} + +func getHeadSize(b []byte, defaultValue int) int { + if len(b) < 2 { + return defaultValue + } + headType := b[0] & 7 + switch headType { + case 1: + return 7 + case 4: + return 19 + case 3: + return 4 + int(b[1]) + } + return defaultValue +} + +func getDataLength(b []byte) int { + bLength := len(b) + dataLength := getHeadSize(b, 30) + rand.Intn(32) + if bLength < dataLength { + return bLength } - return nil, fmt.Errorf("Protocol %s not supported", name) + return dataLength } diff --git a/component/ssr/protocol/stream.go b/component/ssr/protocol/stream.go index c76d35eb1b..53f53ead40 100644 --- a/component/ssr/protocol/stream.go +++ b/component/ssr/protocol/stream.go @@ -5,31 +5,21 @@ import ( "net" "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ssr/tools" ) -// NewConn wraps a stream-oriented net.Conn with protocol decoding/encoding -func NewConn(c net.Conn, p Protocol, iv []byte) net.Conn { - return &Conn{Conn: c, Protocol: p.initForConn(iv)} -} - -// Conn represents a protocol connection type Conn struct { net.Conn Protocol - buf []byte - offset int + decoded bytes.Buffer underDecoded bytes.Buffer } func (c *Conn) Read(b []byte) (int, error) { - if c.buf != nil { - n := copy(b, c.buf[c.offset:]) - c.offset += n - if c.offset == len(c.buf) { - c.buf = nil - } - return n, nil + if c.decoded.Len() > 0 { + return c.decoded.Read(b) } + buf := pool.Get(pool.RelayBufferSize) defer pool.Put(buf) n, err := c.Conn.Read(buf) @@ -37,32 +27,26 @@ func (c *Conn) Read(b []byte) (int, error) { return 0, err } c.underDecoded.Write(buf[:n]) - underDecoded := c.underDecoded.Bytes() - decoded, length, err := c.Decode(underDecoded) + err = c.Decode(&c.decoded, &c.underDecoded) if err != nil { - c.underDecoded.Reset() - return 0, nil - } - if length == 0 { - return 0, nil - } - c.underDecoded.Next(length) - n = copy(b, decoded) - if len(decoded) > len(b) { - c.buf = decoded - c.offset = n + return 0, err } + n, _ = c.decoded.Read(b) return n, nil } func (c *Conn) Write(b []byte) (int, error) { - encoded, err := c.Encode(b) + bLength := len(b) + buf := tools.BufPool.Get().(*bytes.Buffer) + defer tools.BufPool.Put(buf) + defer buf.Reset() + err := c.Encode(buf, b) if err != nil { return 0, err } - _, err = c.Conn.Write(encoded) + _, err = c.Conn.Write(buf.Bytes()) if err != nil { return 0, err } - return len(b), nil + return bLength, nil } diff --git a/component/ssr/tools/bufPool.go b/component/ssr/tools/bufPool.go new file mode 100644 index 0000000000..f3c45c469f --- /dev/null +++ b/component/ssr/tools/bufPool.go @@ -0,0 +1,18 @@ +package tools + +import ( + "bytes" + "math/rand" + "sync" + + "github.com/Dreamacro/clash/common/pool" +) + +var BufPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} + +func AppendRandBytes(b *bytes.Buffer, length int) { + randBytes := pool.Get(length) + defer pool.Put(randBytes) + rand.Read(randBytes) + b.Write(randBytes) +} diff --git a/component/ssr/tools/encrypt.go b/component/ssr/tools/crypto.go similarity index 88% rename from component/ssr/tools/encrypt.go rename to component/ssr/tools/crypto.go index 5fef1654bc..b2a41561e9 100644 --- a/component/ssr/tools/encrypt.go +++ b/component/ssr/tools/crypto.go @@ -11,13 +11,13 @@ const HmacSHA1Len = 10 func HmacMD5(key, data []byte) []byte { hmacMD5 := hmac.New(md5.New, key) hmacMD5.Write(data) - return hmacMD5.Sum(nil)[:16] + return hmacMD5.Sum(nil) } func HmacSHA1(key, data []byte) []byte { hmacSHA1 := hmac.New(sha1.New, key) hmacSHA1.Write(data) - return hmacSHA1.Sum(nil)[:20] + return hmacSHA1.Sum(nil) } func MD5Sum(b []byte) []byte { diff --git a/component/ssr/tools/random.go b/component/ssr/tools/random.go new file mode 100644 index 0000000000..338543ea6e --- /dev/null +++ b/component/ssr/tools/random.go @@ -0,0 +1,57 @@ +package tools + +import ( + "encoding/binary" + + "github.com/Dreamacro/clash/common/pool" +) + +// XorShift128Plus - a pseudorandom number generator +type XorShift128Plus struct { + s [2]uint64 +} + +func (r *XorShift128Plus) Next() uint64 { + x := r.s[0] + y := r.s[1] + r.s[0] = y + x ^= x << 23 + x ^= y ^ (x >> 17) ^ (y >> 26) + r.s[1] = x + return x + y +} + +func (r *XorShift128Plus) InitFromBin(bin []byte) { + var full []byte + if len(bin) < 16 { + full := pool.Get(16)[:0] + defer pool.Put(full) + full = append(full, bin...) + for len(full) < 16 { + full = append(full, 0) + } + } else { + full = bin + } + r.s[0] = binary.LittleEndian.Uint64(full[:8]) + r.s[1] = binary.LittleEndian.Uint64(full[8:16]) +} + +func (r *XorShift128Plus) InitFromBinAndLength(bin []byte, length int) { + var full []byte + if len(bin) < 16 { + full := pool.Get(16)[:0] + defer pool.Put(full) + full = append(full, bin...) + for len(full) < 16 { + full = append(full, 0) + } + } + full = bin + binary.LittleEndian.PutUint16(full, uint16(length)) + r.s[0] = binary.LittleEndian.Uint64(full[:8]) + r.s[1] = binary.LittleEndian.Uint64(full[8:16]) + for i := 0; i < 4; i++ { + r.Next() + } +} From aa81193d5b94aabcad82507c8ab28ffaabfd2e93 Mon Sep 17 00:00:00 2001 From: peeweep Date: Thu, 18 Feb 2021 18:15:09 +0800 Subject: [PATCH 532/535] Feature: add darwin arm64 to Makefile (Apple Silicon) (#1234) --- .github/workflows/go.yml | 2 +- Makefile | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index aed4f6ce43..c73e2db85a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,7 +9,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v2 with: - go-version: 1.15.x + go-version: 1.16 - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/Makefile b/Makefile index e1a913f95b..cfe4ed1a06 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-X "github.com/Dreamacro/clas PLATFORM_LIST = \ darwin-amd64 \ + darwin-arm64 \ linux-386 \ linux-amd64 \ linux-armv5 \ @@ -36,6 +37,9 @@ docker: darwin-amd64: GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ +darwin-arm64: + GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + linux-386: GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ From 14bbf6eedceb76230db7e97b34c7a1e38d2f1ba8 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Thu, 18 Feb 2021 23:41:50 +0800 Subject: [PATCH 533/535] Feature: support store group selected node to cache (enable by default) --- component/profile/cachefile/cache.go | 115 +++++++++++++++++++++++++++ component/profile/profile.go | 10 +++ config/config.go | 13 ++- constant/path.go | 4 + hub/executor/executor.go | 40 ++++++++++ hub/route/proxies.go | 2 + 6 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 component/profile/cachefile/cache.go create mode 100644 component/profile/profile.go diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go new file mode 100644 index 0000000000..cb23f87b3c --- /dev/null +++ b/component/profile/cachefile/cache.go @@ -0,0 +1,115 @@ +package cachefile + +import ( + "bytes" + "encoding/gob" + "io/ioutil" + "os" + "sync" + + "github.com/Dreamacro/clash/component/profile" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" +) + +var ( + initOnce sync.Once + fileMode os.FileMode = 0666 + defaultCache *CacheFile +) + +type cache struct { + Selected map[string]string +} + +// CacheFile store and update the cache file +type CacheFile struct { + path string + model *cache + enc *gob.Encoder + buf *bytes.Buffer + mux sync.Mutex +} + +func (c *CacheFile) SetSelected(group, selected string) { + if !profile.StoreSelected.Load() { + return + } + + c.mux.Lock() + defer c.mux.Unlock() + + model, err := c.element() + if err != nil { + log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error()) + return + } + + model.Selected[group] = selected + c.buf.Reset() + if err := c.enc.Encode(model); err != nil { + log.Warnln("[CacheFile] encode gob failed: %s", err.Error()) + return + } + + if err := ioutil.WriteFile(c.path, c.buf.Bytes(), fileMode); err != nil { + log.Warnln("[CacheFile] write cache to %s failed: %s", c.path, err.Error()) + return + } +} + +func (c *CacheFile) SelectedMap() map[string]string { + if !profile.StoreSelected.Load() { + return nil + } + + c.mux.Lock() + defer c.mux.Unlock() + + model, err := c.element() + if err != nil { + log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error()) + return nil + } + + mapping := map[string]string{} + for k, v := range model.Selected { + mapping[k] = v + } + return mapping +} + +func (c *CacheFile) element() (*cache, error) { + if c.model != nil { + return c.model, nil + } + + model := &cache{ + Selected: map[string]string{}, + } + + if buf, err := ioutil.ReadFile(c.path); err == nil { + bufReader := bytes.NewBuffer(buf) + dec := gob.NewDecoder(bufReader) + if err := dec.Decode(model); err != nil { + return nil, err + } + } + + c.model = model + return c.model, nil +} + +// Cache return singleton of CacheFile +func Cache() *CacheFile { + initOnce.Do(func() { + buf := &bytes.Buffer{} + defaultCache = &CacheFile{ + path: C.Path.Cache(), + buf: buf, + enc: gob.NewEncoder(buf), + } + }) + + return defaultCache +} diff --git a/component/profile/profile.go b/component/profile/profile.go new file mode 100644 index 0000000000..ca9b1b8844 --- /dev/null +++ b/component/profile/profile.go @@ -0,0 +1,10 @@ +package profile + +import ( + "go.uber.org/atomic" +) + +var ( + // StoreSelected is a global switch for storing selected proxy to cache + StoreSelected = atomic.NewBool(true) +) diff --git a/config/config.go b/config/config.go index 8408fb52d5..51faac9185 100644 --- a/config/config.go +++ b/config/config.go @@ -73,6 +73,11 @@ type FallbackFilter struct { Domain []string `yaml:"domain"` } +// Profile config +type Profile struct { + StoreSelected bool `yaml:"store-selected"` +} + // Experimental config type Experimental struct{} @@ -82,6 +87,7 @@ type Config struct { DNS *DNS Experimental *Experimental Hosts *trie.DomainTrie + Profile *Profile Rules []C.Rule Users []auth.AuthUser Proxies map[string]C.Proxy @@ -129,6 +135,7 @@ type RawConfig struct { Hosts map[string]string `yaml:"hosts"` DNS RawDNS `yaml:"dns"` Experimental Experimental `yaml:"experimental"` + Profile Profile `yaml:"profile"` Proxy []map[string]interface{} `yaml:"proxies"` ProxyGroup []map[string]interface{} `yaml:"proxy-groups"` Rule []string `yaml:"rules"` @@ -145,7 +152,7 @@ func Parse(buf []byte) (*Config, error) { } func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { - // config with some default value + // config with default value rawCfg := &RawConfig{ AllowLan: false, BindAddress: "*", @@ -169,6 +176,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { "8.8.8.8", }, }, + Profile: Profile{ + StoreSelected: true, + }, } if err := yaml.Unmarshal(buf, &rawCfg); err != nil { @@ -182,6 +192,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config := &Config{} config.Experimental = &rawCfg.Experimental + config.Profile = &rawCfg.Profile general, err := parseGeneral(rawCfg) if err != nil { diff --git a/constant/path.go b/constant/path.go index 6f4af046aa..021721ec4e 100644 --- a/constant/path.go +++ b/constant/path.go @@ -56,3 +56,7 @@ func (p *path) Resolve(path string) string { func (p *path) MMDB() string { return P.Join(p.homeDir, "Country.mmdb") } + +func (p *path) Cache() string { + return P.Join(p.homeDir, ".cache") +} diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 4bf40f1711..bea2a1abdf 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -6,9 +6,13 @@ import ( "os" "sync" + "github.com/Dreamacro/clash/adapters/outbound" + "github.com/Dreamacro/clash/adapters/outboundgroup" "github.com/Dreamacro/clash/adapters/provider" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/profile" + "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/config" @@ -72,6 +76,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateDNS(cfg.DNS) updateHosts(cfg.Hosts) updateExperimental(cfg) + updateProfile(cfg) } func GetGeneral() *config.General { @@ -209,3 +214,38 @@ func updateUsers(users []auth.AuthUser) { log.Infoln("Authentication of local server updated") } } + +func updateProfile(cfg *config.Config) { + profileCfg := cfg.Profile + + profile.StoreSelected.Store(profileCfg.StoreSelected) + if profileCfg.StoreSelected { + patchSelectGroup(cfg.Proxies) + } +} + +func patchSelectGroup(proxies map[string]C.Proxy) { + mapping := cachefile.Cache().SelectedMap() + if mapping == nil { + return + } + + for name, proxy := range proxies { + outbound, ok := proxy.(*outbound.Proxy) + if !ok { + continue + } + + selector, ok := outbound.ProxyAdapter.(*outboundgroup.Selector) + if !ok { + continue + } + + selected, exist := mapping[name] + if !exist { + continue + } + + selector.Set(selected) + } +} diff --git a/hub/route/proxies.go b/hub/route/proxies.go index e3cb066cc6..693f6c7945 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -9,6 +9,7 @@ import ( "github.com/Dreamacro/clash/adapters/outbound" "github.com/Dreamacro/clash/adapters/outboundgroup" + "github.com/Dreamacro/clash/component/profile/cachefile" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" @@ -91,6 +92,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { return } + cachefile.Cache().SetSelected(proxy.Name(), req.Name) render.NoContent(w, r) } From b3c1b4a84056523b6959f03d2cbfeee82c1e7fde Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Fri, 19 Feb 2021 20:35:10 +0800 Subject: [PATCH 534/535] Chore: update dependencies --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 240aee601a..40315c57ce 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,22 @@ module github.com/Dreamacro/clash -go 1.15 +go 1.16 require ( github.com/Dreamacro/go-shadowsocks2 v0.1.6 github.com/go-chi/chi v4.1.2+incompatible github.com/go-chi/cors v1.1.1 github.com/go-chi/render v1.0.1 - github.com/gofrs/uuid v3.3.0+incompatible + github.com/gofrs/uuid v4.0.0+incompatible github.com/gorilla/websocket v1.4.2 - github.com/miekg/dns v1.1.35 + github.com/miekg/dns v1.1.38 github.com/oschwald/geoip2-golang v1.4.0 - github.com/sirupsen/logrus v1.7.0 - github.com/stretchr/testify v1.6.1 + github.com/sirupsen/logrus v1.8.0 + github.com/stretchr/testify v1.7.0 go.uber.org/atomic v1.7.0 - golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad - golang.org/x/net v0.0.0-20201224014010-6772e930b67b + golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df + golang.org/x/net v0.0.0-20210119194325-5f4716e94777 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a - golang.org/x/sys v0.0.0-20201223074533-0d417f636930 + golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 24cbd209a7..8b4cf73705 100644 --- a/go.sum +++ b/go.sum @@ -9,39 +9,41 @@ github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= -github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= -github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= +github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw= +github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/oschwald/geoip2-golang v1.4.0 h1:5RlrjCgRyIGDz/mBmPfnAF4h8k0IAcRv9PvrpOfz+Ug= github.com/oschwald/geoip2-golang v1.4.0/go.mod h1:8QwxJvRImBH+Zl6Aa6MaIcs5YdlZSTKtzmPGzQqi9ng= github.com/oschwald/maxminddb-golang v1.6.0 h1:KAJSjdHQ8Kv45nFIbtoLGrGWqHFajOIm7skTyz/+Dls= github.com/oschwald/maxminddb-golang v1.6.0/go.mod h1:DUJFucBg2cvqx42YmDa/+xHvb0elJtOm3o4aFQ/nb/w= 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/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= +github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df h1:y7QZzfUiTwWam+xBn29Ulb8CBwVN5UdzmMDavl9Whlw= +golang.org/x/crypto v0.0.0-20210218145215-b8e89b74b9df/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -50,13 +52,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo= -golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g= +golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From a37243cf300dcf0810299d41b3377beda5416c68 Mon Sep 17 00:00:00 2001 From: Dreamacro <8615343+Dreamacro@users.noreply.github.com> Date: Sun, 21 Feb 2021 01:07:22 +0800 Subject: [PATCH 535/535] Fix: store cache correctly --- component/profile/cachefile/cache.go | 30 ++++++++-------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/component/profile/cachefile/cache.go b/component/profile/cachefile/cache.go index cb23f87b3c..b23eeb97f2 100644 --- a/component/profile/cachefile/cache.go +++ b/component/profile/cachefile/cache.go @@ -26,7 +26,6 @@ type cache struct { type CacheFile struct { path string model *cache - enc *gob.Encoder buf *bytes.Buffer mux sync.Mutex } @@ -39,15 +38,11 @@ func (c *CacheFile) SetSelected(group, selected string) { c.mux.Lock() defer c.mux.Unlock() - model, err := c.element() - if err != nil { - log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error()) - return - } + model := c.element() model.Selected[group] = selected c.buf.Reset() - if err := c.enc.Encode(model); err != nil { + if err := gob.NewEncoder(c.buf).Encode(model); err != nil { log.Warnln("[CacheFile] encode gob failed: %s", err.Error()) return } @@ -66,11 +61,7 @@ func (c *CacheFile) SelectedMap() map[string]string { c.mux.Lock() defer c.mux.Unlock() - model, err := c.element() - if err != nil { - log.Warnln("[CacheFile] read cache %s failed: %s", c.path, err.Error()) - return nil - } + model := c.element() mapping := map[string]string{} for k, v := range model.Selected { @@ -79,9 +70,9 @@ func (c *CacheFile) SelectedMap() map[string]string { return mapping } -func (c *CacheFile) element() (*cache, error) { +func (c *CacheFile) element() *cache { if c.model != nil { - return c.model, nil + return c.model } model := &cache{ @@ -90,24 +81,19 @@ func (c *CacheFile) element() (*cache, error) { if buf, err := ioutil.ReadFile(c.path); err == nil { bufReader := bytes.NewBuffer(buf) - dec := gob.NewDecoder(bufReader) - if err := dec.Decode(model); err != nil { - return nil, err - } + gob.NewDecoder(bufReader).Decode(model) } c.model = model - return c.model, nil + return c.model } // Cache return singleton of CacheFile func Cache() *CacheFile { initOnce.Do(func() { - buf := &bytes.Buffer{} defaultCache = &CacheFile{ path: C.Path.Cache(), - buf: buf, - enc: gob.NewEncoder(buf), + buf: &bytes.Buffer{}, } })