123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- package clashapi
- import (
- "context"
- "fmt"
- "net/http"
- "sort"
- "strconv"
- "time"
- "github.com/sagernet/sing-box/adapter"
- "github.com/sagernet/sing-box/common/badjson"
- "github.com/sagernet/sing-box/common/urltest"
- C "github.com/sagernet/sing-box/constant"
- "github.com/sagernet/sing-box/outbound"
- "github.com/sagernet/sing/common"
- F "github.com/sagernet/sing/common/format"
- N "github.com/sagernet/sing/common/network"
- "github.com/go-chi/chi/v5"
- "github.com/go-chi/render"
- )
- func proxyRouter(server *Server, router adapter.Router) http.Handler {
- r := chi.NewRouter()
- r.Get("/", getProxies(server, router))
- r.Route("/{name}", func(r chi.Router) {
- r.Use(parseProxyName, findProxyByName(router))
- r.Get("/", getProxy(server))
- r.Get("/delay", getProxyDelay(server))
- r.Put("/", updateProxy)
- })
- return r
- }
- func parseProxyName(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- name := getEscapeParam(r, "name")
- ctx := context.WithValue(r.Context(), CtxKeyProxyName, name)
- next.ServeHTTP(w, r.WithContext(ctx))
- })
- }
- func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- name := r.Context().Value(CtxKeyProxyName).(string)
- proxy, exist := router.Outbound(name)
- if !exist {
- render.Status(r, http.StatusNotFound)
- render.JSON(w, r, ErrNotFound)
- return
- }
- ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy)
- next.ServeHTTP(w, r.WithContext(ctx))
- })
- }
- }
- func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
- var info badjson.JSONObject
- var clashType string
- switch detour.Type() {
- case C.TypeDirect:
- clashType = "Direct"
- case C.TypeBlock:
- clashType = "Reject"
- case C.TypeSocks:
- clashType = "Socks"
- case C.TypeHTTP:
- clashType = "HTTP"
- case C.TypeShadowsocks:
- clashType = "Shadowsocks"
- case C.TypeVMess:
- clashType = "VMess"
- case C.TypeTrojan:
- clashType = "Trojan"
- case C.TypeHysteria:
- clashType = "Hysteria"
- case C.TypeWireGuard:
- clashType = "WireGuard"
- case C.TypeShadowsocksR:
- clashType = "ShadowsocksR"
- case C.TypeVLESS:
- clashType = "VLESS"
- case C.TypeTor:
- clashType = "Tor"
- case C.TypeSSH:
- clashType = "SSH"
- case C.TypeSelector:
- clashType = "Selector"
- case C.TypeURLTest:
- clashType = "URLTest"
- default:
- clashType = "Direct"
- }
- info.Put("type", clashType)
- info.Put("name", detour.Tag())
- info.Put("udp", common.Contains(detour.Network(), N.NetworkUDP))
- delayHistory := server.urlTestHistory.LoadURLTestHistory(adapter.OutboundTag(detour))
- if delayHistory != nil {
- info.Put("history", []*urltest.History{delayHistory})
- } else {
- info.Put("history", []*urltest.History{})
- }
- if group, isGroup := detour.(adapter.OutboundGroup); isGroup {
- info.Put("now", group.Now())
- info.Put("all", group.All())
- }
- return &info
- }
- func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- var proxyMap badjson.JSONObject
- outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool {
- return detour.Tag() != ""
- })
- allProxies := make([]string, 0, len(outbounds))
- for _, detour := range outbounds {
- switch detour.Type() {
- case C.TypeDirect, C.TypeBlock, C.TypeDNS:
- continue
- }
- allProxies = append(allProxies, detour.Tag())
- }
- defaultTag := router.DefaultOutbound(N.NetworkTCP).Tag()
- if defaultTag == "" {
- defaultTag = allProxies[0]
- }
- sort.Slice(allProxies, func(i, j int) bool {
- return allProxies[i] == defaultTag
- })
- // fix clash dashboard
- proxyMap.Put("GLOBAL", map[string]any{
- "type": "Fallback",
- "name": "GLOBAL",
- "udp": true,
- "history": []*urltest.History{},
- "all": allProxies,
- "now": defaultTag,
- })
- for i, detour := range outbounds {
- var tag string
- if detour.Tag() == "" {
- tag = F.ToString(i)
- } else {
- tag = detour.Tag()
- }
- proxyMap.Put(tag, proxyInfo(server, detour))
- }
- var responseMap badjson.JSONObject
- responseMap.Put("proxies", &proxyMap)
- response, err := responseMap.MarshalJSON()
- if err != nil {
- render.Status(r, http.StatusInternalServerError)
- render.JSON(w, r, newError(err.Error()))
- return
- }
- w.Write(response)
- }
- }
- func getProxy(server *Server) func(w http.ResponseWriter, r *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
- response, err := proxyInfo(server, proxy).MarshalJSON()
- if err != nil {
- render.Status(r, http.StatusInternalServerError)
- render.JSON(w, r, newError(err.Error()))
- return
- }
- w.Write(response)
- }
- }
- type UpdateProxyRequest struct {
- Name string `json:"name"`
- }
- func updateProxy(w http.ResponseWriter, r *http.Request) {
- req := UpdateProxyRequest{}
- if err := render.DecodeJSON(r.Body, &req); err != nil {
- render.Status(r, http.StatusBadRequest)
- render.JSON(w, r, ErrBadRequest)
- return
- }
- proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
- selector, ok := proxy.(*outbound.Selector)
- if !ok {
- render.Status(r, http.StatusBadRequest)
- render.JSON(w, r, newError("Must be a Selector"))
- return
- }
- if !selector.SelectOutbound(req.Name) {
- render.Status(r, http.StatusBadRequest)
- render.JSON(w, r, newError(fmt.Sprintf("Selector update error: not found")))
- return
- }
- render.NoContent(w, r)
- }
- func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
- return func(w http.ResponseWriter, r *http.Request) {
- query := r.URL.Query()
- url := query.Get("url")
- timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
- if err != nil {
- render.Status(r, http.StatusBadRequest)
- render.JSON(w, r, ErrBadRequest)
- return
- }
- proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
- ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
- defer cancel()
- delay, err := urltest.URLTest(ctx, url, proxy)
- defer func() {
- realTag := outbound.RealTag(proxy)
- if err != nil {
- server.urlTestHistory.DeleteURLTestHistory(realTag)
- } else {
- server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
- Time: time.Now(),
- Delay: delay,
- })
- }
- }()
- if ctx.Err() != nil {
- render.Status(r, http.StatusGatewayTimeout)
- render.JSON(w, r, ErrRequestTimeout)
- return
- }
- if err != nil || delay == 0 {
- render.Status(r, http.StatusServiceUnavailable)
- render.JSON(w, r, newError("An error occurred in the delay test"))
- return
- }
- render.JSON(w, r, render.M{
- "delay": delay,
- })
- }
- }
|