proxies.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package clashapi
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "sort"
  7. "strconv"
  8. "time"
  9. "github.com/sagernet/sing-box/adapter"
  10. "github.com/sagernet/sing-box/common/badjson"
  11. "github.com/sagernet/sing-box/common/urltest"
  12. C "github.com/sagernet/sing-box/constant"
  13. "github.com/sagernet/sing-box/outbound"
  14. "github.com/sagernet/sing/common"
  15. F "github.com/sagernet/sing/common/format"
  16. "github.com/go-chi/chi/v5"
  17. "github.com/go-chi/render"
  18. )
  19. func proxyRouter(server *Server, router adapter.Router) http.Handler {
  20. r := chi.NewRouter()
  21. r.Get("/", getProxies(server, router))
  22. r.Route("/{name}", func(r chi.Router) {
  23. r.Use(parseProxyName, findProxyByName(router))
  24. r.Get("/", getProxy(server))
  25. r.Get("/delay", getProxyDelay(server))
  26. r.Put("/", updateProxy)
  27. })
  28. return r
  29. }
  30. func parseProxyName(next http.Handler) http.Handler {
  31. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  32. name := getEscapeParam(r, "name")
  33. ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
  34. next.ServeHTTP(w, r.WithContext(ctx))
  35. })
  36. }
  37. func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler {
  38. return func(next http.Handler) http.Handler {
  39. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  40. name := r.Context().Value(CtxKeyProxyName).(string)
  41. proxy, exist := router.Outbound(name)
  42. if !exist {
  43. render.Status(r, http.StatusNotFound)
  44. render.JSON(w, r, ErrNotFound)
  45. return
  46. }
  47. ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
  48. next.ServeHTTP(w, r.WithContext(ctx))
  49. })
  50. }
  51. }
  52. func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
  53. var info badjson.JSONObject
  54. var clashType string
  55. var isGroup bool
  56. switch detour.Type() {
  57. case C.TypeDirect:
  58. clashType = "Direct"
  59. case C.TypeBlock:
  60. clashType = "Reject"
  61. case C.TypeSocks:
  62. clashType = "Socks"
  63. case C.TypeHTTP:
  64. clashType = "Http"
  65. case C.TypeShadowsocks:
  66. clashType = "Shadowsocks"
  67. case C.TypeVMess:
  68. clashType = "Vmess"
  69. case C.TypeSelector:
  70. clashType = "Selector"
  71. isGroup = true
  72. case C.TypeURLTest:
  73. clashType = "URLTest"
  74. isGroup = true
  75. default:
  76. clashType = "Unknown"
  77. }
  78. info.Put("type", clashType)
  79. info.Put("name", detour.Tag())
  80. info.Put("udp", common.Contains(detour.Network(), C.NetworkUDP))
  81. delayHistory := server.router.URLTestHistoryStorage(false).LoadURLTestHistory(outbound.RealTag(detour))
  82. if delayHistory != nil {
  83. info.Put("history", []*urltest.History{delayHistory})
  84. } else {
  85. info.Put("history", []*urltest.History{})
  86. }
  87. if isGroup {
  88. selector := detour.(adapter.OutboundGroup)
  89. info.Put("now", selector.Now())
  90. info.Put("all", selector.All())
  91. }
  92. return &info
  93. }
  94. func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
  95. return func(w http.ResponseWriter, r *http.Request) {
  96. var proxyMap badjson.JSONObject
  97. outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool {
  98. return detour.Tag() != ""
  99. })
  100. allProxies := make([]string, 0, len(outbounds))
  101. for _, detour := range outbounds {
  102. switch detour.Type() {
  103. case C.TypeDirect, C.TypeBlock:
  104. continue
  105. }
  106. allProxies = append(allProxies, detour.Tag())
  107. }
  108. defaultTag := router.DefaultOutbound(C.NetworkTCP).Tag()
  109. if defaultTag == "" {
  110. defaultTag = allProxies[0]
  111. }
  112. sort.Slice(allProxies, func(i, j int) bool {
  113. return allProxies[i] == defaultTag
  114. })
  115. // fix clash dashboard
  116. proxyMap.Put("GLOBAL", map[string]any{
  117. "type": "Fallback",
  118. "name": "GLOBAL",
  119. "udp": true,
  120. "history": []*urltest.History{},
  121. "all": allProxies,
  122. "now": defaultTag,
  123. })
  124. for i, detour := range outbounds {
  125. var tag string
  126. if detour.Tag() == "" {
  127. tag = F.ToString(i)
  128. } else {
  129. tag = detour.Tag()
  130. }
  131. proxyMap.Put(tag, proxyInfo(server, detour))
  132. }
  133. var responseMap badjson.JSONObject
  134. responseMap.Put("proxies", &proxyMap)
  135. response, err := responseMap.MarshalJSON()
  136. if err != nil {
  137. render.Status(r, http.StatusInternalServerError)
  138. render.JSON(w, r, newError(err.Error()))
  139. return
  140. }
  141. w.Write(response)
  142. }
  143. }
  144. func getProxy(server *Server) func(w http.ResponseWriter, r *http.Request) {
  145. return func(w http.ResponseWriter, r *http.Request) {
  146. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  147. response, err := proxyInfo(server, proxy).MarshalJSON()
  148. if err != nil {
  149. render.Status(r, http.StatusInternalServerError)
  150. render.JSON(w, r, newError(err.Error()))
  151. return
  152. }
  153. w.Write(response)
  154. }
  155. }
  156. type UpdateProxyRequest struct {
  157. Name string `json:"name"`
  158. }
  159. func updateProxy(w http.ResponseWriter, r *http.Request) {
  160. req := UpdateProxyRequest{}
  161. if err := render.DecodeJSON(r.Body, &req); err != nil {
  162. render.Status(r, http.StatusBadRequest)
  163. render.JSON(w, r, ErrBadRequest)
  164. return
  165. }
  166. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  167. selector, ok := proxy.(*outbound.Selector)
  168. if !ok {
  169. render.Status(r, http.StatusBadRequest)
  170. render.JSON(w, r, newError("Must be a Selector"))
  171. return
  172. }
  173. if !selector.SelectOutbound(req.Name) {
  174. render.Status(r, http.StatusBadRequest)
  175. render.JSON(w, r, newError(fmt.Sprintf("Selector update error: not found")))
  176. return
  177. }
  178. render.NoContent(w, r)
  179. }
  180. func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
  181. return func(w http.ResponseWriter, r *http.Request) {
  182. query := r.URL.Query()
  183. url := query.Get("url")
  184. timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
  185. if err != nil {
  186. render.Status(r, http.StatusBadRequest)
  187. render.JSON(w, r, ErrBadRequest)
  188. return
  189. }
  190. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  191. ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
  192. defer cancel()
  193. delay, err := urltest.URLTest(ctx, url, proxy)
  194. defer func() {
  195. realTag := outbound.RealTag(proxy)
  196. if err != nil {
  197. server.router.URLTestHistoryStorage(true).DeleteURLTestHistory(realTag)
  198. } else {
  199. server.router.URLTestHistoryStorage(true).StoreURLTestHistory(realTag, &urltest.History{
  200. Time: time.Now(),
  201. Delay: delay,
  202. })
  203. }
  204. }()
  205. if ctx.Err() != nil {
  206. render.Status(r, http.StatusGatewayTimeout)
  207. render.JSON(w, r, ErrRequestTimeout)
  208. return
  209. }
  210. if err != nil || delay == 0 {
  211. render.Status(r, http.StatusServiceUnavailable)
  212. render.JSON(w, r, newError("An error occurred in the delay test"))
  213. return
  214. }
  215. render.JSON(w, r, render.M{
  216. "delay": delay,
  217. })
  218. }
  219. }