github.com/yaling888/clash@v1.53.0/hub/route/configs.go (about)

     1  package route
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"net/http"
     7  	"path/filepath"
     8  
     9  	"github.com/go-chi/chi/v5"
    10  	"github.com/go-chi/render"
    11  	"github.com/phuslu/log"
    12  	"github.com/samber/lo"
    13  
    14  	"github.com/yaling888/clash/component/resolver"
    15  	"github.com/yaling888/clash/config"
    16  	C "github.com/yaling888/clash/constant"
    17  	"github.com/yaling888/clash/hub/executor"
    18  	"github.com/yaling888/clash/listener"
    19  	L "github.com/yaling888/clash/log"
    20  	"github.com/yaling888/clash/tunnel"
    21  )
    22  
    23  func configRouter() http.Handler {
    24  	r := chi.NewRouter()
    25  	r.Get("/", getConfigs)
    26  	r.Put("/", updateConfigs)
    27  	r.Patch("/", patchConfigs)
    28  	return r
    29  }
    30  
    31  func getConfigs(w http.ResponseWriter, r *http.Request) {
    32  	general := executor.GetGeneral()
    33  	render.JSON(w, r, general)
    34  }
    35  
    36  func patchConfigs(w http.ResponseWriter, r *http.Request) {
    37  	type tun struct {
    38  		Enable              *bool       `json:"enable,omitempty"`
    39  		Device              *string     `json:"device,omitempty"`
    40  		Stack               *C.TUNStack `json:"stack,omitempty"`
    41  		DNSHijack           *[]C.DNSUrl `json:"dns-hijack,omitempty"`
    42  		AutoRoute           *bool       `json:"auto-route,omitempty"`
    43  		AutoDetectInterface *bool       `json:"auto-detect-interface,omitempty"`
    44  	}
    45  	general := struct {
    46  		Port        *int               `json:"port,omitempty"`
    47  		SocksPort   *int               `json:"socks-port,omitempty"`
    48  		RedirPort   *int               `json:"redir-port,omitempty"`
    49  		TProxyPort  *int               `json:"tproxy-port,omitempty"`
    50  		MixedPort   *int               `json:"mixed-port,omitempty"`
    51  		MitmPort    *int               `json:"mitm-port,omitempty"`
    52  		AllowLan    *bool              `json:"allow-lan,omitempty"`
    53  		BindAddress *string            `json:"bind-address,omitempty"`
    54  		Mode        *tunnel.TunnelMode `json:"mode,omitempty"`
    55  		LogLevel    *L.LogLevel        `json:"log-level,omitempty"`
    56  		IPv6        *bool              `json:"ipv6,omitempty"`
    57  		Sniffing    *bool              `json:"sniffing,omitempty"`
    58  		Tun         *tun               `json:"tun,omitempty"`
    59  	}{}
    60  
    61  	if err := render.DecodeJSON(r.Body, &general); err != nil {
    62  		render.Status(r, http.StatusBadRequest)
    63  		render.JSON(w, r, ErrBadRequest)
    64  		return
    65  	}
    66  
    67  	if general.AllowLan != nil {
    68  		listener.SetAllowLan(*general.AllowLan)
    69  	}
    70  
    71  	if general.BindAddress != nil {
    72  		listener.SetBindAddress(*general.BindAddress)
    73  	}
    74  
    75  	if general.Mode != nil {
    76  		tunnel.SetMode(*general.Mode)
    77  	}
    78  
    79  	if general.LogLevel != nil {
    80  		L.SetLevel(*general.LogLevel)
    81  	}
    82  
    83  	if general.IPv6 != nil {
    84  		resolver.SetDisableIPv6(!*general.IPv6)
    85  	}
    86  
    87  	if general.Sniffing != nil {
    88  		tunnel.SetSniffing(*general.Sniffing)
    89  	}
    90  
    91  	tcpIn := tunnel.TCPIn()
    92  	udpIn := tunnel.UDPIn()
    93  
    94  	ports := listener.GetPorts()
    95  	ports.Port = lo.FromPtrOr(general.Port, ports.Port)
    96  	ports.SocksPort = lo.FromPtrOr(general.SocksPort, ports.SocksPort)
    97  	ports.RedirPort = lo.FromPtrOr(general.RedirPort, ports.RedirPort)
    98  	ports.TProxyPort = lo.FromPtrOr(general.TProxyPort, ports.TProxyPort)
    99  	ports.MixedPort = lo.FromPtrOr(general.MixedPort, ports.MixedPort)
   100  	ports.MitmPort = lo.FromPtrOr(general.MitmPort, ports.MitmPort)
   101  
   102  	listener.ReCreatePortsListeners(*ports, tcpIn, udpIn)
   103  
   104  	if general.Tun != nil {
   105  		tunSchema := general.Tun
   106  		tunConf := C.GetTunConf()
   107  		tunConf.StopRouteListener = true
   108  
   109  		tunConf.Enable = lo.FromPtrOr(tunSchema.Enable, tunConf.Enable)
   110  		tunConf.Device = lo.FromPtrOr(tunSchema.Device, tunConf.Device)
   111  		tunConf.Stack = lo.FromPtrOr(tunSchema.Stack, tunConf.Stack)
   112  		tunConf.DNSHijack = lo.FromPtrOr(tunSchema.DNSHijack, tunConf.DNSHijack)
   113  		tunConf.AutoRoute = lo.FromPtrOr(tunSchema.AutoRoute, tunConf.AutoRoute)
   114  		tunConf.AutoDetectInterface = lo.FromPtrOr(tunSchema.AutoDetectInterface, tunConf.AutoDetectInterface)
   115  
   116  		listener.ReCreateTun(&tunConf, tcpIn, udpIn)
   117  		listener.ReCreateRedirToTun(tunConf.RedirectToTun)
   118  	}
   119  
   120  	msg, _ := json.Marshal(general)
   121  	log.Warn().Str("data", string(msg)).Msg("[API] patch config")
   122  
   123  	render.NoContent(w, r)
   124  }
   125  
   126  func updateConfigs(w http.ResponseWriter, r *http.Request) {
   127  	req := struct {
   128  		Path    string `json:"path"`
   129  		Payload string `json:"payload"`
   130  	}{}
   131  	if err := render.DecodeJSON(r.Body, &req); err != nil {
   132  		render.Status(r, http.StatusBadRequest)
   133  		render.JSON(w, r, ErrBadRequest)
   134  		return
   135  	}
   136  
   137  	var (
   138  		cfg      *config.Config
   139  		err      error
   140  		hasPath  bool
   141  		oldLevel = L.Level()
   142  		force    = r.URL.Query().Get("force") == "true"
   143  	)
   144  
   145  	defer func() {
   146  		if err == nil {
   147  			oldLevel = L.Level()
   148  			L.SetLevel(L.INFO)
   149  			if req.Payload != "" {
   150  				log.Info().Msg("[API] payload config updated")
   151  			} else {
   152  				if hasPath {
   153  					C.SetConfig(req.Path)
   154  				}
   155  				log.Info().Str("path", req.Path).Msg("[API] configuration file reloaded")
   156  			}
   157  		}
   158  		L.SetLevel(oldLevel)
   159  	}()
   160  
   161  	L.SetLevel(L.INFO)
   162  
   163  	if req.Payload != "" {
   164  		log.Info().Msg("[API] payload config updating...")
   165  
   166  		cfg, err = executor.ParseWithBytes([]byte(req.Payload))
   167  		if err != nil {
   168  			log.Error().Err(err).Msg("[API] update config failed")
   169  
   170  			render.Status(r, http.StatusBadRequest)
   171  			render.JSON(w, r, newError(err.Error()))
   172  			return
   173  		}
   174  	} else {
   175  		if hasPath = req.Path != ""; !hasPath {
   176  			req.Path = C.Path.Config()
   177  		}
   178  
   179  		log.Info().Str("path", req.Path).Msg("[API] configuration file reloading...")
   180  
   181  		if !filepath.IsAbs(req.Path) {
   182  			err = errors.New("path is not a absolute path")
   183  			log.Error().
   184  				Err(err).
   185  				Str("path", req.Path).
   186  				Msg("[API] reload config failed")
   187  
   188  			render.Status(r, http.StatusBadRequest)
   189  			render.JSON(w, r, newError(err.Error()))
   190  			return
   191  		}
   192  
   193  		cfg, err = executor.ParseWithPath(req.Path)
   194  		if err != nil {
   195  			log.Error().
   196  				Err(err).
   197  				Str("path", req.Path).
   198  				Msg("[API] reload config failed")
   199  
   200  			render.Status(r, http.StatusBadRequest)
   201  			render.JSON(w, r, newError(err.Error()))
   202  			return
   203  		}
   204  	}
   205  
   206  	executor.ApplyConfig(cfg, force)
   207  	render.NoContent(w, r)
   208  }