server.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. package clashapi
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "net"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "time"
  11. "github.com/sagernet/sing-box/adapter"
  12. "github.com/sagernet/sing-box/common/urltest"
  13. C "github.com/sagernet/sing-box/constant"
  14. "github.com/sagernet/sing-box/experimental"
  15. "github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
  16. "github.com/sagernet/sing-box/log"
  17. "github.com/sagernet/sing-box/option"
  18. "github.com/sagernet/sing/common"
  19. E "github.com/sagernet/sing/common/exceptions"
  20. F "github.com/sagernet/sing/common/format"
  21. "github.com/sagernet/sing/common/json"
  22. N "github.com/sagernet/sing/common/network"
  23. "github.com/sagernet/sing/service"
  24. "github.com/sagernet/sing/service/filemanager"
  25. "github.com/sagernet/ws"
  26. "github.com/sagernet/ws/wsutil"
  27. "github.com/go-chi/chi/v5"
  28. "github.com/go-chi/cors"
  29. "github.com/go-chi/render"
  30. )
  31. func init() {
  32. experimental.RegisterClashServerConstructor(NewServer)
  33. }
  34. var _ adapter.ClashServer = (*Server)(nil)
  35. type Server struct {
  36. ctx context.Context
  37. router adapter.Router
  38. logger log.Logger
  39. httpServer *http.Server
  40. trafficManager *trafficontrol.Manager
  41. urlTestHistory *urltest.HistoryStorage
  42. mode string
  43. modeList []string
  44. modeUpdateHook chan<- struct{}
  45. externalController bool
  46. externalUI string
  47. externalUIDownloadURL string
  48. externalUIDownloadDetour string
  49. }
  50. func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
  51. trafficManager := trafficontrol.NewManager()
  52. chiRouter := chi.NewRouter()
  53. server := &Server{
  54. ctx: ctx,
  55. router: router,
  56. logger: logFactory.NewLogger("clash-api"),
  57. httpServer: &http.Server{
  58. Addr: options.ExternalController,
  59. Handler: chiRouter,
  60. },
  61. trafficManager: trafficManager,
  62. modeList: options.ModeList,
  63. externalController: options.ExternalController != "",
  64. externalUIDownloadURL: options.ExternalUIDownloadURL,
  65. externalUIDownloadDetour: options.ExternalUIDownloadDetour,
  66. }
  67. server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
  68. if server.urlTestHistory == nil {
  69. server.urlTestHistory = urltest.NewHistoryStorage()
  70. }
  71. defaultMode := "Rule"
  72. if options.DefaultMode != "" {
  73. defaultMode = options.DefaultMode
  74. }
  75. if !common.Contains(server.modeList, defaultMode) {
  76. server.modeList = append([]string{defaultMode}, server.modeList...)
  77. }
  78. server.mode = defaultMode
  79. //goland:noinspection GoDeprecation
  80. //nolint:staticcheck
  81. if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" {
  82. return nil, E.New("cache_file and related fields in Clash API is deprecated in sing-box 1.8.0, use experimental.cache_file instead.")
  83. }
  84. cors := cors.New(cors.Options{
  85. AllowedOrigins: []string{"*"},
  86. AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
  87. AllowedHeaders: []string{"Content-Type", "Authorization"},
  88. MaxAge: 300,
  89. })
  90. chiRouter.Use(cors.Handler)
  91. chiRouter.Group(func(r chi.Router) {
  92. r.Use(authentication(options.Secret))
  93. r.Get("/", hello(options.ExternalUI != ""))
  94. r.Get("/logs", getLogs(logFactory))
  95. r.Get("/traffic", traffic(trafficManager))
  96. r.Get("/version", version)
  97. r.Mount("/configs", configRouter(server, logFactory))
  98. r.Mount("/proxies", proxyRouter(server, router))
  99. r.Mount("/rules", ruleRouter(router))
  100. r.Mount("/connections", connectionRouter(router, trafficManager))
  101. r.Mount("/providers/proxies", proxyProviderRouter())
  102. r.Mount("/providers/rules", ruleProviderRouter())
  103. r.Mount("/script", scriptRouter())
  104. r.Mount("/profile", profileRouter())
  105. r.Mount("/cache", cacheRouter(ctx))
  106. r.Mount("/dns", dnsRouter(router))
  107. server.setupMetaAPI(r)
  108. })
  109. if options.ExternalUI != "" {
  110. server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
  111. chiRouter.Group(func(r chi.Router) {
  112. fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
  113. r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
  114. r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
  115. fs.ServeHTTP(w, r)
  116. })
  117. })
  118. }
  119. return server, nil
  120. }
  121. func (s *Server) PreStart() error {
  122. cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
  123. if cacheFile != nil {
  124. mode := cacheFile.LoadMode()
  125. if common.Any(s.modeList, func(it string) bool {
  126. return strings.EqualFold(it, mode)
  127. }) {
  128. s.mode = mode
  129. }
  130. }
  131. return nil
  132. }
  133. func (s *Server) Start() error {
  134. if s.externalController {
  135. s.checkAndDownloadExternalUI()
  136. listener, err := net.Listen("tcp", s.httpServer.Addr)
  137. if err != nil {
  138. return E.Cause(err, "external controller listen error")
  139. }
  140. s.logger.Info("restful api listening at ", listener.Addr())
  141. go func() {
  142. err = s.httpServer.Serve(listener)
  143. if err != nil && !errors.Is(err, http.ErrServerClosed) {
  144. s.logger.Error("external controller serve error: ", err)
  145. }
  146. }()
  147. }
  148. return nil
  149. }
  150. func (s *Server) Close() error {
  151. return common.Close(
  152. common.PtrOrNil(s.httpServer),
  153. s.trafficManager,
  154. s.urlTestHistory,
  155. )
  156. }
  157. func (s *Server) Mode() string {
  158. return s.mode
  159. }
  160. func (s *Server) ModeList() []string {
  161. return s.modeList
  162. }
  163. func (s *Server) SetModeUpdateHook(hook chan<- struct{}) {
  164. s.modeUpdateHook = hook
  165. }
  166. func (s *Server) SetMode(newMode string) {
  167. if !common.Contains(s.modeList, newMode) {
  168. newMode = common.Find(s.modeList, func(it string) bool {
  169. return strings.EqualFold(it, newMode)
  170. })
  171. }
  172. if !common.Contains(s.modeList, newMode) {
  173. return
  174. }
  175. if newMode == s.mode {
  176. return
  177. }
  178. s.mode = newMode
  179. if s.modeUpdateHook != nil {
  180. select {
  181. case s.modeUpdateHook <- struct{}{}:
  182. default:
  183. }
  184. }
  185. s.router.ClearDNSCache()
  186. cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
  187. if cacheFile != nil {
  188. err := cacheFile.StoreMode(newMode)
  189. if err != nil {
  190. s.logger.Error(E.Cause(err, "save mode"))
  191. }
  192. }
  193. s.logger.Info("updated mode: ", newMode)
  194. }
  195. func (s *Server) HistoryStorage() *urltest.HistoryStorage {
  196. return s.urlTestHistory
  197. }
  198. func (s *Server) TrafficManager() *trafficontrol.Manager {
  199. return s.trafficManager
  200. }
  201. func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
  202. tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
  203. return tracker, tracker
  204. }
  205. func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
  206. tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
  207. return tracker, tracker
  208. }
  209. func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
  210. var inbound string
  211. if metadata.Inbound != "" {
  212. inbound = metadata.InboundType + "/" + metadata.Inbound
  213. } else {
  214. inbound = metadata.InboundType
  215. }
  216. var domain string
  217. if metadata.Domain != "" {
  218. domain = metadata.Domain
  219. } else {
  220. domain = metadata.Destination.Fqdn
  221. }
  222. var processPath string
  223. if metadata.ProcessInfo != nil {
  224. if metadata.ProcessInfo.ProcessPath != "" {
  225. processPath = metadata.ProcessInfo.ProcessPath
  226. } else if metadata.ProcessInfo.PackageName != "" {
  227. processPath = metadata.ProcessInfo.PackageName
  228. }
  229. if processPath == "" {
  230. if metadata.ProcessInfo.UserId != -1 {
  231. processPath = F.ToString(metadata.ProcessInfo.UserId)
  232. }
  233. } else if metadata.ProcessInfo.User != "" {
  234. processPath = F.ToString(processPath, " (", metadata.ProcessInfo.User, ")")
  235. } else if metadata.ProcessInfo.UserId != -1 {
  236. processPath = F.ToString(processPath, " (", metadata.ProcessInfo.UserId, ")")
  237. }
  238. }
  239. return trafficontrol.Metadata{
  240. NetWork: metadata.Network,
  241. Type: inbound,
  242. SrcIP: metadata.Source.Addr,
  243. DstIP: metadata.Destination.Addr,
  244. SrcPort: F.ToString(metadata.Source.Port),
  245. DstPort: F.ToString(metadata.Destination.Port),
  246. Host: domain,
  247. DNSMode: "normal",
  248. ProcessPath: processPath,
  249. }
  250. }
  251. func authentication(serverSecret string) func(next http.Handler) http.Handler {
  252. return func(next http.Handler) http.Handler {
  253. fn := func(w http.ResponseWriter, r *http.Request) {
  254. if serverSecret == "" {
  255. next.ServeHTTP(w, r)
  256. return
  257. }
  258. // Browser websocket not support custom header
  259. if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
  260. token := r.URL.Query().Get("token")
  261. if token != serverSecret {
  262. render.Status(r, http.StatusUnauthorized)
  263. render.JSON(w, r, ErrUnauthorized)
  264. return
  265. }
  266. next.ServeHTTP(w, r)
  267. return
  268. }
  269. header := r.Header.Get("Authorization")
  270. bearer, token, found := strings.Cut(header, " ")
  271. hasInvalidHeader := bearer != "Bearer"
  272. hasInvalidSecret := !found || token != serverSecret
  273. if hasInvalidHeader || hasInvalidSecret {
  274. render.Status(r, http.StatusUnauthorized)
  275. render.JSON(w, r, ErrUnauthorized)
  276. return
  277. }
  278. next.ServeHTTP(w, r)
  279. }
  280. return http.HandlerFunc(fn)
  281. }
  282. }
  283. func hello(redirect bool) func(w http.ResponseWriter, r *http.Request) {
  284. return func(w http.ResponseWriter, r *http.Request) {
  285. if redirect {
  286. http.Redirect(w, r, "/ui/", http.StatusTemporaryRedirect)
  287. } else {
  288. render.JSON(w, r, render.M{"hello": "clash"})
  289. }
  290. }
  291. }
  292. type Traffic struct {
  293. Up int64 `json:"up"`
  294. Down int64 `json:"down"`
  295. }
  296. func traffic(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
  297. return func(w http.ResponseWriter, r *http.Request) {
  298. var conn net.Conn
  299. if r.Header.Get("Upgrade") == "websocket" {
  300. var err error
  301. conn, _, _, err = ws.UpgradeHTTP(r, w)
  302. if err != nil {
  303. return
  304. }
  305. defer conn.Close()
  306. }
  307. if conn == nil {
  308. w.Header().Set("Content-Type", "application/json")
  309. render.Status(r, http.StatusOK)
  310. }
  311. tick := time.NewTicker(time.Second)
  312. defer tick.Stop()
  313. buf := &bytes.Buffer{}
  314. var err error
  315. for range tick.C {
  316. buf.Reset()
  317. up, down := trafficManager.Now()
  318. if err := json.NewEncoder(buf).Encode(Traffic{
  319. Up: up,
  320. Down: down,
  321. }); err != nil {
  322. break
  323. }
  324. if conn == nil {
  325. _, err = w.Write(buf.Bytes())
  326. w.(http.Flusher).Flush()
  327. } else {
  328. err = wsutil.WriteServerText(conn, buf.Bytes())
  329. }
  330. if err != nil {
  331. break
  332. }
  333. }
  334. }
  335. }
  336. type Log struct {
  337. Type string `json:"type"`
  338. Payload string `json:"payload"`
  339. }
  340. func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *http.Request) {
  341. return func(w http.ResponseWriter, r *http.Request) {
  342. levelText := r.URL.Query().Get("level")
  343. if levelText == "" {
  344. levelText = "info"
  345. }
  346. level, ok := log.ParseLevel(levelText)
  347. if ok != nil {
  348. render.Status(r, http.StatusBadRequest)
  349. render.JSON(w, r, ErrBadRequest)
  350. return
  351. }
  352. subscription, done, err := logFactory.Subscribe()
  353. if err != nil {
  354. render.Status(r, http.StatusNoContent)
  355. return
  356. }
  357. defer logFactory.UnSubscribe(subscription)
  358. var conn net.Conn
  359. if r.Header.Get("Upgrade") == "websocket" {
  360. conn, _, _, err = ws.UpgradeHTTP(r, w)
  361. if err != nil {
  362. return
  363. }
  364. defer conn.Close()
  365. }
  366. if conn == nil {
  367. w.Header().Set("Content-Type", "application/json")
  368. render.Status(r, http.StatusOK)
  369. }
  370. buf := &bytes.Buffer{}
  371. var logEntry log.Entry
  372. for {
  373. select {
  374. case <-done:
  375. return
  376. case logEntry = <-subscription:
  377. }
  378. if logEntry.Level > level {
  379. continue
  380. }
  381. buf.Reset()
  382. err = json.NewEncoder(buf).Encode(Log{
  383. Type: log.FormatLevel(logEntry.Level),
  384. Payload: logEntry.Message,
  385. })
  386. if err != nil {
  387. break
  388. }
  389. if conn == nil {
  390. _, err = w.Write(buf.Bytes())
  391. w.(http.Flusher).Flush()
  392. } else {
  393. err = wsutil.WriteServerText(conn, buf.Bytes())
  394. }
  395. if err != nil {
  396. break
  397. }
  398. }
  399. }
  400. }
  401. func version(w http.ResponseWriter, r *http.Request) {
  402. render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true, "meta": true})
  403. }