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 }