github.com/kelleygo/clashcore@v1.0.2/hub/route/configs.go (about) 1 package route 2 3 import ( 4 "net/http" 5 "net/netip" 6 "path/filepath" 7 "sync" 8 9 "github.com/kelleygo/clashcore/adapter/inbound" 10 "github.com/kelleygo/clashcore/component/dialer" 11 "github.com/kelleygo/clashcore/component/resolver" 12 "github.com/kelleygo/clashcore/config" 13 C "github.com/kelleygo/clashcore/constant" 14 "github.com/kelleygo/clashcore/hub/executor" 15 P "github.com/kelleygo/clashcore/listener" 16 LC "github.com/kelleygo/clashcore/listener/config" 17 "github.com/kelleygo/clashcore/log" 18 "github.com/kelleygo/clashcore/tunnel" 19 20 "github.com/go-chi/chi/v5" 21 "github.com/go-chi/render" 22 ) 23 24 var ( 25 updateGeoMux sync.Mutex 26 updatingGeo = false 27 ) 28 29 func configRouter() http.Handler { 30 r := chi.NewRouter() 31 r.Get("/", getConfigs) 32 r.Put("/", updateConfigs) 33 r.Post("/geo", updateGeoDatabases) 34 r.Patch("/", patchConfigs) 35 return r 36 } 37 38 type configSchema struct { 39 Port *int `json:"port"` 40 SocksPort *int `json:"socks-port"` 41 RedirPort *int `json:"redir-port"` 42 TProxyPort *int `json:"tproxy-port"` 43 MixedPort *int `json:"mixed-port"` 44 Tun *tunSchema `json:"tun"` 45 TuicServer *tuicServerSchema `json:"tuic-server"` 46 ShadowSocksConfig *string `json:"ss-config"` 47 VmessConfig *string `json:"vmess-config"` 48 TcptunConfig *string `json:"tcptun-config"` 49 UdptunConfig *string `json:"udptun-config"` 50 AllowLan *bool `json:"allow-lan"` 51 SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"` 52 LanAllowedIPs *[]netip.Prefix `json:"lan-allowed-ips"` 53 LanDisAllowedIPs *[]netip.Prefix `json:"lan-disallowed-ips"` 54 BindAddress *string `json:"bind-address"` 55 Mode *tunnel.TunnelMode `json:"mode"` 56 LogLevel *log.LogLevel `json:"log-level"` 57 IPv6 *bool `json:"ipv6"` 58 Sniffing *bool `json:"sniffing"` 59 TcpConcurrent *bool `json:"tcp-concurrent"` 60 InterfaceName *string `json:"interface-name"` 61 } 62 63 type tunSchema struct { 64 Enable bool `yaml:"enable" json:"enable"` 65 Device *string `yaml:"device" json:"device"` 66 Stack *C.TUNStack `yaml:"stack" json:"stack"` 67 DNSHijack *[]string `yaml:"dns-hijack" json:"dns-hijack"` 68 AutoRoute *bool `yaml:"auto-route" json:"auto-route"` 69 AutoDetectInterface *bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` 70 //RedirectToTun []string `yaml:"-" json:"-"` 71 72 MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"` 73 GSO *bool `yaml:"gso" json:"gso,omitempty"` 74 GSOMaxSize *uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` 75 //Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` 76 Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` 77 StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` 78 Inet4RouteAddress *[]netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"` 79 Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` 80 Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` 81 Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` 82 IncludeInterface *[]string `yaml:"include-interface" json:"include-interface,omitempty"` 83 ExcludeInterface *[]string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` 84 IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` 85 IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` 86 ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` 87 ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` 88 IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"` 89 IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"` 90 ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"` 91 EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"` 92 UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"` 93 FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"` 94 TableIndex *int `yaml:"table-index" json:"table-index"` 95 } 96 97 type tuicServerSchema struct { 98 Enable bool `yaml:"enable" json:"enable"` 99 Listen *string `yaml:"listen" json:"listen"` 100 Token *[]string `yaml:"token" json:"token"` 101 Users *map[string]string `yaml:"users" json:"users,omitempty"` 102 Certificate *string `yaml:"certificate" json:"certificate"` 103 PrivateKey *string `yaml:"private-key" json:"private-key"` 104 CongestionController *string `yaml:"congestion-controller" json:"congestion-controller,omitempty"` 105 MaxIdleTime *int `yaml:"max-idle-time" json:"max-idle-time,omitempty"` 106 AuthenticationTimeout *int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"` 107 ALPN *[]string `yaml:"alpn" json:"alpn,omitempty"` 108 MaxUdpRelayPacketSize *int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"` 109 CWND *int `yaml:"cwnd" json:"cwnd,omitempty"` 110 } 111 112 func getConfigs(w http.ResponseWriter, r *http.Request) { 113 general := executor.GetGeneral() 114 render.JSON(w, r, general) 115 } 116 117 func pointerOrDefault(p *int, def int) int { 118 if p != nil { 119 return *p 120 } 121 return def 122 } 123 124 func pointerOrDefaultString(p *string, def string) string { 125 if p != nil { 126 return *p 127 } 128 129 return def 130 } 131 132 func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun { 133 if p != nil { 134 def.Enable = p.Enable 135 if p.Device != nil { 136 def.Device = *p.Device 137 } 138 if p.Stack != nil { 139 def.Stack = *p.Stack 140 } 141 if p.DNSHijack != nil { 142 def.DNSHijack = *p.DNSHijack 143 } 144 if p.AutoRoute != nil { 145 def.AutoRoute = *p.AutoRoute 146 } 147 if p.AutoDetectInterface != nil { 148 def.AutoDetectInterface = *p.AutoDetectInterface 149 } 150 if p.MTU != nil { 151 def.MTU = *p.MTU 152 } 153 if p.GSO != nil { 154 def.GSO = *p.GSO 155 } 156 if p.GSOMaxSize != nil { 157 def.GSOMaxSize = *p.GSOMaxSize 158 } 159 //if p.Inet4Address != nil { 160 // def.Inet4Address = *p.Inet4Address 161 //} 162 if p.Inet6Address != nil { 163 def.Inet6Address = *p.Inet6Address 164 } 165 if p.Inet4RouteAddress != nil { 166 def.Inet4RouteAddress = *p.Inet4RouteAddress 167 } 168 if p.Inet6RouteAddress != nil { 169 def.Inet6RouteAddress = *p.Inet6RouteAddress 170 } 171 if p.Inet4RouteExcludeAddress != nil { 172 def.Inet4RouteExcludeAddress = *p.Inet4RouteExcludeAddress 173 } 174 if p.Inet6RouteExcludeAddress != nil { 175 def.Inet6RouteExcludeAddress = *p.Inet6RouteExcludeAddress 176 } 177 if p.IncludeInterface != nil { 178 def.IncludeInterface = *p.IncludeInterface 179 } 180 if p.ExcludeInterface != nil { 181 def.ExcludeInterface = *p.ExcludeInterface 182 } 183 if p.IncludeUID != nil { 184 def.IncludeUID = *p.IncludeUID 185 } 186 if p.IncludeUIDRange != nil { 187 def.IncludeUIDRange = *p.IncludeUIDRange 188 } 189 if p.ExcludeUID != nil { 190 def.ExcludeUID = *p.ExcludeUID 191 } 192 if p.ExcludeUIDRange != nil { 193 def.ExcludeUIDRange = *p.ExcludeUIDRange 194 } 195 if p.IncludeAndroidUser != nil { 196 def.IncludeAndroidUser = *p.IncludeAndroidUser 197 } 198 if p.IncludePackage != nil { 199 def.IncludePackage = *p.IncludePackage 200 } 201 if p.ExcludePackage != nil { 202 def.ExcludePackage = *p.ExcludePackage 203 } 204 if p.EndpointIndependentNat != nil { 205 def.EndpointIndependentNat = *p.EndpointIndependentNat 206 } 207 if p.UDPTimeout != nil { 208 def.UDPTimeout = *p.UDPTimeout 209 } 210 if p.FileDescriptor != nil { 211 def.FileDescriptor = *p.FileDescriptor 212 } 213 if p.TableIndex != nil { 214 def.TableIndex = *p.TableIndex 215 } 216 } 217 return def 218 } 219 220 func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicServer { 221 if p != nil { 222 def.Enable = p.Enable 223 if p.Listen != nil { 224 def.Listen = *p.Listen 225 } 226 if p.Token != nil { 227 def.Token = *p.Token 228 } 229 if p.Users != nil { 230 def.Users = *p.Users 231 } 232 if p.Certificate != nil { 233 def.Certificate = *p.Certificate 234 } 235 if p.PrivateKey != nil { 236 def.PrivateKey = *p.PrivateKey 237 } 238 if p.CongestionController != nil { 239 def.CongestionController = *p.CongestionController 240 } 241 if p.MaxIdleTime != nil { 242 def.MaxIdleTime = *p.MaxIdleTime 243 } 244 if p.AuthenticationTimeout != nil { 245 def.AuthenticationTimeout = *p.AuthenticationTimeout 246 } 247 if p.ALPN != nil { 248 def.ALPN = *p.ALPN 249 } 250 if p.MaxUdpRelayPacketSize != nil { 251 def.MaxUdpRelayPacketSize = *p.MaxUdpRelayPacketSize 252 } 253 if p.CWND != nil { 254 def.CWND = *p.CWND 255 } 256 } 257 return def 258 } 259 260 func patchConfigs(w http.ResponseWriter, r *http.Request) { 261 general := &configSchema{} 262 if err := render.DecodeJSON(r.Body, &general); err != nil { 263 render.Status(r, http.StatusBadRequest) 264 render.JSON(w, r, ErrBadRequest) 265 return 266 } 267 268 if general.AllowLan != nil { 269 P.SetAllowLan(*general.AllowLan) 270 } 271 272 if general.SkipAuthPrefixes != nil { 273 inbound.SetSkipAuthPrefixes(*general.SkipAuthPrefixes) 274 } 275 276 if general.LanAllowedIPs != nil { 277 inbound.SetAllowedIPs(*general.LanAllowedIPs) 278 } 279 280 if general.LanDisAllowedIPs != nil { 281 inbound.SetDisAllowedIPs(*general.LanDisAllowedIPs) 282 } 283 284 if general.BindAddress != nil { 285 P.SetBindAddress(*general.BindAddress) 286 } 287 288 if general.Sniffing != nil { 289 tunnel.SetSniffing(*general.Sniffing) 290 } 291 292 if general.TcpConcurrent != nil { 293 dialer.SetTcpConcurrent(*general.TcpConcurrent) 294 } 295 296 if general.InterfaceName != nil { 297 dialer.DefaultInterface.Store(*general.InterfaceName) 298 } 299 300 ports := P.GetPorts() 301 302 P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tunnel.Tunnel) 303 P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tunnel.Tunnel) 304 P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tunnel.Tunnel) 305 P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tunnel.Tunnel) 306 P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tunnel.Tunnel) 307 P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tunnel.Tunnel) 308 P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tunnel.Tunnel) 309 P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tunnel.Tunnel) 310 P.ReCreateTuic(pointerOrDefaultTuicServer(general.TuicServer, P.LastTuicConf), tunnel.Tunnel) 311 312 if general.Mode != nil { 313 tunnel.SetMode(*general.Mode) 314 } 315 316 if general.LogLevel != nil { 317 log.SetLevel(*general.LogLevel) 318 } 319 320 if general.IPv6 != nil { 321 resolver.DisableIPv6 = !*general.IPv6 322 } 323 324 render.NoContent(w, r) 325 } 326 327 func updateConfigs(w http.ResponseWriter, r *http.Request) { 328 req := struct { 329 Path string `json:"path"` 330 Payload string `json:"payload"` 331 }{} 332 if err := render.DecodeJSON(r.Body, &req); err != nil { 333 render.Status(r, http.StatusBadRequest) 334 render.JSON(w, r, ErrBadRequest) 335 return 336 } 337 338 force := r.URL.Query().Get("force") == "true" 339 var cfg *config.Config 340 var err error 341 342 if req.Payload != "" { 343 cfg, err = executor.ParseWithBytes([]byte(req.Payload)) 344 if err != nil { 345 render.Status(r, http.StatusBadRequest) 346 render.JSON(w, r, newError(err.Error())) 347 return 348 } 349 } else { 350 if req.Path == "" { 351 req.Path = C.Path.Config() 352 } 353 if !filepath.IsAbs(req.Path) { 354 render.Status(r, http.StatusBadRequest) 355 render.JSON(w, r, newError("path is not a absolute path")) 356 return 357 } 358 359 cfg, err = executor.ParseWithPath(req.Path) 360 if err != nil { 361 render.Status(r, http.StatusBadRequest) 362 render.JSON(w, r, newError(err.Error())) 363 return 364 } 365 } 366 367 executor.ApplyConfig(cfg, force) 368 render.NoContent(w, r) 369 } 370 371 func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { 372 updateGeoMux.Lock() 373 374 if updatingGeo { 375 updateGeoMux.Unlock() 376 render.Status(r, http.StatusBadRequest) 377 render.JSON(w, r, newError("updating...")) 378 return 379 } 380 381 updatingGeo = true 382 updateGeoMux.Unlock() 383 384 go func() { 385 defer func() { 386 updatingGeo = false 387 }() 388 389 log.Warnln("[REST-API] updating GEO databases...") 390 391 if err := config.UpdateGeoDatabases(); err != nil { 392 log.Errorln("[REST-API] update GEO databases failed: %v", err) 393 return 394 } 395 396 cfg, err := executor.ParseWithPath(C.Path.Config()) 397 if err != nil { 398 log.Errorln("[REST-API] update GEO databases failed: %v", err) 399 return 400 } 401 402 log.Warnln("[REST-API] update GEO databases successful, apply config...") 403 404 executor.ApplyConfig(cfg, false) 405 }() 406 407 render.NoContent(w, r) 408 }