github.com/kelleygo/clashcore@v1.0.2/hub/route/server.go (about)

     1  package route
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/subtle"
     6  	"crypto/tls"
     7  	"encoding/json"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime/debug"
    13  	"strings"
    14  	"syscall"
    15  	"time"
    16  
    17  	"github.com/kelleygo/clashcore/adapter/inbound"
    18  	CN "github.com/kelleygo/clashcore/common/net"
    19  	"github.com/kelleygo/clashcore/common/utils"
    20  	C "github.com/kelleygo/clashcore/constant"
    21  	"github.com/kelleygo/clashcore/log"
    22  	"github.com/kelleygo/clashcore/tunnel/statistic"
    23  
    24  	"github.com/go-chi/chi/v5"
    25  	"github.com/go-chi/chi/v5/middleware"
    26  	"github.com/go-chi/cors"
    27  	"github.com/go-chi/render"
    28  	"github.com/gobwas/ws"
    29  	"github.com/gobwas/ws/wsutil"
    30  )
    31  
    32  var (
    33  	serverSecret = ""
    34  	serverAddr   = ""
    35  
    36  	uiPath = ""
    37  )
    38  
    39  type Traffic struct {
    40  	Up   int64 `json:"up"`
    41  	Down int64 `json:"down"`
    42  }
    43  
    44  type Memory struct {
    45  	Inuse   uint64 `json:"inuse"`
    46  	OSLimit uint64 `json:"oslimit"` // maybe we need it in the future
    47  }
    48  
    49  func SetUIPath(path string) {
    50  	uiPath = C.Path.Resolve(path)
    51  }
    52  
    53  func router(isDebug bool, withAuth bool) *chi.Mux {
    54  	r := chi.NewRouter()
    55  	corsM := cors.New(cors.Options{
    56  		AllowedOrigins: []string{"*"},
    57  		AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
    58  		AllowedHeaders: []string{"Content-Type", "Authorization"},
    59  		MaxAge:         300,
    60  	})
    61  	r.Use(setPrivateNetworkAccess)
    62  	r.Use(corsM.Handler)
    63  	if isDebug {
    64  		r.Mount("/debug", func() http.Handler {
    65  			r := chi.NewRouter()
    66  			r.Put("/gc", func(w http.ResponseWriter, r *http.Request) {
    67  				debug.FreeOSMemory()
    68  			})
    69  			handler := middleware.Profiler
    70  			r.Mount("/", handler())
    71  			return r
    72  		}())
    73  	}
    74  	r.Group(func(r chi.Router) {
    75  		if withAuth {
    76  			r.Use(authentication)
    77  		}
    78  		r.Get("/", hello)
    79  		r.Get("/logs", getLogs)
    80  		r.Get("/traffic", traffic)
    81  		r.Get("/memory", memory)
    82  		r.Get("/version", version)
    83  		r.Mount("/configs", configRouter())
    84  		r.Mount("/proxies", proxyRouter())
    85  		r.Mount("/group", GroupRouter())
    86  		r.Mount("/rules", ruleRouter())
    87  		r.Mount("/connections", connectionRouter())
    88  		r.Mount("/providers/proxies", proxyProviderRouter())
    89  		r.Mount("/providers/rules", ruleProviderRouter())
    90  		r.Mount("/cache", cacheRouter())
    91  		r.Mount("/dns", dnsRouter())
    92  		r.Mount("/restart", restartRouter())
    93  		r.Mount("/upgrade", upgradeRouter())
    94  		addExternalRouters(r)
    95  
    96  	})
    97  
    98  	if uiPath != "" {
    99  		r.Group(func(r chi.Router) {
   100  			fs := http.StripPrefix("/ui", http.FileServer(http.Dir(uiPath)))
   101  			r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
   102  			r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
   103  				fs.ServeHTTP(w, r)
   104  			})
   105  		})
   106  	}
   107  	return r
   108  }
   109  
   110  func Start(addr string, tlsAddr string, secret string,
   111  	certificate, privateKey string, isDebug bool) {
   112  	if serverAddr != "" {
   113  		return
   114  	}
   115  
   116  	serverAddr = addr
   117  	serverSecret = secret
   118  
   119  	if len(tlsAddr) > 0 {
   120  		go func() {
   121  			c, err := CN.ParseCert(certificate, privateKey, C.Path)
   122  			if err != nil {
   123  				log.Errorln("External controller tls listen error: %s", err)
   124  				return
   125  			}
   126  
   127  			l, err := inbound.Listen("tcp", tlsAddr)
   128  			if err != nil {
   129  				log.Errorln("External controller tls listen error: %s", err)
   130  				return
   131  			}
   132  
   133  			serverAddr = l.Addr().String()
   134  			log.Infoln("RESTful API tls listening at: %s", serverAddr)
   135  			tlsServe := &http.Server{
   136  				Handler: router(isDebug, true),
   137  				TLSConfig: &tls.Config{
   138  					Certificates: []tls.Certificate{c},
   139  				},
   140  			}
   141  			if err = tlsServe.ServeTLS(l, "", ""); err != nil {
   142  				log.Errorln("External controller tls serve error: %s", err)
   143  			}
   144  		}()
   145  	}
   146  
   147  	l, err := inbound.Listen("tcp", addr)
   148  	if err != nil {
   149  		log.Errorln("External controller listen error: %s", err)
   150  		return
   151  	}
   152  	serverAddr = l.Addr().String()
   153  	log.Infoln("RESTful API listening at: %s", serverAddr)
   154  
   155  	if err = http.Serve(l, router(isDebug, true)); err != nil {
   156  		log.Errorln("External controller serve error: %s", err)
   157  	}
   158  
   159  }
   160  
   161  func StartUnix(addr string, isDebug bool) {
   162  	addr = C.Path.Resolve(addr)
   163  
   164  	dir := filepath.Dir(addr)
   165  	if _, err := os.Stat(dir); os.IsNotExist(err) {
   166  		if err := os.MkdirAll(dir, 0o755); err != nil {
   167  			log.Errorln("External controller unix listen error: %s", err)
   168  			return
   169  		}
   170  	}
   171  
   172  	// https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
   173  	//
   174  	// Note: As mentioned above in the ‘security’ section, when a socket binds a socket to a valid pathname address,
   175  	// a socket file is created within the filesystem. On Linux, the application is expected to unlink
   176  	// (see the notes section in the man page for AF_UNIX) before any other socket can be bound to the same address.
   177  	// The same applies to Windows unix sockets, except that, DeleteFile (or any other file delete API)
   178  	// should be used to delete the socket file prior to calling bind with the same path.
   179  	_ = syscall.Unlink(addr)
   180  
   181  	l, err := inbound.Listen("unix", addr)
   182  	if err != nil {
   183  		log.Errorln("External controller unix listen error: %s", err)
   184  		return
   185  	}
   186  	serverAddr = l.Addr().String()
   187  	log.Infoln("RESTful API unix listening at: %s", serverAddr)
   188  
   189  	if err = http.Serve(l, router(isDebug, false)); err != nil {
   190  		log.Errorln("External controller unix serve error: %s", err)
   191  	}
   192  }
   193  
   194  func setPrivateNetworkAccess(next http.Handler) http.Handler {
   195  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   196  		if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
   197  			w.Header().Add("Access-Control-Allow-Private-Network", "true")
   198  		}
   199  		next.ServeHTTP(w, r)
   200  	})
   201  }
   202  
   203  func safeEuqal(a, b string) bool {
   204  	aBuf := utils.ImmutableBytesFromString(a)
   205  	bBuf := utils.ImmutableBytesFromString(b)
   206  	return subtle.ConstantTimeCompare(aBuf, bBuf) == 1
   207  }
   208  
   209  func authentication(next http.Handler) http.Handler {
   210  	fn := func(w http.ResponseWriter, r *http.Request) {
   211  		if serverSecret == "" {
   212  			next.ServeHTTP(w, r)
   213  			return
   214  		}
   215  
   216  		// Browser websocket not support custom header
   217  		if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
   218  			token := r.URL.Query().Get("token")
   219  			if !safeEuqal(token, serverSecret) {
   220  				render.Status(r, http.StatusUnauthorized)
   221  				render.JSON(w, r, ErrUnauthorized)
   222  				return
   223  			}
   224  			next.ServeHTTP(w, r)
   225  			return
   226  		}
   227  
   228  		header := r.Header.Get("Authorization")
   229  		bearer, token, found := strings.Cut(header, " ")
   230  
   231  		hasInvalidHeader := bearer != "Bearer"
   232  		hasInvalidSecret := !found || !safeEuqal(token, serverSecret)
   233  		if hasInvalidHeader || hasInvalidSecret {
   234  			render.Status(r, http.StatusUnauthorized)
   235  			render.JSON(w, r, ErrUnauthorized)
   236  			return
   237  		}
   238  		next.ServeHTTP(w, r)
   239  	}
   240  	return http.HandlerFunc(fn)
   241  }
   242  
   243  func hello(w http.ResponseWriter, r *http.Request) {
   244  	render.JSON(w, r, render.M{"hello": "yiclashcore"})
   245  }
   246  
   247  func traffic(w http.ResponseWriter, r *http.Request) {
   248  	var wsConn net.Conn
   249  	if r.Header.Get("Upgrade") == "websocket" {
   250  		var err error
   251  		wsConn, _, _, err = ws.UpgradeHTTP(r, w)
   252  		if err != nil {
   253  			return
   254  		}
   255  	}
   256  
   257  	if wsConn == nil {
   258  		w.Header().Set("Content-Type", "application/json")
   259  		render.Status(r, http.StatusOK)
   260  	}
   261  
   262  	tick := time.NewTicker(time.Second)
   263  	defer tick.Stop()
   264  	t := statistic.DefaultManager
   265  	buf := &bytes.Buffer{}
   266  	var err error
   267  	for range tick.C {
   268  		buf.Reset()
   269  		up, down := t.Now()
   270  		if err := json.NewEncoder(buf).Encode(Traffic{
   271  			Up:   up,
   272  			Down: down,
   273  		}); err != nil {
   274  			break
   275  		}
   276  
   277  		if wsConn == nil {
   278  			_, err = w.Write(buf.Bytes())
   279  			w.(http.Flusher).Flush()
   280  		} else {
   281  			err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
   282  		}
   283  
   284  		if err != nil {
   285  			break
   286  		}
   287  	}
   288  }
   289  
   290  func memory(w http.ResponseWriter, r *http.Request) {
   291  	var wsConn net.Conn
   292  	if r.Header.Get("Upgrade") == "websocket" {
   293  		var err error
   294  		wsConn, _, _, err = ws.UpgradeHTTP(r, w)
   295  		if err != nil {
   296  			return
   297  		}
   298  	}
   299  
   300  	if wsConn == nil {
   301  		w.Header().Set("Content-Type", "application/json")
   302  		render.Status(r, http.StatusOK)
   303  	}
   304  
   305  	tick := time.NewTicker(time.Second)
   306  	defer tick.Stop()
   307  	t := statistic.DefaultManager
   308  	buf := &bytes.Buffer{}
   309  	var err error
   310  	first := true
   311  	for range tick.C {
   312  		buf.Reset()
   313  
   314  		inuse := t.Memory()
   315  		// make chat.js begin with zero
   316  		// this is shit var,but we need output 0 for first time
   317  		if first {
   318  			inuse = 0
   319  			first = false
   320  		}
   321  		if err := json.NewEncoder(buf).Encode(Memory{
   322  			Inuse:   inuse,
   323  			OSLimit: 0,
   324  		}); err != nil {
   325  			break
   326  		}
   327  		if wsConn == nil {
   328  			_, err = w.Write(buf.Bytes())
   329  			w.(http.Flusher).Flush()
   330  		} else {
   331  			err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
   332  		}
   333  
   334  		if err != nil {
   335  			break
   336  		}
   337  	}
   338  }
   339  
   340  type Log struct {
   341  	Type    string `json:"type"`
   342  	Payload string `json:"payload"`
   343  }
   344  type LogStructuredField struct {
   345  	Key   string `json:"key"`
   346  	Value string `json:"value"`
   347  }
   348  type LogStructured struct {
   349  	Time    string               `json:"time"`
   350  	Level   string               `json:"level"`
   351  	Message string               `json:"message"`
   352  	Fields  []LogStructuredField `json:"fields"`
   353  }
   354  
   355  func getLogs(w http.ResponseWriter, r *http.Request) {
   356  	levelText := r.URL.Query().Get("level")
   357  	if levelText == "" {
   358  		levelText = "info"
   359  	}
   360  
   361  	formatText := r.URL.Query().Get("format")
   362  	isStructured := false
   363  	if formatText == "structured" {
   364  		isStructured = true
   365  	}
   366  
   367  	level, ok := log.LogLevelMapping[levelText]
   368  	if !ok {
   369  		render.Status(r, http.StatusBadRequest)
   370  		render.JSON(w, r, ErrBadRequest)
   371  		return
   372  	}
   373  
   374  	var wsConn net.Conn
   375  	if r.Header.Get("Upgrade") == "websocket" {
   376  		var err error
   377  		wsConn, _, _, err = ws.UpgradeHTTP(r, w)
   378  		if err != nil {
   379  			return
   380  		}
   381  	}
   382  
   383  	if wsConn == nil {
   384  		w.Header().Set("Content-Type", "application/json")
   385  		render.Status(r, http.StatusOK)
   386  	}
   387  
   388  	ch := make(chan log.Event, 1024)
   389  	sub := log.Subscribe()
   390  	defer log.UnSubscribe(sub)
   391  	buf := &bytes.Buffer{}
   392  
   393  	go func() {
   394  		for logM := range sub {
   395  			select {
   396  			case ch <- logM:
   397  			default:
   398  			}
   399  		}
   400  		close(ch)
   401  	}()
   402  
   403  	for logM := range ch {
   404  		if logM.LogLevel < level {
   405  			continue
   406  		}
   407  		buf.Reset()
   408  
   409  		if !isStructured {
   410  			if err := json.NewEncoder(buf).Encode(Log{
   411  				Type:    logM.Type(),
   412  				Payload: logM.Payload,
   413  			}); err != nil {
   414  				break
   415  			}
   416  		} else {
   417  			newLevel := logM.Type()
   418  			if newLevel == "warning" {
   419  				newLevel = "warn"
   420  			}
   421  			if err := json.NewEncoder(buf).Encode(LogStructured{
   422  				Time:    time.Now().Format(time.TimeOnly),
   423  				Level:   newLevel,
   424  				Message: logM.Payload,
   425  				Fields:  []LogStructuredField{},
   426  			}); err != nil {
   427  				break
   428  			}
   429  		}
   430  
   431  		var err error
   432  		if wsConn == nil {
   433  			_, err = w.Write(buf.Bytes())
   434  			w.(http.Flusher).Flush()
   435  		} else {
   436  			err = wsutil.WriteMessage(wsConn, ws.StateServerSide, ws.OpText, buf.Bytes())
   437  		}
   438  
   439  		if err != nil {
   440  			break
   441  		}
   442  	}
   443  }
   444  
   445  func version(w http.ResponseWriter, r *http.Request) {
   446  	render.JSON(w, r, render.M{"meta": C.Meta, "version": C.Version})
   447  }