proxies.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. package clashapi
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "net/http"
  7. "net/url"
  8. "strconv"
  9. "time"
  10. "github.com/sagernet/sing-box/adapter"
  11. "github.com/sagernet/sing-box/common/badjson"
  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. M "github.com/sagernet/sing/common/metadata"
  17. "github.com/go-chi/chi/v5"
  18. "github.com/go-chi/render"
  19. "sort"
  20. )
  21. func proxyRouter(server *Server, router adapter.Router) http.Handler {
  22. r := chi.NewRouter()
  23. r.Get("/", getProxies(server, router))
  24. r.Route("/{name}", func(r chi.Router) {
  25. r.Use(parseProxyName, findProxyByName(router))
  26. r.Get("/", getProxy(server))
  27. r.Get("/delay", getProxyDelay(server))
  28. r.Put("/", updateProxy)
  29. })
  30. return r
  31. }
  32. func parseProxyName(next http.Handler) http.Handler {
  33. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  34. name := getEscapeParam(r, "name")
  35. ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
  36. next.ServeHTTP(w, r.WithContext(ctx))
  37. })
  38. }
  39. func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler {
  40. return func(next http.Handler) http.Handler {
  41. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  42. name := r.Context().Value(CtxKeyProxyName).(string)
  43. proxy, exist := router.Outbound(name)
  44. if !exist {
  45. render.Status(r, http.StatusNotFound)
  46. render.JSON(w, r, ErrNotFound)
  47. return
  48. }
  49. ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
  50. next.ServeHTTP(w, r.WithContext(ctx))
  51. })
  52. }
  53. }
  54. func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
  55. var info badjson.JSONObject
  56. var clashType string
  57. var isSelector bool
  58. switch detour.Type() {
  59. case C.TypeDirect:
  60. clashType = "Direct"
  61. case C.TypeBlock:
  62. clashType = "Reject"
  63. case C.TypeSocks:
  64. clashType = "Socks"
  65. case C.TypeHTTP:
  66. clashType = "Http"
  67. case C.TypeShadowsocks:
  68. clashType = "Shadowsocks"
  69. case C.TypeVMess:
  70. clashType = "Vmess"
  71. case C.TypeSelector:
  72. clashType = "Selector"
  73. isSelector = true
  74. default:
  75. clashType = "Unknown"
  76. }
  77. info.Put("type", clashType)
  78. info.Put("name", detour.Tag())
  79. info.Put("udp", common.Contains(detour.Network(), C.NetworkUDP))
  80. var delayHistory *DelayHistory
  81. var loaded bool
  82. if isSelector {
  83. selector := detour.(*outbound.Selector)
  84. info.Put("now", selector.Now())
  85. info.Put("all", selector.All())
  86. delayHistory, loaded = server.delayHistory[selector.Now()]
  87. } else {
  88. delayHistory, loaded = server.delayHistory[detour.Tag()]
  89. }
  90. if loaded {
  91. info.Put("history", []*DelayHistory{delayHistory})
  92. } else {
  93. info.Put("history", []*DelayHistory{})
  94. }
  95. return &info
  96. }
  97. func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
  98. return func(w http.ResponseWriter, r *http.Request) {
  99. var proxyMap badjson.JSONObject
  100. outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool {
  101. return detour.Tag() != ""
  102. })
  103. allProxies := make([]string, 0, len(outbounds))
  104. for _, detour := range outbounds {
  105. switch detour.Type() {
  106. case C.TypeDirect, C.TypeBlock:
  107. continue
  108. }
  109. allProxies = append(allProxies, detour.Tag())
  110. }
  111. defaultTag := router.DefaultOutbound(C.NetworkTCP).Tag()
  112. if defaultTag == "" {
  113. defaultTag = allProxies[0]
  114. }
  115. sort.Slice(allProxies, func(i, j int) bool {
  116. return allProxies[i] == defaultTag
  117. })
  118. // fix clash dashboard
  119. proxyMap.Put("GLOBAL", map[string]any{
  120. "type": "Fallback",
  121. "name": "GLOBAL",
  122. "udp": true,
  123. "history": []*DelayHistory{},
  124. "all": allProxies,
  125. "now": defaultTag,
  126. })
  127. for i, detour := range outbounds {
  128. var tag string
  129. if detour.Tag() == "" {
  130. tag = F.ToString(i)
  131. } else {
  132. tag = detour.Tag()
  133. }
  134. proxyMap.Put(tag, proxyInfo(server, detour))
  135. }
  136. var responseMap badjson.JSONObject
  137. responseMap.Put("proxies", &proxyMap)
  138. response, err := responseMap.MarshalJSON()
  139. if err != nil {
  140. render.Status(r, http.StatusInternalServerError)
  141. render.JSON(w, r, newError(err.Error()))
  142. return
  143. }
  144. w.Write(response)
  145. }
  146. }
  147. func getProxy(server *Server) func(w http.ResponseWriter, r *http.Request) {
  148. return func(w http.ResponseWriter, r *http.Request) {
  149. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  150. response, err := proxyInfo(server, proxy).MarshalJSON()
  151. if err != nil {
  152. render.Status(r, http.StatusInternalServerError)
  153. render.JSON(w, r, newError(err.Error()))
  154. return
  155. }
  156. w.Write(response)
  157. }
  158. }
  159. type UpdateProxyRequest struct {
  160. Name string `json:"name"`
  161. }
  162. func updateProxy(w http.ResponseWriter, r *http.Request) {
  163. req := UpdateProxyRequest{}
  164. if err := render.DecodeJSON(r.Body, &req); err != nil {
  165. render.Status(r, http.StatusBadRequest)
  166. render.JSON(w, r, ErrBadRequest)
  167. return
  168. }
  169. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  170. selector, ok := proxy.(*outbound.Selector)
  171. if !ok {
  172. render.Status(r, http.StatusBadRequest)
  173. render.JSON(w, r, newError("Must be a Selector"))
  174. return
  175. }
  176. if !selector.SelectOutbound(req.Name) {
  177. render.Status(r, http.StatusBadRequest)
  178. render.JSON(w, r, newError(fmt.Sprintf("Selector update error: not found")))
  179. return
  180. }
  181. render.NoContent(w, r)
  182. }
  183. func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
  184. return func(w http.ResponseWriter, r *http.Request) {
  185. query := r.URL.Query()
  186. url := query.Get("url")
  187. timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
  188. if err != nil {
  189. render.Status(r, http.StatusBadRequest)
  190. render.JSON(w, r, ErrBadRequest)
  191. return
  192. }
  193. proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
  194. ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
  195. defer cancel()
  196. delay, err := URLTest(ctx, url, proxy)
  197. if ctx.Err() != nil {
  198. render.Status(r, http.StatusGatewayTimeout)
  199. render.JSON(w, r, ErrRequestTimeout)
  200. return
  201. }
  202. if err != nil || delay == 0 {
  203. render.Status(r, http.StatusServiceUnavailable)
  204. render.JSON(w, r, newError("An error occurred in the delay test"))
  205. return
  206. }
  207. server.delayHistory[proxy.Tag()] = &DelayHistory{
  208. Time: time.Now(),
  209. Delay: delay,
  210. }
  211. render.JSON(w, r, render.M{
  212. "delay": delay,
  213. })
  214. }
  215. }
  216. func URLTest(ctx context.Context, link string, detour adapter.Outbound) (t uint16, err error) {
  217. linkURL, err := url.Parse(link)
  218. if err != nil {
  219. return
  220. }
  221. hostname := linkURL.Hostname()
  222. port := linkURL.Port()
  223. if port == "" {
  224. switch linkURL.Scheme {
  225. case "http":
  226. port = "80"
  227. case "https":
  228. port = "443"
  229. }
  230. }
  231. start := time.Now()
  232. instance, err := detour.DialContext(ctx, "tcp", M.ParseSocksaddrHostPortStr(hostname, port))
  233. if err != nil {
  234. return
  235. }
  236. defer instance.Close()
  237. req, err := http.NewRequest(http.MethodHead, link, nil)
  238. if err != nil {
  239. return
  240. }
  241. req = req.WithContext(ctx)
  242. transport := &http.Transport{
  243. Dial: func(string, string) (net.Conn, error) {
  244. return instance, nil
  245. },
  246. // from http.DefaultTransport
  247. MaxIdleConns: 100,
  248. IdleConnTimeout: 90 * time.Second,
  249. TLSHandshakeTimeout: 10 * time.Second,
  250. ExpectContinueTimeout: 1 * time.Second,
  251. }
  252. client := http.Client{
  253. Transport: transport,
  254. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  255. return http.ErrUseLastResponse
  256. },
  257. }
  258. defer client.CloseIdleConnections()
  259. resp, err := client.Do(req)
  260. if err != nil {
  261. return
  262. }
  263. resp.Body.Close()
  264. t = uint16(time.Since(start) / time.Millisecond)
  265. return
  266. }