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 {