diff --git a/api/api.go b/api/api.go index b132272..6e01717 100644 --- a/api/api.go +++ b/api/api.go @@ -165,6 +165,18 @@ func registerConfig(config *gin.RouterGroup) { config.PUT("/routers/:router", updateRouter) config.DELETE("/routers/:router", deleteRouter) + config.POST("/observers", createObserver) + config.PUT("/observers/:observer", updateObserver) + config.DELETE("/observers/:observer", deleteObserver) + + config.POST("/recorders", createRecorder) + config.PUT("/recorders/:recorder", updateRecorder) + config.DELETE("/recorders/:recorder", deleteRecorder) + + config.POST("/sds", createSD) + config.PUT("/sds/:sd", updateSD) + config.DELETE("/sds/:sd", deleteSD) + config.POST("/limiters", createLimiter) config.PUT("/limiters/:limiter", updateLimiter) config.DELETE("/limiters/:limiter", deleteLimiter) diff --git a/api/config.go b/api/config.go index 3cb09ea..0bfab47 100644 --- a/api/config.go +++ b/api/config.go @@ -144,7 +144,7 @@ func saveConfig(ctx *gin.Context) { if err != nil { writeError(ctx, &Error{ statusCode: http.StatusInternalServerError, - Code: 40005, + Code: ErrCodeSaveConfigFailed, Msg: fmt.Sprintf("create file: %s", err.Error()), }) return @@ -154,8 +154,8 @@ func saveConfig(ctx *gin.Context) { if err := config.Global().Write(f, req.Format); err != nil { writeError(ctx, &Error{ statusCode: http.StatusInternalServerError, - Code: 40006, - Msg: fmt.Sprintf("write: %s", err.Error()), + Code: ErrCodeSaveConfigFailed, + Msg: fmt.Sprintf("save config: %s", err.Error()), }) return } diff --git a/api/config_admission.go b/api/config_admission.go index 0be8c81..5f0fb93 100644 --- a/api/config_admission.go +++ b/api/config_admission.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,22 @@ func createAdmission(ctx *gin.Context) { var req createAdmissionRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "admission name is required")) + return + } + req.Data.Name = name + + if registry.AdmissionRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name))) return } v := parser.ParseAdmission(&req.Data) - if err := registry.AdmissionRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.AdmissionRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name))) return } @@ -87,25 +96,27 @@ func updateAdmission(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.AdmissionRegistry().IsRegistered(req.Admission) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Admission) + + if !registry.AdmissionRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name))) return } - req.Data.Name = req.Admission + req.Data.Name = name v := parser.ParseAdmission(&req.Data) - registry.AdmissionRegistry().Unregister(req.Admission) + registry.AdmissionRegistry().Unregister(name) - if err := registry.AdmissionRegistry().Register(req.Admission, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.AdmissionRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Admissions { - if c.Admissions[i].Name == req.Admission { + if c.Admissions[i].Name == name { c.Admissions[i] = &req.Data break } @@ -145,17 +156,19 @@ func deleteAdmission(ctx *gin.Context) { var req deleteAdmissionRequest ctx.ShouldBindUri(&req) - if !registry.AdmissionRegistry().IsRegistered(req.Admission) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Admission) + + if !registry.AdmissionRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name))) return } - registry.AdmissionRegistry().Unregister(req.Admission) + registry.AdmissionRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { admissiones := c.Admissions c.Admissions = nil for _, s := range admissiones { - if s.Name == req.Admission { + if s.Name == name { continue } c.Admissions = append(c.Admissions, s) diff --git a/api/config_auther.go b/api/config_auther.go index 68264c4..1a76165 100644 --- a/api/config_auther.go +++ b/api/config_auther.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,14 +37,21 @@ func createAuther(ctx *gin.Context) { var req createAutherRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "auther name is required")) + return + } + req.Data.Name = name + + if registry.AutherRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name))) return } v := parser.ParseAuther(&req.Data) - if err := registry.AutherRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.AutherRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name))) return } @@ -86,24 +95,26 @@ func updateAuther(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.AutherRegistry().IsRegistered(req.Auther) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Auther) + + if !registry.AutherRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name))) return } - req.Data.Name = req.Auther + req.Data.Name = name v := parser.ParseAuther(&req.Data) - registry.AutherRegistry().Unregister(req.Auther) + registry.AutherRegistry().Unregister(name) - if err := registry.AutherRegistry().Register(req.Auther, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.AutherRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Authers { - if c.Authers[i].Name == req.Auther { + if c.Authers[i].Name == name { c.Authers[i] = &req.Data break } @@ -143,17 +154,19 @@ func deleteAuther(ctx *gin.Context) { var req deleteAutherRequest ctx.ShouldBindUri(&req) - if !registry.AutherRegistry().IsRegistered(req.Auther) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Auther) + + if !registry.AutherRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name))) return } - registry.AutherRegistry().Unregister(req.Auther) + registry.AutherRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { authers := c.Authers c.Authers = nil for _, s := range authers { - if s.Name == req.Auther { + if s.Name == name { continue } c.Authers = append(c.Authers, s) diff --git a/api/config_bypass.go b/api/config_bypass.go index 03aabbc..c4e7e0f 100644 --- a/api/config_bypass.go +++ b/api/config_bypass.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,22 @@ func createBypass(ctx *gin.Context) { var req createBypassRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "bypass name is required")) + return + } + req.Data.Name = name + + if registry.BypassRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name))) return } v := parser.ParseBypass(&req.Data) - if err := registry.BypassRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.BypassRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name))) return } @@ -87,25 +96,27 @@ func updateBypass(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.BypassRegistry().IsRegistered(req.Bypass) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Bypass) + + if !registry.BypassRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name))) return } - req.Data.Name = req.Bypass + req.Data.Name = name v := parser.ParseBypass(&req.Data) - registry.BypassRegistry().Unregister(req.Bypass) + registry.BypassRegistry().Unregister(name) - if err := registry.BypassRegistry().Register(req.Bypass, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.BypassRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Bypasses { - if c.Bypasses[i].Name == req.Bypass { + if c.Bypasses[i].Name == name { c.Bypasses[i] = &req.Data break } @@ -145,17 +156,19 @@ func deleteBypass(ctx *gin.Context) { var req deleteBypassRequest ctx.ShouldBindUri(&req) - if !registry.BypassRegistry().IsRegistered(req.Bypass) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Bypass) + + if !registry.BypassRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name))) return } - registry.BypassRegistry().Unregister(req.Bypass) + registry.BypassRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { bypasses := c.Bypasses c.Bypasses = nil for _, s := range bypasses { - if s.Name == req.Bypass { + if s.Name == name { continue } c.Bypasses = append(c.Bypasses, s) diff --git a/api/config_chain.go b/api/config_chain.go index d5eb33a..80bbfd1 100644 --- a/api/config_chain.go +++ b/api/config_chain.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/core/logger" @@ -36,19 +38,26 @@ func createChain(ctx *gin.Context) { var req createChainRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "chain name is required")) + return + } + req.Data.Name = name + + if registry.ChainRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name))) return } v, err := parser.ParseChain(&req.Data, logger.Default()) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error()))) return } - if err := registry.ChainRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.ChainRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name))) return } @@ -93,29 +102,31 @@ func updateChain(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.ChainRegistry().IsRegistered(req.Chain) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Chain) + + if !registry.ChainRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name))) return } - req.Data.Name = req.Chain + req.Data.Name = name v, err := parser.ParseChain(&req.Data, logger.Default()) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error()))) return } - registry.ChainRegistry().Unregister(req.Chain) + registry.ChainRegistry().Unregister(name) - if err := registry.ChainRegistry().Register(req.Chain, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.ChainRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Chains { - if c.Chains[i].Name == req.Chain { + if c.Chains[i].Name == name { c.Chains[i] = &req.Data break } @@ -155,17 +166,19 @@ func deleteChain(ctx *gin.Context) { var req deleteChainRequest ctx.ShouldBindUri(&req) - if !registry.ChainRegistry().IsRegistered(req.Chain) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Chain) + + if !registry.ChainRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name))) return } - registry.ChainRegistry().Unregister(req.Chain) + registry.ChainRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { chains := c.Chains c.Chains = nil for _, s := range chains { - if s.Name == req.Chain { + if s.Name == name { continue } c.Chains = append(c.Chains, s) diff --git a/api/config_conn_limiter.go b/api/config_conn_limiter.go index 62e916a..492cd62 100644 --- a/api/config_conn_limiter.go +++ b/api/config_conn_limiter.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,21 @@ func createConnLimiter(ctx *gin.Context) { var req createConnLimiterRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required")) + return + } + req.Data.Name = name + + if registry.ConnLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } v := parser.ParseConnLimiter(&req.Data) - - if err := registry.ConnLimiterRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.ConnLimiterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create limmiter %s failed: %s", name, err.Error()))) return } @@ -87,25 +95,27 @@ func updateConnLimiter(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.ConnLimiterRegistry().IsRegistered(req.Limiter) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Limiter) + + if !registry.ConnLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name))) return } - req.Data.Name = req.Limiter + req.Data.Name = name v := parser.ParseConnLimiter(&req.Data) - registry.ConnLimiterRegistry().Unregister(req.Limiter) + registry.ConnLimiterRegistry().Unregister(name) - if err := registry.ConnLimiterRegistry().Register(req.Limiter, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.ConnLimiterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.CLimiters { - if c.CLimiters[i].Name == req.Limiter { + if c.CLimiters[i].Name == name { c.CLimiters[i] = &req.Data break } @@ -145,17 +155,19 @@ func deleteConnLimiter(ctx *gin.Context) { var req deleteConnLimiterRequest ctx.ShouldBindUri(&req) - if !registry.ConnLimiterRegistry().IsRegistered(req.Limiter) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Limiter) + + if !registry.ConnLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name))) return } - registry.ConnLimiterRegistry().Unregister(req.Limiter) + registry.ConnLimiterRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { limiteres := c.CLimiters c.CLimiters = nil for _, s := range limiteres { - if s.Name == req.Limiter { + if s.Name == name { continue } c.CLimiters = append(c.CLimiters, s) diff --git a/api/config_hop.go b/api/config_hop.go index 01f45f7..28fdb3c 100644 --- a/api/config_hop.go +++ b/api/config_hop.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/core/logger" @@ -36,19 +38,26 @@ func createHop(ctx *gin.Context) { var req createHopRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "hop name is required")) + return + } + req.Data.Name = name + + if registry.HopRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name))) return } v, err := parser.ParseHop(&req.Data, logger.Default()) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error()))) return } - if err := registry.HopRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.HopRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name))) return } @@ -93,29 +102,30 @@ func updateHop(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.HopRegistry().IsRegistered(req.Hop) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Hop) + if !registry.HopRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name))) return } - req.Data.Name = req.Hop + req.Data.Name = name v, err := parser.ParseHop(&req.Data, logger.Default()) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error()))) return } - registry.HopRegistry().Unregister(req.Hop) + registry.HopRegistry().Unregister(name) - if err := registry.HopRegistry().Register(req.Hop, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.HopRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Hops { - if c.Hops[i].Name == req.Hop { + if c.Hops[i].Name == name { c.Hops[i] = &req.Data break } @@ -155,17 +165,19 @@ func deleteHop(ctx *gin.Context) { var req deleteHopRequest ctx.ShouldBindUri(&req) - if !registry.HopRegistry().IsRegistered(req.Hop) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Hop) + + if !registry.HopRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name))) return } - registry.HopRegistry().Unregister(req.Hop) + registry.HopRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { hops := c.Hops c.Hops = nil for _, s := range hops { - if s.Name == req.Hop { + if s.Name == name { continue } c.Hops = append(c.Hops, s) diff --git a/api/config_hosts.go b/api/config_hosts.go index 7d65dbb..5688690 100644 --- a/api/config_hosts.go +++ b/api/config_hosts.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,22 @@ func createHosts(ctx *gin.Context) { var req createHostsRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "hosts name is required")) + return + } + req.Data.Name = name + + if registry.HostsRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name))) return } v := parser.ParseHostMapper(&req.Data) - if err := registry.HostsRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.HostsRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name))) return } @@ -87,25 +96,27 @@ func updateHosts(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.HostsRegistry().IsRegistered(req.Hosts) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Hosts) + + if !registry.HostsRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name))) return } - req.Data.Name = req.Hosts + req.Data.Name = name v := parser.ParseHostMapper(&req.Data) - registry.HostsRegistry().Unregister(req.Hosts) + registry.HostsRegistry().Unregister(name) - if err := registry.HostsRegistry().Register(req.Hosts, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.HostsRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Hosts { - if c.Hosts[i].Name == req.Hosts { + if c.Hosts[i].Name == name { c.Hosts[i] = &req.Data break } @@ -145,17 +156,19 @@ func deleteHosts(ctx *gin.Context) { var req deleteHostsRequest ctx.ShouldBindUri(&req) - if !registry.HostsRegistry().IsRegistered(req.Hosts) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Hosts) + + if !registry.HostsRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name))) return } - registry.HostsRegistry().Unregister(req.Hosts) + registry.HostsRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { hosts := c.Hosts c.Hosts = nil for _, s := range hosts { - if s.Name == req.Hosts { + if s.Name == name { continue } c.Hosts = append(c.Hosts, s) diff --git a/api/config_ingress.go b/api/config_ingress.go index 5258ed5..a6bb8f7 100644 --- a/api/config_ingress.go +++ b/api/config_ingress.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,22 @@ func createIngress(ctx *gin.Context) { var req createIngressRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "ingress name is required")) + return + } + req.Data.Name = name + + if registry.IngressRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name))) return } v := parser.ParseIngress(&req.Data) - if err := registry.IngressRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.IngressRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name))) return } @@ -87,25 +96,27 @@ func updateIngress(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.IngressRegistry().IsRegistered(req.Ingress) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Ingress) + + if !registry.IngressRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name))) return } - req.Data.Name = req.Ingress + req.Data.Name = name v := parser.ParseIngress(&req.Data) - registry.IngressRegistry().Unregister(req.Ingress) + registry.IngressRegistry().Unregister(name) - if err := registry.IngressRegistry().Register(req.Ingress, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.IngressRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Ingresses { - if c.Ingresses[i].Name == req.Ingress { + if c.Ingresses[i].Name == name { c.Ingresses[i] = &req.Data break } @@ -145,17 +156,19 @@ func deleteIngress(ctx *gin.Context) { var req deleteIngressRequest ctx.ShouldBindUri(&req) - if !registry.IngressRegistry().IsRegistered(req.Ingress) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Ingress) + + if !registry.IngressRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name))) return } - registry.IngressRegistry().Unregister(req.Ingress) + registry.IngressRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { ingresses := c.Ingresses c.Ingresses = nil for _, s := range ingresses { - if s.Name == req.Ingress { + if s.Name == name { continue } c.Ingresses = append(c.Ingresses, s) diff --git a/api/config_limiter.go b/api/config_limiter.go index 73ecaa7..471b54a 100644 --- a/api/config_limiter.go +++ b/api/config_limiter.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,22 @@ func createLimiter(ctx *gin.Context) { var req createLimiterRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required")) + return + } + req.Data.Name = name + + if registry.TrafficLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } v := parser.ParseTrafficLimiter(&req.Data) - if err := registry.TrafficLimiterRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } @@ -87,25 +96,27 @@ func updateLimiter(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.TrafficLimiterRegistry().IsRegistered(req.Limiter) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Limiter) + + if !registry.TrafficLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name))) return } - req.Data.Name = req.Limiter + req.Data.Name = name v := parser.ParseTrafficLimiter(&req.Data) - registry.TrafficLimiterRegistry().Unregister(req.Limiter) + registry.TrafficLimiterRegistry().Unregister(name) - if err := registry.TrafficLimiterRegistry().Register(req.Limiter, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Limiters { - if c.Limiters[i].Name == req.Limiter { + if c.Limiters[i].Name == name { c.Limiters[i] = &req.Data break } @@ -145,17 +156,19 @@ func deleteLimiter(ctx *gin.Context) { var req deleteLimiterRequest ctx.ShouldBindUri(&req) - if !registry.TrafficLimiterRegistry().IsRegistered(req.Limiter) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Limiter) + + if !registry.TrafficLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name))) return } - registry.TrafficLimiterRegistry().Unregister(req.Limiter) + registry.TrafficLimiterRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { limiteres := c.Limiters c.Limiters = nil for _, s := range limiteres { - if s.Name == req.Limiter { + if s.Name == name { continue } c.Limiters = append(c.Limiters, s) diff --git a/api/config_observer.go b/api/config_observer.go new file mode 100644 index 0000000..d3024c4 --- /dev/null +++ b/api/config_observer.go @@ -0,0 +1,182 @@ +package api + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-gost/x/config" + parser "github.com/go-gost/x/config/parsing/observer" + "github.com/go-gost/x/registry" +) + +// swagger:parameters createObserverRequest +type createObserverRequest struct { + // in: body + Data config.ObserverConfig `json:"data"` +} + +// successful operation. +// swagger:response createObserverResponse +type createObserverResponse struct { + Data Response +} + +func createObserver(ctx *gin.Context) { + // swagger:route POST /config/observers Observer createObserverRequest + // + // Create a new observer, the name of the observer must be unique in observer list. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: createObserverResponse + + var req createObserverRequest + ctx.ShouldBindJSON(&req.Data) + + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "observer name is required")) + return + } + req.Data.Name = name + + if registry.ObserverRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("observer %s already exists", name))) + return + } + + v := parser.ParseObserver(&req.Data) + + if err := registry.ObserverRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("observer %s already exists", name))) + return + } + + config.OnUpdate(func(c *config.Config) error { + c.Observers = append(c.Observers, &req.Data) + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters updateObserverRequest +type updateObserverRequest struct { + // in: path + // required: true + Observer string `uri:"observer" json:"observer"` + // in: body + Data config.ObserverConfig `json:"data"` +} + +// successful operation. +// swagger:response updateObserverResponse +type updateObserverResponse struct { + Data Response +} + +func updateObserver(ctx *gin.Context) { + // swagger:route PUT /config/observers/{observer} Observer updateObserverRequest + // + // Update observer by name, the observer must already exist. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: updateObserverResponse + + var req updateObserverRequest + ctx.ShouldBindUri(&req) + ctx.ShouldBindJSON(&req.Data) + + name := strings.TrimSpace(req.Observer) + + if !registry.ObserverRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("observer %s not found", name))) + return + } + + req.Data.Name = name + + v := parser.ParseObserver(&req.Data) + + registry.ObserverRegistry().Unregister(name) + + if err := registry.ObserverRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("observer %s already exists", name))) + return + } + + config.OnUpdate(func(c *config.Config) error { + for i := range c.Observers { + if c.Observers[i].Name == name { + c.Observers[i] = &req.Data + break + } + } + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters deleteObserverRequest +type deleteObserverRequest struct { + // in: path + // required: true + Observer string `uri:"observer" json:"observer"` +} + +// successful operation. +// swagger:response deleteObserverResponse +type deleteObserverResponse struct { + Data Response +} + +func deleteObserver(ctx *gin.Context) { + // swagger:route DELETE /config/observers/{observer} Observer deleteObserverRequest + // + // Delete observer by name. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: deleteObserverResponse + + var req deleteObserverRequest + ctx.ShouldBindUri(&req) + + name := strings.TrimSpace(req.Observer) + + if !registry.ObserverRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("observer %s not found", name))) + return + } + registry.ObserverRegistry().Unregister(name) + + config.OnUpdate(func(c *config.Config) error { + observers := c.Observers + c.Observers = nil + for _, s := range observers { + if s.Name == name { + continue + } + c.Observers = append(c.Observers, s) + } + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} diff --git a/api/config_rate_limiter.go b/api/config_rate_limiter.go index e491e6e..15cb0e4 100644 --- a/api/config_rate_limiter.go +++ b/api/config_rate_limiter.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,22 @@ func createRateLimiter(ctx *gin.Context) { var req createRateLimiterRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required")) + return + } + req.Data.Name = name + + if registry.RateLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } v := parser.ParseRateLimiter(&req.Data) - if err := registry.RateLimiterRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.RateLimiterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } @@ -87,25 +96,27 @@ func updateRateLimiter(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Limiter) + + if !registry.RateLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name))) return } - req.Data.Name = req.Limiter + req.Data.Name = name v := parser.ParseRateLimiter(&req.Data) - registry.RateLimiterRegistry().Unregister(req.Limiter) + registry.RateLimiterRegistry().Unregister(name) - if err := registry.RateLimiterRegistry().Register(req.Limiter, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.RateLimiterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.RLimiters { - if c.RLimiters[i].Name == req.Limiter { + if c.RLimiters[i].Name == name { c.RLimiters[i] = &req.Data break } @@ -145,17 +156,19 @@ func deleteRateLimiter(ctx *gin.Context) { var req deleteRateLimiterRequest ctx.ShouldBindUri(&req) - if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Limiter) + + if !registry.RateLimiterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name))) return } - registry.RateLimiterRegistry().Unregister(req.Limiter) + registry.RateLimiterRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { limiteres := c.RLimiters c.RLimiters = nil for _, s := range limiteres { - if s.Name == req.Limiter { + if s.Name == name { continue } c.RLimiters = append(c.RLimiters, s) diff --git a/api/config_recorder.go b/api/config_recorder.go new file mode 100644 index 0000000..189f2c2 --- /dev/null +++ b/api/config_recorder.go @@ -0,0 +1,181 @@ +package api + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-gost/x/config" + parser "github.com/go-gost/x/config/parsing/recorder" + "github.com/go-gost/x/registry" +) + +// swagger:parameters createRecorderRequest +type createRecorderRequest struct { + // in: body + Data config.RecorderConfig `json:"data"` +} + +// successful operation. +// swagger:response createRecorderResponse +type createRecorderResponse struct { + Data Response +} + +func createRecorder(ctx *gin.Context) { + // swagger:route POST /config/recorders Recorder createRecorderRequest + // + // Create a new recorder, the name of the recorder must be unique in recorder list. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: createRecorderResponse + + var req createRecorderRequest + ctx.ShouldBindJSON(&req.Data) + + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "recorder name is required")) + return + } + req.Data.Name = name + + if registry.RecorderRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("recorder %s already exists", name))) + return + } + v := parser.ParseRecorder(&req.Data) + + if err := registry.RecorderRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("recorder %s already exists", name))) + return + } + + config.OnUpdate(func(c *config.Config) error { + c.Recorders = append(c.Recorders, &req.Data) + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters updateRecorderRequest +type updateRecorderRequest struct { + // in: path + // required: true + Recorder string `uri:"recorder" json:"recorder"` + // in: body + Data config.RecorderConfig `json:"data"` +} + +// successful operation. +// swagger:response updateRecorderResponse +type updateRecorderResponse struct { + Data Response +} + +func updateRecorder(ctx *gin.Context) { + // swagger:route PUT /config/recorders/{recorder} Recorder updateRecorderRequest + // + // Update recorder by name, the recorder must already exist. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: updateRecorderResponse + + var req updateRecorderRequest + ctx.ShouldBindUri(&req) + ctx.ShouldBindJSON(&req.Data) + + name := strings.TrimSpace(req.Recorder) + + if !registry.RecorderRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("recorder %s not found", name))) + return + } + + req.Data.Name = name + + v := parser.ParseRecorder(&req.Data) + + registry.RecorderRegistry().Unregister(name) + + if err := registry.RecorderRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("recorder %s already exists", name))) + return + } + + config.OnUpdate(func(c *config.Config) error { + for i := range c.Recorders { + if c.Recorders[i].Name == name { + c.Recorders[i] = &req.Data + break + } + } + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters deleteRecorderRequest +type deleteRecorderRequest struct { + // in: path + // required: true + Recorder string `uri:"recorder" json:"recorder"` +} + +// successful operation. +// swagger:response deleteRecorderResponse +type deleteRecorderResponse struct { + Data Response +} + +func deleteRecorder(ctx *gin.Context) { + // swagger:route DELETE /config/recorders/{recorder} Recorder deleteRecorderRequest + // + // Delete recorder by name. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: deleteRecorderResponse + + var req deleteRecorderRequest + ctx.ShouldBindUri(&req) + + name := strings.TrimSpace(req.Recorder) + + if !registry.RecorderRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("recorder %s not found", name))) + return + } + registry.RecorderRegistry().Unregister(name) + + config.OnUpdate(func(c *config.Config) error { + recorders := c.Recorders + c.Recorders = nil + for _, s := range recorders { + if s.Name == name { + continue + } + c.Recorders = append(c.Recorders, s) + } + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} diff --git a/api/config_resolver.go b/api/config_resolver.go index 485ce49..f46988c 100644 --- a/api/config_resolver.go +++ b/api/config_resolver.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,19 +37,26 @@ func createResolver(ctx *gin.Context) { var req createResolverRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "resolver name is required")) + return + } + req.Data.Name = name + + if registry.ResolverRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name))) return } v, err := parser.ParseResolver(&req.Data) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error()))) return } - if err := registry.ResolverRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.ResolverRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name))) return } @@ -91,29 +100,31 @@ func updateResolver(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.ResolverRegistry().IsRegistered(req.Resolver) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Resolver) + + if !registry.ResolverRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name))) return } - req.Data.Name = req.Resolver + req.Data.Name = name v, err := parser.ParseResolver(&req.Data) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error()))) return } - registry.ResolverRegistry().Unregister(req.Resolver) + registry.ResolverRegistry().Unregister(name) - if err := registry.ResolverRegistry().Register(req.Resolver, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.ResolverRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Resolvers { - if c.Resolvers[i].Name == req.Resolver { + if c.Resolvers[i].Name == name { c.Resolvers[i] = &req.Data break } @@ -153,17 +164,19 @@ func deleteResolver(ctx *gin.Context) { var req deleteResolverRequest ctx.ShouldBindUri(&req) - if !registry.ResolverRegistry().IsRegistered(req.Resolver) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Resolver) + + if !registry.ResolverRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name))) return } - registry.ResolverRegistry().Unregister(req.Resolver) + registry.ResolverRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { resolvers := c.Resolvers c.Resolvers = nil for _, s := range resolvers { - if s.Name == req.Resolver { + if s.Name == name { continue } c.Resolvers = append(c.Resolvers, s) diff --git a/api/config_router.go b/api/config_router.go index 6d0c624..5ad84f5 100644 --- a/api/config_router.go +++ b/api/config_router.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,15 +37,22 @@ func createRouter(ctx *gin.Context) { var req createRouterRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "router name is required")) + return + } + req.Data.Name = name + + if registry.RouterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("router %s already exists", name))) return } v := parser.ParseRouter(&req.Data) - if err := registry.RouterRegistry().Register(req.Data.Name, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.RouterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("router %s already exists", name))) return } @@ -87,25 +96,27 @@ func updateRouter(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - if !registry.RouterRegistry().IsRegistered(req.Router) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Router) + + if !registry.RouterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("router %s not found", name))) return } - req.Data.Name = req.Router + req.Data.Name = name v := parser.ParseRouter(&req.Data) - registry.RouterRegistry().Unregister(req.Router) + registry.RouterRegistry().Unregister(name) - if err := registry.RouterRegistry().Register(req.Router, v); err != nil { - writeError(ctx, ErrDup) + if err := registry.RouterRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("router %s already exists", name))) return } config.OnUpdate(func(c *config.Config) error { for i := range c.Routers { - if c.Routers[i].Name == req.Router { + if c.Routers[i].Name == name { c.Routers[i] = &req.Data break } @@ -145,17 +156,19 @@ func deleteRouter(ctx *gin.Context) { var req deleteRouterRequest ctx.ShouldBindUri(&req) - if !registry.RouterRegistry().IsRegistered(req.Router) { - writeError(ctx, ErrNotFound) + name := strings.TrimSpace(req.Router) + + if !registry.RouterRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("router %s not found", name))) return } - registry.RouterRegistry().Unregister(req.Router) + registry.RouterRegistry().Unregister(name) config.OnUpdate(func(c *config.Config) error { routeres := c.Routers c.Routers = nil for _, s := range routeres { - if s.Name == req.Router { + if s.Name == name { continue } c.Routers = append(c.Routers, s) diff --git a/api/config_sd.go b/api/config_sd.go new file mode 100644 index 0000000..944eeb1 --- /dev/null +++ b/api/config_sd.go @@ -0,0 +1,182 @@ +package api + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-gost/x/config" + parser "github.com/go-gost/x/config/parsing/sd" + "github.com/go-gost/x/registry" +) + +// swagger:parameters createSDRequest +type createSDRequest struct { + // in: body + Data config.SDConfig `json:"data"` +} + +// successful operation. +// swagger:response createSDResponse +type createSDResponse struct { + Data Response +} + +func createSD(ctx *gin.Context) { + // swagger:route POST /config/sds SD createSDRequest + // + // Create a new SD, the name of the SD must be unique in SD list. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: createSDResponse + + var req createSDRequest + ctx.ShouldBindJSON(&req.Data) + + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "sd name is required")) + return + } + req.Data.Name = name + + if registry.SDRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("sd %s already exists", name))) + return + } + + v := parser.ParseSD(&req.Data) + + if err := registry.SDRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("sd %s already exists", name))) + return + } + + config.OnUpdate(func(c *config.Config) error { + c.SDs = append(c.SDs, &req.Data) + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters updateSDRequest +type updateSDRequest struct { + // in: path + // required: true + SD string `uri:"sd" json:"sd"` + // in: body + Data config.SDConfig `json:"data"` +} + +// successful operation. +// swagger:response updateSDResponse +type updateSDResponse struct { + Data Response +} + +func updateSD(ctx *gin.Context) { + // swagger:route PUT /config/sds/{sd} SD updateSDRequest + // + // Update SD by name, the SD must already exist. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: updateSDResponse + + var req updateSDRequest + ctx.ShouldBindUri(&req) + ctx.ShouldBindJSON(&req.Data) + + name := strings.TrimSpace(req.SD) + + if !registry.SDRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("sd %s not found", name))) + return + } + + req.Data.Name = name + + v := parser.ParseSD(&req.Data) + + registry.SDRegistry().Unregister(name) + + if err := registry.SDRegistry().Register(name, v); err != nil { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("sd %s already exists", name))) + return + } + + config.OnUpdate(func(c *config.Config) error { + for i := range c.SDs { + if c.SDs[i].Name == name { + c.SDs[i] = &req.Data + break + } + } + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters deleteSDRequest +type deleteSDRequest struct { + // in: path + // required: true + SD string `uri:"sd" json:"sd"` +} + +// successful operation. +// swagger:response deleteSDResponse +type deleteSDResponse struct { + Data Response +} + +func deleteSD(ctx *gin.Context) { + // swagger:route DELETE /config/sds/{sd} SD deleteSDRequest + // + // Delete SD by name. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: deleteSDResponse + + var req deleteSDRequest + ctx.ShouldBindUri(&req) + + name := strings.TrimSpace(req.SD) + + if !registry.SDRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("sd %s not found", name))) + return + } + registry.SDRegistry().Unregister(name) + + config.OnUpdate(func(c *config.Config) error { + sds := c.SDs + c.SDs = nil + for _, s := range sds { + if s.Name == name { + continue + } + c.SDs = append(c.SDs, s) + } + return nil + }) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} diff --git a/api/config_service.go b/api/config_service.go index f174042..d8c349b 100644 --- a/api/config_service.go +++ b/api/config_service.go @@ -1,7 +1,9 @@ package api import ( + "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/x/config" @@ -35,25 +37,27 @@ func createService(ctx *gin.Context) { var req createServiceRequest ctx.ShouldBindJSON(&req.Data) - if req.Data.Name == "" { - writeError(ctx, ErrInvalid) + name := strings.TrimSpace(req.Data.Name) + if name == "" { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required")) return } + req.Data.Name = name - if registry.ServiceRegistry().IsRegistered(req.Data.Name) { - writeError(ctx, ErrDup) + if registry.ServiceRegistry().IsRegistered(name) { + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name))) return } svc, err := parser.ParseService(&req.Data) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error()))) return } - if err := registry.ServiceRegistry().Register(req.Data.Name, svc); err != nil { + if err := registry.ServiceRegistry().Register(name, svc); err != nil { svc.Close() - writeError(ctx, ErrDup) + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name))) return } @@ -99,26 +103,28 @@ func updateService(ctx *gin.Context) { ctx.ShouldBindUri(&req) ctx.ShouldBindJSON(&req.Data) - old := registry.ServiceRegistry().Get(req.Service) + name := strings.TrimSpace(req.Service) + + old := registry.ServiceRegistry().Get(name) if old == nil { - writeError(ctx, ErrNotFound) + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name))) return } old.Close() - req.Data.Name = req.Service + req.Data.Name = name svc, err := parser.ParseService(&req.Data) if err != nil { - writeError(ctx, ErrCreate) + writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error()))) return } - registry.ServiceRegistry().Unregister(req.Service) + registry.ServiceRegistry().Unregister(name) - if err := registry.ServiceRegistry().Register(req.Service, svc); err != nil { + if err := registry.ServiceRegistry().Register(name, svc); err != nil { svc.Close() - writeError(ctx, ErrDup) + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name))) return } @@ -126,7 +132,7 @@ func updateService(ctx *gin.Context) { config.OnUpdate(func(c *config.Config) error { for i := range c.Services { - if c.Services[i].Name == req.Service { + if c.Services[i].Name == name { c.Services[i] = &req.Data break } @@ -166,20 +172,22 @@ func deleteService(ctx *gin.Context) { var req deleteServiceRequest ctx.ShouldBindUri(&req) - svc := registry.ServiceRegistry().Get(req.Service) + name := strings.TrimSpace(req.Service) + + svc := registry.ServiceRegistry().Get(name) if svc == nil { - writeError(ctx, ErrNotFound) + writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name))) return } - registry.ServiceRegistry().Unregister(req.Service) + registry.ServiceRegistry().Unregister(name) svc.Close() config.OnUpdate(func(c *config.Config) error { services := c.Services c.Services = nil for _, s := range services { - if s.Name == req.Service { + if s.Name == name { continue } c.Services = append(c.Services, s) diff --git a/api/error.go b/api/error.go index 8fdc3f9..be89fc3 100644 --- a/api/error.go +++ b/api/error.go @@ -15,6 +15,16 @@ var ( ErrSave = &Error{statusCode: http.StatusInternalServerError, Code: 40005, Msg: "save config failed"} ) +type ErrCode int + +const ( + ErrCodeInvalid = 40001 + ErrCodeDup = 40002 + ErrCodeFailed = 40003 + ErrCodeNotFound = 40004 + ErrCodeSaveConfigFailed = 40005 +) + // Error is an api error. type Error struct { statusCode int @@ -22,6 +32,14 @@ type Error struct { Msg string `json:"msg"` } +func NewError(status, code int, msg string) error { + return &Error{ + statusCode: status, + Code: code, + Msg: msg, + } +} + func (e *Error) Error() string { b, _ := json.Marshal(e) return string(b) diff --git a/api/middleware.go b/api/middleware.go index 27202c3..8a49037 100644 --- a/api/middleware.go +++ b/api/middleware.go @@ -37,7 +37,11 @@ func mwBasicAuth(auther auth.Authenticator) gin.HandlerFunc { u, p, _ := c.Request.BasicAuth() if _, ok := auther.Authenticate(c, u, p); !ok { c.Writer.Header().Set("WWW-Authenticate", "Basic") - c.AbortWithStatus(http.StatusUnauthorized) + c.JSON(http.StatusUnauthorized, Response{ + Code: http.StatusUnauthorized, + Msg: "Unauthorized", + }) + c.Abort() } } } diff --git a/api/swagger.yaml b/api/swagger.yaml index dd00c58..e120dfb 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -188,8 +188,18 @@ definitions: x-go-name: Limiters log: $ref: '#/definitions/LogConfig' + loggers: + items: + $ref: '#/definitions/LoggerConfig' + type: array + x-go-name: Loggers metrics: $ref: '#/definitions/MetricsConfig' + observers: + items: + $ref: '#/definitions/ObserverConfig' + type: array + x-go-name: Observers profiling: $ref: '#/definitions/ProfilingConfig' recorders: @@ -296,11 +306,18 @@ definitions: type: string type: array x-go-name: Bypasses + filter: + $ref: '#/definitions/NodeFilterConfig' host: + description: DEPRECATED by filter.host type: string x-go-name: Host http: $ref: '#/definitions/HTTPNodeConfig' + metadata: + additionalProperties: {} + type: object + x-go-name: Metadata name: type: string x-go-name: Name @@ -308,9 +325,11 @@ definitions: type: string x-go-name: Network path: + description: DEPRECATED by filter.path type: string x-go-name: Path protocol: + description: DEPRECATED by filter.protocol type: string x-go-name: Protocol tls: @@ -319,7 +338,12 @@ definitions: x-go-package: github.com/go-gost/x/config ForwarderConfig: properties: + hop: + description: the referenced hop name + type: string + x-go-name: Hop name: + description: DEPRECATED by hop field type: string x-go-name: Name nodes: @@ -342,6 +366,8 @@ definitions: x-go-package: github.com/go-gost/x/config HTTPNodeConfig: properties: + auth: + $ref: '#/definitions/AuthConfig' header: additionalProperties: type: string @@ -350,6 +376,11 @@ definitions: host: type: string x-go-name: Host + rewrite: + items: + $ref: '#/definitions/HTTPURLRewriteConfig' + type: array + x-go-name: Rewrite type: object x-go-package: github.com/go-gost/x/config HTTPRecorder: @@ -361,6 +392,14 @@ definitions: x-go-name: URL type: object x-go-package: github.com/go-gost/x/config + HTTPURLRewriteConfig: + properties: + Match: + type: string + Replacement: + type: string + type: object + x-go-package: github.com/go-gost/x/config HandlerConfig: properties: auth: @@ -385,6 +424,9 @@ definitions: additionalProperties: {} type: object x-go-name: Metadata + observer: + type: string + x-go-name: Observer retries: format: int64 type: integer @@ -615,6 +657,15 @@ definitions: x-go-name: MaxSize type: object x-go-package: github.com/go-gost/x/config + LoggerConfig: + properties: + log: + $ref: '#/definitions/LogConfig' + name: + type: string + x-go-name: Name + type: object + x-go-package: github.com/go-gost/x/config MetricsConfig: properties: addr: @@ -664,8 +715,6 @@ definitions: addr: type: string x-go-name: Addr - auth: - $ref: '#/definitions/AuthConfig' bypass: type: string x-go-name: Bypass @@ -678,9 +727,8 @@ definitions: $ref: '#/definitions/ConnectorConfig' dialer: $ref: '#/definitions/DialerConfig' - host: - type: string - x-go-name: Host + filter: + $ref: '#/definitions/NodeFilterConfig' hosts: type: string x-go-name: Hosts @@ -699,12 +747,6 @@ definitions: network: type: string x-go-name: Network - path: - type: string - x-go-name: Path - protocol: - type: string - x-go-name: Protocol resolver: type: string x-go-name: Resolver @@ -714,6 +756,28 @@ definitions: $ref: '#/definitions/TLSNodeConfig' type: object x-go-package: github.com/go-gost/x/config + NodeFilterConfig: + properties: + host: + type: string + x-go-name: Host + path: + type: string + x-go-name: Path + protocol: + type: string + x-go-name: Protocol + type: object + x-go-package: github.com/go-gost/x/config + ObserverConfig: + properties: + name: + type: string + x-go-name: Name + plugin: + $ref: '#/definitions/PluginConfig' + type: object + x-go-package: github.com/go-gost/x/config PluginConfig: properties: addr: @@ -927,6 +991,14 @@ definitions: x-go-name: Limiter listener: $ref: '#/definitions/ListenerConfig' + logger: + type: string + x-go-name: Logger + loggers: + items: + type: string + type: array + x-go-name: Loggers metadata: additionalProperties: {} type: object @@ -934,6 +1006,9 @@ definitions: name: type: string x-go-name: Name + observer: + type: string + x-go-name: Observer recorders: items: $ref: '#/definitions/RecorderObject' @@ -947,6 +1022,61 @@ definitions: x-go-name: RLimiter sockopts: $ref: '#/definitions/SockOptsConfig' + status: + $ref: '#/definitions/ServiceStatus' + type: object + x-go-package: github.com/go-gost/x/config + ServiceEvent: + properties: + msg: + type: string + x-go-name: Msg + time: + format: int64 + type: integer + x-go-name: Time + type: object + x-go-package: github.com/go-gost/x/config + ServiceStats: + properties: + currentConns: + format: uint64 + type: integer + x-go-name: CurrentConns + inputBytes: + format: uint64 + type: integer + x-go-name: InputBytes + outputBytes: + format: uint64 + type: integer + x-go-name: OutputBytes + totalConns: + format: uint64 + type: integer + x-go-name: TotalConns + totalErrs: + format: uint64 + type: integer + x-go-name: TotalErrs + type: object + x-go-package: github.com/go-gost/x/config + ServiceStatus: + properties: + createTime: + format: int64 + type: integer + x-go-name: CreateTime + events: + items: + $ref: '#/definitions/ServiceEvent' + type: array + x-go-name: Events + state: + type: string + x-go-name: State + stats: + $ref: '#/definitions/ServiceStats' type: object x-go-package: github.com/go-gost/x/config SockOptsConfig: @@ -1588,6 +1718,122 @@ paths: summary: Update limiter by name, the limiter must already exist. tags: - Limiter + /config/observers: + post: + operationId: createObserverRequest + parameters: + - in: body + name: data + schema: + $ref: '#/definitions/ObserverConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/createObserverResponse' + security: + - basicAuth: + - '[]' + summary: Create a new observer, the name of the observer must be unique in observer list. + tags: + - Observer + /config/observers/{observer}: + delete: + operationId: deleteObserverRequest + parameters: + - in: path + name: observer + required: true + type: string + x-go-name: Observer + responses: + "200": + $ref: '#/responses/deleteObserverResponse' + security: + - basicAuth: + - '[]' + summary: Delete observer by name. + tags: + - Observer + put: + operationId: updateObserverRequest + parameters: + - in: path + name: observer + required: true + type: string + x-go-name: Observer + - in: body + name: data + schema: + $ref: '#/definitions/ObserverConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/updateObserverResponse' + security: + - basicAuth: + - '[]' + summary: Update observer by name, the observer must already exist. + tags: + - Observer + /config/recorders: + post: + operationId: createRecorderRequest + parameters: + - in: body + name: data + schema: + $ref: '#/definitions/RecorderConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/createRecorderResponse' + security: + - basicAuth: + - '[]' + summary: Create a new recorder, the name of the recorder must be unique in recorder list. + tags: + - Recorder + /config/recorders/{recorder}: + delete: + operationId: deleteRecorderRequest + parameters: + - in: path + name: recorder + required: true + type: string + x-go-name: Recorder + responses: + "200": + $ref: '#/responses/deleteRecorderResponse' + security: + - basicAuth: + - '[]' + summary: Delete recorder by name. + tags: + - Recorder + put: + operationId: updateRecorderRequest + parameters: + - in: path + name: recorder + required: true + type: string + x-go-name: Recorder + - in: body + name: data + schema: + $ref: '#/definitions/RecorderConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/updateRecorderResponse' + security: + - basicAuth: + - '[]' + summary: Update recorder by name, the recorder must already exist. + tags: + - Recorder /config/resolvers: post: operationId: createResolverRequest @@ -1762,6 +2008,64 @@ paths: summary: Update router by name, the router must already exist. tags: - Router + /config/sds: + post: + operationId: createSDRequest + parameters: + - in: body + name: data + schema: + $ref: '#/definitions/SDConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/createSDResponse' + security: + - basicAuth: + - '[]' + summary: Create a new SD, the name of the SD must be unique in SD list. + tags: + - SD + /config/sds/{sd}: + delete: + operationId: deleteSDRequest + parameters: + - in: path + name: sd + required: true + type: string + x-go-name: SD + responses: + "200": + $ref: '#/responses/deleteSDResponse' + security: + - basicAuth: + - '[]' + summary: Delete SD by name. + tags: + - SD + put: + operationId: updateSDRequest + parameters: + - in: path + name: sd + required: true + type: string + x-go-name: SD + - in: body + name: data + schema: + $ref: '#/definitions/SDConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/updateSDResponse' + security: + - basicAuth: + - '[]' + summary: Update SD by name, the SD must already exist. + tags: + - SD /config/services: post: operationId: createServiceRequest @@ -1877,12 +2181,24 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + createObserverResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' createRateLimiterResponse: description: successful operation. headers: Data: {} schema: $ref: '#/definitions/Response' + createRecorderResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' createResolverResponse: description: successful operation. headers: @@ -1895,6 +2211,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + createSDResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' createServiceResponse: description: successful operation. headers: @@ -1955,12 +2277,24 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + deleteObserverResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' deleteRateLimiterResponse: description: successful operation. headers: Data: {} schema: $ref: '#/definitions/Response' + deleteRecorderResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' deleteResolverResponse: description: successful operation. headers: @@ -1973,6 +2307,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + deleteSDResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' deleteServiceResponse: description: successful operation. headers: @@ -2045,12 +2385,24 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + updateObserverResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' updateRateLimiterResponse: description: successful operation. headers: Data: {} schema: $ref: '#/definitions/Response' + updateRecorderResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' updateResolverResponse: description: successful operation. headers: @@ -2063,6 +2415,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + updateSDResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' updateServiceResponse: description: successful operation. headers: diff --git a/bypass/bypass.go b/bypass/bypass.go index ea4f131..3da5cb7 100644 --- a/bypass/bypass.go +++ b/bypass/bypass.go @@ -235,11 +235,17 @@ func (bp *localBypass) Contains(ctx context.Context, network, addr string, opts b := !bp.options.whitelist && matched || bp.options.whitelist && !matched if b { - bp.options.logger.Debugf("bypass: %s", addr) + bp.options.logger.Debugf("bypass: %s, whitelist: %t", addr, bp.options.whitelist) + } else { + bp.options.logger.Debugf("pass: %s, whitelist: %t", addr, bp.options.whitelist) } return b } +func (p *localBypass) IsWhitelist() bool { + return p.options.whitelist +} + func (bp *localBypass) parseLine(s string) string { if n := strings.IndexByte(s, '#'); n >= 0 { s = s[:n] diff --git a/bypass/plugin/grpc.go b/bypass/plugin/grpc.go index 37ad21f..d4d7250 100644 --- a/bypass/plugin/grpc.go +++ b/bypass/plugin/grpc.go @@ -75,3 +75,7 @@ func (p *grpcPlugin) Close() error { } return nil } + +func (p *grpcPlugin) IsWhitelist() bool { + return false +} diff --git a/bypass/plugin/http.go b/bypass/plugin/http.go index 2035251..17792fe 100644 --- a/bypass/plugin/http.go +++ b/bypass/plugin/http.go @@ -96,3 +96,7 @@ func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts .. } return res.OK } + +func (p *httpPlugin) IsWhitelist() bool { + return false +} diff --git a/chain/chain.go b/chain/chain.go index 4a78fcb..458045a 100644 --- a/chain/chain.go +++ b/chain/chain.go @@ -104,7 +104,7 @@ func (c *Chain) Route(ctx context.Context, network, address string, opts ...chai tr.Options().Route = rt node = node.Copy() node.Options().Transport = tr - rt = NewRoute() + rt = NewRoute(ChainRouteOption(c)) } rt.addNode(node) diff --git a/chain/route.go b/chain/route.go index 4e176f1..c915a9b 100644 --- a/chain/route.go +++ b/chain/route.go @@ -129,10 +129,11 @@ func (r *route) connect(ctx context.Context, logger logger.Logger) (conn net.Con metrics.Labels{"chain": name, "node": node.Name}); v != nil { v.Inc() } - } else { - if marker != nil { - marker.Reset() - } + return + } + + if marker != nil { + marker.Reset() } } }() diff --git a/config/config.go b/config/config.go index 389bc95..c97dfa8 100644 --- a/config/config.go +++ b/config/config.go @@ -297,9 +297,9 @@ type RedisRecorder struct { } type RecorderObject struct { - Name string `json:"name"` - Record string `json:"record"` - Metadata map[string]any + Name string `json:"name"` + Record string `json:"record"` + Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"` } type LimiterConfig struct { @@ -343,25 +343,32 @@ type HandlerConfig struct { } type ForwarderConfig struct { - Name string `yaml:",omitempty" json:"name,omitempty"` + // DEPRECATED by hop field + Name string `yaml:",omitempty" json:"name,omitempty"` + // the referenced hop name + Hop string `yaml:",omitempty" json:"hop,omitempty"` Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"` Nodes []*ForwardNodeConfig `json:"nodes"` } type ForwardNodeConfig struct { - Name string `yaml:",omitempty" json:"name,omitempty"` - Addr string `yaml:",omitempty" json:"addr,omitempty"` - Host string `yaml:",omitempty" json:"host,omitempty"` - Network string `yaml:",omitempty" json:"network,omitempty"` - Protocol string `yaml:",omitempty" json:"protocol,omitempty"` - Path string `yaml:",omitempty" json:"path,omitempty"` - Bypass string `yaml:",omitempty" json:"bypass,omitempty"` - Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"` - HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"` - TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"` - // DEPRECATED by HTTP.Auth - Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"` - Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"` + Name string `yaml:",omitempty" json:"name,omitempty"` + Addr string `yaml:",omitempty" json:"addr,omitempty"` + Network string `yaml:",omitempty" json:"network,omitempty"` + Bypass string `yaml:",omitempty" json:"bypass,omitempty"` + Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"` + // DEPRECATED by filter.protocol + Protocol string `yaml:",omitempty" json:"protocol,omitempty"` + // DEPRECATED by filter.host + Host string `yaml:",omitempty" json:"host,omitempty"` + // DEPRECATED by filter.path + Path string `yaml:",omitempty" json:"path,omitempty"` + // DEPRECATED by http.auth + Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"` + Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"` + HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"` + TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"` + Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"` } type HTTPURLRewriteConfig struct { @@ -369,11 +376,17 @@ type HTTPURLRewriteConfig struct { Replacement string } +type NodeFilterConfig struct { + Host string `yaml:",omitempty" json:"host,omitempty"` + Protocol string `yaml:",omitempty" json:"protocol,omitempty"` + Path string `yaml:",omitempty" json:"path,omitempty"` +} + type HTTPNodeConfig struct { Host string `yaml:",omitempty" json:"host,omitempty"` Header map[string]string `yaml:",omitempty" json:"header,omitempty"` - Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"` Rewrite []HTTPURLRewriteConfig `yaml:",omitempty" json:"rewrite,omitempty"` + Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"` } type TLSNodeConfig struct { @@ -477,24 +490,21 @@ type HopConfig struct { } type NodeConfig struct { - Name string `json:"name"` - Addr string `yaml:",omitempty" json:"addr,omitempty"` - Host string `yaml:",omitempty" json:"host,omitempty"` - Network string `yaml:",omitempty" json:"network,omitempty"` - Protocol string `yaml:",omitempty" json:"protocol,omitempty"` - Path string `yaml:",omitempty" json:"path,omitempty"` - Interface string `yaml:",omitempty" json:"interface,omitempty"` - SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"` - Bypass string `yaml:",omitempty" json:"bypass,omitempty"` - Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"` - Resolver string `yaml:",omitempty" json:"resolver,omitempty"` - Hosts string `yaml:",omitempty" json:"hosts,omitempty"` - Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"` - Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"` - HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"` - TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"` - Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"` - Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"` + Name string `json:"name"` + Addr string `yaml:",omitempty" json:"addr,omitempty"` + Network string `yaml:",omitempty" json:"network,omitempty"` + Bypass string `yaml:",omitempty" json:"bypass,omitempty"` + Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"` + Resolver string `yaml:",omitempty" json:"resolver,omitempty"` + Hosts string `yaml:",omitempty" json:"hosts,omitempty"` + Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"` + Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"` + Interface string `yaml:",omitempty" json:"interface,omitempty"` + SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"` + Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"` + HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"` + TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"` + Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"` } type Config struct { diff --git a/config/parsing/hop/parse.go b/config/parsing/hop/parse.go index fadd088..3fb9dc5 100644 --- a/config/parsing/hop/parse.go +++ b/config/parsing/hop/parse.go @@ -32,7 +32,7 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) { } } switch strings.ToLower(cfg.Plugin.Type) { - case "http": + case plugin.HTTP: return hop_plugin.NewHTTPPlugin( cfg.Name, cfg.Plugin.Addr, plugin.TLSConfigOption(tlsCfg), @@ -67,15 +67,17 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) { } if v.Connector == nil { - v.Connector = &config.ConnectorConfig{ - Type: "http", - } + v.Connector = &config.ConnectorConfig{} + } + if strings.TrimSpace(v.Connector.Type) == "" { + v.Connector.Type = "http" } if v.Dialer == nil { - v.Dialer = &config.DialerConfig{ - Type: "tcp", - } + v.Dialer = &config.DialerConfig{} + } + if strings.TrimSpace(v.Dialer.Type) == "" { + v.Dialer.Type = "tcp" } node, err := node_parser.ParseNode(cfg.Name, v, log) diff --git a/config/parsing/node/parse.go b/config/parsing/node/parse.go index b1eb9c2..e91df94 100644 --- a/config/parsing/node/parse.go +++ b/config/parsing/node/parse.go @@ -147,46 +147,47 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No chain.TimeoutTransportOption(10*time.Second), ) - // convert *.example.com to .example.com - // convert *example.com to example.com - host := cfg.Host - if strings.HasPrefix(host, "*") { - host = host[1:] - if !strings.HasPrefix(host, ".") { - host = "." + host - } - } - opts := []chain.NodeOption{ chain.TransportNodeOption(tr), chain.BypassNodeOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)), chain.ResoloverNodeOption(registry.ResolverRegistry().Get(cfg.Resolver)), chain.HostMapperNodeOption(registry.HostsRegistry().Get(cfg.Hosts)), chain.MetadataNodeOption(nm), - chain.HostNodeOption(host), - chain.ProtocolNodeOption(cfg.Protocol), - chain.PathNodeOption(cfg.Path), chain.NetworkNodeOption(cfg.Network), } + + if filter := cfg.Filter; filter != nil { + // convert *.example.com to .example.com + // convert *example.com to example.com + host := filter.Host + if strings.HasPrefix(host, "*") { + host = host[1:] + if !strings.HasPrefix(host, ".") { + host = "." + host + } + } + + settings := &chain.NodeFilterSettings{ + Protocol: filter.Protocol, + Host: host, + Path: filter.Path, + } + opts = append(opts, chain.NodeFilterOption(settings)) + } + if cfg.HTTP != nil { settings := &chain.HTTPNodeSettings{ Host: cfg.HTTP.Host, Header: cfg.HTTP.Header, } - auth := cfg.HTTP.Auth - if auth == nil { - auth = cfg.Auth - } - if auth != nil { + if auth := cfg.HTTP.Auth; auth != nil && auth.Username != "" { settings.Auther = xauth.NewAuthenticator( xauth.AuthsOption(map[string]string{auth.Username: auth.Password}), xauth.LoggerOption(log.WithFields(map[string]any{ - "kind": "node", - "node": cfg.Name, - "addr": cfg.Addr, - "host": cfg.Host, - "protocol": cfg.Protocol, + "kind": "node", + "node": cfg.Name, + "addr": cfg.Addr, })), ) } @@ -200,6 +201,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No } opts = append(opts, chain.HTTPNodeOption(settings)) } + if cfg.TLS != nil { tlsCfg := &chain.TLSNodeSettings{ ServerName: cfg.TLS.ServerName, diff --git a/config/parsing/service/parse.go b/config/parsing/service/parse.go index 6dfa36e..3554256 100644 --- a/config/parsing/service/parse.go +++ b/config/parsing/service/parse.go @@ -2,6 +2,8 @@ package service import ( "fmt" + "strings" + "time" "github.com/go-gost/core/admission" "github.com/go-gost/core/auth" @@ -34,14 +36,17 @@ import ( func ParseService(cfg *config.ServiceConfig) (service.Service, error) { if cfg.Listener == nil { - cfg.Listener = &config.ListenerConfig{ - Type: "tcp", - } + cfg.Listener = &config.ListenerConfig{} } + if strings.TrimSpace(cfg.Listener.Type) == "" { + cfg.Listener.Type = "tcp" + } + if cfg.Handler == nil { - cfg.Handler = &config.HandlerConfig{ - Type: "auto", - } + cfg.Handler = &config.HandlerConfig{} + } + if strings.TrimSpace(cfg.Handler.Type) == "" { + cfg.Handler.Type = "auto" } log := logger.Default() @@ -98,6 +103,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { var preUp, preDown, postUp, postDown []string var ignoreChain bool var pStats *stats.Stats + var observePeriod time.Duration if cfg.Metadata != nil { md := metadata.NewMetadata(cfg.Metadata) ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol) @@ -118,6 +124,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { if mdutil.GetBool(md, parsing.MDKeyEnableStats) { pStats = &stats.Stats{} } + observePeriod = mdutil.GetDuration(md, "observePeriod") } listenOpts := []listener.Option{ @@ -143,7 +150,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { if rf := registry.ListenerRegistry().Get(cfg.Listener.Type); rf != nil { ln = rf(listenOpts...) } else { - return nil, fmt.Errorf("unregistered listener: %s", cfg.Listener.Type) + return nil, fmt.Errorf("unknown listener: %s", cfg.Listener.Type) } if cfg.Listener.Metadata == nil { @@ -230,7 +237,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { handler.ServiceOption(cfg.Name), ) } else { - return nil, fmt.Errorf("unregistered handler: %s", cfg.Handler.Type) + return nil, fmt.Errorf("unknown handler: %s", cfg.Handler.Type) } if forwarder, ok := h.(handler.Forwarder); ok { @@ -259,6 +266,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) { xservice.RecordersOption(recorders...), xservice.StatsOption(pStats), xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)), + xservice.ObservePeriodOption(observePeriod), xservice.LoggerOption(serviceLogger), ) @@ -271,6 +279,14 @@ func parseForwarder(cfg *config.ForwarderConfig, log logger.Logger) (hop.Hop, er return nil, nil } + hopName := cfg.Hop + if hopName == "" { + hopName = cfg.Name + } + if hopName != "" { + return registry.HopRegistry().Get(hopName), nil + } + hc := config.HopConfig{ Name: cfg.Name, Selector: cfg.Selector, @@ -286,27 +302,42 @@ func parseForwarder(cfg *config.ForwarderConfig, log logger.Logger) (hop.Hop, er if i > 0 { name = fmt.Sprintf("%s-%d", node.Name, i) } + + filter := node.Filter + if filter == nil { + if node.Protocol != "" || node.Host != "" || node.Path != "" { + filter = &config.NodeFilterConfig{ + Protocol: node.Protocol, + Host: node.Host, + Path: node.Path, + } + } + } + + httpCfg := node.HTTP + if node.Auth != nil { + if httpCfg == nil { + httpCfg = &config.HTTPNodeConfig{} + } + if httpCfg.Auth == nil { + httpCfg.Auth = node.Auth + } + } hc.Nodes = append(hc.Nodes, &config.NodeConfig{ Name: name, Addr: addr, - Host: node.Host, Network: node.Network, - Protocol: node.Protocol, - Path: node.Path, Bypass: node.Bypass, Bypasses: node.Bypasses, - HTTP: node.HTTP, + Filter: filter, + HTTP: httpCfg, TLS: node.TLS, - Auth: node.Auth, Metadata: node.Metadata, }) } } } - if len(hc.Nodes) > 0 { - return hop_parser.ParseHop(&hc, log) - } - return registry.HopRegistry().Get(hc.Name), nil + return hop_parser.ParseHop(&hc, log) } func chainGroup(name string, group *config.ChainGroupConfig) chain.Chainer { diff --git a/dialer/http2/dialer.go b/dialer/http2/dialer.go index fbee078..369640e 100644 --- a/dialer/http2/dialer.go +++ b/dialer/http2/dialer.go @@ -70,6 +70,19 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D opt(&options) } + { + // Check whether the connection is established properly + netd := options.NetDialer + if netd == nil { + netd = net_dialer.DefaultNetDialer + } + conn, err := netd.Dial(ctx, "tcp", address) + if err != nil { + return nil, err + } + conn.Close() + } + client = &http.Client{ Transport: &http.Transport{ TLSClientConfig: d.options.TLSConfig, @@ -81,17 +94,16 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D return netd.Dial(ctx, network, addr) }, ForceAttemptHTTP2: true, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, + MaxIdleConns: 16, + IdleConnTimeout: 30 * time.Second, + TLSHandshakeTimeout: 30 * time.Second, + ExpectContinueTimeout: 15 * time.Second, }, } d.clients[address] = client } - var c net.Conn - c = &conn{ + var c net.Conn = &conn{ localAddr: &net.TCPAddr{}, remoteAddr: raddr, onClose: func() { diff --git a/dialer/http3/dialer.go b/dialer/http3/dialer.go index ba9c816..6ec0deb 100644 --- a/dialer/http3/dialer.go +++ b/dialer/http3/dialer.go @@ -86,7 +86,7 @@ func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.Dial return quic.DialEarly(context.Background(), udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg) }, - QuicConfig: &quic.Config{ + QUICConfig: &quic.Config{ KeepAlivePeriod: d.md.keepAlivePeriod, HandshakeIdleTimeout: d.md.handshakeTimeout, MaxIdleTimeout: d.md.maxIdleTimeout, diff --git a/dialer/http3/wt/dialer.go b/dialer/http3/wt/dialer.go index 7df98f6..696053b 100644 --- a/dialer/http3/wt/dialer.go +++ b/dialer/http3/wt/dialer.go @@ -10,7 +10,6 @@ import ( md "github.com/go-gost/core/metadata" "github.com/go-gost/x/registry" "github.com/quic-go/quic-go" - "github.com/quic-go/quic-go/http3" wt "github.com/quic-go/webtransport-go" ) @@ -74,33 +73,32 @@ func (d *wtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOpt path: d.md.path, header: d.md.header, dialer: &wt.Dialer{ - RoundTripper: &http3.RoundTripper{ - TLSClientConfig: d.options.TLSConfig, - Dial: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { - // d.options.Logger.Infof("dial: %s, %s, %s", addr, adr, host) - udpAddr, err := net.ResolveUDPAddr("udp", addr) - if err != nil { - return nil, err - } + TLSClientConfig: d.options.TLSConfig, + DialAddr: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + // d.options.Logger.Infof("dial: %s, %s, %s", addr, adr, host) + udpAddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } - udpConn, err := options.NetDialer.Dial(ctx, "udp", "") - if err != nil { - return nil, err - } + udpConn, err := options.NetDialer.Dial(ctx, "udp", "") + if err != nil { + return nil, err + } - return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg) - }, - QuicConfig: &quic.Config{ - KeepAlivePeriod: d.md.keepAlivePeriod, - HandshakeIdleTimeout: d.md.handshakeTimeout, - MaxIdleTimeout: d.md.maxIdleTimeout, - /* - Versions: []quic.VersionNumber{ - quic.Version1, - }, - */ - MaxIncomingStreams: int64(d.md.maxStreams), - }, + return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg) + }, + QUICConfig: &quic.Config{ + KeepAlivePeriod: d.md.keepAlivePeriod, + HandshakeIdleTimeout: d.md.handshakeTimeout, + MaxIdleTimeout: d.md.maxIdleTimeout, + /* + Versions: []quic.VersionNumber{ + quic.Version1, + }, + */ + MaxIncomingStreams: int64(d.md.maxStreams), + EnableDatagrams: true, }, }, } diff --git a/dialer/kcp/metadata.go b/dialer/kcp/metadata.go index a9a8c27..7e38445 100644 --- a/dialer/kcp/metadata.go +++ b/dialer/kcp/metadata.go @@ -49,7 +49,12 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) { d.md.config.KeepAlive = mdutil.GetInt(md, "kcp.keepalive") d.md.config.Interval = mdutil.GetInt(md, "kcp.interval") d.md.config.MTU = mdutil.GetInt(md, "kcp.mtu") + d.md.config.RcvWnd = mdutil.GetInt(md, "kcp.rcvwnd") + d.md.config.SndWnd = mdutil.GetInt(md, "kcp.sndwnd") d.md.config.SmuxVer = mdutil.GetInt(md, "kcp.smuxver") + d.md.config.SmuxBuf = mdutil.GetInt(md, "kcp.smuxbuf") + d.md.config.StreamBuf = mdutil.GetInt(md, "kcp.streambuf") + d.md.config.NoComp = mdutil.GetBool(md, "kcp.nocomp") d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout) return diff --git a/dialer/obfs/http/conn.go b/dialer/obfs/http/conn.go index 54d8496..83dfeb1 100644 --- a/dialer/obfs/http/conn.go +++ b/dialer/obfs/http/conn.go @@ -18,6 +18,7 @@ import ( type obfsHTTPConn struct { net.Conn host string + path string rbuf bytes.Buffer wbuf bytes.Buffer headerDrained bool diff --git a/dialer/obfs/http/dialer.go b/dialer/obfs/http/dialer.go index 653102d..d8b4a22 100644 --- a/dialer/obfs/http/dialer.go +++ b/dialer/obfs/http/dialer.go @@ -2,6 +2,7 @@ package http import ( "context" + "crypto/tls" "net" "github.com/go-gost/core/dialer" @@ -12,11 +13,13 @@ import ( func init() { registry.DialerRegistry().Register("ohttp", NewDialer) + registry.DialerRegistry().Register("ohttps", NewDialer) } type obfsHTTPDialer struct { - md metadata - logger logger.Logger + tlsEnabled bool + md metadata + logger logger.Logger } func NewDialer(opts ...dialer.Option) dialer.Dialer { @@ -30,6 +33,18 @@ func NewDialer(opts ...dialer.Option) dialer.Dialer { } } +func NewTLSDialer(opts ...dialer.Option) dialer.Dialer { + options := &dialer.Options{} + for _, opt := range opts { + opt(options) + } + + return &obfsHTTPDialer{ + tlsEnabled: true, + logger: options.Logger, + } +} + func (d *obfsHTTPDialer) Init(md md.Metadata) (err error) { return d.parseMetadata(md) } @@ -59,9 +74,16 @@ func (d *obfsHTTPDialer) Handshake(ctx context.Context, conn net.Conn, options . host = opts.Addr } + if d.tlsEnabled { + conn = tls.Client(conn, &tls.Config{ + ServerName: host, + }) + } + return &obfsHTTPConn{ Conn: conn, host: host, + path: d.md.path, header: d.md.header, logger: d.logger, }, nil diff --git a/dialer/obfs/http/metadata.go b/dialer/obfs/http/metadata.go index c736209..6b1b2ee 100644 --- a/dialer/obfs/http/metadata.go +++ b/dialer/obfs/http/metadata.go @@ -7,24 +7,29 @@ import ( mdutil "github.com/go-gost/core/metadata/util" ) +const ( + defaultPath = "/" +) + type metadata struct { host string + path string header http.Header } func (d *obfsHTTPDialer) parseMetadata(md mdata.Metadata) (err error) { - const ( - header = "header" - host = "host" - ) + d.md.host = mdutil.GetString(md, "obfs.host", "host") + d.md.path = mdutil.GetString(md, "obfs.path", "path") + if d.md.path == "" { + d.md.path = defaultPath + } - if m := mdutil.GetStringMapString(md, header); len(m) > 0 { + if m := mdutil.GetStringMapString(md, "obfs.header", "header"); len(m) > 0 { h := http.Header{} for k, v := range m { h.Add(k, v) } d.md.header = h } - d.md.host = mdutil.GetString(md, host) return } diff --git a/dialer/ssh/metadata.go b/dialer/ssh/metadata.go index 2fa8315..5392ffe 100644 --- a/dialer/ssh/metadata.go +++ b/dialer/ssh/metadata.go @@ -1,11 +1,14 @@ package ssh import ( + "fmt" "os" "time" mdata "github.com/go-gost/core/metadata" mdutil "github.com/go-gost/core/metadata/util" + "github.com/mitchellh/go-homedir" + "github.com/zalando/go-keyring" "golang.org/x/crypto/ssh" ) @@ -20,21 +23,35 @@ type metadata struct { func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) { const ( - handshakeTimeout = "handshakeTimeout" - privateKeyFile = "privateKeyFile" - passphrase = "passphrase" + handshakeTimeout = "handshakeTimeout" + privateKeyFile = "privateKeyFile" + passphrase = "passphrase" + passphraseFromKeyring = "passphraseFromKeyring" ) if key := mdutil.GetString(md, privateKeyFile); key != "" { + key, err = homedir.Expand(key) + if err != nil { + return err + } data, err := os.ReadFile(key) if err != nil { return err } - if pp := mdutil.GetString(md, passphrase); pp != "" { - d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp)) + var pp string + if mdutil.GetBool(md, passphraseFromKeyring) { + pp, err = keyring.Get(fmt.Sprintf("SSH %s", key), d.options.Auth.Username()) + if err != nil { + return fmt.Errorf("unable to get secret(%s) from keyring: %w", key, err) + } } else { + pp = mdutil.GetString(md, passphrase) + } + if pp == "" { d.md.signer, err = ssh.ParsePrivateKey(data) + } else { + d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp)) } if err != nil { return err diff --git a/dialer/sshd/metadata.go b/dialer/sshd/metadata.go index 4a9b484..bea46a5 100644 --- a/dialer/sshd/metadata.go +++ b/dialer/sshd/metadata.go @@ -1,11 +1,14 @@ package sshd import ( + "fmt" "os" "time" mdata "github.com/go-gost/core/metadata" mdutil "github.com/go-gost/core/metadata/util" + "github.com/mitchellh/go-homedir" + "github.com/zalando/go-keyring" "golang.org/x/crypto/ssh" ) @@ -26,12 +29,24 @@ func (d *sshdDialer) parseMetadata(md mdata.Metadata) (err error) { ) if key := mdutil.GetString(md, privateKeyFile); key != "" { + key, err = homedir.Expand(key) + if err != nil { + return err + } data, err := os.ReadFile(key) if err != nil { return err } - pp := mdutil.GetString(md, passphrase) + var pp string + if mdutil.GetBool(md, "passphraseFromKeyring") { + pp, err = keyring.Get(fmt.Sprintf("SSH %s", key), key) + if err != nil { + return fmt.Errorf("unable to get secret(%s) from keyring: %w", key, err) + } + } else { + pp = mdutil.GetString(md, passphrase) + } if pp == "" { d.md.signer, err = ssh.ParsePrivateKey(data) } else { diff --git a/go.mod b/go.mod index 58423c2..5752105 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,15 @@ module github.com/go-gost/x -go 1.21 +go 1.22 + +toolchain go1.22.2 require ( github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/gin-contrib/cors v1.5.0 github.com/gin-gonic/gin v1.9.1 - github.com/go-gost/core v0.0.0-20240131151724-a06608ccafbf + github.com/go-gost/core v0.0.0-20240508132029-8d554ddcf77c github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks5 v0.4.0 github.com/go-gost/plugin v0.0.0-20240103125338-9c84e29cb81a @@ -16,16 +18,16 @@ require ( github.com/go-redis/redis/v8 v8.11.5 github.com/gobwas/glob v0.2.3 github.com/golang/snappy v0.0.4 - github.com/google/uuid v1.4.0 + github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.1 github.com/miekg/dns v1.1.57 + github.com/mitchellh/go-homedir v1.1.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/dtls/v2 v2.2.6 github.com/pires/go-proxyproto v0.7.0 - github.com/prometheus/client_golang v1.17.0 - github.com/quic-go/quic-go v0.40.1 - github.com/quic-go/webtransport-go v0.6.0 - github.com/refraction-networking/utls v1.5.4 + github.com/prometheus/client_golang v1.19.1 + github.com/quic-go/quic-go v0.45.0 + github.com/quic-go/webtransport-go v0.8.0 github.com/rs/xid v1.3.0 github.com/shadowsocks/go-shadowsocks2 v0.1.5 github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 @@ -37,25 +39,28 @@ require ( github.com/xtaci/smux v1.5.24 github.com/xtaci/tcpraw v1.2.25 github.com/yl2chen/cidranger v1.0.2 - golang.org/x/crypto v0.17.0 - golang.org/x/net v0.19.0 - golang.org/x/sys v0.15.0 + github.com/zalando/go-keyring v0.2.4 + golang.org/x/crypto v0.24.0 + golang.org/x/net v0.26.0 + golang.org/x/sys v0.21.0 golang.org/x/time v0.5.0 golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 - google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.2 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/alessio/shellescape v1.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.10.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/coreos/go-iptables v0.5.0 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect @@ -63,11 +68,11 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect + github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect @@ -75,21 +80,19 @@ require ( github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/onsi/ginkgo/v2 v2.12.0 // indirect + github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pion/logging v0.2.2 // indirect github.com/pion/transport/v2 v2.0.2 // indirect github.com/pion/udp/v2 v2.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.1 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -104,14 +107,15 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect - go.uber.org/mock v0.3.0 // indirect + go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.6.0 // indirect - golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.16.0 // indirect + golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index c3ba021..23e313b 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -26,6 +28,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-iptables v0.5.0 h1:mw6SAibtHKZcNzAsOxjoHIG0gy5YFHhypWSSNc6EjbQ= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -49,8 +53,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-gost/core v0.0.0-20240131151724-a06608ccafbf h1:akQ96Ibm+P7IftDluZPoMCzBzbLR/TjFu8Wpjy3H7hM= -github.com/go-gost/core v0.0.0-20240131151724-a06608ccafbf/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E= +github.com/go-gost/core v0.0.0-20240508132029-8d554ddcf77c h1:1ahtn+3bQB50at5ubWDOrA4yja8vWpWNrGSRaCztNWg= +github.com/go-gost/core v0.0.0-20240508132029-8d554ddcf77c/go.mod h1:j08tDHkFzk7dfOeLhl3RWsASdf9YWWRfWBUQqbQvx3A= github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s= github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc= github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI= @@ -61,8 +65,8 @@ github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c= github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8= github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I= github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -73,12 +77,14 @@ github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqR github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -90,25 +96,21 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 h1:gpptm606MZYGaMHMsB4Srmb6EbW/IVHnt04rcMXnkBQ= -github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g= +github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -131,10 +133,10 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -146,10 +148,10 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= -github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= @@ -169,24 +171,21 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= -github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= -github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= -github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= -github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= -github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= -github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= -github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= -github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= -github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= +github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE= +github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/quic-go/webtransport-go v0.8.0 h1:HxSrwun11U+LlmwpgM1kEqIqH90IT4N8auv/cD7QFJg= +github.com/quic-go/webtransport-go v0.8.0/go.mod h1:N99tjprW432Ut5ONql/aUhSLT0YVSlwHohQsuac9WaM= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= @@ -217,11 +216,11 @@ github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -256,8 +255,10 @@ github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1Gg github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= -go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68= +github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -270,19 +271,19 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE= -golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= +golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -294,16 +295,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -317,22 +317,22 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -343,8 +343,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -356,24 +356,22 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/handler/http/handler.go b/handler/http/handler.go index 149e349..0473e22 100644 --- a/handler/http/handler.go +++ b/handler/http/handler.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "hash/crc32" + "io" "net" "net/http" "net/http/httputil" @@ -148,7 +149,7 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt fields := map[string]any{ "dst": addr, } - if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization"), log); u != "" { + if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")); u != "" { fields["user"] = u } log = log.WithFields(fields) @@ -222,26 +223,6 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt } defer cc.Close() - if req.Method == http.MethodConnect { - resp.StatusCode = http.StatusOK - resp.Status = "200 Connection established" - - if log.IsLevelEnabled(logger.TraceLevel) { - dump, _ := httputil.DumpResponse(resp, false) - log.Trace(string(dump)) - } - if err = resp.Write(conn); err != nil { - log.Error(err) - return err - } - } else { - req.Header.Del("Proxy-Connection") - if err = req.Write(cc); err != nil { - log.Error(err) - return err - } - } - rw := traffic_wrapper.WrapReadWriter(h.options.Limiter, conn, traffic.NetworkOption(network), traffic.AddrOption(addr), @@ -256,6 +237,22 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt rw = stats_wrapper.WrapReadWriter(rw, pstats) } + if req.Method != http.MethodConnect { + return h.handleProxy(rw, cc, req, log) + } + + resp.StatusCode = http.StatusOK + resp.Status = "200 Connection established" + + if log.IsLevelEnabled(logger.TraceLevel) { + dump, _ := httputil.DumpResponse(resp, false) + log.Trace(string(dump)) + } + if err = resp.Write(rw); err != nil { + log.Error(err) + return err + } + start := time.Now() log.Infof("%s <-> %s", conn.RemoteAddr(), addr) netpkg.Transport(rw, cc) @@ -266,6 +263,49 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt return nil } +func (h *httpHandler) handleProxy(rw, cc io.ReadWriter, req *http.Request, log logger.Logger) (err error) { + req.Header.Del("Proxy-Connection") + + if err = req.Write(cc); err != nil { + log.Error(err) + return err + } + + ch := make(chan error, 1) + + go func() { + ch <- netpkg.CopyBuffer(rw, cc, 32*1024) + }() + + for { + err := func() error { + req, err := http.ReadRequest(bufio.NewReader(rw)) + if err != nil { + return err + } + + if log.IsLevelEnabled(logger.TraceLevel) { + dump, _ := httputil.DumpRequest(req, false) + log.Trace(string(dump)) + } + + req.Header.Del("Proxy-Connection") + + if err = req.Write(cc); err != nil { + return err + } + return nil + }() + ch <- err + + if err != nil { + break + } + } + + return <-ch +} + func (h *httpHandler) decodeServerName(s string) (string, error) { b, err := base64.RawURLEncoding.DecodeString(s) if err != nil { @@ -284,7 +324,7 @@ func (h *httpHandler) decodeServerName(s string) (string, error) { return string(v), nil } -func (h *httpHandler) basicProxyAuth(proxyAuth string, log logger.Logger) (username, password string, ok bool) { +func (h *httpHandler) basicProxyAuth(proxyAuth string) (username, password string, ok bool) { if proxyAuth == "" { return } @@ -306,7 +346,7 @@ func (h *httpHandler) basicProxyAuth(proxyAuth string, log logger.Logger) (usern } func (h *httpHandler) authenticate(ctx context.Context, conn net.Conn, req *http.Request, resp *http.Response, log logger.Logger) (id string, ok bool) { - u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization"), log) + u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")) if h.options.Auther == nil { return "", true } @@ -412,7 +452,11 @@ func (h *httpHandler) observeStats(ctx context.Context) { return } - ticker := time.NewTicker(5 * time.Second) + d := h.md.observePeriod + if d < time.Millisecond { + d = 5 * time.Second + } + ticker := time.NewTicker(d) defer ticker.Stop() for { diff --git a/handler/http/metadata.go b/handler/http/metadata.go index fd9af19..4f7b4e1 100644 --- a/handler/http/metadata.go +++ b/handler/http/metadata.go @@ -3,6 +3,7 @@ package http import ( "net/http" "strings" + "time" mdata "github.com/go-gost/core/metadata" mdutil "github.com/go-gost/core/metadata/util" @@ -18,20 +19,11 @@ type metadata struct { header http.Header hash string authBasicRealm string + observePeriod time.Duration } func (h *httpHandler) parseMetadata(md mdata.Metadata) error { - const ( - header = "header" - probeResistKey = "probeResistance" - probeResistKeyX = "probe_resist" - knock = "knock" - enableUDP = "udp" - hash = "hash" - authBasicRealm = "authBasicRealm" - ) - - if m := mdutil.GetStringMapString(md, header); len(m) > 0 { + if m := mdutil.GetStringMapString(md, "http.header", "header"); len(m) > 0 { hd := http.Header{} for k, v := range m { hd.Add(k, v) @@ -39,22 +31,20 @@ func (h *httpHandler) parseMetadata(md mdata.Metadata) error { h.md.header = hd } - pr := mdutil.GetString(md, probeResistKey) - if pr == "" { - pr = mdutil.GetString(md, probeResistKeyX) - } - if pr != "" { + if pr := mdutil.GetString(md, "probeResist", "probe_resist"); pr != "" { if ss := strings.SplitN(pr, ":", 2); len(ss) == 2 { h.md.probeResistance = &probeResistance{ Type: ss[0], Value: ss[1], - Knock: mdutil.GetString(md, knock), + Knock: mdutil.GetString(md, "knock"), } } } - h.md.enableUDP = mdutil.GetBool(md, enableUDP) - h.md.hash = mdutil.GetString(md, hash) - h.md.authBasicRealm = mdutil.GetString(md, authBasicRealm) + h.md.enableUDP = mdutil.GetBool(md, "udp") + h.md.hash = mdutil.GetString(md, "hash") + h.md.authBasicRealm = mdutil.GetString(md, "authBasicRealm") + + h.md.observePeriod = mdutil.GetDuration(md, "observePeriod") return nil } diff --git a/handler/http2/handler.go b/handler/http2/handler.go index 97ae18a..b7f94c9 100644 --- a/handler/http2/handler.go +++ b/handler/http2/handler.go @@ -419,7 +419,11 @@ func (h *http2Handler) observeStats(ctx context.Context) { return } - ticker := time.NewTicker(5 * time.Second) + d := h.md.observePeriod + if d < time.Millisecond { + d = 5 * time.Second + } + ticker := time.NewTicker(d) defer ticker.Stop() for { diff --git a/handler/http2/metadata.go b/handler/http2/metadata.go index 9d21b9d..fcd8e9b 100644 --- a/handler/http2/metadata.go +++ b/handler/http2/metadata.go @@ -3,6 +3,7 @@ package http2 import ( "net/http" "strings" + "time" mdata "github.com/go-gost/core/metadata" mdutil "github.com/go-gost/core/metadata/util" @@ -17,19 +18,11 @@ type metadata struct { header http.Header hash string authBasicRealm string + observePeriod time.Duration } func (h *http2Handler) parseMetadata(md mdata.Metadata) error { - const ( - header = "header" - probeResistKey = "probeResistance" - probeResistKeyX = "probe_resist" - knock = "knock" - hash = "hash" - authBasicRealm = "authBasicRealm" - ) - - if m := mdutil.GetStringMapString(md, header); len(m) > 0 { + if m := mdutil.GetStringMapString(md, "http.header", "header"); len(m) > 0 { hd := http.Header{} for k, v := range m { hd.Add(k, v) @@ -37,21 +30,19 @@ func (h *http2Handler) parseMetadata(md mdata.Metadata) error { h.md.header = hd } - pr := mdutil.GetString(md, probeResistKey) - if pr == "" { - pr = mdutil.GetString(md, probeResistKeyX) - } - if pr != "" { + if pr := mdutil.GetString(md, "probeResist", "probe_resist"); pr != "" { if ss := strings.SplitN(pr, ":", 2); len(ss) == 2 { h.md.probeResistance = &probeResistance{ Type: ss[0], Value: ss[1], - Knock: mdutil.GetString(md, knock), + Knock: mdutil.GetString(md, "knock"), } } } - h.md.hash = mdutil.GetString(md, hash) - h.md.authBasicRealm = mdutil.GetString(md, authBasicRealm) + h.md.hash = mdutil.GetString(md, "hash") + h.md.authBasicRealm = mdutil.GetString(md, "authBasicRealm") + + h.md.observePeriod = mdutil.GetDuration(md, "observePeriod") return nil } diff --git a/handler/redirect/tcp/handler_darwin.go b/handler/redirect/tcp/handler_darwin.go new file mode 100644 index 0000000..2e71686 --- /dev/null +++ b/handler/redirect/tcp/handler_darwin.go @@ -0,0 +1,64 @@ +package redirect + +import ( + "fmt" + "net" + "os/exec" + "strconv" + "strings" +) + +func (h *redirectHandler) getOriginalDstAddr(conn net.Conn) (addr net.Addr, err error) { + host, port, err := localToRemote(conn) + if err != nil { + return nil, err + } + portNumber, _ := strconv.Atoi(port) + addr = &net.TCPAddr{ + IP: net.ParseIP(host), + Port: portNumber, + } + + return +} + +func localToRemote(clientConn net.Conn) (string, string, error) { + host, port, err := net.SplitHostPort(clientConn.RemoteAddr().String()) + if err != nil { + return "", "", err + } + out, err := exec.Command("sudo", "-n", "/sbin/pfctl", "-s", "state").Output() + if err != nil { + return "", "", err + } + remoteAddr, remotePort, err := translatePfctlOutput(host, port, string(out)) + if err != nil { + return "", "", err + } + return remoteAddr, remotePort, err +} + +func translatePfctlOutput(address string, port, s string) (string, string, error) { + // We may get an ipv4-mapped ipv6 address here, e.g. ::ffff:127.0.0.1. + // Those still appear as "127.0.0.1" in the table, so we need to strip the prefix. + // re := regexp.MustCompile(`^::ffff:((\d+\.\d+\.\d+\.\d+$))`) + // strippedAddress := re.ReplaceAllString(address, "") + strippedAddress := address + + // ALL tcp 192.168.1.13:57474 -> 23.205.82.58:443 ESTABLISHED:ESTABLISHED + spec := net.JoinHostPort(strippedAddress, port) + + lines := strings.Split(s, "\n") + for _, line := range lines { + if strings.Contains(line, "ESTABLISHED:ESTABLISHED") { + if strings.Contains(line, spec) { + fields := strings.Fields(line) + if len(fields) > 4 { + return net.SplitHostPort(fields[4]) + } + } + } + } + + return "", "", fmt.Errorf("could not resolve original destination") +} diff --git a/handler/redirect/tcp/handler_other.go b/handler/redirect/tcp/handler_other.go index c91c633..8b09c2d 100644 --- a/handler/redirect/tcp/handler_other.go +++ b/handler/redirect/tcp/handler_other.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !darwin package redirect @@ -7,7 +7,7 @@ import ( "net" ) -func (h *redirectHandler) getOriginalDstAddr(conn net.Conn) (addr net.Addr, err error) { +func (h *redirectHandler) getOriginalDstAddr(_ net.Conn) (addr net.Addr, err error) { err = errors.New("TCP redirect is not available on non-linux platform") return } diff --git a/handler/relay/handler.go b/handler/relay/handler.go index 850cae8..4b368ae 100644 --- a/handler/relay/handler.go +++ b/handler/relay/handler.go @@ -204,7 +204,11 @@ func (h *relayHandler) observeStats(ctx context.Context) { return } - ticker := time.NewTicker(5 * time.Second) + d := h.md.observePeriod + if d < time.Millisecond { + d = 5 * time.Second + } + ticker := time.NewTicker(d) defer ticker.Stop() for { diff --git a/handler/relay/metadata.go b/handler/relay/metadata.go index 06479c0..bc82712 100644 --- a/handler/relay/metadata.go +++ b/handler/relay/metadata.go @@ -16,28 +16,21 @@ type metadata struct { noDelay bool hash string muxCfg *mux.Config + observePeriod time.Duration } func (h *relayHandler) parseMetadata(md mdata.Metadata) (err error) { - const ( - readTimeout = "readTimeout" - enableBind = "bind" - udpBufferSize = "udpBufferSize" - noDelay = "nodelay" - hash = "hash" - ) + h.md.readTimeout = mdutil.GetDuration(md, "readTimeout") + h.md.enableBind = mdutil.GetBool(md, "bind") + h.md.noDelay = mdutil.GetBool(md, "nodelay") - h.md.readTimeout = mdutil.GetDuration(md, readTimeout) - h.md.enableBind = mdutil.GetBool(md, enableBind) - h.md.noDelay = mdutil.GetBool(md, noDelay) - - if bs := mdutil.GetInt(md, udpBufferSize); bs > 0 { + if bs := mdutil.GetInt(md, "udpBufferSize"); bs > 0 { h.md.udpBufferSize = int(math.Min(math.Max(float64(bs), 512), 64*1024)) } else { h.md.udpBufferSize = 4096 } - h.md.hash = mdutil.GetString(md, hash) + h.md.hash = mdutil.GetString(md, "hash") h.md.muxCfg = &mux.Config{ Version: mdutil.GetInt(md, "mux.version"), @@ -49,5 +42,7 @@ func (h *relayHandler) parseMetadata(md mdata.Metadata) (err error) { MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"), } + h.md.observePeriod = mdutil.GetDuration(md, "observePeriod") + return } diff --git a/handler/socks/v4/handler.go b/handler/socks/v4/handler.go index c0f06ab..53d274c 100644 --- a/handler/socks/v4/handler.go +++ b/handler/socks/v4/handler.go @@ -14,9 +14,9 @@ import ( "github.com/go-gost/gosocks4" ctxvalue "github.com/go-gost/x/ctx" netpkg "github.com/go-gost/x/internal/net" + stats_util "github.com/go-gost/x/internal/util/stats" "github.com/go-gost/x/limiter/traffic/wrapper" "github.com/go-gost/x/registry" - stats_util "github.com/go-gost/x/internal/util/stats" "github.com/go-gost/x/stats" stats_wrapper "github.com/go-gost/x/stats/wrapper" ) @@ -218,7 +218,11 @@ func (h *socks4Handler) observeStats(ctx context.Context) { return } - ticker := time.NewTicker(5 * time.Second) + d := h.md.observePeriod + if d < time.Millisecond { + d = 5 * time.Second + } + ticker := time.NewTicker(d) defer ticker.Stop() for { diff --git a/handler/socks/v4/metadata.go b/handler/socks/v4/metadata.go index 21ad759..318221a 100644 --- a/handler/socks/v4/metadata.go +++ b/handler/socks/v4/metadata.go @@ -8,17 +8,14 @@ import ( ) type metadata struct { - readTimeout time.Duration - hash string + readTimeout time.Duration + hash string + observePeriod time.Duration } func (h *socks4Handler) parseMetadata(md mdata.Metadata) (err error) { - const ( - readTimeout = "readTimeout" - hash = "hash" - ) - - h.md.readTimeout = mdutil.GetDuration(md, readTimeout) - h.md.hash = mdutil.GetString(md, hash) + h.md.readTimeout = mdutil.GetDuration(md, "readTimeout") + h.md.hash = mdutil.GetString(md, "hash") + h.md.observePeriod = mdutil.GetDuration(md, "observePeriod") return } diff --git a/handler/socks/v5/handler.go b/handler/socks/v5/handler.go index 034b29e..3d14482 100644 --- a/handler/socks/v5/handler.go +++ b/handler/socks/v5/handler.go @@ -160,7 +160,11 @@ func (h *socks5Handler) observeStats(ctx context.Context) { return } - ticker := time.NewTicker(5 * time.Second) + d := h.md.observePeriod + if d < time.Millisecond { + d = 5 * time.Second + } + ticker := time.NewTicker(d) defer ticker.Stop() for { diff --git a/handler/socks/v5/metadata.go b/handler/socks/v5/metadata.go index ec31575..0b23c0d 100644 --- a/handler/socks/v5/metadata.go +++ b/handler/socks/v5/metadata.go @@ -18,32 +18,23 @@ type metadata struct { compatibilityMode bool hash string muxCfg *mux.Config + observePeriod time.Duration } func (h *socks5Handler) parseMetadata(md mdata.Metadata) (err error) { - const ( - readTimeout = "readTimeout" - noTLS = "notls" - enableBind = "bind" - enableUDP = "udp" - udpBufferSize = "udpBufferSize" - compatibilityMode = "comp" - hash = "hash" - ) + h.md.readTimeout = mdutil.GetDuration(md, "readTimeout") + h.md.noTLS = mdutil.GetBool(md, "notls") + h.md.enableBind = mdutil.GetBool(md, "bind") + h.md.enableUDP = mdutil.GetBool(md, "udp") - h.md.readTimeout = mdutil.GetDuration(md, readTimeout) - h.md.noTLS = mdutil.GetBool(md, noTLS) - h.md.enableBind = mdutil.GetBool(md, enableBind) - h.md.enableUDP = mdutil.GetBool(md, enableUDP) - - if bs := mdutil.GetInt(md, udpBufferSize); bs > 0 { + if bs := mdutil.GetInt(md, "udpBufferSize"); bs > 0 { h.md.udpBufferSize = int(math.Min(math.Max(float64(bs), 512), 64*1024)) } else { h.md.udpBufferSize = 4096 } - h.md.compatibilityMode = mdutil.GetBool(md, compatibilityMode) - h.md.hash = mdutil.GetString(md, hash) + h.md.compatibilityMode = mdutil.GetBool(md, "comp") + h.md.hash = mdutil.GetString(md, "hash") h.md.muxCfg = &mux.Config{ Version: mdutil.GetInt(md, "mux.version"), @@ -55,5 +46,7 @@ func (h *socks5Handler) parseMetadata(md mdata.Metadata) (err error) { MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"), } + h.md.observePeriod = mdutil.GetDuration(md, "observePeriod") + return nil } diff --git a/handler/tun/handler.go b/handler/tun/handler.go index 445190e..2f87ffb 100644 --- a/handler/tun/handler.go +++ b/handler/tun/handler.go @@ -12,7 +12,6 @@ import ( "github.com/go-gost/core/handler" "github.com/go-gost/core/hop" md "github.com/go-gost/core/metadata" - "github.com/go-gost/core/router" tun_util "github.com/go-gost/x/internal/util/tun" "github.com/go-gost/x/registry" "github.com/songgao/water/waterutil" @@ -109,23 +108,6 @@ func (h *tunHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler. return h.handleServer(ctx, conn, config, log) } -func (h *tunHandler) findRouteFor(ctx context.Context, dst net.IP, router router.Router) net.Addr { - if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok { - return v.(net.Addr) - } - - if router == nil { - return nil - } - - if route := router.GetRoute(ctx, dst); route != nil && route.Gateway != nil { - if v, ok := h.routes.Load(ipToTunRouteKey(route.Gateway)); ok { - return v.(net.Addr) - } - } - return nil -} - var mIPProts = map[waterutil.IPProtocol]string{ waterutil.HOPOPT: "HOPOPT", waterutil.ICMP: "ICMP", diff --git a/handler/tun/metadata.go b/handler/tun/metadata.go index 5abb881..0c173eb 100644 --- a/handler/tun/metadata.go +++ b/handler/tun/metadata.go @@ -16,28 +16,23 @@ type metadata struct { bufferSize int keepAlivePeriod time.Duration passphrase string + p2p bool } func (h *tunHandler) parseMetadata(md mdata.Metadata) (err error) { - const ( - bufferSize = "bufferSize" - keepAlive = "keepAlive" - keepAlivePeriod = "ttl" - passphrase = "passphrase" - ) - - h.md.bufferSize = mdutil.GetInt(md, bufferSize) + h.md.bufferSize = mdutil.GetInt(md, "tun.bufsize", "bufsize", "buffersize") if h.md.bufferSize <= 0 { h.md.bufferSize = defaultBufferSize } - if mdutil.GetBool(md, keepAlive) { - h.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod) + if mdutil.GetBool(md, "tun.keepalive", "keepalive") { + h.md.keepAlivePeriod = mdutil.GetDuration(md, "tun.ttl", "ttl") if h.md.keepAlivePeriod <= 0 { h.md.keepAlivePeriod = defaultKeepAlivePeriod } } - h.md.passphrase = mdutil.GetString(md, passphrase) + h.md.passphrase = mdutil.GetString(md, "tun.token", "token", "passphrase") + h.md.p2p = mdutil.GetBool(md, "tun.p2p", "p2p") return } diff --git a/handler/tun/server.go b/handler/tun/server.go index 64c9b8e..ad22d20 100644 --- a/handler/tun/server.go +++ b/handler/tun/server.go @@ -10,6 +10,7 @@ import ( "github.com/go-gost/core/common/bufpool" "github.com/go-gost/core/logger" + "github.com/go-gost/core/router" tun_util "github.com/go-gost/x/internal/util/tun" "github.com/songgao/water/waterutil" "golang.org/x/net/ipv4" @@ -203,11 +204,13 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con return nil } - if addr := h.findRouteFor(ctx, dst, config.Router); addr != nil { - log.Debugf("find route: %s -> %s", dst, addr) + if !h.md.p2p { + if addr := h.findRouteFor(ctx, dst, config.Router); addr != nil { + log.Debugf("find route: %s -> %s", dst, addr) - _, err := conn.WriteTo(b[:n], addr) - return err + _, err := conn.WriteTo(b[:n], addr) + return err + } } if _, err := tun.Write(b[:n]); err != nil { @@ -231,6 +234,9 @@ func (h *tunHandler) transportServer(ctx context.Context, tun io.ReadWriter, con } func (h *tunHandler) updateRoute(ip net.IP, addr net.Addr, log logger.Logger) { + if h.md.p2p { + ip = net.IPv6zero + } rkey := ipToTunRouteKey(ip) if actual, loaded := h.routes.LoadOrStore(rkey, addr); loaded { if actual.(net.Addr).String() != addr.String() { @@ -242,3 +248,25 @@ func (h *tunHandler) updateRoute(ip net.IP, addr net.Addr, log logger.Logger) { log.Debugf("new route: %s -> %s", ip, addr) } } + +func (h *tunHandler) findRouteFor(ctx context.Context, dst net.IP, router router.Router) net.Addr { + if h.md.p2p { + dst = net.IPv6zero + router = nil + } + + if v, ok := h.routes.Load(ipToTunRouteKey(dst)); ok { + return v.(net.Addr) + } + + if router == nil { + return nil + } + + if route := router.GetRoute(ctx, dst); route != nil && route.Gateway != nil { + if v, ok := h.routes.Load(ipToTunRouteKey(route.Gateway)); ok { + return v.(net.Addr) + } + } + return nil +} diff --git a/hop/hop.go b/hop/hop.go index 689bb30..c98623b 100644 --- a/hop/hop.go +++ b/hop/hop.go @@ -1,7 +1,6 @@ package hop import ( - "bytes" "context" "encoding/json" "io" @@ -181,7 +180,11 @@ func (p *chainHop) filterByHost(host string, nodes ...*chain.Node) (filters []*c if node == nil { continue } - vhost := node.Options().Host + + var vhost string + if filter := node.Options().Filter; filter != nil { + vhost = filter.Host + } if vhost == "" { // backup node if !found { filters = append(filters, node) @@ -216,14 +219,18 @@ func (p *chainHop) filterByProtocol(protocol string, nodes ...*chain.Node) (filt continue } - if node.Options().Protocol == "" { + var prot string + if filter := node.Options().Filter; filter != nil { + prot = filter.Protocol + } + if prot == "" { if !found { filters = append(filters, node) } continue } - if node.Options().Protocol == protocol { + if prot == protocol { if !found { filters = nil } @@ -244,19 +251,31 @@ func (p *chainHop) filterByPath(path string, nodes ...*chain.Node) (filters []*c p.options.logger.Debugf("filter by path: %s", path) sort.SliceStable(nodes, func(i, j int) bool { - return len(nodes[i].Options().Path) > len(nodes[j].Options().Path) + filter1 := nodes[i].Options().Filter + if filter1 == nil { + return false + } + filter2 := nodes[j].Options().Filter + if filter2 == nil { + return true + } + return len(filter1.Path) > len(filter2.Path) }) found := false for _, node := range nodes { - if node.Options().Path == "" { + var pathFilter string + if filter := node.Options().Filter; filter != nil { + pathFilter = filter.Path + } + if pathFilter == "" { if !found { filters = append(filters, node) } continue } - if strings.HasPrefix(path, node.Options().Path) { + if strings.HasPrefix(path, pathFilter) { if !found { filters = nil } @@ -308,33 +327,30 @@ func (p *chainHop) reload(ctx context.Context) (err error) { } func (p *chainHop) load(ctx context.Context) (nodes []*chain.Node, err error) { - if p.options.fileLoader != nil { - r, er := p.options.fileLoader.Load(ctx) + if loader := p.options.fileLoader; loader != nil { + r, er := loader.Load(ctx) if er != nil { p.options.logger.Warnf("file loader: %v", er) } nodes, _ = p.parseNode(r) } - if p.options.redisLoader != nil { - if lister, ok := p.options.redisLoader.(loader.Lister); ok { - list, er := lister.List(ctx) - if er != nil { - p.options.logger.Warnf("redis loader: %v", er) - } - for _, s := range list { - nl, _ := p.parseNode(bytes.NewReader([]byte(s))) - nodes = append(nodes, nl...) - } + if loader := p.options.redisLoader; loader != nil { + r, er := loader.Load(ctx) + if er != nil { + p.options.logger.Warnf("redis loader: %v", er) } + ns, _ := p.parseNode(r) + nodes = append(nodes, ns...) } - if p.options.httpLoader != nil { - r, er := p.options.httpLoader.Load(ctx) + + if loader := p.options.httpLoader; loader != nil { + r, er := loader.Load(ctx) if er != nil { p.options.logger.Warnf("http loader: %v", er) } - if node, _ := p.parseNode(r); node != nil { - nodes = append(nodes, node...) + if ns, _ := p.parseNode(r); ns != nil { + nodes = append(nodes, ns...) } } @@ -342,6 +358,10 @@ func (p *chainHop) load(ctx context.Context) (nodes []*chain.Node, err error) { } func (p *chainHop) parseNode(r io.Reader) ([]*chain.Node, error) { + if r == nil { + return nil, nil + } + var ncs []*config.NodeConfig if err := json.NewDecoder(r).Decode(&ncs); err != nil { return nil, err diff --git a/hop/plugin/grpc.go b/hop/plugin/grpc.go index 2b26ace..ed5671b 100644 --- a/hop/plugin/grpc.go +++ b/hop/plugin/grpc.go @@ -94,6 +94,10 @@ func (p *grpcPlugin) Select(ctx context.Context, opts ...hop.SelectOption) *chai } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/hosts/plugin/grpc.go b/hosts/plugin/grpc.go index 5585c07..3d76d83 100644 --- a/hosts/plugin/grpc.go +++ b/hosts/plugin/grpc.go @@ -71,6 +71,10 @@ func (p *grpcPlugin) Lookup(ctx context.Context, network, host string, opts ...h } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/ingress/plugin/grpc.go b/ingress/plugin/grpc.go index ffb6535..c062770 100644 --- a/ingress/plugin/grpc.go +++ b/ingress/plugin/grpc.go @@ -82,6 +82,10 @@ func (p *grpcPlugin) SetRule(ctx context.Context, rule *ingress.Rule, opts ...in } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index 56a7428..a7043c3 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -12,6 +12,11 @@ import ( "google.golang.org/grpc/credentials/insecure" ) +const ( + GRPC string = "grpc" + HTTP string = "http" +) + type Options struct { Token string TLSConfig *tls.Config diff --git a/internal/util/pht/server.go b/internal/util/pht/server.go index 411e4aa..42a006a 100644 --- a/internal/util/pht/server.go +++ b/internal/util/pht/server.go @@ -160,7 +160,7 @@ func NewHTTP3Server(addr string, quicConfig *quic.Config, opts ...ServerOption) http3Server: &http3.Server{ Addr: addr, TLSConfig: options.tlsConfig, - QuicConfig: quicConfig, + QUICConfig: quicConfig, }, cqueue: make(chan net.Conn, options.backlog), closed: make(chan struct{}), diff --git a/limiter/traffic/plugin/grpc.go b/limiter/traffic/plugin/grpc.go index dbe456b..c05a5e4 100644 --- a/limiter/traffic/plugin/grpc.go +++ b/limiter/traffic/plugin/grpc.go @@ -95,6 +95,10 @@ func (p *grpcPlugin) Out(ctx context.Context, key string, opts ...traffic.Option } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/listener/http3/listener.go b/listener/http3/listener.go index 60bf7f0..e263c53 100644 --- a/listener/http3/listener.go +++ b/listener/http3/listener.go @@ -58,7 +58,7 @@ func (l *http3Listener) Init(md md.Metadata) (err error) { l.server = &http3.Server{ Addr: l.options.Addr, TLSConfig: l.options.TLSConfig, - QuicConfig: &quic.Config{ + QUICConfig: &quic.Config{ KeepAlivePeriod: l.md.keepAlivePeriod, HandshakeIdleTimeout: l.md.handshakeTimeout, MaxIdleTimeout: l.md.maxIdleTimeout, diff --git a/listener/http3/wt/listener.go b/listener/http3/wt/listener.go index d2455ed..cba4cf9 100644 --- a/listener/http3/wt/listener.go +++ b/listener/http3/wt/listener.go @@ -66,7 +66,7 @@ func (l *wtListener) Init(md md.Metadata) (err error) { H3: http3.Server{ Addr: l.options.Addr, TLSConfig: l.options.TLSConfig, - QuicConfig: &quic.Config{ + QUICConfig: &quic.Config{ KeepAlivePeriod: l.md.keepAlivePeriod, HandshakeIdleTimeout: l.md.handshakeTimeout, MaxIdleTimeout: l.md.maxIdleTimeout, diff --git a/listener/kcp/metadata.go b/listener/kcp/metadata.go index 6fe8c96..c7472c2 100644 --- a/listener/kcp/metadata.go +++ b/listener/kcp/metadata.go @@ -52,7 +52,12 @@ func (l *kcpListener) parseMetadata(md mdata.Metadata) (err error) { l.md.config.KeepAlive = mdutil.GetInt(md, "kcp.keepalive") l.md.config.Interval = mdutil.GetInt(md, "kcp.interval") l.md.config.MTU = mdutil.GetInt(md, "kcp.mtu") - l.md.config.SmuxVer = mdutil.GetInt(md, "kcp.smuxVer") + l.md.config.RcvWnd = mdutil.GetInt(md, "kcp.rcvwnd") + l.md.config.SndWnd = mdutil.GetInt(md, "kcp.sndwnd") + l.md.config.SmuxVer = mdutil.GetInt(md, "kcp.smuxver") + l.md.config.SmuxBuf = mdutil.GetInt(md, "kcp.smuxbuf") + l.md.config.StreamBuf = mdutil.GetInt(md, "kcp.streambuf") + l.md.config.NoComp = mdutil.GetBool(md, "kcp.nocomp") l.md.backlog = mdutil.GetInt(md, backlog) if l.md.backlog <= 0 { diff --git a/listener/redirect/tcp/listener_darwin.go b/listener/redirect/tcp/listener_darwin.go new file mode 100644 index 0000000..d42f002 --- /dev/null +++ b/listener/redirect/tcp/listener_darwin.go @@ -0,0 +1,9 @@ +package tcp + +import ( + "syscall" +) + +func (l *redirectListener) control(network, address string, c syscall.RawConn) error { + return nil +} diff --git a/listener/redirect/tcp/listener_other.go b/listener/redirect/tcp/listener_other.go index c065d19..7447113 100644 --- a/listener/redirect/tcp/listener_other.go +++ b/listener/redirect/tcp/listener_other.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !darwin package tcp diff --git a/listener/ssh/metadata.go b/listener/ssh/metadata.go index cb9c32e..c291f9f 100644 --- a/listener/ssh/metadata.go +++ b/listener/ssh/metadata.go @@ -6,6 +6,7 @@ import ( mdata "github.com/go-gost/core/metadata" mdutil "github.com/go-gost/core/metadata/util" ssh_util "github.com/go-gost/x/internal/util/ssh" + "github.com/mitchellh/go-homedir" "golang.org/x/crypto/ssh" ) @@ -29,6 +30,10 @@ func (l *sshListener) parseMetadata(md mdata.Metadata) (err error) { ) if key := mdutil.GetString(md, privateKeyFile); key != "" { + key, err = homedir.Expand(key) + if err != nil { + return err + } data, err := os.ReadFile(key) if err != nil { return err diff --git a/listener/sshd/metadata.go b/listener/sshd/metadata.go index f4d3497..58fa8ff 100644 --- a/listener/sshd/metadata.go +++ b/listener/sshd/metadata.go @@ -1,11 +1,14 @@ package ssh import ( + "fmt" "os" mdata "github.com/go-gost/core/metadata" mdutil "github.com/go-gost/core/metadata/util" ssh_util "github.com/go-gost/x/internal/util/ssh" + "github.com/mitchellh/go-homedir" + "github.com/zalando/go-keyring" "golang.org/x/crypto/ssh" ) @@ -29,12 +32,24 @@ func (l *sshdListener) parseMetadata(md mdata.Metadata) (err error) { ) if key := mdutil.GetString(md, privateKeyFile); key != "" { + key, err = homedir.Expand(key) + if err != nil { + return err + } data, err := os.ReadFile(key) if err != nil { return err } - pp := mdutil.GetString(md, passphrase) + var pp string + if mdutil.GetBool(md, "passphraseFromKeyring") { + pp, err = keyring.Get(fmt.Sprintf("SSH %s", key), l.options.Auth.Username()) + if err != nil { + return fmt.Errorf("unable to get secret(%s) from keyring: %w", key, err) + } + } else { + pp = mdutil.GetString(md, passphrase) + } if pp == "" { l.md.signer, err = ssh.ParsePrivateKey(data) } else { diff --git a/observer/plugin/grpc.go b/observer/plugin/grpc.go index ecbad4b..4bdaabf 100644 --- a/observer/plugin/grpc.go +++ b/observer/plugin/grpc.go @@ -33,6 +33,7 @@ func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) observer.Obs conn, err := plugin.NewGRPCConn(addr, &options) if err != nil { log.Error(err) + return nil } p := &grpcPlugin{ diff --git a/observer/plugin/http.go b/observer/plugin/http.go index 321e35b..01e9a38 100644 --- a/observer/plugin/http.go +++ b/observer/plugin/http.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "github.com/go-gost/core/logger" "github.com/go-gost/core/observer" @@ -58,6 +59,9 @@ func NewHTTPPlugin(name string, url string, opts ...plugin.Option) observer.Obse opt(&options) } + if !strings.HasPrefix(url, "http") { + url = "http://" + url + } return &httpPlugin{ url: url, client: plugin.NewHTTPClient(&options), diff --git a/recorder/plugin/grpc.go b/recorder/plugin/grpc.go index 11fa028..5aa8544 100644 --- a/recorder/plugin/grpc.go +++ b/recorder/plugin/grpc.go @@ -69,6 +69,10 @@ func (p *grpcPlugin) Record(ctx context.Context, b []byte, opts ...recorder.Reco } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/registry/bypass.go b/registry/bypass.go index 3ad7b1e..59b167e 100644 --- a/registry/bypass.go +++ b/registry/bypass.go @@ -37,3 +37,11 @@ func (w *bypassWrapper) Contains(ctx context.Context, network, addr string, opts } return bp.Contains(ctx, network, addr, opts...) } + +func (p *bypassWrapper) IsWhitelist() bool { + bp := p.r.get(p.name) + if bp == nil { + return false + } + return bp.IsWhitelist() +} diff --git a/resolver/plugin/grpc.go b/resolver/plugin/grpc.go index 1d18c00..c6cbdac 100644 --- a/resolver/plugin/grpc.go +++ b/resolver/plugin/grpc.go @@ -70,6 +70,10 @@ func (p *grpcPlugin) Resolve(ctx context.Context, network, host string, opts ... } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/router/plugin/grpc.go b/router/plugin/grpc.go index d966f34..f413d15 100644 --- a/router/plugin/grpc.go +++ b/router/plugin/grpc.go @@ -63,6 +63,10 @@ func (p *grpcPlugin) GetRoute(ctx context.Context, dst net.IP, opts ...router.Op } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/sd/plugin/grpc.go b/sd/plugin/grpc.go index 0b08f70..0ef7c01 100644 --- a/sd/plugin/grpc.go +++ b/sd/plugin/grpc.go @@ -127,6 +127,10 @@ func (p *grpcPlugin) Get(ctx context.Context, name string) ([]*sd.Service, error } func (p *grpcPlugin) Close() error { + if p.conn == nil { + return nil + } + if closer, ok := p.conn.(io.Closer); ok { return closer.Close() } diff --git a/service/service.go b/service/service.go index 1f037f3..d7ccd44 100644 --- a/service/service.go +++ b/service/service.go @@ -24,15 +24,16 @@ import ( ) type options struct { - admission admission.Admission - recorders []recorder.RecorderObject - preUp []string - postUp []string - preDown []string - postDown []string - stats *stats.Stats - observer observer.Observer - logger logger.Logger + admission admission.Admission + recorders []recorder.RecorderObject + preUp []string + postUp []string + preDown []string + postDown []string + stats *stats.Stats + observer observer.Observer + observePeriod time.Duration + logger logger.Logger } type Option func(opts *options) @@ -85,6 +86,12 @@ func ObserverOption(observer observer.Observer) Option { } } +func ObservePeriodOption(period time.Duration) Option { + return func(opts *options) { + opts.observePeriod = period + } +} + func LoggerOption(logger logger.Logger) Option { return func(opts *options) { opts.logger = logger @@ -129,6 +136,10 @@ func (s *defaultService) Addr() net.Addr { func (s *defaultService) Serve() error { s.execCmds("post-up", s.options.postUp) s.setState(StateReady) + s.status.addEvent(Event{ + Time: time.Now(), + Message: fmt.Sprintf("service %s is listening on %s", s.name, s.listener.Addr()), + }) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -290,7 +301,12 @@ func (s *defaultService) observeStats(ctx context.Context) { return } - ticker := time.NewTicker(5 * time.Second) + d := s.options.observePeriod + if d < time.Millisecond { + d = 5 * time.Second + } + + ticker := time.NewTicker(d) defer ticker.Stop() for { diff --git a/service/status.go b/service/status.go index 0d3a913..424a77a 100644 --- a/service/status.go +++ b/service/status.go @@ -72,5 +72,8 @@ func (p *Status) addEvent(event Event) { } func (p *Status) Stats() *stats.Stats { + if p == nil { + return nil + } return p.stats }