Update: add config route

This commit is contained in:
Dreamacro 2018-07-15 22:23:20 +08:00
parent 0eef9bbf5d
commit 3cacfb8a7f
12 changed files with 319 additions and 77 deletions

View file

@ -16,8 +16,8 @@ import (
const ( const (
Name = "clash" Name = "clash"
DefalutHTTPPort = "7890" DefalutHTTPPort = 7890
DefalutSOCKSPort = "7891" DefalutSOCKSPort = 7891
) )
var ( var (
@ -26,6 +26,13 @@ var (
MMDBPath string 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() { func init() {
currentUser, err := user.Current() currentUser, err := user.Current()
if err != nil { if err != nil {

7
constant/proxy.go Normal file
View file

@ -0,0 +1,7 @@
package constant
// ProxySignal is used to handle graceful shutdown of proxy
type ProxySignal struct {
Done chan<- struct{}
Closed <-chan struct{}
}

31
hub/common.go Normal file
View file

@ -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}
}

View file

@ -1,9 +1,12 @@
package hub package hub
import ( import (
"fmt"
"net/http" "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/chi"
"github.com/go-chi/render" "github.com/go-chi/render"
@ -11,22 +14,26 @@ import (
func configRouter() http.Handler { func configRouter() http.Handler {
r := chi.NewRouter() r := chi.NewRouter()
r.Put("/", updateConfig) r.Get("/", getConfigs)
r.Put("/", updateConfigs)
return r return r
} }
type General struct { var modeMapping = map[string]T.Mode{
Mode string `json:mode` "Global": T.Global,
"Rule": T.Rule,
"Direct": T.Direct,
} }
var modeMapping = map[string]tunnel.Mode{ func getConfigs(w http.ResponseWriter, r *http.Request) {
"global": tunnel.Global, info := listener.Info()
"rule": tunnel.Rule, mode := tunnel.GetMode().String()
"direct": tunnel.Direct, info.Mode = &mode
render.JSON(w, r, info)
} }
func updateConfig(w http.ResponseWriter, r *http.Request) { func updateConfigs(w http.ResponseWriter, r *http.Request) {
general := &General{} general := &C.General{}
err := render.DecodeJSON(r.Body, general) err := render.DecodeJSON(r.Body, general)
if err != nil { if err != nil {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
@ -36,14 +43,32 @@ func updateConfig(w http.ResponseWriter, r *http.Request) {
return return
} }
mode, ok := modeMapping[general.Mode] // 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 { if !ok {
w.WriteHeader(http.StatusBadRequest) modeErr = fmt.Errorf("Mode error")
render.JSON(w, r, Error{ } else {
Error: "Mode error", tunnel.SetMode(mode)
}
}
hasError, errors := formatErrors(map[string]error{
"proxy": proxyErr,
"mode": modeErr,
}) })
if hasError {
w.WriteHeader(http.StatusBadRequest)
render.JSON(w, r, errors)
return return
} }
tun.SetMode(mode)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }

View file

@ -61,7 +61,7 @@ type GetProxysResponse struct {
} }
func getProxys(w http.ResponseWriter, r *http.Request) { func getProxys(w http.ResponseWriter, r *http.Request) {
_, rawProxys := tun.Config() _, rawProxys := tunnel.Config()
proxys := make(map[string]interface{}) proxys := make(map[string]interface{})
for name, proxy := range rawProxys { for name, proxy := range rawProxys {
proxys[name] = transformProxy(proxy) proxys[name] = transformProxy(proxy)
@ -71,7 +71,7 @@ func getProxys(w http.ResponseWriter, r *http.Request) {
func getProxy(w http.ResponseWriter, r *http.Request) { func getProxy(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name") name := chi.URLParam(r, "name")
_, proxys := tun.Config() _, proxys := tunnel.Config()
proxy, exist := proxys[name] proxy, exist := proxys[name]
if !exist { if !exist {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@ -98,7 +98,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
} }
name := chi.URLParam(r, "name") name := chi.URLParam(r, "name")
_, proxys := tun.Config() _, proxys := tunnel.Config()
proxy, exist := proxys[name] proxy, exist := proxys[name]
if !exist { if !exist {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)

View file

@ -24,7 +24,7 @@ type GetRulesResponse struct {
} }
func getRules(w http.ResponseWriter, r *http.Request) { func getRules(w http.ResponseWriter, r *http.Request) {
rulesCfg, _ := tun.Config() rulesCfg, _ := tunnel.Config()
var rules []Rule var rules []Rule
for _, rule := range rulesCfg { for _, rule := range rulesCfg {
@ -41,7 +41,7 @@ func getRules(w http.ResponseWriter, r *http.Request) {
} }
func updateRules(w http.ResponseWriter, r *http.Request) { func updateRules(w http.ResponseWriter, r *http.Request) {
err := tun.UpdateConfig() err := tunnel.UpdateConfig()
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
render.JSON(w, r, Error{ render.JSON(w, r, Error{

View file

@ -5,26 +5,18 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/Dreamacro/clash/tunnel" T "github.com/Dreamacro/clash/tunnel"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/render" "github.com/go-chi/render"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var (
tun = tunnel.GetInstance()
)
type Traffic struct { type Traffic struct {
Up int64 `json:"up"` Up int64 `json:"up"`
Down int64 `json:"down"` Down int64 `json:"down"`
} }
type Error struct {
Error string `json:"error"`
}
func NewHub(addr string) { func NewHub(addr string) {
r := chi.NewRouter() r := chi.NewRouter()
@ -44,7 +36,7 @@ func traffic(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
tick := time.NewTicker(time.Second) tick := time.NewTicker(time.Second)
t := tun.Traffic() t := tunnel.Traffic()
for range tick.C { for range tick.C {
up, down := t.Now() up, down := t.Now()
if err := json.NewEncoder(w).Encode(Traffic{ if err := json.NewEncoder(w).Encode(Traffic{
@ -73,11 +65,11 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
req.Level = "info" req.Level = "info"
} }
mapping := map[string]tunnel.LogLevel{ mapping := map[string]T.LogLevel{
"info": tunnel.INFO, "info": T.INFO,
"debug": tunnel.DEBUG, "debug": T.DEBUG,
"error": tunnel.ERROR, "error": T.ERROR,
"warning": tunnel.WARNING, "warning": T.WARNING,
} }
level, ok := mapping[req.Level] level, ok := mapping[req.Level]
@ -89,7 +81,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
return return
} }
src := tun.Log() src := tunnel.Log()
sub, err := src.Subscribe() sub, err := src.Subscribe()
defer src.UnSubscribe(sub) defer src.UnSubscribe(sub)
if err != nil { if err != nil {
@ -101,7 +93,7 @@ func getLogs(w http.ResponseWriter, r *http.Request) {
} }
render.Status(r, http.StatusOK) render.Status(r, http.StatusOK)
for elm := range sub { for elm := range sub {
log := elm.(tunnel.Log) log := elm.(T.Log)
if log.LogLevel > level { if log.LogLevel > level {
continue continue
} }

30
main.go
View file

@ -7,38 +7,28 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/hub"
"github.com/Dreamacro/clash/proxy/http" "github.com/Dreamacro/clash/proxy"
"github.com/Dreamacro/clash/proxy/socks"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
func main() { 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())
}
// Hub
cfg, err := C.GetConfig() cfg, err := C.GetConfig()
if err != nil { if err != nil {
log.Fatalf("Read config error: %s", err.Error()) log.Fatalf("Read config error: %s", err.Error())
} }
port, socksPort := C.DefalutHTTPPort, C.DefalutSOCKSPort
section := cfg.Section("General") section := cfg.Section("General")
if key, err := section.GetKey("port"); err == nil {
port = key.Value()
}
if key, err := section.GetKey("socks-port"); err == nil {
socksPort = key.Value()
}
err = tunnel.GetInstance().UpdateConfig()
if err != nil {
log.Fatalf("Parse config error: %s", err.Error())
}
go http.NewHttpProxy(port)
go socks.NewSocksProxy(socksPort)
// Hub
if key, err := section.GetKey("external-controller"); err == nil { if key, err := section.GetKey("external-controller"); err == nil {
go hub.NewHub(key.Value()) go hub.NewHub(key.Value())
} }

View file

@ -1,7 +1,7 @@
package http package http
import ( import (
"fmt" "context"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@ -17,9 +17,20 @@ var (
tun = tunnel.GetInstance() 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{ server := &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect { if r.Method == http.MethodConnect {
handleTunneling(w, r) 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) { func handleHTTP(w http.ResponseWriter, r *http.Request) {

143
proxy/listener.go Normal file
View file

@ -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
}

View file

@ -1,7 +1,6 @@
package socks package socks
import ( import (
"fmt"
"io" "io"
"net" "net"
"strconv" "strconv"
@ -17,20 +16,41 @@ var (
tun = tunnel.GetInstance() tun = tunnel.GetInstance()
) )
func NewSocksProxy(port string) { func NewSocksProxy(addr string) (*C.ProxySignal, error) {
l, err := net.Listen("tcp", fmt.Sprintf(":%s", port)) l, err := net.Listen("tcp", addr)
defer l.Close()
if err != nil { if err != nil {
return return nil, err
} }
log.Infof("SOCKS proxy :%s", port)
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 { for {
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
if _, open := <-done; !open {
break
}
continue continue
} }
go handleSocks(c) go handleSocks(c)
} }
}()
go func() {
<-done
close(done)
l.Close()
closed <- struct{}{}
}()
return signal, nil
} }
func handleSocks(conn net.Conn) { func handleSocks(conn net.Conn) {

View file

@ -52,6 +52,10 @@ func (t *Tunnel) SetMode(mode Mode) {
t.mode = mode t.mode = mode
} }
func (t *Tunnel) GetMode() Mode {
return t.mode
}
func (t *Tunnel) UpdateConfig() (err error) { func (t *Tunnel) UpdateConfig() (err error) {
cfg, err := C.GetConfig() cfg, err := C.GetConfig()
if err != nil { if err != nil {