diff --git a/adapters/direct.go b/adapters/direct.go index f561ee9c..3df410da 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 897e1c60..91bdfd1e 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 00000000..62651530 --- /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 f4950c59..6884aacc 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 7608024f..a68b2b90 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 101115c5..df265bdc 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 3cc46a32..63179e8a 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() +type General struct { + Mode string `json:mode` +} - 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()}) - } - - 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: err.Error(), + Error: "Format error", }) return } + + mode, ok := modeMapping[general.Mode] + if !ok { + w.WriteHeader(http.StatusBadRequest) + render.JSON(w, r, 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 00000000..04ff6431 --- /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 00000000..6524134c --- /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 5c9f4038..1689d0e8 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 00000000..d82a0aee --- /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 d1d581ab..5208ec92 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()