diff --git a/api/apiHandler.go b/api/apiHandler.go index 3237bde..1405076 100644 --- a/api/apiHandler.go +++ b/api/apiHandler.go @@ -97,6 +97,8 @@ func (a *APIHandler) getHandler(c *gin.Context) { a.ApiService.GetTokens(c) case "singbox-config": a.ApiService.GetSingboxConfig(c) + case "checkOutbound": + a.ApiService.GetCheckOutbound(c) default: jsonMsg(c, "failed", common.NewError("unknown action: ", action)) } diff --git a/api/apiService.go b/api/apiService.go index 8e6317e..2f02793 100644 --- a/api/apiService.go +++ b/api/apiService.go @@ -396,3 +396,10 @@ func (a *ApiService) GetSingboxConfig(c *gin.Context) { c.Header("Content-Disposition", "attachment; filename=config_"+time.Now().Format("20060102-150405")+".json") c.Writer.Write(rawConfig) } + +func (a *ApiService) GetCheckOutbound(c *gin.Context) { + tag := c.Query("tag") + link := c.Query("link") + result := a.ConfigService.CheckOutbound(tag, link) + jsonObj(c, result, nil) +} diff --git a/api/apiV2Handler.go b/api/apiV2Handler.go index 39cfd69..6eb08bf 100644 --- a/api/apiV2Handler.go +++ b/api/apiV2Handler.go @@ -86,6 +86,8 @@ func (a *APIv2Handler) getHandler(c *gin.Context) { a.ApiService.GetKeypairs(c) case "getdb": a.ApiService.GetDb(c) + case "checkOutbound": + a.ApiService.GetCheckOutbound(c) default: jsonMsg(c, "failed", common.NewError("unknown action: ", action)) } diff --git a/core/outbound_check.go b/core/outbound_check.go new file mode 100644 index 0000000..a91b512 --- /dev/null +++ b/core/outbound_check.go @@ -0,0 +1,40 @@ +package core + +import ( + "context" + "time" + + urltest "github.com/sagernet/sing-box/common/urltest" +) + +const checkTimeout = 15 * time.Second + +type CheckOutboundResult struct { + OK bool + Delay uint16 + Error string +} + +func CheckOutbound(ctx context.Context, tag string, link string) (result CheckOutboundResult) { + if outbound_manager == nil { + result.Error = "core not running" + return result + } + ob, ok := outbound_manager.Outbound(tag) + if !ok { + result.Error = "outbound not found" + return result + } + + ctx, cancel := context.WithTimeout(ctx, checkTimeout) + defer cancel() + + delay, err := urltest.URLTest(ctx, link, ob) + if err != nil { + result.Error = err.Error() + return result + } + result.OK = true + result.Delay = delay + return result +} diff --git a/service/config.go b/service/config.go index b8b3969..eb1f76b 100644 --- a/service/config.go +++ b/service/config.go @@ -123,6 +123,16 @@ func (s *ConfigService) StopCore() error { return nil } +func (s *ConfigService) CheckOutbound(tag string, link string) core.CheckOutboundResult { + if tag == "" { + return core.CheckOutboundResult{Error: "missing query parameter: tag"} + } + if corePtr == nil || !corePtr.IsRunning() { + return core.CheckOutboundResult{Error: "core not running"} + } + return core.CheckOutbound(corePtr.GetCtx(), tag, link) +} + func (s *ConfigService) Save(obj string, act string, data json.RawMessage, initUsers string, loginUser string, hostname string) ([]string, error) { var err error var objs []string = []string{obj}