Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
490e6b40f5 | ||
|
bc0d6953bc | ||
|
22e522e933 | ||
|
5e8a8a4b4d | ||
|
fa16373d66 | ||
|
1a776dc759 | ||
|
12ef82e41f | ||
|
3656ba9315 | ||
|
f73960ad36 | ||
|
c0a80400d2 | ||
|
2a75be91b0 | ||
|
4a4c64cc66 | ||
|
f2e32080e4 | ||
|
59b99a5b44 | ||
|
c1d0887a9b | ||
|
96f4d7bf5c | ||
|
949c98adc0 | ||
|
13c9e3ba97 | ||
|
3c1985e980 | ||
|
22537ff0d2 | ||
|
b583e29a56 | ||
|
ba2a83a51d | ||
|
74dc03bd66 | ||
|
b99292bed8 | ||
|
f9bfca76ed | ||
|
2ae0462822 | ||
|
423dd1e35d | ||
|
15f28c667a | ||
|
9bae597cbb | ||
|
6d819a0c06 | ||
|
784e4b2b01 | ||
|
e793b2743b | ||
|
ce60160cd7 | ||
|
118ee91c95 | ||
|
754b2fdeac | ||
|
40f709880d | ||
|
332a3a1cd0 | ||
|
f2a5089c29 | ||
|
55058573d6 | ||
|
41b5e62207 | ||
|
766ce7fdaa | ||
|
254875cd30 | ||
|
66104cd079 | ||
|
871afeeb6d | ||
|
c3b133a2de | ||
|
68f9690494 | ||
|
c35a79b2c9 | ||
|
a2ab48c423 | ||
|
902e24e7e8 | ||
|
d26bf4f05c | ||
|
5ea88aa5cf | ||
|
77a8f28edc | ||
|
7bf0537243 | ||
|
6ba22b0935 | ||
|
7da8b2a710 | ||
|
d9b7585856 | ||
|
63ad7f2354 | ||
|
25dcf536c6 | ||
|
3d2a7b7d3b | ||
|
5cc2c3de82 | ||
|
5ee7746aab | ||
|
3616a0d8a4 | ||
|
b5b39de62c | ||
|
43d37d0a5f | ||
|
8bdd7ee172 | ||
|
a618998b36 | ||
|
bb1a9908d4 | ||
|
01168e9846 | ||
|
53aab11764 | ||
|
c04c28e1fd | ||
|
936954ecf2 | ||
|
262ac0e9a5 | ||
|
c959fc2f73 | ||
|
4e1a70ec6d | ||
|
e1ae379048 | ||
|
1117723913 | ||
|
590a6ae6ff | ||
|
9fa95cc8b3 | ||
|
40e9a8ce7b | ||
|
4a1b225d2c | ||
|
c4b95b180e | ||
|
cc07ccb276 | ||
|
f847fa533e | ||
|
b1390dda1c | ||
|
8ef341dc88 | ||
|
94f8afdf45 | ||
|
6ea815eb36 | ||
|
ee80eedac3 | ||
|
7cc1ef436f | ||
|
116c7b51fe | ||
|
9be710dc19 | ||
|
e8be8d625a | ||
|
c87faa2017 | ||
|
79c15f2c37 | ||
|
44064e4dd1 | ||
|
c95edd6ed3 | ||
|
74639e9c4e | ||
|
88cc6ff4d5 | ||
|
330631fd79 | ||
|
42a4ccb24c | ||
|
2c82233b4f | ||
|
a465210bd6 | ||
|
f5a20fd0fc | ||
|
34b9e3b16e | ||
|
3038eb66d8 | ||
|
52aa2027d0 | ||
|
9584bdbf4c | ||
|
d7b7ac6357 | ||
|
6108000cce | ||
|
ca1f44d93c |
67
admission/plugin/grpc.go
Normal file
67
admission/plugin/grpc.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/admission"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/plugin/admission/proto"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type grpcPlugin struct {
|
||||||
|
conn grpc.ClientConnInterface
|
||||||
|
client proto.AdmissionClient
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGRPCPlugin creates an Admission plugin based on gRPC.
|
||||||
|
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) admission.Admission {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "admission",
|
||||||
|
"admission": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &grpcPlugin{
|
||||||
|
conn: conn,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
if conn != nil {
|
||||||
|
p.client = proto.NewAdmissionClient(conn)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
|
||||||
|
if p.client == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := p.client.Admit(ctx,
|
||||||
|
&proto.AdmissionRequest{
|
||||||
|
Addr: addr,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
p.log.Error(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return r.Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) Close() error {
|
||||||
|
if closer, ok := p.conn.(io.Closer); ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,71 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-gost/core/admission"
|
"github.com/go-gost/core/admission"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/admission/proto"
|
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type grpcPlugin struct {
|
|
||||||
conn grpc.ClientConnInterface
|
|
||||||
client proto.AdmissionClient
|
|
||||||
log logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGRPCPlugin creates an Admission plugin based on gRPC.
|
|
||||||
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) admission.Admission {
|
|
||||||
var options plugin.Options
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
log := logger.Default().WithFields(map[string]any{
|
|
||||||
"kind": "admission",
|
|
||||||
"admission": name,
|
|
||||||
})
|
|
||||||
conn, err := plugin.NewGRPCConn(addr, &options)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &grpcPlugin{
|
|
||||||
conn: conn,
|
|
||||||
log: log,
|
|
||||||
}
|
|
||||||
if conn != nil {
|
|
||||||
p.client = proto.NewAdmissionClient(conn)
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *grpcPlugin) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
|
|
||||||
if p.client == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := p.client.Admit(ctx,
|
|
||||||
&proto.AdmissionRequest{
|
|
||||||
Addr: addr,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
p.log.Error(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return r.Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *grpcPlugin) Close() error {
|
|
||||||
if closer, ok := p.conn.(io.Closer); ok {
|
|
||||||
return closer.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpPluginRequest struct {
|
type httpPluginRequest struct {
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
}
|
}
|
176
api/api.go
176
api/api.go
@ -2,6 +2,13 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/cors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-gost/core/auth"
|
||||||
|
"github.com/go-gost/core/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -13,3 +20,172 @@ type Response struct {
|
|||||||
Code int `json:"code,omitempty"`
|
Code int `json:"code,omitempty"`
|
||||||
Msg string `json:"msg,omitempty"`
|
Msg string `json:"msg,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
accessLog bool
|
||||||
|
pathPrefix string
|
||||||
|
auther auth.Authenticator
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*options)
|
||||||
|
|
||||||
|
func PathPrefixOption(pathPrefix string) Option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.pathPrefix = pathPrefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AccessLogOption(enable bool) Option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.accessLog = enable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AutherOption(auther auth.Authenticator) Option {
|
||||||
|
return func(o *options) {
|
||||||
|
o.auther = auther
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
s *http.Server
|
||||||
|
ln net.Listener
|
||||||
|
cclose chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(addr string, opts ...Option) (service.Service, error) {
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var options options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(
|
||||||
|
cors.New((cors.Config{
|
||||||
|
AllowAllOrigins: true,
|
||||||
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||||
|
AllowHeaders: []string{"*"},
|
||||||
|
AllowPrivateNetwork: true,
|
||||||
|
})),
|
||||||
|
gin.Recovery(),
|
||||||
|
)
|
||||||
|
if options.accessLog {
|
||||||
|
r.Use(mwLogger())
|
||||||
|
}
|
||||||
|
|
||||||
|
router := r.Group("")
|
||||||
|
if options.pathPrefix != "" {
|
||||||
|
router = router.Group(options.pathPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.StaticFS("/docs", http.FS(swaggerDoc))
|
||||||
|
|
||||||
|
config := router.Group("/config")
|
||||||
|
config.Use(mwBasicAuth(options.auther))
|
||||||
|
registerConfig(config)
|
||||||
|
|
||||||
|
return &server{
|
||||||
|
s: &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
},
|
||||||
|
ln: ln,
|
||||||
|
cclose: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Serve() error {
|
||||||
|
return s.s.Serve(s.ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Addr() net.Addr {
|
||||||
|
return s.ln.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) Close() error {
|
||||||
|
return s.s.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) IsClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-s.cclose:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerConfig(config *gin.RouterGroup) {
|
||||||
|
config.GET("", getConfig)
|
||||||
|
config.POST("", saveConfig)
|
||||||
|
|
||||||
|
config.POST("/services", createService)
|
||||||
|
config.PUT("/services/:service", updateService)
|
||||||
|
config.DELETE("/services/:service", deleteService)
|
||||||
|
|
||||||
|
config.POST("/chains", createChain)
|
||||||
|
config.PUT("/chains/:chain", updateChain)
|
||||||
|
config.DELETE("/chains/:chain", deleteChain)
|
||||||
|
|
||||||
|
config.POST("/hops", createHop)
|
||||||
|
config.PUT("/hops/:hop", updateHop)
|
||||||
|
config.DELETE("/hops/:hop", deleteHop)
|
||||||
|
|
||||||
|
config.POST("/authers", createAuther)
|
||||||
|
config.PUT("/authers/:auther", updateAuther)
|
||||||
|
config.DELETE("/authers/:auther", deleteAuther)
|
||||||
|
|
||||||
|
config.POST("/admissions", createAdmission)
|
||||||
|
config.PUT("/admissions/:admission", updateAdmission)
|
||||||
|
config.DELETE("/admissions/:admission", deleteAdmission)
|
||||||
|
|
||||||
|
config.POST("/bypasses", createBypass)
|
||||||
|
config.PUT("/bypasses/:bypass", updateBypass)
|
||||||
|
config.DELETE("/bypasses/:bypass", deleteBypass)
|
||||||
|
|
||||||
|
config.POST("/resolvers", createResolver)
|
||||||
|
config.PUT("/resolvers/:resolver", updateResolver)
|
||||||
|
config.DELETE("/resolvers/:resolver", deleteResolver)
|
||||||
|
|
||||||
|
config.POST("/hosts", createHosts)
|
||||||
|
config.PUT("/hosts/:hosts", updateHosts)
|
||||||
|
config.DELETE("/hosts/:hosts", deleteHosts)
|
||||||
|
|
||||||
|
config.POST("/ingresses", createIngress)
|
||||||
|
config.PUT("/ingresses/:ingress", updateIngress)
|
||||||
|
config.DELETE("/ingresses/:ingress", deleteIngress)
|
||||||
|
|
||||||
|
config.POST("/routers", createRouter)
|
||||||
|
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)
|
||||||
|
|
||||||
|
config.POST("/climiters", createConnLimiter)
|
||||||
|
config.PUT("/climiters/:limiter", updateConnLimiter)
|
||||||
|
config.DELETE("/climiters/:limiter", deleteConnLimiter)
|
||||||
|
|
||||||
|
config.POST("/rlimiters", createRateLimiter)
|
||||||
|
config.PUT("/rlimiters/:limiter", updateRateLimiter)
|
||||||
|
config.DELETE("/rlimiters/:limiter", deleteRateLimiter)
|
||||||
|
}
|
||||||
|
@ -7,9 +7,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-gost/core/observer/stats"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
|
"github.com/go-gost/x/registry"
|
||||||
|
"github.com/go-gost/x/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type serviceStatus interface {
|
||||||
|
Status() *service.Status
|
||||||
|
}
|
||||||
|
|
||||||
// swagger:parameters getConfigRequest
|
// swagger:parameters getConfigRequest
|
||||||
type getConfigRequest struct {
|
type getConfigRequest struct {
|
||||||
// output format, one of yaml|json, default is json.
|
// output format, one of yaml|json, default is json.
|
||||||
@ -37,6 +44,40 @@ func getConfig(ctx *gin.Context) {
|
|||||||
var req getConfigRequest
|
var req getConfigRequest
|
||||||
ctx.ShouldBindQuery(&req)
|
ctx.ShouldBindQuery(&req)
|
||||||
|
|
||||||
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
|
for _, svc := range c.Services {
|
||||||
|
if svc == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := registry.ServiceRegistry().Get(svc.Name)
|
||||||
|
ss, ok := s.(serviceStatus)
|
||||||
|
if ok && ss != nil {
|
||||||
|
status := ss.Status()
|
||||||
|
svc.Status = &config.ServiceStatus{
|
||||||
|
CreateTime: status.CreateTime().Unix(),
|
||||||
|
State: string(status.State()),
|
||||||
|
}
|
||||||
|
if st := status.Stats(); st != nil {
|
||||||
|
svc.Status.Stats = &config.ServiceStats{
|
||||||
|
TotalConns: st.Get(stats.KindTotalConns),
|
||||||
|
CurrentConns: st.Get(stats.KindCurrentConns),
|
||||||
|
TotalErrs: st.Get(stats.KindTotalErrs),
|
||||||
|
InputBytes: st.Get(stats.KindInputBytes),
|
||||||
|
OutputBytes: st.Get(stats.KindOutputBytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ev := range status.Events() {
|
||||||
|
if !ev.Time.IsZero() {
|
||||||
|
svc.Status.Events = append(svc.Status.Events, config.ServiceEvent{
|
||||||
|
Time: ev.Time.Unix(),
|
||||||
|
Msg: ev.Message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
var resp getConfigResponse
|
var resp getConfigResponse
|
||||||
resp.Config = config.Global()
|
resp.Config = config.Global()
|
||||||
|
|
||||||
@ -62,6 +103,9 @@ type saveConfigRequest struct {
|
|||||||
// output format, one of yaml|json, default is yaml.
|
// output format, one of yaml|json, default is yaml.
|
||||||
// in: query
|
// in: query
|
||||||
Format string `form:"format" json:"format"`
|
Format string `form:"format" json:"format"`
|
||||||
|
// file path, default is gost.yaml|gost.json in current working directory.
|
||||||
|
// in: query
|
||||||
|
Path string `form:"path" json:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// successful operation.
|
// successful operation.
|
||||||
@ -92,11 +136,15 @@ func saveConfig(ctx *gin.Context) {
|
|||||||
req.Format = "yaml"
|
req.Format = "yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if req.Path != "" {
|
||||||
|
file = req.Path
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.Create(file)
|
f, err := os.Create(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, &Error{
|
writeError(ctx, &Error{
|
||||||
statusCode: http.StatusInternalServerError,
|
statusCode: http.StatusInternalServerError,
|
||||||
Code: 40005,
|
Code: ErrCodeSaveConfigFailed,
|
||||||
Msg: fmt.Sprintf("create file: %s", err.Error()),
|
Msg: fmt.Sprintf("create file: %s", err.Error()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@ -106,8 +154,8 @@ func saveConfig(ctx *gin.Context) {
|
|||||||
if err := config.Global().Write(f, req.Format); err != nil {
|
if err := config.Global().Write(f, req.Format); err != nil {
|
||||||
writeError(ctx, &Error{
|
writeError(ctx, &Error{
|
||||||
statusCode: http.StatusInternalServerError,
|
statusCode: http.StatusInternalServerError,
|
||||||
Code: 40006,
|
Code: ErrCodeSaveConfigFailed,
|
||||||
Msg: fmt.Sprintf("write: %s", err.Error()),
|
Msg: fmt.Sprintf("save config: %s", err.Error()),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,15 +37,22 @@ func createAdmission(ctx *gin.Context) {
|
|||||||
var req createAdmissionRequest
|
var req createAdmissionRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseAdmission(&req.Data)
|
v := parser.ParseAdmission(&req.Data)
|
||||||
|
|
||||||
if err := registry.AdmissionRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.AdmissionRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,25 +96,27 @@ func updateAdmission(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.AdmissionRegistry().IsRegistered(req.Admission) {
|
name := strings.TrimSpace(req.Admission)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.AdmissionRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Admission
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseAdmission(&req.Data)
|
v := parser.ParseAdmission(&req.Data)
|
||||||
|
|
||||||
registry.AdmissionRegistry().Unregister(req.Admission)
|
registry.AdmissionRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.AdmissionRegistry().Register(req.Admission, v); err != nil {
|
if err := registry.AdmissionRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Admissions {
|
for i := range c.Admissions {
|
||||||
if c.Admissions[i].Name == req.Admission {
|
if c.Admissions[i].Name == name {
|
||||||
c.Admissions[i] = &req.Data
|
c.Admissions[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -145,17 +156,19 @@ func deleteAdmission(ctx *gin.Context) {
|
|||||||
var req deleteAdmissionRequest
|
var req deleteAdmissionRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.AdmissionRegistry().IsRegistered(req.Admission) {
|
name := strings.TrimSpace(req.Admission)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.AdmissionRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.AdmissionRegistry().Unregister(req.Admission)
|
registry.AdmissionRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
admissiones := c.Admissions
|
admissiones := c.Admissions
|
||||||
c.Admissions = nil
|
c.Admissions = nil
|
||||||
for _, s := range admissiones {
|
for _, s := range admissiones {
|
||||||
if s.Name == req.Admission {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Admissions = append(c.Admissions, s)
|
c.Admissions = append(c.Admissions, s)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,14 +37,21 @@ func createAuther(ctx *gin.Context) {
|
|||||||
var req createAutherRequest
|
var req createAutherRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseAuther(&req.Data)
|
v := parser.ParseAuther(&req.Data)
|
||||||
if err := registry.AutherRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.AutherRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,24 +95,26 @@ func updateAuther(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.AutherRegistry().IsRegistered(req.Auther) {
|
name := strings.TrimSpace(req.Auther)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.AutherRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Auther
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseAuther(&req.Data)
|
v := parser.ParseAuther(&req.Data)
|
||||||
registry.AutherRegistry().Unregister(req.Auther)
|
registry.AutherRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.AutherRegistry().Register(req.Auther, v); err != nil {
|
if err := registry.AutherRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Authers {
|
for i := range c.Authers {
|
||||||
if c.Authers[i].Name == req.Auther {
|
if c.Authers[i].Name == name {
|
||||||
c.Authers[i] = &req.Data
|
c.Authers[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -143,17 +154,19 @@ func deleteAuther(ctx *gin.Context) {
|
|||||||
var req deleteAutherRequest
|
var req deleteAutherRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.AutherRegistry().IsRegistered(req.Auther) {
|
name := strings.TrimSpace(req.Auther)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.AutherRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.AutherRegistry().Unregister(req.Auther)
|
registry.AutherRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
authers := c.Authers
|
authers := c.Authers
|
||||||
c.Authers = nil
|
c.Authers = nil
|
||||||
for _, s := range authers {
|
for _, s := range authers {
|
||||||
if s.Name == req.Auther {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Authers = append(c.Authers, s)
|
c.Authers = append(c.Authers, s)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,15 +37,22 @@ func createBypass(ctx *gin.Context) {
|
|||||||
var req createBypassRequest
|
var req createBypassRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseBypass(&req.Data)
|
v := parser.ParseBypass(&req.Data)
|
||||||
|
|
||||||
if err := registry.BypassRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.BypassRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,25 +96,27 @@ func updateBypass(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.BypassRegistry().IsRegistered(req.Bypass) {
|
name := strings.TrimSpace(req.Bypass)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.BypassRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Bypass
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseBypass(&req.Data)
|
v := parser.ParseBypass(&req.Data)
|
||||||
|
|
||||||
registry.BypassRegistry().Unregister(req.Bypass)
|
registry.BypassRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.BypassRegistry().Register(req.Bypass, v); err != nil {
|
if err := registry.BypassRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Bypasses {
|
for i := range c.Bypasses {
|
||||||
if c.Bypasses[i].Name == req.Bypass {
|
if c.Bypasses[i].Name == name {
|
||||||
c.Bypasses[i] = &req.Data
|
c.Bypasses[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -145,17 +156,19 @@ func deleteBypass(ctx *gin.Context) {
|
|||||||
var req deleteBypassRequest
|
var req deleteBypassRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.BypassRegistry().IsRegistered(req.Bypass) {
|
name := strings.TrimSpace(req.Bypass)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.BypassRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.BypassRegistry().Unregister(req.Bypass)
|
registry.BypassRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
bypasses := c.Bypasses
|
bypasses := c.Bypasses
|
||||||
c.Bypasses = nil
|
c.Bypasses = nil
|
||||||
for _, s := range bypasses {
|
for _, s := range bypasses {
|
||||||
if s.Name == req.Bypass {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Bypasses = append(c.Bypasses, s)
|
c.Bypasses = append(c.Bypasses, s)
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
parser "github.com/go-gost/x/config/parsing/chain"
|
parser "github.com/go-gost/x/config/parsing/chain"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
@ -35,19 +38,26 @@ func createChain(ctx *gin.Context) {
|
|||||||
var req createChainRequest
|
var req createChainRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := parser.ParseChain(&req.Data)
|
v, err := parser.ParseChain(&req.Data, logger.Default())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := registry.ChainRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.ChainRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,29 +102,31 @@ func updateChain(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.ChainRegistry().IsRegistered(req.Chain) {
|
name := strings.TrimSpace(req.Chain)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.ChainRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Chain
|
req.Data.Name = name
|
||||||
|
|
||||||
v, err := parser.ParseChain(&req.Data)
|
v, err := parser.ParseChain(&req.Data, logger.Default())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.ChainRegistry().Unregister(req.Chain)
|
registry.ChainRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.ChainRegistry().Register(req.Chain, v); err != nil {
|
if err := registry.ChainRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Chains {
|
for i := range c.Chains {
|
||||||
if c.Chains[i].Name == req.Chain {
|
if c.Chains[i].Name == name {
|
||||||
c.Chains[i] = &req.Data
|
c.Chains[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -154,17 +166,19 @@ func deleteChain(ctx *gin.Context) {
|
|||||||
var req deleteChainRequest
|
var req deleteChainRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.ChainRegistry().IsRegistered(req.Chain) {
|
name := strings.TrimSpace(req.Chain)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.ChainRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.ChainRegistry().Unregister(req.Chain)
|
registry.ChainRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
chains := c.Chains
|
chains := c.Chains
|
||||||
c.Chains = nil
|
c.Chains = nil
|
||||||
for _, s := range chains {
|
for _, s := range chains {
|
||||||
if s.Name == req.Chain {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Chains = append(c.Chains, s)
|
c.Chains = append(c.Chains, s)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,15 +37,21 @@ func createConnLimiter(ctx *gin.Context) {
|
|||||||
var req createConnLimiterRequest
|
var req createConnLimiterRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseConnLimiter(&req.Data)
|
v := parser.ParseConnLimiter(&req.Data)
|
||||||
|
if err := registry.ConnLimiterRegistry().Register(name, v); err != nil {
|
||||||
if err := registry.ConnLimiterRegistry().Register(req.Data.Name, v); err != nil {
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create limmiter %s failed: %s", name, err.Error())))
|
||||||
writeError(ctx, ErrDup)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,25 +95,27 @@ func updateConnLimiter(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.ConnLimiterRegistry().IsRegistered(req.Limiter) {
|
name := strings.TrimSpace(req.Limiter)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Limiter
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseConnLimiter(&req.Data)
|
v := parser.ParseConnLimiter(&req.Data)
|
||||||
|
|
||||||
registry.ConnLimiterRegistry().Unregister(req.Limiter)
|
registry.ConnLimiterRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.ConnLimiterRegistry().Register(req.Limiter, v); err != nil {
|
if err := registry.ConnLimiterRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.CLimiters {
|
for i := range c.CLimiters {
|
||||||
if c.CLimiters[i].Name == req.Limiter {
|
if c.CLimiters[i].Name == name {
|
||||||
c.CLimiters[i] = &req.Data
|
c.CLimiters[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -145,17 +155,19 @@ func deleteConnLimiter(ctx *gin.Context) {
|
|||||||
var req deleteConnLimiterRequest
|
var req deleteConnLimiterRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.ConnLimiterRegistry().IsRegistered(req.Limiter) {
|
name := strings.TrimSpace(req.Limiter)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.ConnLimiterRegistry().Unregister(req.Limiter)
|
registry.ConnLimiterRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
limiteres := c.CLimiters
|
limiteres := c.CLimiters
|
||||||
c.CLimiters = nil
|
c.CLimiters = nil
|
||||||
for _, s := range limiteres {
|
for _, s := range limiteres {
|
||||||
if s.Name == req.Limiter {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.CLimiters = append(c.CLimiters, s)
|
c.CLimiters = append(c.CLimiters, s)
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
parser "github.com/go-gost/x/config/parsing/hop"
|
parser "github.com/go-gost/x/config/parsing/hop"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
@ -35,19 +38,26 @@ func createHop(ctx *gin.Context) {
|
|||||||
var req createHopRequest
|
var req createHopRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := parser.ParseHop(&req.Data)
|
v, err := parser.ParseHop(&req.Data, logger.Default())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := registry.HopRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.HopRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,29 +102,30 @@ func updateHop(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.HopRegistry().IsRegistered(req.Hop) {
|
name := strings.TrimSpace(req.Hop)
|
||||||
writeError(ctx, ErrNotFound)
|
if !registry.HopRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Hop
|
req.Data.Name = name
|
||||||
|
|
||||||
v, err := parser.ParseHop(&req.Data)
|
v, err := parser.ParseHop(&req.Data, logger.Default())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.HopRegistry().Unregister(req.Hop)
|
registry.HopRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.HopRegistry().Register(req.Hop, v); err != nil {
|
if err := registry.HopRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Hops {
|
for i := range c.Hops {
|
||||||
if c.Hops[i].Name == req.Hop {
|
if c.Hops[i].Name == name {
|
||||||
c.Hops[i] = &req.Data
|
c.Hops[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -154,17 +165,19 @@ func deleteHop(ctx *gin.Context) {
|
|||||||
var req deleteHopRequest
|
var req deleteHopRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.HopRegistry().IsRegistered(req.Hop) {
|
name := strings.TrimSpace(req.Hop)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.HopRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.HopRegistry().Unregister(req.Hop)
|
registry.HopRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
hops := c.Hops
|
hops := c.Hops
|
||||||
c.Hops = nil
|
c.Hops = nil
|
||||||
for _, s := range hops {
|
for _, s := range hops {
|
||||||
if s.Name == req.Hop {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Hops = append(c.Hops, s)
|
c.Hops = append(c.Hops, s)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,15 +37,22 @@ func createHosts(ctx *gin.Context) {
|
|||||||
var req createHostsRequest
|
var req createHostsRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseHostMapper(&req.Data)
|
v := parser.ParseHostMapper(&req.Data)
|
||||||
|
|
||||||
if err := registry.HostsRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.HostsRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,25 +96,27 @@ func updateHosts(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.HostsRegistry().IsRegistered(req.Hosts) {
|
name := strings.TrimSpace(req.Hosts)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.HostsRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Hosts
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseHostMapper(&req.Data)
|
v := parser.ParseHostMapper(&req.Data)
|
||||||
|
|
||||||
registry.HostsRegistry().Unregister(req.Hosts)
|
registry.HostsRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.HostsRegistry().Register(req.Hosts, v); err != nil {
|
if err := registry.HostsRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Hosts {
|
for i := range c.Hosts {
|
||||||
if c.Hosts[i].Name == req.Hosts {
|
if c.Hosts[i].Name == name {
|
||||||
c.Hosts[i] = &req.Data
|
c.Hosts[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -145,17 +156,19 @@ func deleteHosts(ctx *gin.Context) {
|
|||||||
var req deleteHostsRequest
|
var req deleteHostsRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.HostsRegistry().IsRegistered(req.Hosts) {
|
name := strings.TrimSpace(req.Hosts)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.HostsRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.HostsRegistry().Unregister(req.Hosts)
|
registry.HostsRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
hosts := c.Hosts
|
hosts := c.Hosts
|
||||||
c.Hosts = nil
|
c.Hosts = nil
|
||||||
for _, s := range hosts {
|
for _, s := range hosts {
|
||||||
if s.Name == req.Hosts {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Hosts = append(c.Hosts, s)
|
c.Hosts = append(c.Hosts, s)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,15 +37,22 @@ func createIngress(ctx *gin.Context) {
|
|||||||
var req createIngressRequest
|
var req createIngressRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseIngress(&req.Data)
|
v := parser.ParseIngress(&req.Data)
|
||||||
|
|
||||||
if err := registry.IngressRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.IngressRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,25 +96,27 @@ func updateIngress(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.IngressRegistry().IsRegistered(req.Ingress) {
|
name := strings.TrimSpace(req.Ingress)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.IngressRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Ingress
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseIngress(&req.Data)
|
v := parser.ParseIngress(&req.Data)
|
||||||
|
|
||||||
registry.IngressRegistry().Unregister(req.Ingress)
|
registry.IngressRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.IngressRegistry().Register(req.Ingress, v); err != nil {
|
if err := registry.IngressRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Ingresses {
|
for i := range c.Ingresses {
|
||||||
if c.Ingresses[i].Name == req.Ingress {
|
if c.Ingresses[i].Name == name {
|
||||||
c.Ingresses[i] = &req.Data
|
c.Ingresses[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -145,17 +156,19 @@ func deleteIngress(ctx *gin.Context) {
|
|||||||
var req deleteIngressRequest
|
var req deleteIngressRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.IngressRegistry().IsRegistered(req.Ingress) {
|
name := strings.TrimSpace(req.Ingress)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.IngressRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.IngressRegistry().Unregister(req.Ingress)
|
registry.IngressRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
ingresses := c.Ingresses
|
ingresses := c.Ingresses
|
||||||
c.Ingresses = nil
|
c.Ingresses = nil
|
||||||
for _, s := range ingresses {
|
for _, s := range ingresses {
|
||||||
if s.Name == req.Ingress {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Ingresses = append(c.Ingresses, s)
|
c.Ingresses = append(c.Ingresses, s)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,15 +37,22 @@ func createLimiter(ctx *gin.Context) {
|
|||||||
var req createLimiterRequest
|
var req createLimiterRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseTrafficLimiter(&req.Data)
|
v := parser.ParseTrafficLimiter(&req.Data)
|
||||||
|
|
||||||
if err := registry.TrafficLimiterRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,25 +96,27 @@ func updateLimiter(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.TrafficLimiterRegistry().IsRegistered(req.Limiter) {
|
name := strings.TrimSpace(req.Limiter)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Limiter
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseTrafficLimiter(&req.Data)
|
v := parser.ParseTrafficLimiter(&req.Data)
|
||||||
|
|
||||||
registry.TrafficLimiterRegistry().Unregister(req.Limiter)
|
registry.TrafficLimiterRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.TrafficLimiterRegistry().Register(req.Limiter, v); err != nil {
|
if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Limiters {
|
for i := range c.Limiters {
|
||||||
if c.Limiters[i].Name == req.Limiter {
|
if c.Limiters[i].Name == name {
|
||||||
c.Limiters[i] = &req.Data
|
c.Limiters[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -145,17 +156,19 @@ func deleteLimiter(ctx *gin.Context) {
|
|||||||
var req deleteLimiterRequest
|
var req deleteLimiterRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.TrafficLimiterRegistry().IsRegistered(req.Limiter) {
|
name := strings.TrimSpace(req.Limiter)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.TrafficLimiterRegistry().Unregister(req.Limiter)
|
registry.TrafficLimiterRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
limiteres := c.Limiters
|
limiteres := c.Limiters
|
||||||
c.Limiters = nil
|
c.Limiters = nil
|
||||||
for _, s := range limiteres {
|
for _, s := range limiteres {
|
||||||
if s.Name == req.Limiter {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Limiters = append(c.Limiters, s)
|
c.Limiters = append(c.Limiters, s)
|
||||||
|
182
api/config_observer.go
Normal file
182
api/config_observer.go
Normal file
@ -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",
|
||||||
|
})
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,15 +37,22 @@ func createRateLimiter(ctx *gin.Context) {
|
|||||||
var req createRateLimiterRequest
|
var req createRateLimiterRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v := parser.ParseRateLimiter(&req.Data)
|
v := parser.ParseRateLimiter(&req.Data)
|
||||||
|
|
||||||
if err := registry.RateLimiterRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.RateLimiterRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,25 +96,27 @@ func updateRateLimiter(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) {
|
name := strings.TrimSpace(req.Limiter)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.RateLimiterRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Limiter
|
req.Data.Name = name
|
||||||
|
|
||||||
v := parser.ParseRateLimiter(&req.Data)
|
v := parser.ParseRateLimiter(&req.Data)
|
||||||
|
|
||||||
registry.RateLimiterRegistry().Unregister(req.Limiter)
|
registry.RateLimiterRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.RateLimiterRegistry().Register(req.Limiter, v); err != nil {
|
if err := registry.RateLimiterRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.RLimiters {
|
for i := range c.RLimiters {
|
||||||
if c.RLimiters[i].Name == req.Limiter {
|
if c.RLimiters[i].Name == name {
|
||||||
c.RLimiters[i] = &req.Data
|
c.RLimiters[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -145,17 +156,19 @@ func deleteRateLimiter(ctx *gin.Context) {
|
|||||||
var req deleteRateLimiterRequest
|
var req deleteRateLimiterRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) {
|
name := strings.TrimSpace(req.Limiter)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.RateLimiterRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.RateLimiterRegistry().Unregister(req.Limiter)
|
registry.RateLimiterRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
limiteres := c.RLimiters
|
limiteres := c.RLimiters
|
||||||
c.RLimiters = nil
|
c.RLimiters = nil
|
||||||
for _, s := range limiteres {
|
for _, s := range limiteres {
|
||||||
if s.Name == req.Limiter {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.RLimiters = append(c.RLimiters, s)
|
c.RLimiters = append(c.RLimiters, s)
|
||||||
|
181
api/config_recorder.go
Normal file
181
api/config_recorder.go
Normal file
@ -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",
|
||||||
|
})
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,19 +37,26 @@ func createResolver(ctx *gin.Context) {
|
|||||||
var req createResolverRequest
|
var req createResolverRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := parser.ParseResolver(&req.Data)
|
v, err := parser.ParseResolver(&req.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := registry.ResolverRegistry().Register(req.Data.Name, v); err != nil {
|
if err := registry.ResolverRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,29 +100,31 @@ func updateResolver(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if !registry.ResolverRegistry().IsRegistered(req.Resolver) {
|
name := strings.TrimSpace(req.Resolver)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.ResolverRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Data.Name = req.Resolver
|
req.Data.Name = name
|
||||||
|
|
||||||
v, err := parser.ParseResolver(&req.Data)
|
v, err := parser.ParseResolver(&req.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.ResolverRegistry().Unregister(req.Resolver)
|
registry.ResolverRegistry().Unregister(name)
|
||||||
|
|
||||||
if err := registry.ResolverRegistry().Register(req.Resolver, v); err != nil {
|
if err := registry.ResolverRegistry().Register(name, v); err != nil {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Resolvers {
|
for i := range c.Resolvers {
|
||||||
if c.Resolvers[i].Name == req.Resolver {
|
if c.Resolvers[i].Name == name {
|
||||||
c.Resolvers[i] = &req.Data
|
c.Resolvers[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -153,17 +164,19 @@ func deleteResolver(ctx *gin.Context) {
|
|||||||
var req deleteResolverRequest
|
var req deleteResolverRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
if !registry.ResolverRegistry().IsRegistered(req.Resolver) {
|
name := strings.TrimSpace(req.Resolver)
|
||||||
writeError(ctx, ErrNotFound)
|
|
||||||
|
if !registry.ResolverRegistry().IsRegistered(name) {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
registry.ResolverRegistry().Unregister(req.Resolver)
|
registry.ResolverRegistry().Unregister(name)
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
resolvers := c.Resolvers
|
resolvers := c.Resolvers
|
||||||
c.Resolvers = nil
|
c.Resolvers = nil
|
||||||
for _, s := range resolvers {
|
for _, s := range resolvers {
|
||||||
if s.Name == req.Resolver {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Resolvers = append(c.Resolvers, s)
|
c.Resolvers = append(c.Resolvers, s)
|
||||||
|
182
api/config_router.go
Normal file
182
api/config_router.go
Normal file
@ -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/router"
|
||||||
|
"github.com/go-gost/x/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// swagger:parameters createRouterRequest
|
||||||
|
type createRouterRequest struct {
|
||||||
|
// in: body
|
||||||
|
Data config.RouterConfig `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// successful operation.
|
||||||
|
// swagger:response createRouterResponse
|
||||||
|
type createRouterResponse struct {
|
||||||
|
Data Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRouter(ctx *gin.Context) {
|
||||||
|
// swagger:route POST /config/routers Router createRouterRequest
|
||||||
|
//
|
||||||
|
// Create a new router, the name of the router must be unique in router list.
|
||||||
|
//
|
||||||
|
// Security:
|
||||||
|
// basicAuth: []
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: createRouterResponse
|
||||||
|
|
||||||
|
var req createRouterRequest
|
||||||
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
|
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(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 {
|
||||||
|
c.Routers = append(c.Routers, &req.Data)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, Response{
|
||||||
|
Msg: "OK",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:parameters updateRouterRequest
|
||||||
|
type updateRouterRequest struct {
|
||||||
|
// in: path
|
||||||
|
// required: true
|
||||||
|
Router string `uri:"router" json:"router"`
|
||||||
|
// in: body
|
||||||
|
Data config.RouterConfig `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// successful operation.
|
||||||
|
// swagger:response updateRouterResponse
|
||||||
|
type updateRouterResponse struct {
|
||||||
|
Data Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateRouter(ctx *gin.Context) {
|
||||||
|
// swagger:route PUT /config/routers/{router} Router updateRouterRequest
|
||||||
|
//
|
||||||
|
// Update router by name, the router must already exist.
|
||||||
|
//
|
||||||
|
// Security:
|
||||||
|
// basicAuth: []
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: updateRouterResponse
|
||||||
|
|
||||||
|
var req updateRouterRequest
|
||||||
|
ctx.ShouldBindUri(&req)
|
||||||
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
|
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 = name
|
||||||
|
|
||||||
|
v := parser.ParseRouter(&req.Data)
|
||||||
|
|
||||||
|
registry.RouterRegistry().Unregister(name)
|
||||||
|
|
||||||
|
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 == name {
|
||||||
|
c.Routers[i] = &req.Data
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, Response{
|
||||||
|
Msg: "OK",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:parameters deleteRouterRequest
|
||||||
|
type deleteRouterRequest struct {
|
||||||
|
// in: path
|
||||||
|
// required: true
|
||||||
|
Router string `uri:"router" json:"router"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// successful operation.
|
||||||
|
// swagger:response deleteRouterResponse
|
||||||
|
type deleteRouterResponse struct {
|
||||||
|
Data Response
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteRouter(ctx *gin.Context) {
|
||||||
|
// swagger:route DELETE /config/routers/{router} Router deleteRouterRequest
|
||||||
|
//
|
||||||
|
// Delete router by name.
|
||||||
|
//
|
||||||
|
// Security:
|
||||||
|
// basicAuth: []
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: deleteRouterResponse
|
||||||
|
|
||||||
|
var req deleteRouterRequest
|
||||||
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
|
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(name)
|
||||||
|
|
||||||
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
|
routeres := c.Routers
|
||||||
|
c.Routers = nil
|
||||||
|
for _, s := range routeres {
|
||||||
|
if s.Name == name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.Routers = append(c.Routers, s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, Response{
|
||||||
|
Msg: "OK",
|
||||||
|
})
|
||||||
|
}
|
182
api/config_sd.go
Normal file
182
api/config_sd.go
Normal file
@ -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",
|
||||||
|
})
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
@ -35,25 +37,27 @@ func createService(ctx *gin.Context) {
|
|||||||
var req createServiceRequest
|
var req createServiceRequest
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
if req.Data.Name == "" {
|
name := strings.TrimSpace(req.Data.Name)
|
||||||
writeError(ctx, ErrInvalid)
|
if name == "" {
|
||||||
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
req.Data.Name = name
|
||||||
|
|
||||||
if registry.ServiceRegistry().IsRegistered(req.Data.Name) {
|
if registry.ServiceRegistry().IsRegistered(name) {
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
svc, err := parser.ParseService(&req.Data)
|
svc, err := parser.ParseService(&req.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := registry.ServiceRegistry().Register(req.Data.Name, svc); err != nil {
|
if err := registry.ServiceRegistry().Register(name, svc); err != nil {
|
||||||
svc.Close()
|
svc.Close()
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,26 +103,28 @@ func updateService(ctx *gin.Context) {
|
|||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
ctx.ShouldBindJSON(&req.Data)
|
ctx.ShouldBindJSON(&req.Data)
|
||||||
|
|
||||||
old := registry.ServiceRegistry().Get(req.Service)
|
name := strings.TrimSpace(req.Service)
|
||||||
|
|
||||||
|
old := registry.ServiceRegistry().Get(name)
|
||||||
if old == nil {
|
if old == nil {
|
||||||
writeError(ctx, ErrNotFound)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
old.Close()
|
old.Close()
|
||||||
|
|
||||||
req.Data.Name = req.Service
|
req.Data.Name = name
|
||||||
|
|
||||||
svc, err := parser.ParseService(&req.Data)
|
svc, err := parser.ParseService(&req.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(ctx, ErrCreate)
|
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
||||||
return
|
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()
|
svc.Close()
|
||||||
writeError(ctx, ErrDup)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +132,7 @@ func updateService(ctx *gin.Context) {
|
|||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
for i := range c.Services {
|
for i := range c.Services {
|
||||||
if c.Services[i].Name == req.Service {
|
if c.Services[i].Name == name {
|
||||||
c.Services[i] = &req.Data
|
c.Services[i] = &req.Data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -166,20 +172,22 @@ func deleteService(ctx *gin.Context) {
|
|||||||
var req deleteServiceRequest
|
var req deleteServiceRequest
|
||||||
ctx.ShouldBindUri(&req)
|
ctx.ShouldBindUri(&req)
|
||||||
|
|
||||||
svc := registry.ServiceRegistry().Get(req.Service)
|
name := strings.TrimSpace(req.Service)
|
||||||
|
|
||||||
|
svc := registry.ServiceRegistry().Get(name)
|
||||||
if svc == nil {
|
if svc == nil {
|
||||||
writeError(ctx, ErrNotFound)
|
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.ServiceRegistry().Unregister(req.Service)
|
registry.ServiceRegistry().Unregister(name)
|
||||||
svc.Close()
|
svc.Close()
|
||||||
|
|
||||||
config.OnUpdate(func(c *config.Config) error {
|
config.OnUpdate(func(c *config.Config) error {
|
||||||
services := c.Services
|
services := c.Services
|
||||||
c.Services = nil
|
c.Services = nil
|
||||||
for _, s := range services {
|
for _, s := range services {
|
||||||
if s.Name == req.Service {
|
if s.Name == name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Services = append(c.Services, s)
|
c.Services = append(c.Services, s)
|
||||||
|
18
api/error.go
18
api/error.go
@ -15,6 +15,16 @@ var (
|
|||||||
ErrSave = &Error{statusCode: http.StatusInternalServerError, Code: 40005, Msg: "save config failed"}
|
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.
|
// Error is an api error.
|
||||||
type Error struct {
|
type Error struct {
|
||||||
statusCode int
|
statusCode int
|
||||||
@ -22,6 +32,14 @@ type Error struct {
|
|||||||
Msg string `json:"msg"`
|
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 {
|
func (e *Error) Error() string {
|
||||||
b, _ := json.Marshal(e)
|
b, _ := json.Marshal(e)
|
||||||
return string(b)
|
return string(b)
|
||||||
|
@ -36,7 +36,12 @@ func mwBasicAuth(auther auth.Authenticator) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
u, p, _ := c.Request.BasicAuth()
|
u, p, _ := c.Request.BasicAuth()
|
||||||
if _, ok := auther.Authenticate(c, u, p); !ok {
|
if _, ok := auther.Authenticate(c, u, p); !ok {
|
||||||
c.AbortWithStatus(http.StatusUnauthorized)
|
c.Writer.Header().Set("WWW-Authenticate", "Basic")
|
||||||
|
c.JSON(http.StatusUnauthorized, Response{
|
||||||
|
Code: http.StatusUnauthorized,
|
||||||
|
Msg: "Unauthorized",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
152
api/service.go
152
api/service.go
@ -1,152 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-contrib/cors"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/go-gost/core/auth"
|
|
||||||
"github.com/go-gost/core/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type options struct {
|
|
||||||
accessLog bool
|
|
||||||
pathPrefix string
|
|
||||||
auther auth.Authenticator
|
|
||||||
}
|
|
||||||
|
|
||||||
type Option func(*options)
|
|
||||||
|
|
||||||
func PathPrefixOption(pathPrefix string) Option {
|
|
||||||
return func(o *options) {
|
|
||||||
o.pathPrefix = pathPrefix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AccessLogOption(enable bool) Option {
|
|
||||||
return func(o *options) {
|
|
||||||
o.accessLog = enable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func AutherOption(auther auth.Authenticator) Option {
|
|
||||||
return func(o *options) {
|
|
||||||
o.auther = auther
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
s *http.Server
|
|
||||||
ln net.Listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewService(addr string, opts ...Option) (service.Service, error) {
|
|
||||||
ln, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var options options
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
|
|
||||||
r := gin.New()
|
|
||||||
r.Use(
|
|
||||||
cors.New((cors.Config{
|
|
||||||
AllowAllOrigins: true,
|
|
||||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
|
||||||
AllowHeaders: []string{"*"},
|
|
||||||
})),
|
|
||||||
gin.Recovery(),
|
|
||||||
)
|
|
||||||
if options.accessLog {
|
|
||||||
r.Use(mwLogger())
|
|
||||||
}
|
|
||||||
|
|
||||||
router := r.Group("")
|
|
||||||
if options.pathPrefix != "" {
|
|
||||||
router = router.Group(options.pathPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
router.StaticFS("/docs", http.FS(swaggerDoc))
|
|
||||||
|
|
||||||
config := router.Group("/config")
|
|
||||||
config.Use(mwBasicAuth(options.auther))
|
|
||||||
registerConfig(config)
|
|
||||||
|
|
||||||
return &server{
|
|
||||||
s: &http.Server{
|
|
||||||
Handler: r,
|
|
||||||
},
|
|
||||||
ln: ln,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) Serve() error {
|
|
||||||
return s.s.Serve(s.ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) Addr() net.Addr {
|
|
||||||
return s.ln.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) Close() error {
|
|
||||||
return s.s.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerConfig(config *gin.RouterGroup) {
|
|
||||||
config.GET("", getConfig)
|
|
||||||
config.POST("", saveConfig)
|
|
||||||
|
|
||||||
config.POST("/services", createService)
|
|
||||||
config.PUT("/services/:service", updateService)
|
|
||||||
config.DELETE("/services/:service", deleteService)
|
|
||||||
|
|
||||||
config.POST("/chains", createChain)
|
|
||||||
config.PUT("/chains/:chain", updateChain)
|
|
||||||
config.DELETE("/chains/:chain", deleteChain)
|
|
||||||
|
|
||||||
config.POST("/hops", createHop)
|
|
||||||
config.PUT("/hops/:hop", updateHop)
|
|
||||||
config.DELETE("/hops/:hop", deleteHop)
|
|
||||||
|
|
||||||
config.POST("/authers", createAuther)
|
|
||||||
config.PUT("/authers/:auther", updateAuther)
|
|
||||||
config.DELETE("/authers/:auther", deleteAuther)
|
|
||||||
|
|
||||||
config.POST("/admissions", createAdmission)
|
|
||||||
config.PUT("/admissions/:admission", updateAdmission)
|
|
||||||
config.DELETE("/admissions/:admission", deleteAdmission)
|
|
||||||
|
|
||||||
config.POST("/bypasses", createBypass)
|
|
||||||
config.PUT("/bypasses/:bypass", updateBypass)
|
|
||||||
config.DELETE("/bypasses/:bypass", deleteBypass)
|
|
||||||
|
|
||||||
config.POST("/resolvers", createResolver)
|
|
||||||
config.PUT("/resolvers/:resolver", updateResolver)
|
|
||||||
config.DELETE("/resolvers/:resolver", deleteResolver)
|
|
||||||
|
|
||||||
config.POST("/hosts", createHosts)
|
|
||||||
config.PUT("/hosts/:hosts", updateHosts)
|
|
||||||
config.DELETE("/hosts/:hosts", deleteHosts)
|
|
||||||
|
|
||||||
config.POST("/ingresses", createIngress)
|
|
||||||
config.PUT("/ingresses/:ingress", updateIngress)
|
|
||||||
config.DELETE("/ingresses/:ingress", deleteIngress)
|
|
||||||
|
|
||||||
config.POST("/limiters", createLimiter)
|
|
||||||
config.PUT("/limiters/:limiter", updateLimiter)
|
|
||||||
config.DELETE("/limiters/:limiter", deleteLimiter)
|
|
||||||
|
|
||||||
config.POST("/climiters", createConnLimiter)
|
|
||||||
config.PUT("/climiters/:limiter", updateConnLimiter)
|
|
||||||
config.DELETE("/climiters/:limiter", deleteConnLimiter)
|
|
||||||
|
|
||||||
config.POST("/rlimiters", createRateLimiter)
|
|
||||||
config.PUT("/rlimiters/:limiter", updateRateLimiter)
|
|
||||||
config.DELETE("/rlimiters/:limiter", deleteRateLimiter)
|
|
||||||
}
|
|
657
api/swagger.yaml
657
api/swagger.yaml
@ -34,6 +34,8 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
redis:
|
redis:
|
||||||
$ref: '#/definitions/RedisLoader'
|
$ref: '#/definitions/RedisLoader'
|
||||||
reload:
|
reload:
|
||||||
@ -71,6 +73,8 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
redis:
|
redis:
|
||||||
$ref: '#/definitions/RedisLoader'
|
$ref: '#/definitions/RedisLoader'
|
||||||
reload:
|
reload:
|
||||||
@ -91,6 +95,8 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
redis:
|
redis:
|
||||||
$ref: '#/definitions/RedisLoader'
|
$ref: '#/definitions/RedisLoader'
|
||||||
reload:
|
reload:
|
||||||
@ -107,9 +113,6 @@ definitions:
|
|||||||
ChainConfig:
|
ChainConfig:
|
||||||
properties:
|
properties:
|
||||||
hops:
|
hops:
|
||||||
description: |-
|
|
||||||
REMOVED since beta.6
|
|
||||||
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/HopConfig'
|
$ref: '#/definitions/HopConfig'
|
||||||
type: array
|
type: array
|
||||||
@ -185,8 +188,18 @@ definitions:
|
|||||||
x-go-name: Limiters
|
x-go-name: Limiters
|
||||||
log:
|
log:
|
||||||
$ref: '#/definitions/LogConfig'
|
$ref: '#/definitions/LogConfig'
|
||||||
|
loggers:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/LoggerConfig'
|
||||||
|
type: array
|
||||||
|
x-go-name: Loggers
|
||||||
metrics:
|
metrics:
|
||||||
$ref: '#/definitions/MetricsConfig'
|
$ref: '#/definitions/MetricsConfig'
|
||||||
|
observers:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/ObserverConfig'
|
||||||
|
type: array
|
||||||
|
x-go-name: Observers
|
||||||
profiling:
|
profiling:
|
||||||
$ref: '#/definitions/ProfilingConfig'
|
$ref: '#/definitions/ProfilingConfig'
|
||||||
recorders:
|
recorders:
|
||||||
@ -204,6 +217,16 @@ definitions:
|
|||||||
$ref: '#/definitions/LimiterConfig'
|
$ref: '#/definitions/LimiterConfig'
|
||||||
type: array
|
type: array
|
||||||
x-go-name: RLimiters
|
x-go-name: RLimiters
|
||||||
|
routers:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/RouterConfig'
|
||||||
|
type: array
|
||||||
|
x-go-name: Routers
|
||||||
|
sds:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/SDConfig'
|
||||||
|
type: array
|
||||||
|
x-go-name: SDs
|
||||||
services:
|
services:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/ServiceConfig'
|
$ref: '#/definitions/ServiceConfig'
|
||||||
@ -273,6 +296,8 @@ definitions:
|
|||||||
addr:
|
addr:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Addr
|
x-go-name: Addr
|
||||||
|
auth:
|
||||||
|
$ref: '#/definitions/AuthConfig'
|
||||||
bypass:
|
bypass:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Bypass
|
x-go-name: Bypass
|
||||||
@ -281,20 +306,44 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
x-go-name: Bypasses
|
x-go-name: Bypasses
|
||||||
|
filter:
|
||||||
|
$ref: '#/definitions/NodeFilterConfig'
|
||||||
host:
|
host:
|
||||||
|
description: DEPRECATED by filter.host
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Host
|
x-go-name: Host
|
||||||
|
http:
|
||||||
|
$ref: '#/definitions/HTTPNodeConfig'
|
||||||
|
metadata:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
|
x-go-name: Metadata
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
network:
|
||||||
|
type: string
|
||||||
|
x-go-name: Network
|
||||||
|
path:
|
||||||
|
description: DEPRECATED by filter.path
|
||||||
|
type: string
|
||||||
|
x-go-name: Path
|
||||||
protocol:
|
protocol:
|
||||||
|
description: DEPRECATED by filter.protocol
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Protocol
|
x-go-name: Protocol
|
||||||
|
tls:
|
||||||
|
$ref: '#/definitions/TLSNodeConfig'
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
ForwarderConfig:
|
ForwarderConfig:
|
||||||
properties:
|
properties:
|
||||||
|
hop:
|
||||||
|
description: the referenced hop name
|
||||||
|
type: string
|
||||||
|
x-go-name: Hop
|
||||||
name:
|
name:
|
||||||
|
description: DEPRECATED by hop field
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
nodes:
|
nodes:
|
||||||
@ -304,12 +353,6 @@ definitions:
|
|||||||
x-go-name: Nodes
|
x-go-name: Nodes
|
||||||
selector:
|
selector:
|
||||||
$ref: '#/definitions/SelectorConfig'
|
$ref: '#/definitions/SelectorConfig'
|
||||||
targets:
|
|
||||||
description: DEPRECATED by nodes since beta.4
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
x-go-name: Targets
|
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
HTTPLoader:
|
HTTPLoader:
|
||||||
@ -321,6 +364,42 @@ definitions:
|
|||||||
x-go-name: URL
|
x-go-name: URL
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
|
HTTPNodeConfig:
|
||||||
|
properties:
|
||||||
|
auth:
|
||||||
|
$ref: '#/definitions/AuthConfig'
|
||||||
|
header:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
x-go-name: Header
|
||||||
|
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:
|
||||||
|
properties:
|
||||||
|
timeout:
|
||||||
|
$ref: '#/definitions/Duration'
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
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:
|
HandlerConfig:
|
||||||
properties:
|
properties:
|
||||||
auth:
|
auth:
|
||||||
@ -338,13 +417,16 @@ definitions:
|
|||||||
x-go-name: Chain
|
x-go-name: Chain
|
||||||
chainGroup:
|
chainGroup:
|
||||||
$ref: '#/definitions/ChainGroupConfig'
|
$ref: '#/definitions/ChainGroupConfig'
|
||||||
ingress:
|
limiter:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Ingress
|
x-go-name: Limiter
|
||||||
metadata:
|
metadata:
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
type: object
|
type: object
|
||||||
x-go-name: Metadata
|
x-go-name: Metadata
|
||||||
|
observer:
|
||||||
|
type: string
|
||||||
|
x-go-name: Observer
|
||||||
retries:
|
retries:
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
@ -366,9 +448,13 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
x-go-name: Bypasses
|
x-go-name: Bypasses
|
||||||
|
file:
|
||||||
|
$ref: '#/definitions/FileLoader'
|
||||||
hosts:
|
hosts:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Hosts
|
x-go-name: Hosts
|
||||||
|
http:
|
||||||
|
$ref: '#/definitions/HTTPLoader'
|
||||||
interface:
|
interface:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Interface
|
x-go-name: Interface
|
||||||
@ -380,6 +466,12 @@ definitions:
|
|||||||
$ref: '#/definitions/NodeConfig'
|
$ref: '#/definitions/NodeConfig'
|
||||||
type: array
|
type: array
|
||||||
x-go-name: Nodes
|
x-go-name: Nodes
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
|
redis:
|
||||||
|
$ref: '#/definitions/RedisLoader'
|
||||||
|
reload:
|
||||||
|
$ref: '#/definitions/Duration'
|
||||||
resolver:
|
resolver:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Resolver
|
x-go-name: Resolver
|
||||||
@ -418,6 +510,8 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
redis:
|
redis:
|
||||||
$ref: '#/definitions/RedisLoader'
|
$ref: '#/definitions/RedisLoader'
|
||||||
reload:
|
reload:
|
||||||
@ -433,6 +527,8 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
redis:
|
redis:
|
||||||
$ref: '#/definitions/RedisLoader'
|
$ref: '#/definitions/RedisLoader'
|
||||||
reload:
|
reload:
|
||||||
@ -468,6 +564,8 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
redis:
|
redis:
|
||||||
$ref: '#/definitions/RedisLoader'
|
$ref: '#/definitions/RedisLoader'
|
||||||
reload:
|
reload:
|
||||||
@ -559,11 +657,25 @@ definitions:
|
|||||||
x-go-name: MaxSize
|
x-go-name: MaxSize
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
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:
|
MetricsConfig:
|
||||||
properties:
|
properties:
|
||||||
addr:
|
addr:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Addr
|
x-go-name: Addr
|
||||||
|
auth:
|
||||||
|
$ref: '#/definitions/AuthConfig'
|
||||||
|
auther:
|
||||||
|
type: string
|
||||||
|
x-go-name: Auther
|
||||||
path:
|
path:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Path
|
x-go-name: Path
|
||||||
@ -574,6 +686,9 @@ definitions:
|
|||||||
addr:
|
addr:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Addr
|
x-go-name: Addr
|
||||||
|
async:
|
||||||
|
type: boolean
|
||||||
|
x-go-name: Async
|
||||||
chain:
|
chain:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Chain
|
x-go-name: Chain
|
||||||
@ -583,6 +698,9 @@ definitions:
|
|||||||
hostname:
|
hostname:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Hostname
|
x-go-name: Hostname
|
||||||
|
only:
|
||||||
|
type: string
|
||||||
|
x-go-name: Only
|
||||||
prefer:
|
prefer:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Prefer
|
x-go-name: Prefer
|
||||||
@ -609,12 +727,13 @@ definitions:
|
|||||||
$ref: '#/definitions/ConnectorConfig'
|
$ref: '#/definitions/ConnectorConfig'
|
||||||
dialer:
|
dialer:
|
||||||
$ref: '#/definitions/DialerConfig'
|
$ref: '#/definitions/DialerConfig'
|
||||||
host:
|
filter:
|
||||||
type: string
|
$ref: '#/definitions/NodeFilterConfig'
|
||||||
x-go-name: Host
|
|
||||||
hosts:
|
hosts:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Hosts
|
x-go-name: Hosts
|
||||||
|
http:
|
||||||
|
$ref: '#/definitions/HTTPNodeConfig'
|
||||||
interface:
|
interface:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Interface
|
x-go-name: Interface
|
||||||
@ -625,14 +744,55 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
protocol:
|
network:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Protocol
|
x-go-name: Network
|
||||||
resolver:
|
resolver:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Resolver
|
x-go-name: Resolver
|
||||||
sockopts:
|
sockopts:
|
||||||
$ref: '#/definitions/SockOptsConfig'
|
$ref: '#/definitions/SockOptsConfig'
|
||||||
|
tls:
|
||||||
|
$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:
|
||||||
|
type: string
|
||||||
|
x-go-name: Addr
|
||||||
|
timeout:
|
||||||
|
$ref: '#/definitions/Duration'
|
||||||
|
tls:
|
||||||
|
$ref: '#/definitions/TLSConfig'
|
||||||
|
token:
|
||||||
|
type: string
|
||||||
|
x-go-name: Token
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
x-go-name: Type
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
ProfilingConfig:
|
ProfilingConfig:
|
||||||
@ -646,15 +806,24 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
file:
|
file:
|
||||||
$ref: '#/definitions/FileRecorder'
|
$ref: '#/definitions/FileRecorder'
|
||||||
|
http:
|
||||||
|
$ref: '#/definitions/HTTPRecorder'
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
redis:
|
redis:
|
||||||
$ref: '#/definitions/RedisRecorder'
|
$ref: '#/definitions/RedisRecorder'
|
||||||
|
tcp:
|
||||||
|
$ref: '#/definitions/TCPRecorder'
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
RecorderObject:
|
RecorderObject:
|
||||||
properties:
|
properties:
|
||||||
|
Metadata:
|
||||||
|
additionalProperties: {}
|
||||||
|
type: object
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
@ -713,6 +882,8 @@ definitions:
|
|||||||
$ref: '#/definitions/NameserverConfig'
|
$ref: '#/definitions/NameserverConfig'
|
||||||
type: array
|
type: array
|
||||||
x-go-name: Nameservers
|
x-go-name: Nameservers
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
Response:
|
Response:
|
||||||
@ -726,6 +897,47 @@ definitions:
|
|||||||
x-go-name: Msg
|
x-go-name: Msg
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/api
|
x-go-package: github.com/go-gost/x/api
|
||||||
|
RouterConfig:
|
||||||
|
properties:
|
||||||
|
file:
|
||||||
|
$ref: '#/definitions/FileLoader'
|
||||||
|
http:
|
||||||
|
$ref: '#/definitions/HTTPLoader'
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
|
redis:
|
||||||
|
$ref: '#/definitions/RedisLoader'
|
||||||
|
reload:
|
||||||
|
$ref: '#/definitions/Duration'
|
||||||
|
routes:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/RouterRouteConfig'
|
||||||
|
type: array
|
||||||
|
x-go-name: Routes
|
||||||
|
type: object
|
||||||
|
x-go-package: github.com/go-gost/x/config
|
||||||
|
RouterRouteConfig:
|
||||||
|
properties:
|
||||||
|
gateway:
|
||||||
|
type: string
|
||||||
|
x-go-name: Gateway
|
||||||
|
net:
|
||||||
|
type: string
|
||||||
|
x-go-name: Net
|
||||||
|
type: object
|
||||||
|
x-go-package: github.com/go-gost/x/config
|
||||||
|
SDConfig:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
x-go-name: Name
|
||||||
|
plugin:
|
||||||
|
$ref: '#/definitions/PluginConfig'
|
||||||
|
type: object
|
||||||
|
x-go-package: github.com/go-gost/x/config
|
||||||
SelectorConfig:
|
SelectorConfig:
|
||||||
properties:
|
properties:
|
||||||
failTimeout:
|
failTimeout:
|
||||||
@ -779,6 +991,14 @@ definitions:
|
|||||||
x-go-name: Limiter
|
x-go-name: Limiter
|
||||||
listener:
|
listener:
|
||||||
$ref: '#/definitions/ListenerConfig'
|
$ref: '#/definitions/ListenerConfig'
|
||||||
|
logger:
|
||||||
|
type: string
|
||||||
|
x-go-name: Logger
|
||||||
|
loggers:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
x-go-name: Loggers
|
||||||
metadata:
|
metadata:
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
type: object
|
type: object
|
||||||
@ -786,6 +1006,9 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Name
|
x-go-name: Name
|
||||||
|
observer:
|
||||||
|
type: string
|
||||||
|
x-go-name: Observer
|
||||||
recorders:
|
recorders:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/RecorderObject'
|
$ref: '#/definitions/RecorderObject'
|
||||||
@ -799,6 +1022,61 @@ definitions:
|
|||||||
x-go-name: RLimiter
|
x-go-name: RLimiter
|
||||||
sockopts:
|
sockopts:
|
||||||
$ref: '#/definitions/SockOptsConfig'
|
$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
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
SockOptsConfig:
|
SockOptsConfig:
|
||||||
@ -809,6 +1087,15 @@ definitions:
|
|||||||
x-go-name: Mark
|
x-go-name: Mark
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
|
TCPRecorder:
|
||||||
|
properties:
|
||||||
|
addr:
|
||||||
|
type: string
|
||||||
|
x-go-name: Addr
|
||||||
|
timeout:
|
||||||
|
$ref: '#/definitions/Duration'
|
||||||
|
type: object
|
||||||
|
x-go-package: github.com/go-gost/x/config
|
||||||
TLSConfig:
|
TLSConfig:
|
||||||
properties:
|
properties:
|
||||||
caFile:
|
caFile:
|
||||||
@ -823,6 +1110,8 @@ definitions:
|
|||||||
keyFile:
|
keyFile:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: KeyFile
|
x-go-name: KeyFile
|
||||||
|
options:
|
||||||
|
$ref: '#/definitions/TLSOptions'
|
||||||
organization:
|
organization:
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Organization
|
x-go-name: Organization
|
||||||
@ -836,6 +1125,33 @@ definitions:
|
|||||||
$ref: '#/definitions/Duration'
|
$ref: '#/definitions/Duration'
|
||||||
type: object
|
type: object
|
||||||
x-go-package: github.com/go-gost/x/config
|
x-go-package: github.com/go-gost/x/config
|
||||||
|
TLSNodeConfig:
|
||||||
|
properties:
|
||||||
|
options:
|
||||||
|
$ref: '#/definitions/TLSOptions'
|
||||||
|
secure:
|
||||||
|
type: boolean
|
||||||
|
x-go-name: Secure
|
||||||
|
serverName:
|
||||||
|
type: string
|
||||||
|
x-go-name: ServerName
|
||||||
|
type: object
|
||||||
|
x-go-package: github.com/go-gost/x/config
|
||||||
|
TLSOptions:
|
||||||
|
properties:
|
||||||
|
cipherSuites:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
x-go-name: CipherSuites
|
||||||
|
maxVersion:
|
||||||
|
type: string
|
||||||
|
x-go-name: MaxVersion
|
||||||
|
minVersion:
|
||||||
|
type: string
|
||||||
|
x-go-name: MinVersion
|
||||||
|
type: object
|
||||||
|
x-go-package: github.com/go-gost/x/config
|
||||||
info:
|
info:
|
||||||
title: Documentation of Web API.
|
title: Documentation of Web API.
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
@ -866,6 +1182,11 @@ paths:
|
|||||||
name: format
|
name: format
|
||||||
type: string
|
type: string
|
||||||
x-go-name: Format
|
x-go-name: Format
|
||||||
|
- description: file path, default is gost.yaml|gost.json in current working directory.
|
||||||
|
in: query
|
||||||
|
name: path
|
||||||
|
type: string
|
||||||
|
x-go-name: Path
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
$ref: '#/responses/saveConfigResponse'
|
$ref: '#/responses/saveConfigResponse'
|
||||||
@ -1397,6 +1718,122 @@ paths:
|
|||||||
summary: Update limiter by name, the limiter must already exist.
|
summary: Update limiter by name, the limiter must already exist.
|
||||||
tags:
|
tags:
|
||||||
- Limiter
|
- 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:
|
/config/resolvers:
|
||||||
post:
|
post:
|
||||||
operationId: createResolverRequest
|
operationId: createResolverRequest
|
||||||
@ -1513,6 +1950,122 @@ paths:
|
|||||||
summary: Update rate limiter by name, the limiter must already exist.
|
summary: Update rate limiter by name, the limiter must already exist.
|
||||||
tags:
|
tags:
|
||||||
- Limiter
|
- Limiter
|
||||||
|
/config/routers:
|
||||||
|
post:
|
||||||
|
operationId: createRouterRequest
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: data
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RouterConfig'
|
||||||
|
x-go-name: Data
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: '#/responses/createRouterResponse'
|
||||||
|
security:
|
||||||
|
- basicAuth:
|
||||||
|
- '[]'
|
||||||
|
summary: Create a new router, the name of the router must be unique in router list.
|
||||||
|
tags:
|
||||||
|
- Router
|
||||||
|
/config/routers/{router}:
|
||||||
|
delete:
|
||||||
|
operationId: deleteRouterRequest
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: router
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
x-go-name: Router
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: '#/responses/deleteRouterResponse'
|
||||||
|
security:
|
||||||
|
- basicAuth:
|
||||||
|
- '[]'
|
||||||
|
summary: Delete router by name.
|
||||||
|
tags:
|
||||||
|
- Router
|
||||||
|
put:
|
||||||
|
operationId: updateRouterRequest
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: router
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
x-go-name: Router
|
||||||
|
- in: body
|
||||||
|
name: data
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RouterConfig'
|
||||||
|
x-go-name: Data
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: '#/responses/updateRouterResponse'
|
||||||
|
security:
|
||||||
|
- basicAuth:
|
||||||
|
- '[]'
|
||||||
|
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:
|
/config/services:
|
||||||
post:
|
post:
|
||||||
operationId: createServiceRequest
|
operationId: createServiceRequest
|
||||||
@ -1628,18 +2181,42 @@ responses:
|
|||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
createObserverResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
createRateLimiterResponse:
|
createRateLimiterResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
createRecorderResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
createResolverResponse:
|
createResolverResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
createRouterResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
|
createSDResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
createServiceResponse:
|
createServiceResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
@ -1700,18 +2277,42 @@ responses:
|
|||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
deleteObserverResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
deleteRateLimiterResponse:
|
deleteRateLimiterResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
deleteRecorderResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
deleteResolverResponse:
|
deleteResolverResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
deleteRouterResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
|
deleteSDResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
deleteServiceResponse:
|
deleteServiceResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
@ -1784,18 +2385,42 @@ responses:
|
|||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
updateObserverResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
updateRateLimiterResponse:
|
updateRateLimiterResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
updateRecorderResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
updateResolverResponse:
|
updateResolverResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
Data: {}
|
Data: {}
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Response'
|
$ref: '#/definitions/Response'
|
||||||
|
updateRouterResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
|
updateSDResponse:
|
||||||
|
description: successful operation.
|
||||||
|
headers:
|
||||||
|
Data: {}
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Response'
|
||||||
updateServiceResponse:
|
updateServiceResponse:
|
||||||
description: successful operation.
|
description: successful operation.
|
||||||
headers:
|
headers:
|
||||||
|
@ -110,7 +110,7 @@ func (p *authenticator) Authenticate(ctx context.Context, user, password string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
v, ok := p.kvs[user]
|
v, ok := p.kvs[user]
|
||||||
return "", ok && (v == "" || password == v)
|
return user, ok && (v == "" || password == v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *authenticator) periodReload(ctx context.Context) error {
|
func (p *authenticator) periodReload(ctx context.Context) error {
|
||||||
@ -145,6 +145,8 @@ func (p *authenticator) reload(ctx context.Context) (err error) {
|
|||||||
kvs[k] = v
|
kvs[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.options.logger.Debugf("load items %d", len(m))
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
@ -206,7 +208,6 @@ func (p *authenticator) load(ctx context.Context) (m map[string]string, err erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.options.logger.Debugf("load items %d", len(m))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
72
auth/plugin/grpc.go
Normal file
72
auth/plugin/grpc.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/auth"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/plugin/auth/proto"
|
||||||
|
ctxvalue "github.com/go-gost/x/ctx"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type grpcPlugin struct {
|
||||||
|
conn grpc.ClientConnInterface
|
||||||
|
client proto.AuthenticatorClient
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGRPCPlugin creates an Authenticator plugin based on gRPC.
|
||||||
|
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) auth.Authenticator {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "auther",
|
||||||
|
"auther": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &grpcPlugin{
|
||||||
|
conn: conn,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn != nil {
|
||||||
|
p.client = proto.NewAuthenticatorClient(conn)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticate checks the validity of the provided user-password pair.
|
||||||
|
func (p *grpcPlugin) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
|
||||||
|
if p.client == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := p.client.Authenticate(ctx,
|
||||||
|
&proto.AuthenticateRequest{
|
||||||
|
Username: user,
|
||||||
|
Password: password,
|
||||||
|
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
p.log.Error(err)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return r.Id, r.Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) Close() error {
|
||||||
|
if closer, ok := p.conn.(io.Closer); ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -4,76 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-gost/core/auth"
|
"github.com/go-gost/core/auth"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/auth/proto"
|
ctxvalue "github.com/go-gost/x/ctx"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
auth_util "github.com/go-gost/x/internal/util/auth"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type grpcPlugin struct {
|
|
||||||
conn grpc.ClientConnInterface
|
|
||||||
client proto.AuthenticatorClient
|
|
||||||
log logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGRPCPlugin creates an Authenticator plugin based on gRPC.
|
|
||||||
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) auth.Authenticator {
|
|
||||||
var options plugin.Options
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
log := logger.Default().WithFields(map[string]any{
|
|
||||||
"kind": "auther",
|
|
||||||
"auther": name,
|
|
||||||
})
|
|
||||||
conn, err := plugin.NewGRPCConn(addr, &options)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &grpcPlugin{
|
|
||||||
conn: conn,
|
|
||||||
log: log,
|
|
||||||
}
|
|
||||||
|
|
||||||
if conn != nil {
|
|
||||||
p.client = proto.NewAuthenticatorClient(conn)
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate checks the validity of the provided user-password pair.
|
|
||||||
func (p *grpcPlugin) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
|
|
||||||
if p.client == nil {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := p.client.Authenticate(ctx,
|
|
||||||
&proto.AuthenticateRequest{
|
|
||||||
Username: user,
|
|
||||||
Password: password,
|
|
||||||
Client: string(auth_util.ClientAddrFromContext(ctx)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
p.log.Error(err)
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return r.Id, r.Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *grpcPlugin) Close() error {
|
|
||||||
if closer, ok := p.conn.(io.Closer); ok {
|
|
||||||
return closer.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpPluginRequest struct {
|
type httpPluginRequest struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
@ -118,7 +56,7 @@ func (p *httpPlugin) Authenticate(ctx context.Context, user, password string, op
|
|||||||
rb := httpPluginRequest{
|
rb := httpPluginRequest{
|
||||||
Username: user,
|
Username: user,
|
||||||
Password: password,
|
Password: password,
|
||||||
Client: string(auth_util.ClientAddrFromContext(ctx)),
|
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
|
||||||
}
|
}
|
||||||
v, err := json.Marshal(&rb)
|
v, err := json.Marshal(&rb)
|
||||||
if err != nil {
|
if err != nil {
|
@ -130,6 +130,7 @@ func (bp *localBypass) reload(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
patterns := append(bp.options.matchers, v...)
|
patterns := append(bp.options.matchers, v...)
|
||||||
|
bp.options.logger.Debugf("load items %d", len(patterns))
|
||||||
|
|
||||||
var addrs []string
|
var addrs []string
|
||||||
var inets []*net.IPNet
|
var inets []*net.IPNet
|
||||||
@ -205,7 +206,6 @@ func (bp *localBypass) load(ctx context.Context) (patterns []string, err error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bp.options.logger.Debugf("load items %d", len(patterns))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,11 +235,17 @@ func (bp *localBypass) Contains(ctx context.Context, network, addr string, opts
|
|||||||
b := !bp.options.whitelist && matched ||
|
b := !bp.options.whitelist && matched ||
|
||||||
bp.options.whitelist && !matched
|
bp.options.whitelist && !matched
|
||||||
if b {
|
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
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *localBypass) IsWhitelist() bool {
|
||||||
|
return p.options.whitelist
|
||||||
|
}
|
||||||
|
|
||||||
func (bp *localBypass) parseLine(s string) string {
|
func (bp *localBypass) parseLine(s string) string {
|
||||||
if n := strings.IndexByte(s, '#'); n >= 0 {
|
if n := strings.IndexByte(s, '#'); n >= 0 {
|
||||||
s = s[:n]
|
s = s[:n]
|
||||||
|
81
bypass/plugin/grpc.go
Normal file
81
bypass/plugin/grpc.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package bypass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/bypass"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/plugin/bypass/proto"
|
||||||
|
ctxvalue "github.com/go-gost/x/ctx"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type grpcPlugin struct {
|
||||||
|
conn grpc.ClientConnInterface
|
||||||
|
client proto.BypassClient
|
||||||
|
log logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGRPCPlugin creates a Bypass plugin based on gRPC.
|
||||||
|
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) bypass.Bypass {
|
||||||
|
var options plugin.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "bypass",
|
||||||
|
"bypass": name,
|
||||||
|
})
|
||||||
|
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &grpcPlugin{
|
||||||
|
conn: conn,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
if conn != nil {
|
||||||
|
p.client = proto.NewBypassClient(conn)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
|
||||||
|
if p.client == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var options bypass.Options
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := p.client.Bypass(ctx,
|
||||||
|
&proto.BypassRequest{
|
||||||
|
Network: network,
|
||||||
|
Addr: addr,
|
||||||
|
Client: string(ctxvalue.ClientIDFromContext(ctx)),
|
||||||
|
Host: options.Host,
|
||||||
|
Path: options.Path,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
p.log.Error(err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return r.Ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) Close() error {
|
||||||
|
if closer, ok := p.conn.(io.Closer); ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *grpcPlugin) IsWhitelist() bool {
|
||||||
|
return false
|
||||||
|
}
|
@ -4,81 +4,14 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-gost/core/bypass"
|
"github.com/go-gost/core/bypass"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/plugin/bypass/proto"
|
ctxvalue "github.com/go-gost/x/ctx"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
auth_util "github.com/go-gost/x/internal/util/auth"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type grpcPlugin struct {
|
|
||||||
conn grpc.ClientConnInterface
|
|
||||||
client proto.BypassClient
|
|
||||||
log logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGRPCPlugin creates a Bypass plugin based on gRPC.
|
|
||||||
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) bypass.Bypass {
|
|
||||||
var options plugin.Options
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
log := logger.Default().WithFields(map[string]any{
|
|
||||||
"kind": "bypass",
|
|
||||||
"bypass": name,
|
|
||||||
})
|
|
||||||
conn, err := plugin.NewGRPCConn(addr, &options)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &grpcPlugin{
|
|
||||||
conn: conn,
|
|
||||||
log: log,
|
|
||||||
}
|
|
||||||
if conn != nil {
|
|
||||||
p.client = proto.NewBypassClient(conn)
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *grpcPlugin) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
|
|
||||||
if p.client == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var options bypass.Options
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := p.client.Bypass(ctx,
|
|
||||||
&proto.BypassRequest{
|
|
||||||
Network: network,
|
|
||||||
Addr: addr,
|
|
||||||
Client: string(auth_util.IDFromContext(ctx)),
|
|
||||||
Host: options.Host,
|
|
||||||
Path: options.Path,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
p.log.Error(err)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return r.Ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *grpcPlugin) Close() error {
|
|
||||||
if closer, ok := p.conn.(io.Closer); ok {
|
|
||||||
return closer.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpPluginRequest struct {
|
type httpPluginRequest struct {
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
@ -129,7 +62,7 @@ func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts ..
|
|||||||
rb := httpPluginRequest{
|
rb := httpPluginRequest{
|
||||||
Network: network,
|
Network: network,
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Client: string(auth_util.IDFromContext(ctx)),
|
Client: string(ctxvalue.ClientIDFromContext(ctx)),
|
||||||
Host: options.Host,
|
Host: options.Host,
|
||||||
Path: options.Path,
|
Path: options.Path,
|
||||||
}
|
}
|
||||||
@ -163,3 +96,7 @@ func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts ..
|
|||||||
}
|
}
|
||||||
return res.OK
|
return res.OK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *httpPlugin) IsWhitelist() bool {
|
||||||
|
return false
|
||||||
|
}
|
@ -104,7 +104,7 @@ func (c *Chain) Route(ctx context.Context, network, address string, opts ...chai
|
|||||||
tr.Options().Route = rt
|
tr.Options().Route = rt
|
||||||
node = node.Copy()
|
node = node.Copy()
|
||||||
node.Options().Transport = tr
|
node.Options().Transport = tr
|
||||||
rt = NewRoute()
|
rt = NewRoute(ChainRouteOption(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
rt.addNode(node)
|
rt.addNode(node)
|
||||||
|
114
chain/route.go
114
chain/route.go
@ -2,6 +2,8 @@ package chain
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -10,9 +12,86 @@ import (
|
|||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/core/metrics"
|
"github.com/go-gost/core/metrics"
|
||||||
"github.com/go-gost/core/selector"
|
"github.com/go-gost/core/selector"
|
||||||
|
xnet "github.com/go-gost/x/internal/net"
|
||||||
|
"github.com/go-gost/x/internal/net/dialer"
|
||||||
|
"github.com/go-gost/x/internal/net/udp"
|
||||||
xmetrics "github.com/go-gost/x/metrics"
|
xmetrics "github.com/go-gost/x/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrEmptyRoute = errors.New("empty route")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultRoute chain.Route = &defaultRoute{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultRoute is a Route without nodes.
|
||||||
|
type defaultRoute struct{}
|
||||||
|
|
||||||
|
func (*defaultRoute) Dial(ctx context.Context, network, address string, opts ...chain.DialOption) (net.Conn, error) {
|
||||||
|
var options chain.DialOptions
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
netd := dialer.Dialer{
|
||||||
|
Interface: options.Interface,
|
||||||
|
Netns: options.Netns,
|
||||||
|
Logger: options.Logger,
|
||||||
|
}
|
||||||
|
if options.SockOpts != nil {
|
||||||
|
netd.Mark = options.SockOpts.Mark
|
||||||
|
}
|
||||||
|
|
||||||
|
return netd.Dial(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*defaultRoute) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (net.Listener, error) {
|
||||||
|
var options chain.BindOptions
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
addr, err := net.ResolveTCPAddr(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return net.ListenTCP(network, addr)
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
addr, err := net.ResolveUDPAddr(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := net.ListenUDP(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logger := logger.Default().WithFields(map[string]any{
|
||||||
|
"network": network,
|
||||||
|
"address": address,
|
||||||
|
})
|
||||||
|
ln := udp.NewListener(conn, &udp.ListenConfig{
|
||||||
|
Backlog: options.Backlog,
|
||||||
|
ReadQueueSize: options.UDPDataQueueSize,
|
||||||
|
ReadBufferSize: options.UDPDataBufferSize,
|
||||||
|
TTL: options.UDPConnTTL,
|
||||||
|
Keepalive: true,
|
||||||
|
Logger: logger,
|
||||||
|
})
|
||||||
|
return ln, err
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("network %s unsupported", network)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *defaultRoute) Nodes() []*chain.Node {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type RouteOptions struct {
|
type RouteOptions struct {
|
||||||
Chain chain.Chainer
|
Chain chain.Chainer
|
||||||
}
|
}
|
||||||
@ -25,12 +104,12 @@ func ChainRouteOption(c chain.Chainer) RouteOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type route struct {
|
type chainRoute struct {
|
||||||
nodes []*chain.Node
|
nodes []*chain.Node
|
||||||
options RouteOptions
|
options RouteOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoute(opts ...RouteOption) *route {
|
func NewRoute(opts ...RouteOption) *chainRoute {
|
||||||
var options RouteOptions
|
var options RouteOptions
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
if opt != nil {
|
if opt != nil {
|
||||||
@ -38,18 +117,18 @@ func NewRoute(opts ...RouteOption) *route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &route{
|
return &chainRoute{
|
||||||
options: options,
|
options: options,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) addNode(nodes ...*chain.Node) {
|
func (r *chainRoute) addNode(nodes ...*chain.Node) {
|
||||||
r.nodes = append(r.nodes, nodes...)
|
r.nodes = append(r.nodes, nodes...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) Dial(ctx context.Context, network, address string, opts ...chain.DialOption) (net.Conn, error) {
|
func (r *chainRoute) Dial(ctx context.Context, network, address string, opts ...chain.DialOption) (net.Conn, error) {
|
||||||
if len(r.Nodes()) == 0 {
|
if len(r.Nodes()) == 0 {
|
||||||
return chain.DefaultRoute.Dial(ctx, network, address, opts...)
|
return DefaultRoute.Dial(ctx, network, address, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var options chain.DialOptions
|
var options chain.DialOptions
|
||||||
@ -73,9 +152,9 @@ func (r *route) Dial(ctx context.Context, network, address string, opts ...chain
|
|||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (net.Listener, error) {
|
func (r *chainRoute) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (net.Listener, error) {
|
||||||
if len(r.Nodes()) == 0 {
|
if len(r.Nodes()) == 0 {
|
||||||
return chain.DefaultRoute.Bind(ctx, network, address, opts...)
|
return DefaultRoute.Bind(ctx, network, address, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var options chain.BindOptions
|
var options chain.BindOptions
|
||||||
@ -106,7 +185,7 @@ func (r *route) Bind(ctx context.Context, network, address string, opts ...chain
|
|||||||
return ln, nil
|
return ln, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) connect(ctx context.Context, logger logger.Logger) (conn net.Conn, err error) {
|
func (r *chainRoute) connect(ctx context.Context, logger logger.Logger) (conn net.Conn, err error) {
|
||||||
network := "ip"
|
network := "ip"
|
||||||
node := r.nodes[0]
|
node := r.nodes[0]
|
||||||
|
|
||||||
@ -129,15 +208,16 @@ func (r *route) connect(ctx context.Context, logger logger.Logger) (conn net.Con
|
|||||||
metrics.Labels{"chain": name, "node": node.Name}); v != nil {
|
metrics.Labels{"chain": name, "node": node.Name}); v != nil {
|
||||||
v.Inc()
|
v.Inc()
|
||||||
}
|
}
|
||||||
} else {
|
return
|
||||||
if marker != nil {
|
}
|
||||||
marker.Reset()
|
|
||||||
}
|
if marker != nil {
|
||||||
|
marker.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, err := chain.Resolve(ctx, network, node.Addr, node.Options().Resolver, node.Options().HostMapper, logger)
|
addr, err := xnet.Resolve(ctx, network, node.Addr, node.Options().Resolver, node.Options().HostMapper, logger)
|
||||||
marker := node.Marker()
|
marker := node.Marker()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if marker != nil {
|
if marker != nil {
|
||||||
@ -181,7 +261,7 @@ func (r *route) connect(ctx context.Context, logger logger.Logger) (conn net.Con
|
|||||||
preNode := node
|
preNode := node
|
||||||
for _, node := range r.nodes[1:] {
|
for _, node := range r.nodes[1:] {
|
||||||
marker := node.Marker()
|
marker := node.Marker()
|
||||||
addr, err = chain.Resolve(ctx, network, node.Addr, node.Options().Resolver, node.Options().HostMapper, logger)
|
addr, err = xnet.Resolve(ctx, network, node.Addr, node.Options().Resolver, node.Options().HostMapper, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cn.Close()
|
cn.Close()
|
||||||
if marker != nil {
|
if marker != nil {
|
||||||
@ -217,14 +297,14 @@ func (r *route) connect(ctx context.Context, logger logger.Logger) (conn net.Con
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) getNode(index int) *chain.Node {
|
func (r *chainRoute) getNode(index int) *chain.Node {
|
||||||
if r == nil || len(r.Nodes()) == 0 || index < 0 || index >= len(r.Nodes()) {
|
if r == nil || len(r.Nodes()) == 0 || index < 0 || index >= len(r.Nodes()) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return r.nodes[index]
|
return r.nodes[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *route) Nodes() []*chain.Node {
|
func (r *chainRoute) Nodes() []*chain.Node {
|
||||||
if r != nil {
|
if r != nil {
|
||||||
return r.nodes
|
return r.nodes
|
||||||
}
|
}
|
||||||
|
207
chain/router.go
Normal file
207
chain/router.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/chain"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/core/recorder"
|
||||||
|
xnet "github.com/go-gost/x/internal/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
options chain.RouterOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(opts ...chain.RouterOption) *Router {
|
||||||
|
r := &Router{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt != nil {
|
||||||
|
opt(&r.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.options.Timeout == 0 {
|
||||||
|
r.options.Timeout = 15 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.options.Logger == nil {
|
||||||
|
r.options.Logger = logger.Default().WithFields(map[string]any{"kind": "router"})
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Options() *chain.RouterOptions {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &r.options
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Dial(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||||
|
host := address
|
||||||
|
if h, _, _ := net.SplitHostPort(address); h != "" {
|
||||||
|
host = h
|
||||||
|
}
|
||||||
|
r.record(ctx, recorder.RecorderServiceRouterDialAddress, []byte(host))
|
||||||
|
|
||||||
|
conn, err = r.dial(ctx, network, address)
|
||||||
|
if err != nil {
|
||||||
|
r.record(ctx, recorder.RecorderServiceRouterDialAddressError, []byte(host))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == "udp" || network == "udp4" || network == "udp6" {
|
||||||
|
if _, ok := conn.(net.PacketConn); !ok {
|
||||||
|
return &packetConn{conn}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) record(ctx context.Context, name string, data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range r.options.Recorders {
|
||||||
|
if rec.Record == name {
|
||||||
|
err := rec.Recorder.Record(ctx, data)
|
||||||
|
if err != nil {
|
||||||
|
r.options.Logger.Errorf("record %s: %v", name, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) dial(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||||
|
count := r.options.Retries + 1
|
||||||
|
if count <= 0 {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
r.options.Logger.Debugf("dial %s/%s", address, network)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
ctx := ctx
|
||||||
|
if r.options.Timeout > 0 {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipAddr string
|
||||||
|
ipAddr, err = xnet.Resolve(ctx, "ip", address, r.options.Resolver, r.options.HostMapper, r.options.Logger)
|
||||||
|
if err != nil {
|
||||||
|
r.options.Logger.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var route chain.Route
|
||||||
|
if r.options.Chain != nil {
|
||||||
|
route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address))
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.options.Logger.IsLevelEnabled(logger.DebugLevel) {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
for _, node := range routePath(route) {
|
||||||
|
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&buf, "%s", ipAddr)
|
||||||
|
r.options.Logger.Debugf("route(retry=%d) %s", i, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if route == nil {
|
||||||
|
route = DefaultRoute
|
||||||
|
}
|
||||||
|
conn, err = route.Dial(ctx, network, ipAddr,
|
||||||
|
chain.InterfaceDialOption(r.options.IfceName),
|
||||||
|
chain.NetnsDialOption(r.options.Netns),
|
||||||
|
chain.SockOptsDialOption(r.options.SockOpts),
|
||||||
|
chain.LoggerDialOption(r.options.Logger),
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.options.Logger.Errorf("route(retry=%d) %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (ln net.Listener, err error) {
|
||||||
|
count := r.options.Retries + 1
|
||||||
|
if count <= 0 {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
r.options.Logger.Debugf("bind on %s/%s", address, network)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
ctx := ctx
|
||||||
|
if r.options.Timeout > 0 {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
var route chain.Route
|
||||||
|
if r.options.Chain != nil {
|
||||||
|
route = r.options.Chain.Route(ctx, network, address)
|
||||||
|
if route == nil || len(route.Nodes()) == 0 {
|
||||||
|
err = ErrEmptyRoute
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.options.Logger.IsLevelEnabled(logger.DebugLevel) {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
for _, node := range routePath(route) {
|
||||||
|
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&buf, "%s", address)
|
||||||
|
r.options.Logger.Debugf("route(retry=%d) %s", i, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if route == nil {
|
||||||
|
route = DefaultRoute
|
||||||
|
}
|
||||||
|
ln, err = route.Bind(ctx, network, address, opts...)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.options.Logger.Errorf("route(retry=%d) %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func routePath(route chain.Route) (path []*chain.Node) {
|
||||||
|
if route == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, node := range route.Nodes() {
|
||||||
|
if tr := node.Options().Transport; tr != nil {
|
||||||
|
path = append(path, routePath(tr.Options().Route)...)
|
||||||
|
}
|
||||||
|
path = append(path, node)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packetConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||||
|
n, err = c.Read(b)
|
||||||
|
addr = c.Conn.RemoteAddr()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
return c.Write(b)
|
||||||
|
}
|
106
chain/transport.go
Normal file
106
chain/transport.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/chain"
|
||||||
|
"github.com/go-gost/core/connector"
|
||||||
|
"github.com/go-gost/core/dialer"
|
||||||
|
net_dialer "github.com/go-gost/x/internal/net/dialer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
dialer dialer.Dialer
|
||||||
|
connector connector.Connector
|
||||||
|
options chain.TransportOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(d dialer.Dialer, c connector.Connector, opts ...chain.TransportOption) *Transport {
|
||||||
|
tr := &Transport{
|
||||||
|
dialer: d,
|
||||||
|
connector: c,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt != nil {
|
||||||
|
opt(&tr.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *Transport) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
||||||
|
netd := &net_dialer.Dialer{
|
||||||
|
Interface: tr.options.IfceName,
|
||||||
|
Netns: tr.options.Netns,
|
||||||
|
}
|
||||||
|
if tr.options.SockOpts != nil {
|
||||||
|
netd.Mark = tr.options.SockOpts.Mark
|
||||||
|
}
|
||||||
|
if tr.options.Route != nil && len(tr.options.Route.Nodes()) > 0 {
|
||||||
|
netd.DialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return tr.options.Route.Dial(ctx, network, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts := []dialer.DialOption{
|
||||||
|
dialer.HostDialOption(tr.options.Addr),
|
||||||
|
dialer.NetDialerDialOption(netd),
|
||||||
|
}
|
||||||
|
return tr.dialer.Dial(ctx, addr, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *Transport) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
if hs, ok := tr.dialer.(dialer.Handshaker); ok {
|
||||||
|
conn, err = hs.Handshake(ctx, conn,
|
||||||
|
dialer.AddrHandshakeOption(tr.options.Addr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hs, ok := tr.connector.(connector.Handshaker); ok {
|
||||||
|
return hs.Handshake(ctx, conn)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *Transport) Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) {
|
||||||
|
netd := &net_dialer.Dialer{
|
||||||
|
Interface: tr.options.IfceName,
|
||||||
|
Netns: tr.options.Netns,
|
||||||
|
}
|
||||||
|
if tr.options.SockOpts != nil {
|
||||||
|
netd.Mark = tr.options.SockOpts.Mark
|
||||||
|
}
|
||||||
|
return tr.connector.Connect(ctx, conn, network, address,
|
||||||
|
connector.DialerConnectOption(netd),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *Transport) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
||||||
|
if binder, ok := tr.connector.(connector.Binder); ok {
|
||||||
|
return binder.Bind(ctx, conn, network, address, opts...)
|
||||||
|
}
|
||||||
|
return nil, connector.ErrBindUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *Transport) Multiplex() bool {
|
||||||
|
if mux, ok := tr.dialer.(dialer.Multiplexer); ok {
|
||||||
|
return mux.Multiplex()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *Transport) Options() *chain.TransportOptions {
|
||||||
|
if tr != nil {
|
||||||
|
return &tr.options
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *Transport) Copy() chain.Transporter {
|
||||||
|
tr2 := &Transport{}
|
||||||
|
*tr2 = *tr
|
||||||
|
return tr
|
||||||
|
}
|
682
config/cmd/cmd.go
Normal file
682
config/cmd/cmd.go
Normal file
@ -0,0 +1,682 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
|
"github.com/go-gost/x/config"
|
||||||
|
xnet "github.com/go-gost/x/internal/net"
|
||||||
|
"github.com/go-gost/x/limiter/conn"
|
||||||
|
"github.com/go-gost/x/limiter/traffic"
|
||||||
|
mdx "github.com/go-gost/x/metadata"
|
||||||
|
"github.com/go-gost/x/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidCmd = errors.New("invalid cmd")
|
||||||
|
ErrInvalidNode = errors.New("invalid node")
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error) {
|
||||||
|
namePrefix := ""
|
||||||
|
cfg := &config.Config{}
|
||||||
|
|
||||||
|
var chain *config.ChainConfig
|
||||||
|
if len(nodeList) > 0 {
|
||||||
|
chain = &config.ChainConfig{
|
||||||
|
Name: fmt.Sprintf("%schain-0", namePrefix),
|
||||||
|
}
|
||||||
|
cfg.Chains = append(cfg.Chains, chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, node := range nodeList {
|
||||||
|
url, err := Norm(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeConfig, err := buildNodeConfig(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodeConfig.Name = fmt.Sprintf("%snode-0", namePrefix)
|
||||||
|
|
||||||
|
var nodes []*config.NodeConfig
|
||||||
|
for _, host := range strings.Split(nodeConfig.Addr, ",") {
|
||||||
|
if host == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nodeCfg := &config.NodeConfig{}
|
||||||
|
*nodeCfg = *nodeConfig
|
||||||
|
nodeCfg.Name = fmt.Sprintf("%snode-%d", namePrefix, len(nodes))
|
||||||
|
nodeCfg.Addr = host
|
||||||
|
nodes = append(nodes, nodeCfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{}
|
||||||
|
for k, v := range url.Query() {
|
||||||
|
if len(v) > 0 {
|
||||||
|
m[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
md := mdx.NewMetadata(m)
|
||||||
|
|
||||||
|
hopConfig := &config.HopConfig{
|
||||||
|
Name: fmt.Sprintf("%shop-%d", namePrefix, i),
|
||||||
|
Selector: parseSelector(m),
|
||||||
|
Nodes: nodes,
|
||||||
|
Metadata: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := mdutil.GetString(md, "bypass"); v != "" {
|
||||||
|
bypassCfg := &config.BypassConfig{
|
||||||
|
Name: fmt.Sprintf("%sbypass-%d", namePrefix, len(cfg.Bypasses)),
|
||||||
|
}
|
||||||
|
if v[0] == '~' {
|
||||||
|
bypassCfg.Whitelist = true
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
|
for _, s := range strings.Split(v, ",") {
|
||||||
|
if s == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
|
||||||
|
}
|
||||||
|
hopConfig.Bypass = bypassCfg.Name
|
||||||
|
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
|
||||||
|
delete(m, "bypass")
|
||||||
|
}
|
||||||
|
if v := mdutil.GetString(md, "resolver"); v != "" {
|
||||||
|
resolverCfg := &config.ResolverConfig{
|
||||||
|
Name: fmt.Sprintf("%sresolver-%d", namePrefix, len(cfg.Resolvers)),
|
||||||
|
}
|
||||||
|
for _, rs := range strings.Split(v, ",") {
|
||||||
|
if rs == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resolverCfg.Nameservers = append(
|
||||||
|
resolverCfg.Nameservers,
|
||||||
|
&config.NameserverConfig{
|
||||||
|
Addr: rs,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
hopConfig.Resolver = resolverCfg.Name
|
||||||
|
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
|
||||||
|
delete(m, "resolver")
|
||||||
|
}
|
||||||
|
if v := mdutil.GetString(md, "hosts"); v != "" {
|
||||||
|
hostsCfg := &config.HostsConfig{
|
||||||
|
Name: fmt.Sprintf("%shosts-%d", namePrefix, len(cfg.Hosts)),
|
||||||
|
}
|
||||||
|
for _, s := range strings.Split(v, ",") {
|
||||||
|
ss := strings.SplitN(s, ":", 2)
|
||||||
|
if len(ss) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hostsCfg.Mappings = append(
|
||||||
|
hostsCfg.Mappings,
|
||||||
|
&config.HostMappingConfig{
|
||||||
|
Hostname: ss[0],
|
||||||
|
IP: ss[1],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
hopConfig.Hosts = hostsCfg.Name
|
||||||
|
cfg.Hosts = append(cfg.Hosts, hostsCfg)
|
||||||
|
delete(m, "hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := mdutil.GetString(md, "interface"); v != "" {
|
||||||
|
hopConfig.Interface = v
|
||||||
|
delete(m, "interface")
|
||||||
|
}
|
||||||
|
if v := mdutil.GetInt(md, "so_mark"); v > 0 {
|
||||||
|
hopConfig.SockOpts = &config.SockOptsConfig{
|
||||||
|
Mark: v,
|
||||||
|
}
|
||||||
|
delete(m, "so_mark")
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.Hops = append(chain.Hops, hopConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
var services []*config.ServiceConfig
|
||||||
|
for _, svc := range serviceList {
|
||||||
|
svc = strings.TrimSpace(svc)
|
||||||
|
if svc == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if svc[0] == ':' || !strings.Contains(svc, "://") {
|
||||||
|
svc = "auto://" + svc
|
||||||
|
}
|
||||||
|
|
||||||
|
host, svc := cutHost(svc)
|
||||||
|
|
||||||
|
url, err := Norm(svc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
url.Host = host
|
||||||
|
|
||||||
|
svcs, err := buildServiceConfig(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
services = append(services, svcs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, service := range services {
|
||||||
|
service.Name = fmt.Sprintf("%sservice-%d", namePrefix, i)
|
||||||
|
if chain != nil {
|
||||||
|
if service.Listener.Type == "rtcp" || service.Listener.Type == "rudp" {
|
||||||
|
service.Listener.Chain = chain.Name
|
||||||
|
} else {
|
||||||
|
service.Handler.Chain = chain.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.Services = append(cfg.Services, service)
|
||||||
|
|
||||||
|
mh := service.Handler.Metadata
|
||||||
|
md := mdx.NewMetadata(mh)
|
||||||
|
if v := mdutil.GetInt(md, "retries"); v > 0 {
|
||||||
|
service.Handler.Retries = v
|
||||||
|
delete(mh, "retries")
|
||||||
|
}
|
||||||
|
if v := mdutil.GetString(md, "admission"); v != "" {
|
||||||
|
admCfg := &config.AdmissionConfig{
|
||||||
|
Name: fmt.Sprintf("%sadmission-%d", namePrefix, len(cfg.Admissions)),
|
||||||
|
}
|
||||||
|
if v[0] == '~' {
|
||||||
|
admCfg.Whitelist = true
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
|
for _, s := range strings.Split(v, ",") {
|
||||||
|
if s == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
admCfg.Matchers = append(admCfg.Matchers, s)
|
||||||
|
}
|
||||||
|
service.Admission = admCfg.Name
|
||||||
|
cfg.Admissions = append(cfg.Admissions, admCfg)
|
||||||
|
delete(mh, "admission")
|
||||||
|
}
|
||||||
|
if v := mdutil.GetString(md, "bypass"); v != "" {
|
||||||
|
bypassCfg := &config.BypassConfig{
|
||||||
|
Name: fmt.Sprintf("%sbypass-%d", namePrefix, len(cfg.Bypasses)),
|
||||||
|
}
|
||||||
|
if v[0] == '~' {
|
||||||
|
bypassCfg.Whitelist = true
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
|
for _, s := range strings.Split(v, ",") {
|
||||||
|
if s == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
|
||||||
|
}
|
||||||
|
service.Bypass = bypassCfg.Name
|
||||||
|
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
|
||||||
|
delete(mh, "bypass")
|
||||||
|
}
|
||||||
|
if v := mdutil.GetString(md, "resolver"); v != "" {
|
||||||
|
resolverCfg := &config.ResolverConfig{
|
||||||
|
Name: fmt.Sprintf("%sresolver-%d", namePrefix, len(cfg.Resolvers)),
|
||||||
|
}
|
||||||
|
for _, rs := range strings.Split(v, ",") {
|
||||||
|
if rs == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resolverCfg.Nameservers = append(
|
||||||
|
resolverCfg.Nameservers,
|
||||||
|
&config.NameserverConfig{
|
||||||
|
Addr: rs,
|
||||||
|
Prefer: mdutil.GetString(md, "prefer"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
service.Resolver = resolverCfg.Name
|
||||||
|
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
|
||||||
|
delete(mh, "resolver")
|
||||||
|
}
|
||||||
|
if v := mdutil.GetString(md, "hosts"); v != "" {
|
||||||
|
hostsCfg := &config.HostsConfig{
|
||||||
|
Name: fmt.Sprintf("%shosts-%d", namePrefix, len(cfg.Hosts)),
|
||||||
|
}
|
||||||
|
for _, s := range strings.Split(v, ",") {
|
||||||
|
ss := strings.SplitN(s, ":", 2)
|
||||||
|
if len(ss) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hostsCfg.Mappings = append(
|
||||||
|
hostsCfg.Mappings,
|
||||||
|
&config.HostMappingConfig{
|
||||||
|
Hostname: ss[0],
|
||||||
|
IP: ss[1],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
service.Hosts = hostsCfg.Name
|
||||||
|
cfg.Hosts = append(cfg.Hosts, hostsCfg)
|
||||||
|
delete(mh, "hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
in := mdutil.GetString(md, "limiter.in")
|
||||||
|
out := mdutil.GetString(md, "limiter.out")
|
||||||
|
cin := mdutil.GetString(md, "limiter.conn.in")
|
||||||
|
cout := mdutil.GetString(md, "limiter.conn.out")
|
||||||
|
if in != "" || cin != "" || out != "" || cout != "" {
|
||||||
|
limiter := &config.LimiterConfig{
|
||||||
|
Name: fmt.Sprintf("%slimiter-%d", namePrefix, len(cfg.Limiters)),
|
||||||
|
}
|
||||||
|
if in != "" || out != "" {
|
||||||
|
limiter.Limits = append(limiter.Limits,
|
||||||
|
fmt.Sprintf("%s %s %s", traffic.GlobalLimitKey, in, out))
|
||||||
|
}
|
||||||
|
if cin != "" || cout != "" {
|
||||||
|
limiter.Limits = append(limiter.Limits,
|
||||||
|
fmt.Sprintf("%s %s %s", traffic.ConnLimitKey, cin, cout))
|
||||||
|
}
|
||||||
|
service.Limiter = limiter.Name
|
||||||
|
cfg.Limiters = append(cfg.Limiters, limiter)
|
||||||
|
delete(mh, "limiter.in")
|
||||||
|
delete(mh, "limiter.out")
|
||||||
|
delete(mh, "limiter.conn.in")
|
||||||
|
delete(mh, "limiter.conn.out")
|
||||||
|
}
|
||||||
|
|
||||||
|
if climit := mdutil.GetInt(md, "climiter"); climit > 0 {
|
||||||
|
limiter := &config.LimiterConfig{
|
||||||
|
Name: fmt.Sprintf("%sclimiter-%d", namePrefix, len(cfg.CLimiters)),
|
||||||
|
Limits: []string{fmt.Sprintf("%s %d", conn.GlobalLimitKey, climit)},
|
||||||
|
}
|
||||||
|
service.CLimiter = limiter.Name
|
||||||
|
cfg.CLimiters = append(cfg.CLimiters, limiter)
|
||||||
|
delete(mh, "climiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rlimit := mdutil.GetFloat(md, "rlimiter"); rlimit > 0 {
|
||||||
|
limiter := &config.LimiterConfig{
|
||||||
|
Name: fmt.Sprintf("%srlimiter-%d", namePrefix, len(cfg.RLimiters)),
|
||||||
|
Limits: []string{fmt.Sprintf("%s %s", conn.GlobalLimitKey, strconv.FormatFloat(rlimit, 'f', -1, 64))},
|
||||||
|
}
|
||||||
|
service.RLimiter = limiter.Name
|
||||||
|
cfg.RLimiters = append(cfg.RLimiters, limiter)
|
||||||
|
delete(mh, "rlimiter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cutHost(s string) (host, remain string) {
|
||||||
|
if s == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n := strings.IndexByte(s, ':')
|
||||||
|
start := n + 3
|
||||||
|
end := strings.IndexAny(s[start:], "/?")
|
||||||
|
if end < 0 {
|
||||||
|
end = len(s)
|
||||||
|
} else {
|
||||||
|
end += start
|
||||||
|
}
|
||||||
|
// auth info
|
||||||
|
if n = strings.LastIndexByte(s[start:end], '@'); n >= 0 {
|
||||||
|
start += (n + 1)
|
||||||
|
}
|
||||||
|
host = s[start:end]
|
||||||
|
|
||||||
|
remain = s[:start] + s[end:]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildServiceConfig(url *url.URL) ([]*config.ServiceConfig, error) {
|
||||||
|
namePrefix := ""
|
||||||
|
if v := os.Getenv("_GOST_ID"); v != "" {
|
||||||
|
namePrefix = fmt.Sprintf("go-%s@", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var handler, listener string
|
||||||
|
schemes := strings.Split(url.Scheme, "+")
|
||||||
|
if len(schemes) == 1 {
|
||||||
|
handler = schemes[0]
|
||||||
|
listener = schemes[0]
|
||||||
|
}
|
||||||
|
if len(schemes) == 2 {
|
||||||
|
handler = schemes[0]
|
||||||
|
listener = schemes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs := xnet.AddrPortRange(url.Host).Addrs()
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
addrs = append(addrs, url.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
var services []*config.ServiceConfig
|
||||||
|
for _, addr := range addrs {
|
||||||
|
services = append(services, &config.ServiceConfig{
|
||||||
|
Addr: addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if h := registry.HandlerRegistry().Get(handler); h == nil {
|
||||||
|
handler = "auto"
|
||||||
|
}
|
||||||
|
if ln := registry.ListenerRegistry().Get(listener); ln == nil {
|
||||||
|
listener = "tcp"
|
||||||
|
if handler == "ssu" {
|
||||||
|
listener = "udp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodes []*config.ForwardNodeConfig
|
||||||
|
// forward mode
|
||||||
|
if remotes := strings.Trim(url.EscapedPath(), "/"); remotes != "" {
|
||||||
|
i := 0
|
||||||
|
for _, addr := range strings.Split(remotes, ",") {
|
||||||
|
addrs := xnet.AddrPortRange(addr).Addrs()
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
|
for _, adr := range addrs {
|
||||||
|
nodes = append(nodes, &config.ForwardNodeConfig{
|
||||||
|
Name: fmt.Sprintf("%starget-%d", namePrefix, i),
|
||||||
|
Addr: adr,
|
||||||
|
})
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler != "relay" {
|
||||||
|
if listener == "tcp" || listener == "udp" ||
|
||||||
|
listener == "rtcp" || listener == "rudp" ||
|
||||||
|
listener == "tun" || listener == "tap" ||
|
||||||
|
listener == "dns" || listener == "unix" ||
|
||||||
|
listener == "serial" {
|
||||||
|
handler = listener
|
||||||
|
} else {
|
||||||
|
handler = "forward"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nodes) > 0 {
|
||||||
|
if len(services) == 1 {
|
||||||
|
services[0].Forwarder = &config.ForwarderConfig{
|
||||||
|
Nodes: nodes,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i, svc := range services {
|
||||||
|
if len(nodes) == 1 {
|
||||||
|
svc.Forwarder = &config.ForwarderConfig{
|
||||||
|
Nodes: nodes,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if i < len(nodes) {
|
||||||
|
svc.Forwarder = &config.ForwarderConfig{
|
||||||
|
Nodes: []*config.ForwardNodeConfig{nodes[i]},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var auth *config.AuthConfig
|
||||||
|
if url.User != nil {
|
||||||
|
auth = &config.AuthConfig{
|
||||||
|
Username: url.User.Username(),
|
||||||
|
}
|
||||||
|
auth.Password, _ = url.User.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{}
|
||||||
|
for k, v := range url.Query() {
|
||||||
|
if len(v) > 0 {
|
||||||
|
m[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
md := mdx.NewMetadata(m)
|
||||||
|
|
||||||
|
if sa := mdutil.GetString(md, "auth"); sa != "" {
|
||||||
|
au, err := parseAuthFromCmd(sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
auth = au
|
||||||
|
}
|
||||||
|
delete(m, "auth")
|
||||||
|
|
||||||
|
tlsConfig := &config.TLSConfig{
|
||||||
|
CertFile: mdutil.GetString(md, "tls.certFile", "certFile", "cert"),
|
||||||
|
KeyFile: mdutil.GetString(md, "tls.keyFile", "keyFile", "key"),
|
||||||
|
CAFile: mdutil.GetString(md, "tls.caFile", "caFile", "ca"),
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m, "tls.certFile")
|
||||||
|
delete(m, "certFile")
|
||||||
|
delete(m, "cert")
|
||||||
|
delete(m, "tls.keyFile")
|
||||||
|
delete(m, "keyFile")
|
||||||
|
delete(m, "key")
|
||||||
|
delete(m, "tls.caFile")
|
||||||
|
delete(m, "caFile")
|
||||||
|
delete(m, "ca")
|
||||||
|
|
||||||
|
if tlsConfig.CertFile == "" {
|
||||||
|
tlsConfig = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := mdutil.GetString(md, "dns"); v != "" {
|
||||||
|
md.Set("dns", strings.Split(v, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
selector := parseSelector(m)
|
||||||
|
handlerCfg := &config.HandlerConfig{
|
||||||
|
Type: handler,
|
||||||
|
Auth: auth,
|
||||||
|
Metadata: m,
|
||||||
|
}
|
||||||
|
listenerCfg := &config.ListenerConfig{
|
||||||
|
Type: listener,
|
||||||
|
TLS: tlsConfig,
|
||||||
|
Metadata: m,
|
||||||
|
}
|
||||||
|
if listenerCfg.Type == "ssh" || listenerCfg.Type == "sshd" {
|
||||||
|
handlerCfg.Auth = nil
|
||||||
|
listenerCfg.Auth = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, svc := range services {
|
||||||
|
if svc.Forwarder != nil {
|
||||||
|
svc.Forwarder.Selector = selector
|
||||||
|
}
|
||||||
|
svc.Handler = handlerCfg
|
||||||
|
svc.Listener = listenerCfg
|
||||||
|
svc.Metadata = m
|
||||||
|
}
|
||||||
|
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
|
||||||
|
var connector, dialer string
|
||||||
|
schemes := strings.Split(url.Scheme, "+")
|
||||||
|
if len(schemes) == 1 {
|
||||||
|
connector = schemes[0]
|
||||||
|
dialer = schemes[0]
|
||||||
|
}
|
||||||
|
if len(schemes) == 2 {
|
||||||
|
connector = schemes[0]
|
||||||
|
dialer = schemes[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]any{}
|
||||||
|
for k, v := range url.Query() {
|
||||||
|
if len(v) > 0 {
|
||||||
|
m[k] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
md := mdx.NewMetadata(m)
|
||||||
|
|
||||||
|
node := &config.NodeConfig{
|
||||||
|
Addr: url.Host,
|
||||||
|
Metadata: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := registry.ConnectorRegistry().Get(connector); c == nil {
|
||||||
|
connector = "http"
|
||||||
|
}
|
||||||
|
if d := registry.DialerRegistry().Get(dialer); d == nil {
|
||||||
|
dialer = "tcp"
|
||||||
|
if connector == "ssu" {
|
||||||
|
dialer = "udp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var auth *config.AuthConfig
|
||||||
|
if url.User != nil {
|
||||||
|
auth = &config.AuthConfig{
|
||||||
|
Username: url.User.Username(),
|
||||||
|
}
|
||||||
|
auth.Password, _ = url.User.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
if sauth := mdutil.GetString(md, "auth"); sauth != "" && auth == nil {
|
||||||
|
au, err := parseAuthFromCmd(sauth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
auth = au
|
||||||
|
}
|
||||||
|
delete(m, "auth")
|
||||||
|
|
||||||
|
tlsConfig := &config.TLSConfig{
|
||||||
|
CertFile: mdutil.GetString(md, "tls.certFile", "certFile", "cert"),
|
||||||
|
KeyFile: mdutil.GetString(md, "tls.keyFile", "keyFile", "key"),
|
||||||
|
CAFile: mdutil.GetString(md, "tls.caFile", "caFile", "ca"),
|
||||||
|
Secure: mdutil.GetBool(md, "tls.secure", "secure"),
|
||||||
|
ServerName: mdutil.GetString(md, "tls.servername", "servername"),
|
||||||
|
}
|
||||||
|
if tlsConfig.ServerName == "" {
|
||||||
|
tlsConfig.ServerName = url.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m, "tls.certFile")
|
||||||
|
delete(m, "certFile")
|
||||||
|
delete(m, "cert")
|
||||||
|
delete(m, "tls.keyFile")
|
||||||
|
delete(m, "keyFile")
|
||||||
|
delete(m, "key")
|
||||||
|
delete(m, "tls.caFile")
|
||||||
|
delete(m, "caFile")
|
||||||
|
delete(m, "ca")
|
||||||
|
delete(m, "tls.secure")
|
||||||
|
delete(m, "secure")
|
||||||
|
delete(m, "tls.servername")
|
||||||
|
delete(m, "serverName")
|
||||||
|
|
||||||
|
if !tlsConfig.Secure && tlsConfig.CertFile == "" && tlsConfig.CAFile == "" && tlsConfig.ServerName == "" {
|
||||||
|
tlsConfig = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Connector = &config.ConnectorConfig{
|
||||||
|
Type: connector,
|
||||||
|
Auth: auth,
|
||||||
|
Metadata: m,
|
||||||
|
}
|
||||||
|
node.Dialer = &config.DialerConfig{
|
||||||
|
Type: dialer,
|
||||||
|
TLS: tlsConfig,
|
||||||
|
Metadata: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Dialer.Type == "ssh" || node.Dialer.Type == "sshd" {
|
||||||
|
node.Connector.Auth = nil
|
||||||
|
node.Dialer.Auth = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Norm(s string) (*url.URL, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return nil, ErrInvalidCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
if s[0] == ':' || !strings.Contains(s, "://") {
|
||||||
|
s = "auto://" + s
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if url.Scheme == "https" {
|
||||||
|
url.Scheme = "http+tls"
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAuthFromCmd(sa string) (*config.AuthConfig, error) {
|
||||||
|
v, err := base64.StdEncoding.DecodeString(sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cs := string(v)
|
||||||
|
n := strings.IndexByte(cs, ':')
|
||||||
|
if n < 0 {
|
||||||
|
return &config.AuthConfig{
|
||||||
|
Username: cs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config.AuthConfig{
|
||||||
|
Username: cs[:n],
|
||||||
|
Password: cs[n+1:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSelector(m map[string]any) *config.SelectorConfig {
|
||||||
|
md := mdx.NewMetadata(m)
|
||||||
|
strategy := mdutil.GetString(md, "strategy")
|
||||||
|
maxFails := mdutil.GetInt(md, "maxFails", "max_fails")
|
||||||
|
failTimeout := mdutil.GetDuration(md, "failTimeout", "fail_timeout")
|
||||||
|
if strategy == "" && maxFails <= 0 && failTimeout <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strategy == "" {
|
||||||
|
strategy = "round"
|
||||||
|
}
|
||||||
|
if maxFails <= 0 {
|
||||||
|
maxFails = 1
|
||||||
|
}
|
||||||
|
if failTimeout <= 0 {
|
||||||
|
failTimeout = 30 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m, "strategy")
|
||||||
|
delete(m, "maxFails")
|
||||||
|
delete(m, "max_fails")
|
||||||
|
delete(m, "failTimeout")
|
||||||
|
delete(m, "fail_timeout")
|
||||||
|
|
||||||
|
return &config.SelectorConfig{
|
||||||
|
Strategy: strategy,
|
||||||
|
MaxFails: maxFails,
|
||||||
|
FailTimeout: failTimeout,
|
||||||
|
}
|
||||||
|
}
|
159
config/config.go
159
config/config.go
@ -79,6 +79,11 @@ type LogRotationConfig struct {
|
|||||||
Compress bool `yaml:"compress,omitempty" json:"compress,omitempty"`
|
Compress bool `yaml:"compress,omitempty" json:"compress,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoggerConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ProfilingConfig struct {
|
type ProfilingConfig struct {
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
}
|
}
|
||||||
@ -244,6 +249,21 @@ type SDConfig struct {
|
|||||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RouterRouteConfig struct {
|
||||||
|
Net string `json:"net"`
|
||||||
|
Gateway string `json:"gateway"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouterConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Routes []*RouterRouteConfig `yaml:",omitempty" json:"routes,omitempty"`
|
||||||
|
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||||
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||||
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||||
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||||
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type RecorderConfig struct {
|
type RecorderConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
File *FileRecorder `yaml:",omitempty" json:"file,omitempty"`
|
File *FileRecorder `yaml:",omitempty" json:"file,omitempty"`
|
||||||
@ -277,9 +297,9 @@ type RedisRecorder struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RecorderObject struct {
|
type RecorderObject struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Record string `json:"record"`
|
Record string `json:"record"`
|
||||||
Metadata map[string]any
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LimiterConfig struct {
|
type LimiterConfig struct {
|
||||||
@ -289,6 +309,12 @@ type LimiterConfig struct {
|
|||||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||||
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObserverConfig struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenerConfig struct {
|
type ListenerConfig struct {
|
||||||
@ -311,33 +337,69 @@ type HandlerConfig struct {
|
|||||||
Authers []string `yaml:",omitempty" json:"authers,omitempty"`
|
Authers []string `yaml:",omitempty" json:"authers,omitempty"`
|
||||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||||
Ingress string `yaml:",omitempty" json:"ingress,omitempty"`
|
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
||||||
|
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
||||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForwarderConfig 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"`
|
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
||||||
Nodes []*ForwardNodeConfig `json:"nodes"`
|
Nodes []*ForwardNodeConfig `json:"nodes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForwardNodeConfig struct {
|
type ForwardNodeConfig struct {
|
||||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||||
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
// DEPRECATED by filter.protocol
|
||||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
||||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
// DEPRECATED by filter.host
|
||||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
// DEPRECATED by filter.path
|
||||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
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 {
|
||||||
|
Match string
|
||||||
|
Replacement string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPBodyRewriteConfig struct {
|
||||||
|
// filter by MIME types
|
||||||
|
Type string
|
||||||
|
Match string
|
||||||
|
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 {
|
type HTTPNodeConfig struct {
|
||||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
// rewrite host header
|
||||||
|
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||||
|
// additional request header
|
||||||
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
|
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
|
||||||
|
// rewrite URL
|
||||||
|
Rewrite []HTTPURLRewriteConfig `yaml:",omitempty" json:"rewrite,omitempty"`
|
||||||
|
// rewrite response body
|
||||||
|
RewriteBody []HTTPBodyRewriteConfig `yaml:"rewriteBody,omitempty" json:"rewriteBody,omitempty"`
|
||||||
|
// HTTP basic auth
|
||||||
|
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TLSNodeConfig struct {
|
type TLSNodeConfig struct {
|
||||||
@ -380,11 +442,36 @@ type ServiceConfig struct {
|
|||||||
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
||||||
CLimiter string `yaml:"climiter,omitempty" json:"climiter,omitempty"`
|
CLimiter string `yaml:"climiter,omitempty" json:"climiter,omitempty"`
|
||||||
RLimiter string `yaml:"rlimiter,omitempty" json:"rlimiter,omitempty"`
|
RLimiter string `yaml:"rlimiter,omitempty" json:"rlimiter,omitempty"`
|
||||||
|
Logger string `yaml:",omitempty" json:"logger,omitempty"`
|
||||||
|
Loggers []string `yaml:",omitempty" json:"loggers,omitempty"`
|
||||||
|
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
||||||
Recorders []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"`
|
Recorders []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"`
|
||||||
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
|
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
|
||||||
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
|
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
|
||||||
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"`
|
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"`
|
||||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||||
|
// service status, read-only
|
||||||
|
Status *ServiceStatus `yaml:",omitempty" json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceStatus struct {
|
||||||
|
CreateTime int64 `yaml:"createTime" json:"createTime"`
|
||||||
|
State string `yaml:"state" json:"state"`
|
||||||
|
Events []ServiceEvent `yaml:",omitempty" json:"events,omitempty"`
|
||||||
|
Stats *ServiceStats `yaml:",omitempty" json:"stats,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceEvent struct {
|
||||||
|
Time int64 `yaml:"time" json:"time"`
|
||||||
|
Msg string `yaml:"msg" json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceStats struct {
|
||||||
|
TotalConns uint64 `yaml:"totalConns" json:"totalConns"`
|
||||||
|
CurrentConns uint64 `yaml:"currentConns" json:"currentConns"`
|
||||||
|
TotalErrs uint64 `yaml:"totalErrs" json:"totalErrs"`
|
||||||
|
InputBytes uint64 `yaml:"inputBytes" json:"inputBytes"`
|
||||||
|
OutputBytes uint64 `yaml:"outputBytes" json:"outputBytes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainConfig struct {
|
type ChainConfig struct {
|
||||||
@ -413,27 +500,26 @@ type HopConfig struct {
|
|||||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||||
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeConfig struct {
|
type NodeConfig struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||||
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
||||||
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
||||||
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
|
||||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
|
||||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
||||||
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
Netns string `yaml:",omitempty" json:"netns,omitempty"`
|
||||||
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
||||||
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
|
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
|
||||||
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
|
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
||||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
|
||||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -446,11 +532,14 @@ type Config struct {
|
|||||||
Resolvers []*ResolverConfig `yaml:",omitempty" json:"resolvers,omitempty"`
|
Resolvers []*ResolverConfig `yaml:",omitempty" json:"resolvers,omitempty"`
|
||||||
Hosts []*HostsConfig `yaml:",omitempty" json:"hosts,omitempty"`
|
Hosts []*HostsConfig `yaml:",omitempty" json:"hosts,omitempty"`
|
||||||
Ingresses []*IngressConfig `yaml:",omitempty" json:"ingresses,omitempty"`
|
Ingresses []*IngressConfig `yaml:",omitempty" json:"ingresses,omitempty"`
|
||||||
|
Routers []*RouterConfig `yaml:",omitempty" json:"routers,omitempty"`
|
||||||
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
|
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
|
||||||
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
|
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
|
||||||
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
|
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
|
||||||
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`
|
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`
|
||||||
RLimiters []*LimiterConfig `yaml:"rlimiters,omitempty" json:"rlimiters,omitempty"`
|
RLimiters []*LimiterConfig `yaml:"rlimiters,omitempty" json:"rlimiters,omitempty"`
|
||||||
|
Observers []*ObserverConfig `yaml:",omitempty" json:"observers,omitempty"`
|
||||||
|
Loggers []*LoggerConfig `yaml:",omitempty" json:"loggers,omitempty"`
|
||||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||||
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
||||||
Profiling *ProfilingConfig `yaml:",omitempty" json:"profiling,omitempty"`
|
Profiling *ProfilingConfig `yaml:",omitempty" json:"profiling,omitempty"`
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/go-gost/core/admission"
|
"github.com/go-gost/core/admission"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
xadmission "github.com/go-gost/x/admission"
|
xadmission "github.com/go-gost/x/admission"
|
||||||
|
admission_plugin "github.com/go-gost/x/admission/plugin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
@ -28,13 +29,13 @@ func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
|
|||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case "http":
|
||||||
return xadmission.NewHTTPPlugin(
|
return admission_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return xadmission.NewGRPCPlugin(
|
return admission_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/go-gost/core/auth"
|
"github.com/go-gost/core/auth"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
xauth "github.com/go-gost/x/auth"
|
xauth "github.com/go-gost/x/auth"
|
||||||
|
auth_plugin "github.com/go-gost/x/auth/plugin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
@ -28,13 +29,13 @@ func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
|
|||||||
}
|
}
|
||||||
switch cfg.Plugin.Type {
|
switch cfg.Plugin.Type {
|
||||||
case "http":
|
case "http":
|
||||||
return xauth.NewHTTPPlugin(
|
return auth_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return xauth.NewGRPCPlugin(
|
return auth_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/go-gost/core/bypass"
|
"github.com/go-gost/core/bypass"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
xbypass "github.com/go-gost/x/bypass"
|
xbypass "github.com/go-gost/x/bypass"
|
||||||
|
bypass_plugin "github.com/go-gost/x/bypass/plugin"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
@ -28,13 +29,13 @@ func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
|
|||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case "http":
|
||||||
return xbypass.NewHTTPPlugin(
|
return bypass_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return xbypass.NewGRPCPlugin(
|
return bypass_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
@ -12,12 +12,12 @@ import (
|
|||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseChain(cfg *config.ChainConfig) (chain.Chainer, error) {
|
func ParseChain(cfg *config.ChainConfig, log logger.Logger) (chain.Chainer, error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
chainLogger := logger.Default().WithFields(map[string]any{
|
chainLogger := log.WithFields(map[string]any{
|
||||||
"kind": "chain",
|
"kind": "chain",
|
||||||
"chain": cfg.Name,
|
"chain": cfg.Name,
|
||||||
})
|
})
|
||||||
@ -37,7 +37,7 @@ func ParseChain(cfg *config.ChainConfig) (chain.Chainer, error) {
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
if ch.Nodes != nil || ch.Plugin != nil {
|
if ch.Nodes != nil || ch.Plugin != nil {
|
||||||
if hop, err = hop_parser.ParseHop(ch); err != nil {
|
if hop, err = hop_parser.ParseHop(ch, log); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,16 +8,20 @@ import (
|
|||||||
"github.com/go-gost/core/chain"
|
"github.com/go-gost/core/chain"
|
||||||
"github.com/go-gost/core/hop"
|
"github.com/go-gost/core/hop"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
|
"github.com/go-gost/x/config/parsing"
|
||||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||||
node_parser "github.com/go-gost/x/config/parsing/node"
|
node_parser "github.com/go-gost/x/config/parsing/node"
|
||||||
selector_parser "github.com/go-gost/x/config/parsing/selector"
|
selector_parser "github.com/go-gost/x/config/parsing/selector"
|
||||||
xhop "github.com/go-gost/x/hop"
|
xhop "github.com/go-gost/x/hop"
|
||||||
|
hop_plugin "github.com/go-gost/x/hop/plugin"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
"github.com/go-gost/x/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -31,14 +35,14 @@ func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case plugin.HTTP:
|
||||||
return xhop.NewHTTPPlugin(
|
return hop_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
), nil
|
), nil
|
||||||
default:
|
default:
|
||||||
return xhop.NewGRPCPlugin(
|
return hop_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
@ -46,6 +50,16 @@ func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ifce := cfg.Interface
|
||||||
|
var netns string
|
||||||
|
if cfg.Metadata != nil {
|
||||||
|
md := metadata.NewMetadata(cfg.Metadata)
|
||||||
|
if v := mdutil.GetString(md, parsing.MDKeyInterface); v != "" {
|
||||||
|
ifce = v
|
||||||
|
}
|
||||||
|
netns = mdutil.GetString(md, "netns")
|
||||||
|
}
|
||||||
|
|
||||||
var nodes []*chain.Node
|
var nodes []*chain.Node
|
||||||
for _, v := range cfg.Nodes {
|
for _, v := range cfg.Nodes {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
@ -59,25 +73,31 @@ func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
|||||||
v.Hosts = cfg.Hosts
|
v.Hosts = cfg.Hosts
|
||||||
}
|
}
|
||||||
if v.Interface == "" {
|
if v.Interface == "" {
|
||||||
v.Interface = cfg.Interface
|
v.Interface = ifce
|
||||||
}
|
}
|
||||||
|
if v.Netns == "" {
|
||||||
|
v.Netns = netns
|
||||||
|
}
|
||||||
|
|
||||||
if v.SockOpts == nil {
|
if v.SockOpts == nil {
|
||||||
v.SockOpts = cfg.SockOpts
|
v.SockOpts = cfg.SockOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Connector == nil {
|
if v.Connector == nil {
|
||||||
v.Connector = &config.ConnectorConfig{
|
v.Connector = &config.ConnectorConfig{}
|
||||||
Type: "http",
|
}
|
||||||
}
|
if strings.TrimSpace(v.Connector.Type) == "" {
|
||||||
|
v.Connector.Type = "http"
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Dialer == nil {
|
if v.Dialer == nil {
|
||||||
v.Dialer = &config.DialerConfig{
|
v.Dialer = &config.DialerConfig{}
|
||||||
Type: "tcp",
|
}
|
||||||
}
|
if strings.TrimSpace(v.Dialer.Type) == "" {
|
||||||
|
v.Dialer.Type = "tcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
node, err := node_parser.ParseNode(cfg.Name, v)
|
node, err := node_parser.ParseNode(cfg.Name, v, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -97,7 +117,7 @@ func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
|||||||
xhop.SelectorOption(sel),
|
xhop.SelectorOption(sel),
|
||||||
xhop.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
xhop.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||||
xhop.ReloadPeriodOption(cfg.Reload),
|
xhop.ReloadPeriodOption(cfg.Reload),
|
||||||
xhop.LoggerOption(logger.Default().WithFields(map[string]any{
|
xhop.LoggerOption(log.WithFields(map[string]any{
|
||||||
"kind": "hop",
|
"kind": "hop",
|
||||||
"hop": cfg.Name,
|
"hop": cfg.Name,
|
||||||
})),
|
})),
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
xhosts "github.com/go-gost/x/hosts"
|
xhosts "github.com/go-gost/x/hosts"
|
||||||
|
hosts_plugin "github.com/go-gost/x/hosts/plugin"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
)
|
)
|
||||||
@ -28,13 +29,13 @@ func ParseHostMapper(cfg *config.HostsConfig) hosts.HostMapper {
|
|||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case "http":
|
||||||
return xhosts.NewHTTPPlugin(
|
return hosts_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return xhosts.NewGRPCPlugin(
|
return hosts_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
xingress "github.com/go-gost/x/ingress"
|
xingress "github.com/go-gost/x/ingress"
|
||||||
|
ingress_plugin "github.com/go-gost/x/ingress/plugin"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
)
|
)
|
||||||
@ -27,13 +28,13 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
|
|||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case "http":
|
||||||
return xingress.NewHTTPPlugin(
|
return ingress_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return xingress.NewGRPCPlugin(
|
return ingress_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
@ -41,13 +42,13 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []xingress.Rule
|
var rules []*ingress.Rule
|
||||||
for _, rule := range cfg.Rules {
|
for _, rule := range cfg.Rules {
|
||||||
if rule.Hostname == "" || rule.Endpoint == "" {
|
if rule.Hostname == "" || rule.Endpoint == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rules = append(rules, xingress.Rule{
|
rules = append(rules, &ingress.Rule{
|
||||||
Hostname: rule.Hostname,
|
Hostname: rule.Hostname,
|
||||||
Endpoint: rule.Endpoint,
|
Endpoint: rule.Endpoint,
|
||||||
})
|
})
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
package limiter
|
package limiter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-gost/core/limiter/conn"
|
"github.com/go-gost/core/limiter/conn"
|
||||||
"github.com/go-gost/core/limiter/rate"
|
"github.com/go-gost/core/limiter/rate"
|
||||||
"github.com/go-gost/core/limiter/traffic"
|
"github.com/go-gost/core/limiter/traffic"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/internal/loader"
|
"github.com/go-gost/x/internal/loader"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
xconn "github.com/go-gost/x/limiter/conn"
|
xconn "github.com/go-gost/x/limiter/conn"
|
||||||
xrate "github.com/go-gost/x/limiter/rate"
|
xrate "github.com/go-gost/x/limiter/rate"
|
||||||
xtraffic "github.com/go-gost/x/limiter/traffic"
|
xtraffic "github.com/go-gost/x/limiter/traffic"
|
||||||
|
traffic_plugin "github.com/go-gost/x/limiter/traffic/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter) {
|
func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter) {
|
||||||
@ -17,6 +22,30 @@ func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Plugin != nil {
|
||||||
|
var tlsCfg *tls.Config
|
||||||
|
if cfg.Plugin.TLS != nil {
|
||||||
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return traffic_plugin.NewHTTPPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return traffic_plugin.NewGRPCPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var opts []xtraffic.Option
|
var opts []xtraffic.Option
|
||||||
|
|
||||||
if cfg.File != nil && cfg.File.Path != "" {
|
if cfg.File != nil && cfg.File.Path != "" {
|
||||||
|
70
config/parsing/logger/parse.go
Normal file
70
config/parsing/logger/parse.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/x/config"
|
||||||
|
xlogger "github.com/go-gost/x/logger"
|
||||||
|
"github.com/go-gost/x/registry"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseLogger(cfg *config.LoggerConfig) logger.Logger {
|
||||||
|
if cfg == nil || cfg.Log == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
opts := []xlogger.Option{
|
||||||
|
xlogger.NameOption(cfg.Name),
|
||||||
|
xlogger.FormatOption(logger.LogFormat(cfg.Log.Format)),
|
||||||
|
xlogger.LevelOption(logger.LogLevel(cfg.Log.Level)),
|
||||||
|
}
|
||||||
|
|
||||||
|
var out io.Writer = os.Stderr
|
||||||
|
switch cfg.Log.Output {
|
||||||
|
case "none", "null":
|
||||||
|
return xlogger.Nop()
|
||||||
|
case "stdout":
|
||||||
|
out = os.Stdout
|
||||||
|
case "stderr", "":
|
||||||
|
out = os.Stderr
|
||||||
|
default:
|
||||||
|
if cfg.Log.Rotation != nil {
|
||||||
|
out = &lumberjack.Logger{
|
||||||
|
Filename: cfg.Log.Output,
|
||||||
|
MaxSize: cfg.Log.Rotation.MaxSize,
|
||||||
|
MaxAge: cfg.Log.Rotation.MaxAge,
|
||||||
|
MaxBackups: cfg.Log.Rotation.MaxBackups,
|
||||||
|
LocalTime: cfg.Log.Rotation.LocalTime,
|
||||||
|
Compress: cfg.Log.Rotation.Compress,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.MkdirAll(filepath.Dir(cfg.Log.Output), 0755)
|
||||||
|
f, err := os.OpenFile(cfg.Log.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
logger.Default().Warn(err)
|
||||||
|
} else {
|
||||||
|
out = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts = append(opts, xlogger.OutputOption(out))
|
||||||
|
|
||||||
|
return xlogger.NewLogger(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func List(name string, names ...string) []logger.Logger {
|
||||||
|
var loggers []logger.Logger
|
||||||
|
if adm := registry.LoggerRegistry().Get(name); adm != nil {
|
||||||
|
loggers = append(loggers, adm)
|
||||||
|
}
|
||||||
|
for _, s := range names {
|
||||||
|
if lg := registry.LoggerRegistry().Get(s); lg != nil {
|
||||||
|
loggers = append(loggers, lg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loggers
|
||||||
|
}
|
@ -3,8 +3,8 @@ package node
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-gost/core/bypass"
|
"github.com/go-gost/core/bypass"
|
||||||
"github.com/go-gost/core/chain"
|
"github.com/go-gost/core/chain"
|
||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/go-gost/core/metadata"
|
"github.com/go-gost/core/metadata"
|
||||||
mdutil "github.com/go-gost/core/metadata/util"
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
xauth "github.com/go-gost/x/auth"
|
xauth "github.com/go-gost/x/auth"
|
||||||
|
xchain "github.com/go-gost/x/chain"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/config/parsing"
|
"github.com/go-gost/x/config/parsing"
|
||||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||||
@ -23,7 +24,7 @@ import (
|
|||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
|
func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.Node, error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -40,7 +41,7 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeLogger := logger.Default().WithFields(map[string]any{
|
nodeLogger := log.WithFields(map[string]any{
|
||||||
"hop": hop,
|
"hop": hop,
|
||||||
"kind": "node",
|
"kind": "node",
|
||||||
"node": cfg.Name,
|
"node": cfg.Name,
|
||||||
@ -139,40 +140,77 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr := chain.NewTransport(d, cr,
|
tr := xchain.NewTransport(d, cr,
|
||||||
chain.AddrTransportOption(cfg.Addr),
|
chain.AddrTransportOption(cfg.Addr),
|
||||||
chain.InterfaceTransportOption(cfg.Interface),
|
chain.InterfaceTransportOption(cfg.Interface),
|
||||||
|
chain.NetnsTransportOption(cfg.Netns),
|
||||||
chain.SockOptsTransportOption(sockOpts),
|
chain.SockOptsTransportOption(sockOpts),
|
||||||
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{
|
opts := []chain.NodeOption{
|
||||||
chain.TransportNodeOption(tr),
|
chain.TransportNodeOption(tr),
|
||||||
chain.BypassNodeOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
chain.BypassNodeOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||||
chain.ResoloverNodeOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
chain.ResoloverNodeOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
||||||
chain.HostMapperNodeOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
chain.HostMapperNodeOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
||||||
chain.MetadataNodeOption(nm),
|
chain.MetadataNodeOption(nm),
|
||||||
chain.HostNodeOption(host),
|
|
||||||
chain.ProtocolNodeOption(cfg.Protocol),
|
|
||||||
chain.PathNodeOption(cfg.Path),
|
|
||||||
chain.NetworkNodeOption(cfg.Network),
|
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 {
|
if cfg.HTTP != nil {
|
||||||
opts = append(opts, chain.HTTPNodeOption(&chain.HTTPNodeSettings{
|
settings := &chain.HTTPNodeSettings{
|
||||||
Host: cfg.HTTP.Host,
|
Host: cfg.HTTP.Host,
|
||||||
Header: cfg.HTTP.Header,
|
Header: cfg.HTTP.Header,
|
||||||
}))
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for _, v := range cfg.HTTP.Rewrite {
|
||||||
|
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
|
||||||
|
settings.RewriteURL = append(settings.RewriteURL, chain.HTTPURLRewriteSetting{
|
||||||
|
Pattern: pattern,
|
||||||
|
Replacement: v.Replacement,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range cfg.HTTP.RewriteBody {
|
||||||
|
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
|
||||||
|
settings.RewriteBody = append(settings.RewriteBody, chain.HTTPBodyRewriteSettings{
|
||||||
|
Type: v.Type,
|
||||||
|
Pattern: pattern,
|
||||||
|
Replacement: []byte(v.Replacement),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts = append(opts, chain.HTTPNodeOption(settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.TLS != nil {
|
if cfg.TLS != nil {
|
||||||
tlsCfg := &chain.TLSNodeSettings{
|
tlsCfg := &chain.TLSNodeSettings{
|
||||||
ServerName: cfg.TLS.ServerName,
|
ServerName: cfg.TLS.ServerName,
|
||||||
@ -185,18 +223,5 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
|
|||||||
}
|
}
|
||||||
opts = append(opts, chain.TLSNodeOption(tlsCfg))
|
opts = append(opts, chain.TLSNodeOption(tlsCfg))
|
||||||
}
|
}
|
||||||
if cfg.Auth != nil {
|
|
||||||
opts = append(opts, chain.AutherNodeOption(
|
|
||||||
xauth.NewAuthenticator(
|
|
||||||
xauth.AuthsOption(map[string]string{cfg.Auth.Username: cfg.Auth.Password}),
|
|
||||||
xauth.LoggerOption(logger.Default().WithFields(map[string]any{
|
|
||||||
"kind": "node",
|
|
||||||
"node": cfg.Name,
|
|
||||||
"addr": cfg.Addr,
|
|
||||||
"host": cfg.Host,
|
|
||||||
"protocol": cfg.Protocol,
|
|
||||||
})),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
return chain.NewNode(cfg.Name, cfg.Addr, opts...), nil
|
return chain.NewNode(cfg.Name, cfg.Addr, opts...), nil
|
||||||
}
|
}
|
||||||
|
39
config/parsing/observer/parse.go
Normal file
39
config/parsing/observer/parse.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package observer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/observer"
|
||||||
|
"github.com/go-gost/x/config"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
observer_plugin "github.com/go-gost/x/observer/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseObserver(cfg *config.ObserverConfig) observer.Observer {
|
||||||
|
if cfg == nil || cfg.Plugin == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsCfg *tls.Config
|
||||||
|
if cfg.Plugin.TLS != nil {
|
||||||
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return observer_plugin.NewHTTPPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return observer_plugin.NewGRPCPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ const (
|
|||||||
MDKeyPostUp = "postUp"
|
MDKeyPostUp = "postUp"
|
||||||
MDKeyPostDown = "postDown"
|
MDKeyPostDown = "postDown"
|
||||||
MDKeyIgnoreChain = "ignoreChain"
|
MDKeyIgnoreChain = "ignoreChain"
|
||||||
|
MDKeyEnableStats = "enableStats"
|
||||||
|
|
||||||
MDKeyRecorderDirection = "direction"
|
MDKeyRecorderDirection = "direction"
|
||||||
MDKeyRecorderTimestampFormat = "timeStampFormat"
|
MDKeyRecorderTimestampFormat = "timeStampFormat"
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
xrecorder "github.com/go-gost/x/recorder"
|
xrecorder "github.com/go-gost/x/recorder"
|
||||||
|
recorder_plugin "github.com/go-gost/x/recorder/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
|
func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
|
||||||
@ -25,13 +26,13 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
|
|||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case "http":
|
||||||
return xrecorder.NewHTTPPlugin(
|
return recorder_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return xrecorder.NewGRPCPlugin(
|
return recorder_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
xresolver "github.com/go-gost/x/resolver"
|
xresolver "github.com/go-gost/x/resolver"
|
||||||
|
resolver_plugin "github.com/go-gost/x/resolver/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
|
func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
|
||||||
@ -28,13 +29,13 @@ func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
|
|||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case "http":
|
||||||
return xresolver.NewHTTPPlugin(
|
return resolver_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
), nil
|
), nil
|
||||||
default:
|
default:
|
||||||
return xresolver.NewGRPCPlugin(
|
return resolver_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
105
config/parsing/router/parse.go
Normal file
105
config/parsing/router/parse.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
"github.com/go-gost/core/router"
|
||||||
|
"github.com/go-gost/x/config"
|
||||||
|
"github.com/go-gost/x/internal/loader"
|
||||||
|
"github.com/go-gost/x/internal/plugin"
|
||||||
|
xrouter "github.com/go-gost/x/router"
|
||||||
|
router_plugin "github.com/go-gost/x/router/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseRouter(cfg *config.RouterConfig) router.Router {
|
||||||
|
if cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Plugin != nil {
|
||||||
|
var tlsCfg *tls.Config
|
||||||
|
if cfg.Plugin.TLS != nil {
|
||||||
|
tlsCfg = &tls.Config{
|
||||||
|
ServerName: cfg.Plugin.TLS.ServerName,
|
||||||
|
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
|
case "http":
|
||||||
|
return router_plugin.NewHTTPPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return router_plugin.NewGRPCPlugin(
|
||||||
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var routes []*router.Route
|
||||||
|
for _, route := range cfg.Routes {
|
||||||
|
_, ipNet, _ := net.ParseCIDR(route.Net)
|
||||||
|
if ipNet == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gw := net.ParseIP(route.Gateway)
|
||||||
|
if gw == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
routes = append(routes, &router.Route{
|
||||||
|
Net: ipNet,
|
||||||
|
Gateway: gw,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
opts := []xrouter.Option{
|
||||||
|
xrouter.RoutesOption(routes),
|
||||||
|
xrouter.ReloadPeriodOption(cfg.Reload),
|
||||||
|
xrouter.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||||
|
"kind": "router",
|
||||||
|
"router": cfg.Name,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
if cfg.File != nil && cfg.File.Path != "" {
|
||||||
|
opts = append(opts, xrouter.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||||
|
}
|
||||||
|
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||||
|
switch cfg.Redis.Type {
|
||||||
|
case "list": // rediss list
|
||||||
|
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisListLoader(
|
||||||
|
cfg.Redis.Addr,
|
||||||
|
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||||
|
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||||
|
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||||
|
)))
|
||||||
|
case "set": // redis set
|
||||||
|
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisSetLoader(
|
||||||
|
cfg.Redis.Addr,
|
||||||
|
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||||
|
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||||
|
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||||
|
)))
|
||||||
|
default: // redis hash
|
||||||
|
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisHashLoader(
|
||||||
|
cfg.Redis.Addr,
|
||||||
|
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||||
|
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||||
|
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||||
|
opts = append(opts, xrouter.HTTPLoaderOption(loader.HTTPLoader(
|
||||||
|
cfg.HTTP.URL,
|
||||||
|
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
return xrouter.NewRouter(opts...)
|
||||||
|
}
|
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/go-gost/core/sd"
|
"github.com/go-gost/core/sd"
|
||||||
"github.com/go-gost/x/config"
|
"github.com/go-gost/x/config"
|
||||||
"github.com/go-gost/x/internal/plugin"
|
"github.com/go-gost/x/internal/plugin"
|
||||||
xsd "github.com/go-gost/x/sd"
|
sd_plugin "github.com/go-gost/x/sd/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseSD(cfg *config.SDConfig) sd.SD {
|
func ParseSD(cfg *config.SDConfig) sd.SD {
|
||||||
@ -24,13 +24,13 @@ func ParseSD(cfg *config.SDConfig) sd.SD {
|
|||||||
}
|
}
|
||||||
switch strings.ToLower(cfg.Plugin.Type) {
|
switch strings.ToLower(cfg.Plugin.Type) {
|
||||||
case "http":
|
case "http":
|
||||||
return xsd.NewHTTPPlugin(
|
return sd_plugin.NewHTTPPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return xsd.NewGRPCPlugin(
|
return sd_plugin.NewGRPCPlugin(
|
||||||
cfg.Name, cfg.Plugin.Addr,
|
cfg.Name, cfg.Plugin.Addr,
|
||||||
plugin.TokenOption(cfg.Plugin.Token),
|
plugin.TokenOption(cfg.Plugin.Token),
|
||||||
plugin.TLSConfigOption(tlsCfg),
|
plugin.TLSConfigOption(tlsCfg),
|
||||||
|
@ -2,6 +2,9 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-gost/core/admission"
|
"github.com/go-gost/core/admission"
|
||||||
"github.com/go-gost/core/auth"
|
"github.com/go-gost/core/auth"
|
||||||
@ -12,6 +15,7 @@ import (
|
|||||||
"github.com/go-gost/core/listener"
|
"github.com/go-gost/core/listener"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
mdutil "github.com/go-gost/core/metadata/util"
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
|
"github.com/go-gost/core/observer/stats"
|
||||||
"github.com/go-gost/core/recorder"
|
"github.com/go-gost/core/recorder"
|
||||||
"github.com/go-gost/core/selector"
|
"github.com/go-gost/core/selector"
|
||||||
"github.com/go-gost/core/service"
|
"github.com/go-gost/core/service"
|
||||||
@ -22,42 +26,49 @@ import (
|
|||||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||||
hop_parser "github.com/go-gost/x/config/parsing/hop"
|
hop_parser "github.com/go-gost/x/config/parsing/hop"
|
||||||
|
logger_parser "github.com/go-gost/x/config/parsing/logger"
|
||||||
selector_parser "github.com/go-gost/x/config/parsing/selector"
|
selector_parser "github.com/go-gost/x/config/parsing/selector"
|
||||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||||
"github.com/go-gost/x/metadata"
|
"github.com/go-gost/x/metadata"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
xservice "github.com/go-gost/x/service"
|
xservice "github.com/go-gost/x/service"
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||||
if cfg.Listener == nil {
|
if cfg.Listener == nil {
|
||||||
cfg.Listener = &config.ListenerConfig{
|
cfg.Listener = &config.ListenerConfig{}
|
||||||
Type: "tcp",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(cfg.Listener.Type) == "" {
|
||||||
|
cfg.Listener.Type = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Handler == nil {
|
if cfg.Handler == nil {
|
||||||
cfg.Handler = &config.HandlerConfig{
|
cfg.Handler = &config.HandlerConfig{}
|
||||||
Type: "auto",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
serviceLogger := logger.Default().WithFields(map[string]any{
|
if strings.TrimSpace(cfg.Handler.Type) == "" {
|
||||||
|
cfg.Handler.Type = "auto"
|
||||||
|
}
|
||||||
|
|
||||||
|
log := logger.Default()
|
||||||
|
if loggers := logger_parser.List(cfg.Logger, cfg.Loggers...); len(loggers) > 0 {
|
||||||
|
log = logger.LoggerGroup(loggers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceLogger := log.WithFields(map[string]any{
|
||||||
"kind": "service",
|
"kind": "service",
|
||||||
"service": cfg.Name,
|
"service": cfg.Name,
|
||||||
"listener": cfg.Listener.Type,
|
"listener": cfg.Listener.Type,
|
||||||
"handler": cfg.Handler.Type,
|
"handler": cfg.Handler.Type,
|
||||||
})
|
})
|
||||||
|
|
||||||
listenerLogger := serviceLogger.WithFields(map[string]any{
|
|
||||||
"kind": "listener",
|
|
||||||
})
|
|
||||||
|
|
||||||
tlsCfg := cfg.Listener.TLS
|
tlsCfg := cfg.Listener.TLS
|
||||||
if tlsCfg == nil {
|
if tlsCfg == nil {
|
||||||
tlsCfg = &config.TLSConfig{}
|
tlsCfg = &config.TLSConfig{}
|
||||||
}
|
}
|
||||||
tlsConfig, err := tls_util.LoadServerConfig(tlsCfg)
|
tlsConfig, err := tls_util.LoadServerConfig(tlsCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listenerLogger.Error(err)
|
serviceLogger.Error(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
@ -88,6 +99,10 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
ifce := cfg.Interface
|
ifce := cfg.Interface
|
||||||
var preUp, preDown, postUp, postDown []string
|
var preUp, preDown, postUp, postDown []string
|
||||||
var ignoreChain bool
|
var ignoreChain bool
|
||||||
|
var pStats *stats.Stats
|
||||||
|
var observePeriod time.Duration
|
||||||
|
var netnsIn, netnsOut string
|
||||||
|
var dialTimeout time.Duration
|
||||||
if cfg.Metadata != nil {
|
if cfg.Metadata != nil {
|
||||||
md := metadata.NewMetadata(cfg.Metadata)
|
md := metadata.NewMetadata(cfg.Metadata)
|
||||||
ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol)
|
ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol)
|
||||||
@ -104,31 +119,83 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
postUp = mdutil.GetStrings(md, parsing.MDKeyPostUp)
|
postUp = mdutil.GetStrings(md, parsing.MDKeyPostUp)
|
||||||
postDown = mdutil.GetStrings(md, parsing.MDKeyPostDown)
|
postDown = mdutil.GetStrings(md, parsing.MDKeyPostDown)
|
||||||
ignoreChain = mdutil.GetBool(md, parsing.MDKeyIgnoreChain)
|
ignoreChain = mdutil.GetBool(md, parsing.MDKeyIgnoreChain)
|
||||||
|
|
||||||
|
if mdutil.GetBool(md, parsing.MDKeyEnableStats) {
|
||||||
|
pStats = &stats.Stats{}
|
||||||
|
}
|
||||||
|
observePeriod = mdutil.GetDuration(md, "observePeriod")
|
||||||
|
netnsIn = mdutil.GetString(md, "netns")
|
||||||
|
netnsOut = mdutil.GetString(md, "netns.out")
|
||||||
|
dialTimeout = mdutil.GetDuration(md, "dialTimeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerLogger := serviceLogger.WithFields(map[string]any{
|
||||||
|
"kind": "listener",
|
||||||
|
})
|
||||||
|
|
||||||
|
routerOpts := []chain.RouterOption{
|
||||||
|
chain.TimeoutRouterOption(dialTimeout),
|
||||||
|
chain.InterfaceRouterOption(ifce),
|
||||||
|
chain.NetnsRouterOption(netnsOut),
|
||||||
|
chain.SockOptsRouterOption(sockOpts),
|
||||||
|
chain.ResolverRouterOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
||||||
|
chain.HostMapperRouterOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
||||||
|
chain.LoggerRouterOption(listenerLogger),
|
||||||
|
}
|
||||||
|
if !ignoreChain {
|
||||||
|
routerOpts = append(routerOpts,
|
||||||
|
chain.ChainRouterOption(chainGroup(cfg.Listener.Chain, cfg.Listener.ChainGroup)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
listenOpts := []listener.Option{
|
listenOpts := []listener.Option{
|
||||||
listener.AddrOption(cfg.Addr),
|
listener.AddrOption(cfg.Addr),
|
||||||
|
listener.RouterOption(xchain.NewRouter(routerOpts...)),
|
||||||
listener.AutherOption(auther),
|
listener.AutherOption(auther),
|
||||||
listener.AuthOption(auth_parser.Info(cfg.Listener.Auth)),
|
listener.AuthOption(auth_parser.Info(cfg.Listener.Auth)),
|
||||||
listener.TLSConfigOption(tlsConfig),
|
listener.TLSConfigOption(tlsConfig),
|
||||||
listener.AdmissionOption(admission.AdmissionGroup(admissions...)),
|
listener.AdmissionOption(admission.AdmissionGroup(admissions...)),
|
||||||
listener.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Limiter)),
|
listener.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Limiter)),
|
||||||
listener.ConnLimiterOption(registry.ConnLimiterRegistry().Get(cfg.CLimiter)),
|
listener.ConnLimiterOption(registry.ConnLimiterRegistry().Get(cfg.CLimiter)),
|
||||||
listener.LoggerOption(listenerLogger),
|
|
||||||
listener.ServiceOption(cfg.Name),
|
listener.ServiceOption(cfg.Name),
|
||||||
listener.ProxyProtocolOption(ppv),
|
listener.ProxyProtocolOption(ppv),
|
||||||
|
listener.StatsOption(pStats),
|
||||||
|
listener.NetnsOption(netnsIn),
|
||||||
|
listener.LoggerOption(listenerLogger),
|
||||||
}
|
}
|
||||||
if !ignoreChain {
|
|
||||||
listenOpts = append(listenOpts,
|
if netnsIn != "" {
|
||||||
listener.ChainOption(chainGroup(cfg.Listener.Chain, cfg.Listener.ChainGroup)),
|
runtime.LockOSThread()
|
||||||
)
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
originNs, err := netns.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("netns.Get(): %v", err)
|
||||||
|
}
|
||||||
|
defer netns.Set(originNs)
|
||||||
|
|
||||||
|
var ns netns.NsHandle
|
||||||
|
|
||||||
|
if strings.HasPrefix(netnsIn, "/") {
|
||||||
|
ns, err = netns.GetFromPath(netnsIn)
|
||||||
|
} else {
|
||||||
|
ns, err = netns.GetFromName(netnsIn)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("netns.Get(%s): %v", netnsIn, err)
|
||||||
|
}
|
||||||
|
defer ns.Close()
|
||||||
|
|
||||||
|
if err := netns.Set(ns); err != nil {
|
||||||
|
return nil, fmt.Errorf("netns.Set(%s): %v", netnsIn, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ln listener.Listener
|
var ln listener.Listener
|
||||||
if rf := registry.ListenerRegistry().Get(cfg.Listener.Type); rf != nil {
|
if rf := registry.ListenerRegistry().Get(cfg.Listener.Type); rf != nil {
|
||||||
ln = rf(listenOpts...)
|
ln = rf(listenOpts...)
|
||||||
} else {
|
} 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 {
|
if cfg.Listener.Metadata == nil {
|
||||||
@ -183,10 +250,11 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
routerOpts := []chain.RouterOption{
|
routerOpts = []chain.RouterOption{
|
||||||
chain.RetriesRouterOption(cfg.Handler.Retries),
|
chain.RetriesRouterOption(cfg.Handler.Retries),
|
||||||
// chain.TimeoutRouterOption(10*time.Second),
|
chain.TimeoutRouterOption(dialTimeout),
|
||||||
chain.InterfaceRouterOption(ifce),
|
chain.InterfaceRouterOption(ifce),
|
||||||
|
chain.NetnsRouterOption(netnsOut),
|
||||||
chain.SockOptsRouterOption(sockOpts),
|
chain.SockOptsRouterOption(sockOpts),
|
||||||
chain.ResolverRouterOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
chain.ResolverRouterOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
||||||
chain.HostMapperRouterOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
chain.HostMapperRouterOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
||||||
@ -198,26 +266,28 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
chain.ChainRouterOption(chainGroup(cfg.Handler.Chain, cfg.Handler.ChainGroup)),
|
chain.ChainRouterOption(chainGroup(cfg.Handler.Chain, cfg.Handler.ChainGroup)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
router := chain.NewRouter(routerOpts...)
|
|
||||||
|
|
||||||
var h handler.Handler
|
var h handler.Handler
|
||||||
if rf := registry.HandlerRegistry().Get(cfg.Handler.Type); rf != nil {
|
if rf := registry.HandlerRegistry().Get(cfg.Handler.Type); rf != nil {
|
||||||
h = rf(
|
h = rf(
|
||||||
handler.RouterOption(router),
|
handler.RouterOption(xchain.NewRouter(routerOpts...)),
|
||||||
handler.AutherOption(auther),
|
handler.AutherOption(auther),
|
||||||
handler.AuthOption(auth_parser.Info(cfg.Handler.Auth)),
|
handler.AuthOption(auth_parser.Info(cfg.Handler.Auth)),
|
||||||
handler.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
handler.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||||
handler.TLSConfigOption(tlsConfig),
|
handler.TLSConfigOption(tlsConfig),
|
||||||
handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)),
|
handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)),
|
||||||
|
handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)),
|
||||||
|
handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)),
|
||||||
handler.LoggerOption(handlerLogger),
|
handler.LoggerOption(handlerLogger),
|
||||||
handler.ServiceOption(cfg.Name),
|
handler.ServiceOption(cfg.Name),
|
||||||
|
handler.NetnsOption(netnsIn),
|
||||||
)
|
)
|
||||||
} else {
|
} 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 {
|
if forwarder, ok := h.(handler.Forwarder); ok {
|
||||||
hop, err := parseForwarder(cfg.Forwarder)
|
hop, err := parseForwarder(cfg.Forwarder, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -240,6 +310,9 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
xservice.PostUpOption(postUp),
|
xservice.PostUpOption(postUp),
|
||||||
xservice.PostDownOption(postDown),
|
xservice.PostDownOption(postDown),
|
||||||
xservice.RecordersOption(recorders...),
|
xservice.RecordersOption(recorders...),
|
||||||
|
xservice.StatsOption(pStats),
|
||||||
|
xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)),
|
||||||
|
xservice.ObservePeriodOption(observePeriod),
|
||||||
xservice.LoggerOption(serviceLogger),
|
xservice.LoggerOption(serviceLogger),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -247,38 +320,61 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseForwarder(cfg *config.ForwarderConfig) (hop.Hop, error) {
|
func parseForwarder(cfg *config.ForwarderConfig, log logger.Logger) (hop.Hop, error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hopName := cfg.Hop
|
||||||
|
if hopName == "" {
|
||||||
|
hopName = cfg.Name
|
||||||
|
}
|
||||||
|
if hopName != "" {
|
||||||
|
return registry.HopRegistry().Get(hopName), nil
|
||||||
|
}
|
||||||
|
|
||||||
hc := config.HopConfig{
|
hc := config.HopConfig{
|
||||||
Name: cfg.Name,
|
Name: cfg.Name,
|
||||||
Selector: cfg.Selector,
|
Selector: cfg.Selector,
|
||||||
}
|
}
|
||||||
for _, node := range cfg.Nodes {
|
for _, node := range cfg.Nodes {
|
||||||
if node != nil {
|
if node == nil {
|
||||||
hc.Nodes = append(hc.Nodes,
|
continue
|
||||||
&config.NodeConfig{
|
|
||||||
Name: node.Name,
|
|
||||||
Addr: node.Addr,
|
|
||||||
Host: node.Host,
|
|
||||||
Network: node.Network,
|
|
||||||
Protocol: node.Protocol,
|
|
||||||
Path: node.Path,
|
|
||||||
Bypass: node.Bypass,
|
|
||||||
Bypasses: node.Bypasses,
|
|
||||||
HTTP: node.HTTP,
|
|
||||||
TLS: node.TLS,
|
|
||||||
Auth: node.Auth,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: node.Name,
|
||||||
|
Addr: node.Addr,
|
||||||
|
Network: node.Network,
|
||||||
|
Bypass: node.Bypass,
|
||||||
|
Bypasses: node.Bypasses,
|
||||||
|
Filter: filter,
|
||||||
|
HTTP: httpCfg,
|
||||||
|
TLS: node.TLS,
|
||||||
|
Metadata: node.Metadata,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if len(hc.Nodes) > 0 {
|
return hop_parser.ParseHop(&hc, log)
|
||||||
return hop_parser.ParseHop(&hc)
|
|
||||||
}
|
|
||||||
return registry.HopRegistry().Get(hc.Name), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func chainGroup(name string, group *config.ChainGroupConfig) chain.Chainer {
|
func chainGroup(name string, group *config.ChainGroupConfig) chain.Chainer {
|
||||||
|
@ -39,14 +39,22 @@ func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, addr
|
|||||||
opt(&cOpts)
|
opt(&cOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := cOpts.NetDialer.Dial(ctx, network, address)
|
conn, err := cOpts.Dialer.Dial(ctx, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localAddr, remoteAddr string
|
||||||
|
if addr := conn.LocalAddr(); addr != nil {
|
||||||
|
localAddr = addr.String()
|
||||||
|
}
|
||||||
|
if addr := conn.RemoteAddr(); addr != nil {
|
||||||
|
remoteAddr = addr.String()
|
||||||
|
}
|
||||||
|
|
||||||
log := c.options.Logger.WithFields(map[string]any{
|
log := c.options.Logger.WithFields(map[string]any{
|
||||||
"remote": conn.RemoteAddr().String(),
|
"remote": remoteAddr,
|
||||||
"local": conn.LocalAddr().String(),
|
"local": localAddr,
|
||||||
"network": network,
|
"network": network,
|
||||||
"address": address,
|
"address": address,
|
||||||
})
|
})
|
||||||
|
@ -6,20 +6,16 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/go-gost/core/common/net/udp"
|
|
||||||
"github.com/go-gost/core/connector"
|
"github.com/go-gost/core/connector"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/relay"
|
"github.com/go-gost/relay"
|
||||||
|
"github.com/go-gost/x/internal/net/udp"
|
||||||
"github.com/go-gost/x/internal/util/mux"
|
"github.com/go-gost/x/internal/util/mux"
|
||||||
relay_util "github.com/go-gost/x/internal/util/relay"
|
relay_util "github.com/go-gost/x/internal/util/relay"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bind implements connector.Binder.
|
// Bind implements connector.Binder.
|
||||||
func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
||||||
if !c.md.tunnelID.IsZero() {
|
|
||||||
return c.bindTunnel(ctx, conn, network, address, c.options.Logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
log := c.options.Logger.WithFields(map[string]any{
|
log := c.options.Logger.WithFields(map[string]any{
|
||||||
"network": network,
|
"network": network,
|
||||||
"address": address,
|
"address": address,
|
||||||
@ -43,86 +39,6 @@ func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, addre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *relayConnector) bindTunnel(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
|
||||||
addr, cid, err := c.initTunnel(conn, network, address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Infof("create tunnel on %s/%s OK, tunnel=%s, connector=%s", addr, network, c.md.tunnelID.String(), cid)
|
|
||||||
|
|
||||||
session, err := mux.ServerSession(conn, c.md.muxCfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bindListener{
|
|
||||||
network: network,
|
|
||||||
addr: addr,
|
|
||||||
session: session,
|
|
||||||
logger: log,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) initTunnel(conn net.Conn, network, address string) (addr net.Addr, cid relay.ConnectorID, err error) {
|
|
||||||
req := relay.Request{
|
|
||||||
Version: relay.Version1,
|
|
||||||
Cmd: relay.CmdBind,
|
|
||||||
}
|
|
||||||
|
|
||||||
if network == "udp" {
|
|
||||||
req.Cmd |= relay.FUDP
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.options.Auth != nil {
|
|
||||||
pwd, _ := c.options.Auth.Password()
|
|
||||||
req.Features = append(req.Features, &relay.UserAuthFeature{
|
|
||||||
Username: c.options.Auth.Username(),
|
|
||||||
Password: pwd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
af := &relay.AddrFeature{}
|
|
||||||
af.ParseFrom(address)
|
|
||||||
|
|
||||||
req.Features = append(req.Features, af,
|
|
||||||
&relay.TunnelFeature{
|
|
||||||
ID: c.md.tunnelID.ID(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if _, err = req.WriteTo(conn); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// first reply, bind status
|
|
||||||
resp := relay.Response{}
|
|
||||||
if _, err = resp.ReadFrom(conn); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Status != relay.StatusOK {
|
|
||||||
err = fmt.Errorf("%d: create tunnel %s failed", resp.Status, c.md.tunnelID.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range resp.Features {
|
|
||||||
switch f.Type() {
|
|
||||||
case relay.FeatureAddr:
|
|
||||||
if feature, _ := f.(*relay.AddrFeature); feature != nil {
|
|
||||||
addr = &bindAddr{
|
|
||||||
network: network,
|
|
||||||
addr: net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case relay.FeatureTunnel:
|
|
||||||
if feature, _ := f.(*relay.TunnelFeature); feature != nil {
|
|
||||||
cid = relay.NewConnectorID(feature.ID[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
||||||
laddr, err := c.bind(conn, relay.CmdBind, network, address)
|
laddr, err := c.bind(conn, relay.CmdBind, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -157,7 +73,7 @@ func (c *relayConnector) bindUDP(ctx context.Context, conn net.Conn, network, ad
|
|||||||
ReadQueueSize: opts.UDPDataQueueSize,
|
ReadQueueSize: opts.UDPDataQueueSize,
|
||||||
ReadBufferSize: opts.UDPDataBufferSize,
|
ReadBufferSize: opts.UDPDataBufferSize,
|
||||||
TTL: opts.UDPConnTTL,
|
TTL: opts.UDPConnTTL,
|
||||||
KeepAlive: true,
|
Keepalive: true,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -177,6 +93,14 @@ func (c *relayConnector) bind(conn net.Conn, cmd relay.CmdType, network, address
|
|||||||
Password: pwd,
|
Password: pwd,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nid := relay.NetworkTCP
|
||||||
|
if network == "udp" || network == "udp4" || network == "udp6" {
|
||||||
|
nid = relay.NetworkUDP
|
||||||
|
}
|
||||||
|
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||||
|
Network: nid,
|
||||||
|
})
|
||||||
fa := &relay.AddrFeature{}
|
fa := &relay.AddrFeature{}
|
||||||
fa.ParseFrom(address)
|
fa.ParseFrom(address)
|
||||||
req.Features = append(req.Features, fa)
|
req.Features = append(req.Features, fa)
|
||||||
|
@ -13,12 +13,14 @@ import (
|
|||||||
"github.com/go-gost/core/common/bufpool"
|
"github.com/go-gost/core/common/bufpool"
|
||||||
mdata "github.com/go-gost/core/metadata"
|
mdata "github.com/go-gost/core/metadata"
|
||||||
"github.com/go-gost/relay"
|
"github.com/go-gost/relay"
|
||||||
|
xrelay "github.com/go-gost/x/internal/util/relay"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tcpConn struct {
|
type tcpConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
wbuf *bytes.Buffer
|
wbuf *bytes.Buffer
|
||||||
once sync.Once
|
once sync.Once
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tcpConn) Read(b []byte) (n int, err error) {
|
func (c *tcpConn) Read(b []byte) (n int, err error) {
|
||||||
@ -36,6 +38,10 @@ func (c *tcpConn) Read(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
func (c *tcpConn) Write(b []byte) (n int, err error) {
|
func (c *tcpConn) Write(b []byte) (n int, err error) {
|
||||||
n = len(b) // force byte length consistent
|
n = len(b) // force byte length consistent
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
||||||
c.wbuf.Write(b) // append the data to the cached header
|
c.wbuf.Write(b) // append the data to the cached header
|
||||||
_, err = c.Conn.Write(c.wbuf.Bytes())
|
_, err = c.Conn.Write(c.wbuf.Bytes())
|
||||||
@ -50,6 +56,7 @@ type udpConn struct {
|
|||||||
net.Conn
|
net.Conn
|
||||||
wbuf *bytes.Buffer
|
wbuf *bytes.Buffer
|
||||||
once sync.Once
|
once sync.Once
|
||||||
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
func (c *udpConn) Read(b []byte) (n int, err error) {
|
||||||
@ -88,6 +95,10 @@ func (c *udpConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n = len(b)
|
n = len(b)
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
||||||
var bb [2]byte
|
var bb [2]byte
|
||||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||||
@ -119,7 +130,7 @@ func readResponse(r io.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resp.Status != relay.StatusOK {
|
if resp.Status != relay.StatusOK {
|
||||||
err = fmt.Errorf("status %d", resp.Status)
|
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -213,16 +224,3 @@ func (c *bindUDPConn) RemoteAddr() net.Addr {
|
|||||||
func (c *bindUDPConn) Metadata() mdata.Metadata {
|
func (c *bindUDPConn) Metadata() mdata.Metadata {
|
||||||
return c.md
|
return c.md
|
||||||
}
|
}
|
||||||
|
|
||||||
type bindAddr struct {
|
|
||||||
network string
|
|
||||||
addr string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *bindAddr) Network() string {
|
|
||||||
return p.network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *bindAddr) String() string {
|
|
||||||
return p.addr
|
|
||||||
}
|
|
||||||
|
@ -101,12 +101,6 @@ func (c *relayConnector) Connect(ctx context.Context, conn net.Conn, network, ad
|
|||||||
req.Features = append(req.Features, af)
|
req.Features = append(req.Features, af)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.md.tunnelID.IsZero() {
|
|
||||||
req.Features = append(req.Features, &relay.TunnelFeature{
|
|
||||||
ID: c.md.tunnelID.ID(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.md.noDelay {
|
if c.md.noDelay {
|
||||||
if _, err := req.WriteTo(conn); err != nil {
|
if _, err := req.WriteTo(conn); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -5,15 +5,12 @@ import (
|
|||||||
|
|
||||||
mdata "github.com/go-gost/core/metadata"
|
mdata "github.com/go-gost/core/metadata"
|
||||||
mdutil "github.com/go-gost/core/metadata/util"
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
"github.com/go-gost/relay"
|
|
||||||
"github.com/go-gost/x/internal/util/mux"
|
"github.com/go-gost/x/internal/util/mux"
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type metadata struct {
|
type metadata struct {
|
||||||
connectTimeout time.Duration
|
connectTimeout time.Duration
|
||||||
noDelay bool
|
noDelay bool
|
||||||
tunnelID relay.TunnelID
|
|
||||||
muxCfg *mux.Config
|
muxCfg *mux.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,14 +23,6 @@ func (c *relayConnector) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
||||||
c.md.noDelay = mdutil.GetBool(md, noDelay)
|
c.md.noDelay = mdutil.GetBool(md, noDelay)
|
||||||
|
|
||||||
if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); s != "" {
|
|
||||||
uuid, err := uuid.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.md.tunnelID = relay.NewTunnelID(uuid[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
c.md.muxCfg = &mux.Config{
|
c.md.muxCfg = &mux.Config{
|
||||||
Version: mdutil.GetInt(md, "mux.version"),
|
Version: mdutil.GetInt(md, "mux.version"),
|
||||||
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
|
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
|
||||||
|
@ -5,10 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/go-gost/core/common/net/udp"
|
|
||||||
"github.com/go-gost/core/connector"
|
"github.com/go-gost/core/connector"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
"github.com/go-gost/gosocks5"
|
"github.com/go-gost/gosocks5"
|
||||||
|
"github.com/go-gost/x/internal/net/udp"
|
||||||
"github.com/go-gost/x/internal/util/mux"
|
"github.com/go-gost/x/internal/util/mux"
|
||||||
"github.com/go-gost/x/internal/util/socks"
|
"github.com/go-gost/x/internal/util/socks"
|
||||||
)
|
)
|
||||||
@ -87,7 +87,7 @@ func (c *socks5Connector) bindUDP(ctx context.Context, conn net.Conn, network, a
|
|||||||
ReadQueueSize: opts.UDPDataQueueSize,
|
ReadQueueSize: opts.UDPDataQueueSize,
|
||||||
ReadBufferSize: opts.UDPDataBufferSize,
|
ReadBufferSize: opts.UDPDataBufferSize,
|
||||||
TTL: opts.UDPConnTTL,
|
TTL: opts.UDPConnTTL,
|
||||||
KeepAlive: true,
|
Keepalive: true,
|
||||||
Logger: log,
|
Logger: log,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -69,6 +69,10 @@ func (c *udpRelayConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
|||||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if socksAddr.Host == "" {
|
||||||
|
socksAddr.Type = gosocks5.AddrIPv4
|
||||||
|
socksAddr.Host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
header := gosocks5.UDPHeader{
|
header := gosocks5.UDPHeader{
|
||||||
Addr: &socksAddr,
|
Addr: &socksAddr,
|
||||||
|
@ -130,6 +130,10 @@ func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, a
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if addr.Host == "" {
|
||||||
|
addr.Type = gosocks5.AddrIPv4
|
||||||
|
addr.Host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
|
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
|
||||||
log.Trace(req)
|
log.Trace(req)
|
||||||
@ -201,16 +205,22 @@ func (c *socks5Connector) relayUDP(ctx context.Context, conn net.Conn, addr net.
|
|||||||
}
|
}
|
||||||
log.Trace(reply)
|
log.Trace(reply)
|
||||||
|
|
||||||
log.Debugf("bind on: %v", reply.Addr)
|
|
||||||
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
if reply.Rep != gosocks5.Succeeded {
|
||||||
return nil, errors.New("get socks5 UDP tunnel failure")
|
return nil, errors.New("get socks5 UDP tunnel failure")
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, err := opts.NetDialer.Dial(ctx, "udp", reply.Addr.String())
|
log.Debugf("bind on: %v", reply.Addr)
|
||||||
|
|
||||||
|
cc, err := opts.Dialer.Dial(ctx, "udp", reply.Addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.options.Logger.Error(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debugf("%s <- %s -> %s", cc.LocalAddr(), cc.RemoteAddr(), addr)
|
||||||
|
|
||||||
|
if c.md.udpTimeout > 0 {
|
||||||
|
cc.SetReadDeadline(time.Now().Add(c.md.udpTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
return &udpRelayConn{
|
return &udpRelayConn{
|
||||||
udpConn: cc.(*net.UDPConn),
|
udpConn: cc.(*net.UDPConn),
|
||||||
|
@ -17,24 +17,19 @@ type metadata struct {
|
|||||||
noTLS bool
|
noTLS bool
|
||||||
relay string
|
relay string
|
||||||
udpBufferSize int
|
udpBufferSize int
|
||||||
|
udpTimeout time.Duration
|
||||||
muxCfg *mux.Config
|
muxCfg *mux.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
|
func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
|
||||||
const (
|
c.md.connectTimeout = mdutil.GetDuration(md, "timeout")
|
||||||
connectTimeout = "timeout"
|
c.md.noTLS = mdutil.GetBool(md, "notls")
|
||||||
noTLS = "notls"
|
c.md.relay = mdutil.GetString(md, "relay")
|
||||||
relay = "relay"
|
c.md.udpBufferSize = mdutil.GetInt(md, "udp.bufferSize", "udpBufferSize")
|
||||||
udpBufferSize = "udpBufferSize"
|
|
||||||
)
|
|
||||||
|
|
||||||
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
|
||||||
c.md.noTLS = mdutil.GetBool(md, noTLS)
|
|
||||||
c.md.relay = mdutil.GetString(md, relay)
|
|
||||||
c.md.udpBufferSize = mdutil.GetInt(md, udpBufferSize)
|
|
||||||
if c.md.udpBufferSize <= 0 {
|
if c.md.udpBufferSize <= 0 {
|
||||||
c.md.udpBufferSize = defaultUDPBufferSize
|
c.md.udpBufferSize = defaultUDPBufferSize
|
||||||
}
|
}
|
||||||
|
c.md.udpTimeout = mdutil.GetDuration(md, "udp.timeout")
|
||||||
|
|
||||||
c.md.muxCfg = &mux.Config{
|
c.md.muxCfg = &mux.Config{
|
||||||
Version: mdutil.GetInt(md, "mux.version"),
|
Version: mdutil.GetInt(md, "mux.version"),
|
||||||
|
@ -27,7 +27,8 @@ func (c *tunnelConnector) Bind(ctx context.Context, conn net.Conn, network, addr
|
|||||||
"endpoint": endpoint,
|
"endpoint": endpoint,
|
||||||
"tunnel": c.md.tunnelID.String(),
|
"tunnel": c.md.tunnelID.String(),
|
||||||
})
|
})
|
||||||
log.Infof("create tunnel on %s/%s OK, tunnel=%s, connector=%s", addr, network, c.md.tunnelID.String(), cid)
|
log.Infof("create tunnel on %s/%s OK, tunnel=%s, connector=%s, weight=%d",
|
||||||
|
addr, network, c.md.tunnelID.String(), cid, cid.Weight())
|
||||||
|
|
||||||
session, err := mux.ServerSession(conn, c.md.muxCfg)
|
session, err := mux.ServerSession(conn, c.md.muxCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -72,7 +73,7 @@ func (c *tunnelConnector) initTunnel(conn net.Conn, network, address string) (ad
|
|||||||
req.Features = append(req.Features, af) // dst address
|
req.Features = append(req.Features, af) // dst address
|
||||||
|
|
||||||
req.Features = append(req.Features, &relay.TunnelFeature{
|
req.Features = append(req.Features, &relay.TunnelFeature{
|
||||||
ID: c.md.tunnelID.ID(),
|
ID: c.md.tunnelID,
|
||||||
})
|
})
|
||||||
if _, err = req.WriteTo(conn); err != nil {
|
if _, err = req.WriteTo(conn); err != nil {
|
||||||
return
|
return
|
||||||
@ -100,7 +101,7 @@ func (c *tunnelConnector) initTunnel(conn net.Conn, network, address string) (ad
|
|||||||
}
|
}
|
||||||
case relay.FeatureTunnel:
|
case relay.FeatureTunnel:
|
||||||
if feature, _ := f.(*relay.TunnelFeature); feature != nil {
|
if feature, _ := f.(*relay.TunnelFeature); feature != nil {
|
||||||
cid = relay.NewConnectorID(feature.ID[:])
|
cid = feature.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,67 +1,24 @@
|
|||||||
package tunnel
|
package tunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-gost/core/common/bufpool"
|
"github.com/go-gost/core/common/bufpool"
|
||||||
mdata "github.com/go-gost/core/metadata"
|
mdata "github.com/go-gost/core/metadata"
|
||||||
"github.com/go-gost/relay"
|
"github.com/go-gost/relay"
|
||||||
|
xrelay "github.com/go-gost/x/internal/util/relay"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tcpConn struct {
|
|
||||||
net.Conn
|
|
||||||
wbuf *bytes.Buffer
|
|
||||||
once sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) Read(b []byte) (n int, err error) {
|
|
||||||
c.once.Do(func() {
|
|
||||||
if c.wbuf != nil {
|
|
||||||
err = readResponse(c.Conn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tcpConn) Write(b []byte) (n int, err error) {
|
|
||||||
n = len(b) // force byte length consistent
|
|
||||||
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
|
||||||
c.wbuf.Write(b) // append the data to the cached header
|
|
||||||
_, err = c.Conn.Write(c.wbuf.Bytes())
|
|
||||||
c.wbuf.Reset()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type udpConn struct {
|
type udpConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
wbuf *bytes.Buffer
|
|
||||||
once sync.Once
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
func (c *udpConn) Read(b []byte) (n int, err error) {
|
||||||
c.once.Do(func() {
|
|
||||||
if c.wbuf != nil {
|
|
||||||
err = readResponse(c.Conn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var bb [2]byte
|
var bb [2]byte
|
||||||
_, err = io.ReadFull(c.Conn, bb[:])
|
_, err = io.ReadFull(c.Conn, bb[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -88,14 +45,6 @@ func (c *udpConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n = len(b)
|
n = len(b)
|
||||||
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
|
||||||
var bb [2]byte
|
|
||||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
|
||||||
c.wbuf.Write(bb[:])
|
|
||||||
c.wbuf.Write(b) // append the data to the cached header
|
|
||||||
_, err = c.wbuf.WriteTo(c.Conn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var bb [2]byte
|
var bb [2]byte
|
||||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||||
@ -119,7 +68,7 @@ func readResponse(r io.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resp.Status != relay.StatusOK {
|
if resp.Status != relay.StatusOK {
|
||||||
err = fmt.Errorf("status %d", resp.Status)
|
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package tunnel
|
package tunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@ -10,7 +9,7 @@ import (
|
|||||||
"github.com/go-gost/core/connector"
|
"github.com/go-gost/core/connector"
|
||||||
md "github.com/go-gost/core/metadata"
|
md "github.com/go-gost/core/metadata"
|
||||||
"github.com/go-gost/relay"
|
"github.com/go-gost/relay"
|
||||||
auth_util "github.com/go-gost/x/internal/util/auth"
|
ctxvalue "github.com/go-gost/x/ctx"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,7 +73,7 @@ func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, a
|
|||||||
}
|
}
|
||||||
|
|
||||||
srcAddr := conn.LocalAddr().String()
|
srcAddr := conn.LocalAddr().String()
|
||||||
if v := auth_util.ClientAddrFromContext(ctx); v != "" {
|
if v := ctxvalue.ClientAddrFromContext(ctx); v != "" {
|
||||||
srcAddr = string(v)
|
srcAddr = string(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,42 +86,23 @@ func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, a
|
|||||||
req.Features = append(req.Features, af) // dst address
|
req.Features = append(req.Features, af) // dst address
|
||||||
|
|
||||||
req.Features = append(req.Features, &relay.TunnelFeature{
|
req.Features = append(req.Features, &relay.TunnelFeature{
|
||||||
ID: c.md.tunnelID.ID(),
|
ID: c.md.tunnelID,
|
||||||
})
|
})
|
||||||
|
|
||||||
if c.md.noDelay {
|
if _, err := req.WriteTo(conn); err != nil {
|
||||||
if _, err := req.WriteTo(conn); err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
// drain the response
|
||||||
// drain the response
|
if err := readResponse(conn); err != nil {
|
||||||
if err := readResponse(conn); err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp", "tcp4", "tcp6":
|
case "tcp", "tcp4", "tcp6":
|
||||||
if !c.md.noDelay {
|
|
||||||
cc := &tcpConn{
|
|
||||||
Conn: conn,
|
|
||||||
wbuf: &bytes.Buffer{},
|
|
||||||
}
|
|
||||||
if _, err := req.WriteTo(cc.wbuf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = cc
|
|
||||||
}
|
|
||||||
case "udp", "udp4", "udp6":
|
case "udp", "udp4", "udp6":
|
||||||
cc := &udpConn{
|
conn = &udpConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
}
|
}
|
||||||
if !c.md.noDelay {
|
|
||||||
cc.wbuf = &bytes.Buffer{}
|
|
||||||
if _, err := req.WriteTo(cc.wbuf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn = cc
|
|
||||||
default:
|
default:
|
||||||
err := fmt.Errorf("network %s is unsupported", network)
|
err := fmt.Errorf("network %s is unsupported", network)
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
@ -18,13 +18,11 @@ var (
|
|||||||
type metadata struct {
|
type metadata struct {
|
||||||
connectTimeout time.Duration
|
connectTimeout time.Duration
|
||||||
tunnelID relay.TunnelID
|
tunnelID relay.TunnelID
|
||||||
noDelay bool
|
|
||||||
muxCfg *mux.Config
|
muxCfg *mux.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) {
|
func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||||
c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout")
|
c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout")
|
||||||
c.md.noDelay = mdutil.GetBool(md, "nodelay")
|
|
||||||
|
|
||||||
if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); s != "" {
|
if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); s != "" {
|
||||||
uuid, err := uuid.Parse(s)
|
uuid, err := uuid.Parse(s)
|
||||||
@ -42,6 +40,10 @@ func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
c.md.tunnelID = relay.NewTunnelID(uuid[:])
|
c.md.tunnelID = relay.NewTunnelID(uuid[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if weight := mdutil.GetInt(md, "tunnel.weight"); weight > 0 {
|
||||||
|
c.md.tunnelID = c.md.tunnelID.SetWeight(uint8(weight))
|
||||||
|
}
|
||||||
|
|
||||||
c.md.muxCfg = &mux.Config{
|
c.md.muxCfg = &mux.Config{
|
||||||
Version: mdutil.GetInt(md, "mux.version"),
|
Version: mdutil.GetInt(md, "mux.version"),
|
||||||
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
|
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
|
||||||
|
76
ctx/value.go
Normal file
76
ctx/value.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package ctx
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// clientAddrKey saves the client address.
|
||||||
|
type clientAddrKey struct{}
|
||||||
|
|
||||||
|
type ClientAddr string
|
||||||
|
|
||||||
|
var (
|
||||||
|
keyClientAddr clientAddrKey
|
||||||
|
)
|
||||||
|
|
||||||
|
func ContextWithClientAddr(ctx context.Context, addr ClientAddr) context.Context {
|
||||||
|
return context.WithValue(ctx, keyClientAddr, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientAddrFromContext(ctx context.Context) ClientAddr {
|
||||||
|
v, _ := ctx.Value(keyClientAddr).(ClientAddr)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// sidKey saves the session ID.
|
||||||
|
type sidKey struct{}
|
||||||
|
type Sid string
|
||||||
|
|
||||||
|
var (
|
||||||
|
keySid sidKey
|
||||||
|
)
|
||||||
|
|
||||||
|
func ContextWithSid(ctx context.Context, sid Sid) context.Context {
|
||||||
|
return context.WithValue(ctx, keySid, sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SidFromContext(ctx context.Context) Sid {
|
||||||
|
v, _ := ctx.Value(keySid).(Sid)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashKey saves the hash source for Selector.
|
||||||
|
type hashKey struct{}
|
||||||
|
|
||||||
|
type Hash struct {
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
clientHashKey = &hashKey{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ContextWithHash(ctx context.Context, hash *Hash) context.Context {
|
||||||
|
return context.WithValue(ctx, clientHashKey, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashFromContext(ctx context.Context) *Hash {
|
||||||
|
if v, _ := ctx.Value(clientHashKey).(*Hash); v != nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientIDKey struct{}
|
||||||
|
type ClientID string
|
||||||
|
|
||||||
|
var (
|
||||||
|
keyClientID = &clientIDKey{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ContextWithClientID(ctx context.Context, clientID ClientID) context.Context {
|
||||||
|
return context.WithValue(ctx, keyClientID, clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientIDFromContext(ctx context.Context) ClientID {
|
||||||
|
v, _ := ctx.Value(keyClientID).(ClientID)
|
||||||
|
return v
|
||||||
|
}
|
@ -45,7 +45,7 @@ func (d *dtlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := options.NetDialer.Dial(ctx, "udp", addr)
|
conn, err := options.Dialer.Dial(ctx, "udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -71,14 +71,13 @@ func (d *grpcDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
grpcOpts := []grpc.DialOption{
|
grpcOpts := []grpc.DialOption{
|
||||||
// grpc.WithBlock(),
|
// grpc.WithBlock(),
|
||||||
grpc.WithContextDialer(func(c context.Context, s string) (net.Conn, error) {
|
grpc.WithContextDialer(func(c context.Context, s string) (net.Conn, error) {
|
||||||
return options.NetDialer.Dial(c, "tcp", s)
|
return options.Dialer.Dial(c, "tcp", s)
|
||||||
}),
|
}),
|
||||||
grpc.WithAuthority(host),
|
grpc.WithAuthority(host),
|
||||||
grpc.WithConnectParams(grpc.ConnectParams{
|
grpc.WithConnectParams(grpc.ConnectParams{
|
||||||
Backoff: backoff.DefaultConfig,
|
Backoff: backoff.DefaultConfig,
|
||||||
MinConnectTimeout: d.md.minConnectTimeout,
|
MinConnectTimeout: d.md.minConnectTimeout,
|
||||||
}),
|
}),
|
||||||
grpc.FailOnNonTempDialError(true),
|
|
||||||
}
|
}
|
||||||
if !d.md.insecure {
|
if !d.md.insecure {
|
||||||
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(credentials.NewTLS(d.options.TLSConfig)))
|
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(credentials.NewTLS(d.options.TLSConfig)))
|
||||||
@ -94,7 +93,7 @@ func (d *grpcDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
cc, err := grpc.DialContext(ctx, addr, grpcOpts...)
|
cc, err := grpc.NewClient(addr, grpcOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.options.Logger.Error(err)
|
d.options.Logger.Error(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
net_dialer "github.com/go-gost/core/common/net/dialer"
|
|
||||||
"github.com/go-gost/core/dialer"
|
"github.com/go-gost/core/dialer"
|
||||||
"github.com/go-gost/core/logger"
|
"github.com/go-gost/core/logger"
|
||||||
md "github.com/go-gost/core/metadata"
|
md "github.com/go-gost/core/metadata"
|
||||||
|
net_dialer "github.com/go-gost/x/internal/net/dialer"
|
||||||
mdx "github.com/go-gost/x/metadata"
|
mdx "github.com/go-gost/x/metadata"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
)
|
)
|
||||||
@ -70,28 +70,40 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Check whether the connection is established properly
|
||||||
|
netd := options.Dialer
|
||||||
|
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{
|
client = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: d.options.TLSConfig,
|
TLSClientConfig: d.options.TLSConfig,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
netd := options.NetDialer
|
netd := options.Dialer
|
||||||
if netd == nil {
|
if netd == nil {
|
||||||
netd = net_dialer.DefaultNetDialer
|
netd = net_dialer.DefaultNetDialer
|
||||||
}
|
}
|
||||||
return netd.Dial(ctx, network, addr)
|
return netd.Dial(ctx, network, addr)
|
||||||
},
|
},
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
MaxIdleConns: 100,
|
MaxIdleConns: 16,
|
||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 30 * time.Second,
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 30 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 15 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
d.clients[address] = client
|
d.clients[address] = client
|
||||||
}
|
}
|
||||||
|
|
||||||
var c net.Conn
|
var c net.Conn = &conn{
|
||||||
c = &conn{
|
|
||||||
localAddr: &net.TCPAddr{},
|
localAddr: &net.TCPAddr{},
|
||||||
remoteAddr: raddr,
|
remoteAddr: raddr,
|
||||||
onClose: func() {
|
onClose: func() {
|
||||||
|
@ -94,14 +94,14 @@ func (d *h2Dialer) Dial(ctx context.Context, address string, opts ...dialer.Dial
|
|||||||
client.Transport = &http2.Transport{
|
client.Transport = &http2.Transport{
|
||||||
AllowHTTP: true,
|
AllowHTTP: true,
|
||||||
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
return options.NetDialer.Dial(ctx, network, addr)
|
return options.Dialer.Dial(ctx, network, addr)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
client.Transport = &http.Transport{
|
client.Transport = &http.Transport{
|
||||||
TLSClientConfig: d.options.TLSConfig,
|
TLSClientConfig: d.options.TLSConfig,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return options.NetDialer.Dial(ctx, network, addr)
|
return options.Dialer.Dial(ctx, network, addr)
|
||||||
},
|
},
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
MaxIdleConns: 100,
|
MaxIdleConns: 100,
|
||||||
|
@ -79,14 +79,14 @@ func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.Dial
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
udpConn, err := options.NetDialer.Dial(ctx, "udp", "")
|
udpConn, err := options.Dialer.Dial(ctx, "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return quic.DialEarly(context.Background(), udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
|
return quic.DialEarly(context.Background(), udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
|
||||||
},
|
},
|
||||||
QuicConfig: &quic.Config{
|
QUICConfig: &quic.Config{
|
||||||
KeepAlivePeriod: d.md.keepAlivePeriod,
|
KeepAlivePeriod: d.md.keepAlivePeriod,
|
||||||
HandshakeIdleTimeout: d.md.handshakeTimeout,
|
HandshakeIdleTimeout: d.md.handshakeTimeout,
|
||||||
MaxIdleTimeout: d.md.maxIdleTimeout,
|
MaxIdleTimeout: d.md.maxIdleTimeout,
|
||||||
|
@ -50,7 +50,7 @@ func (d *http3Dialer) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.md.host = mdutil.GetString(md, "host")
|
d.md.host = mdutil.GetString(md, "host")
|
||||||
if !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
|
if md == nil || !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
|
||||||
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
||||||
if d.md.keepAlivePeriod <= 0 {
|
if d.md.keepAlivePeriod <= 0 {
|
||||||
d.md.keepAlivePeriod = 10 * time.Second
|
d.md.keepAlivePeriod = 10 * time.Second
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
md "github.com/go-gost/core/metadata"
|
md "github.com/go-gost/core/metadata"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/quic-go/quic-go/http3"
|
|
||||||
wt "github.com/quic-go/webtransport-go"
|
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,
|
path: d.md.path,
|
||||||
header: d.md.header,
|
header: d.md.header,
|
||||||
dialer: &wt.Dialer{
|
dialer: &wt.Dialer{
|
||||||
RoundTripper: &http3.RoundTripper{
|
TLSClientConfig: d.options.TLSConfig,
|
||||||
TLSClientConfig: d.options.TLSConfig,
|
DialAddr: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
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)
|
||||||
// d.options.Logger.Infof("dial: %s, %s, %s", addr, adr, host)
|
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
udpConn, err := options.NetDialer.Dial(ctx, "udp", "")
|
udpConn, err := options.Dialer.Dial(ctx, "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
|
return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
|
||||||
},
|
},
|
||||||
QuicConfig: &quic.Config{
|
QUICConfig: &quic.Config{
|
||||||
KeepAlivePeriod: d.md.keepAlivePeriod,
|
KeepAlivePeriod: d.md.keepAlivePeriod,
|
||||||
HandshakeIdleTimeout: d.md.handshakeTimeout,
|
HandshakeIdleTimeout: d.md.handshakeTimeout,
|
||||||
MaxIdleTimeout: d.md.maxIdleTimeout,
|
MaxIdleTimeout: d.md.maxIdleTimeout,
|
||||||
/*
|
/*
|
||||||
Versions: []quic.VersionNumber{
|
Versions: []quic.VersionNumber{
|
||||||
quic.Version1,
|
quic.Version1,
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
MaxIncomingStreams: int64(d.md.maxStreams),
|
MaxIncomingStreams: int64(d.md.maxStreams),
|
||||||
},
|
EnableDatagrams: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func (d *wtDialer) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
d.md.path = defaultPath
|
d.md.path = defaultPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
|
if md == nil || !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
|
||||||
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
||||||
if d.md.keepAlivePeriod <= 0 {
|
if d.md.keepAlivePeriod <= 0 {
|
||||||
d.md.keepAlivePeriod = 10 * time.Second
|
d.md.keepAlivePeriod = 10 * time.Second
|
||||||
|
@ -19,9 +19,11 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registry.DialerRegistry().Register("icmp", NewDialer)
|
registry.DialerRegistry().Register("icmp", NewDialer)
|
||||||
|
registry.DialerRegistry().Register("icmp6", NewDialer6)
|
||||||
}
|
}
|
||||||
|
|
||||||
type icmpDialer struct {
|
type icmpDialer struct {
|
||||||
|
ip6 bool
|
||||||
sessions map[string]*quicSession
|
sessions map[string]*quicSession
|
||||||
sessionMutex sync.Mutex
|
sessionMutex sync.Mutex
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
@ -42,6 +44,19 @@ func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewDialer6(opts ...dialer.Option) dialer.Dialer {
|
||||||
|
options := dialer.Options{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &icmpDialer{
|
||||||
|
ip6: true,
|
||||||
|
sessions: make(map[string]*quicSession),
|
||||||
|
logger: options.Logger,
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
}
|
||||||
func (d *icmpDialer) Init(md md.Metadata) (err error) {
|
func (d *icmpDialer) Init(md md.Metadata) (err error) {
|
||||||
if err = d.parseMetadata(md); err != nil {
|
if err = d.parseMetadata(md); err != nil {
|
||||||
return
|
return
|
||||||
@ -71,7 +86,11 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
pc, err = icmp.ListenPacket("ip4:icmp", "")
|
if d.ip6 {
|
||||||
|
pc, err = icmp.ListenPacket("ip6:ipv6-icmp", "")
|
||||||
|
} else {
|
||||||
|
pc, err = icmp.ListenPacket("ip4:icmp", "")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -81,7 +100,7 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
id = rand.New(rand.NewSource(time.Now().UnixNano())).Intn(math.MaxUint16) + 1
|
id = rand.New(rand.NewSource(time.Now().UnixNano())).Intn(math.MaxUint16) + 1
|
||||||
raddr.Port = id
|
raddr.Port = id
|
||||||
}
|
}
|
||||||
pc = icmp_pkg.ClientConn(pc, id)
|
pc = icmp_pkg.ClientConn(d.ip6, pc, id)
|
||||||
|
|
||||||
session, err = d.initSession(ctx, raddr, pc)
|
session, err = d.initSession(ctx, raddr, pc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,7 +134,7 @@ func (d *icmpDialer) initSession(ctx context.Context, addr net.Addr, conn net.Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
tlsCfg := d.options.TLSConfig
|
tlsCfg := d.options.TLSConfig
|
||||||
tlsCfg.NextProtos = []string{"http/3", "quic/v1"}
|
tlsCfg.NextProtos = []string{"h3", "quic/v1"}
|
||||||
|
|
||||||
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
|
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -14,21 +14,14 @@ type metadata struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *icmpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
func (d *icmpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||||
const (
|
if mdutil.GetBool(md, "keepalive") {
|
||||||
keepAlive = "keepAlive"
|
d.md.keepAlivePeriod = mdutil.GetDuration(md, "ttl")
|
||||||
keepAlivePeriod = "ttl"
|
|
||||||
handshakeTimeout = "handshakeTimeout"
|
|
||||||
maxIdleTimeout = "maxIdleTimeout"
|
|
||||||
)
|
|
||||||
|
|
||||||
if mdutil.GetBool(md, keepAlive) {
|
|
||||||
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
|
||||||
if d.md.keepAlivePeriod <= 0 {
|
if d.md.keepAlivePeriod <= 0 {
|
||||||
d.md.keepAlivePeriod = 10 * time.Second
|
d.md.keepAlivePeriod = 10 * time.Second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
|
d.md.handshakeTimeout = mdutil.GetDuration(md, "handshakeTimeout")
|
||||||
d.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
|
d.md.maxIdleTimeout = mdutil.GetDuration(md, "maxIdleTimeout")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ func (d *kcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
|||||||
PacketConn: pc,
|
PacketConn: pc,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c, err := options.NetDialer.Dial(ctx, "udp", "")
|
c, err := options.Dialer.Dial(ctx, "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -149,7 +149,9 @@ func (d *kcpDialer) initSession(ctx context.Context, addr net.Addr, conn net.Pac
|
|||||||
smuxConfig.Version = config.SmuxVer
|
smuxConfig.Version = config.SmuxVer
|
||||||
smuxConfig.MaxReceiveBuffer = config.SmuxBuf
|
smuxConfig.MaxReceiveBuffer = config.SmuxBuf
|
||||||
smuxConfig.MaxStreamBuffer = config.StreamBuf
|
smuxConfig.MaxStreamBuffer = config.StreamBuf
|
||||||
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
|
if config.KeepAlive > 0 {
|
||||||
|
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
|
||||||
|
}
|
||||||
var cc net.Conn = kcpconn
|
var cc net.Conn = kcpconn
|
||||||
if !config.NoComp {
|
if !config.NoComp {
|
||||||
cc = kcp_util.CompStreamConn(kcpconn)
|
cc = kcp_util.CompStreamConn(kcpconn)
|
||||||
|
@ -21,14 +21,14 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
handshakeTimeout = "handshakeTimeout"
|
handshakeTimeout = "handshakeTimeout"
|
||||||
)
|
)
|
||||||
|
|
||||||
if file := mdutil.GetString(md, configFile); file != "" {
|
if file := mdutil.GetString(md, "kcp.configFile", "configFile", "c"); file != "" {
|
||||||
d.md.config, err = kcp_util.ParseFromFile(file)
|
d.md.config, err = kcp_util.ParseFromFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if m := mdutil.GetStringMap(md, config); len(m) > 0 {
|
if m := mdutil.GetStringMap(md, "kcp.config", "config"); len(m) > 0 {
|
||||||
b, err := json.Marshal(m)
|
b, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -42,6 +42,19 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
if d.md.config == nil {
|
if d.md.config == nil {
|
||||||
d.md.config = kcp_util.DefaultConfig
|
d.md.config = kcp_util.DefaultConfig
|
||||||
}
|
}
|
||||||
|
d.md.config.TCP = mdutil.GetBool(md, "kcp.tcp", "tcp")
|
||||||
|
d.md.config.Key = mdutil.GetString(md, "kcp.key")
|
||||||
|
d.md.config.Crypt = mdutil.GetString(md, "kcp.crypt")
|
||||||
|
d.md.config.Mode = mdutil.GetString(md, "kcp.mode")
|
||||||
|
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)
|
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
|
||||||
return
|
return
|
||||||
|
@ -67,7 +67,7 @@ func (d *mtcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ package mtls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -68,7 +68,7 @@ func (d *mtlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -123,7 +123,20 @@ func (d *mtlsDialer) Handshake(ctx context.Context, conn net.Conn, options ...di
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *mtlsDialer) initSession(ctx context.Context, conn net.Conn) (*muxSession, error) {
|
func (d *mtlsDialer) initSession(ctx context.Context, conn net.Conn) (*muxSession, error) {
|
||||||
tlsConn := tls.Client(conn, d.options.TLSConfig)
|
tlsConfig := d.options.TLSConfig
|
||||||
|
var utlsConf = &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs}
|
||||||
|
if len(tlsConfig.Certificates) > 0 {
|
||||||
|
for _, certificate := range tlsConfig.Certificates {
|
||||||
|
utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{
|
||||||
|
Certificate: certificate.Certificate,
|
||||||
|
PrivateKey: certificate.PrivateKey,
|
||||||
|
OCSPStaple: certificate.OCSPStaple,
|
||||||
|
SignedCertificateTimestamps: certificate.SignedCertificateTimestamps,
|
||||||
|
Leaf: certificate.Leaf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tlsConn := tls.UClient(conn, utlsConf, tls.HelloChrome_Auto)
|
||||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package mws
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/go-gost/x/util"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
@ -82,7 +84,7 @@ func (d *mwsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -158,6 +160,33 @@ func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn,
|
|||||||
if d.tlsEnabled {
|
if d.tlsEnabled {
|
||||||
url.Scheme = "wss"
|
url.Scheme = "wss"
|
||||||
dialer.TLSClientConfig = d.options.TLSConfig
|
dialer.TLSClientConfig = d.options.TLSConfig
|
||||||
|
tlsConfig := d.options.TLSConfig
|
||||||
|
dialer.NetDialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
utlsConf := &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs}
|
||||||
|
if len(tlsConfig.Certificates) > 0 {
|
||||||
|
for _, certificate := range tlsConfig.Certificates {
|
||||||
|
utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{
|
||||||
|
Certificate: certificate.Certificate,
|
||||||
|
PrivateKey: certificate.PrivateKey,
|
||||||
|
OCSPStaple: certificate.OCSPStaple,
|
||||||
|
SignedCertificateTimestamps: certificate.SignedCertificateTimestamps,
|
||||||
|
Leaf: certificate.Leaf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var client *tls.UConn
|
||||||
|
if d.md.useH2 {
|
||||||
|
client = tls.UClient(conn, utlsConf, tls.HelloChrome_Auto)
|
||||||
|
} else {
|
||||||
|
client = tls.UClient(conn, utlsConf, tls.HelloCustom)
|
||||||
|
client.ApplyPreset(util.NewWsSpec())
|
||||||
|
}
|
||||||
|
err := client.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.md.handshakeTimeout > 0 {
|
if d.md.handshakeTimeout > 0 {
|
||||||
|
@ -27,6 +27,9 @@ type metadata struct {
|
|||||||
header http.Header
|
header http.Header
|
||||||
keepaliveInterval time.Duration
|
keepaliveInterval time.Duration
|
||||||
muxCfg *mux.Config
|
muxCfg *mux.Config
|
||||||
|
|
||||||
|
//Evan Enhanced
|
||||||
|
useH2 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
|
func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||||
@ -67,5 +70,6 @@ func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.md.useH2 = mdutil.GetBool(md, "h2")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
type obfsHTTPConn struct {
|
type obfsHTTPConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
host string
|
host string
|
||||||
|
path string
|
||||||
rbuf bytes.Buffer
|
rbuf bytes.Buffer
|
||||||
wbuf bytes.Buffer
|
wbuf bytes.Buffer
|
||||||
headerDrained bool
|
headerDrained bool
|
||||||
|
@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/go-gost/core/dialer"
|
"github.com/go-gost/core/dialer"
|
||||||
@ -12,11 +13,13 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registry.DialerRegistry().Register("ohttp", NewDialer)
|
registry.DialerRegistry().Register("ohttp", NewDialer)
|
||||||
|
registry.DialerRegistry().Register("ohttps", NewDialer)
|
||||||
}
|
}
|
||||||
|
|
||||||
type obfsHTTPDialer struct {
|
type obfsHTTPDialer struct {
|
||||||
md metadata
|
tlsEnabled bool
|
||||||
logger logger.Logger
|
md metadata
|
||||||
|
logger logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
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) {
|
func (d *obfsHTTPDialer) Init(md md.Metadata) (err error) {
|
||||||
return d.parseMetadata(md)
|
return d.parseMetadata(md)
|
||||||
}
|
}
|
||||||
@ -40,7 +55,7 @@ func (d *obfsHTTPDialer) Dial(ctx context.Context, addr string, opts ...dialer.D
|
|||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Error(err)
|
d.logger.Error(err)
|
||||||
}
|
}
|
||||||
@ -59,9 +74,16 @@ func (d *obfsHTTPDialer) Handshake(ctx context.Context, conn net.Conn, options .
|
|||||||
host = opts.Addr
|
host = opts.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if d.tlsEnabled {
|
||||||
|
conn = tls.Client(conn, &tls.Config{
|
||||||
|
ServerName: host,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return &obfsHTTPConn{
|
return &obfsHTTPConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
host: host,
|
host: host,
|
||||||
|
path: d.md.path,
|
||||||
header: d.md.header,
|
header: d.md.header,
|
||||||
logger: d.logger,
|
logger: d.logger,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -7,24 +7,29 @@ import (
|
|||||||
mdutil "github.com/go-gost/core/metadata/util"
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPath = "/"
|
||||||
|
)
|
||||||
|
|
||||||
type metadata struct {
|
type metadata struct {
|
||||||
host string
|
host string
|
||||||
|
path string
|
||||||
header http.Header
|
header http.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *obfsHTTPDialer) parseMetadata(md mdata.Metadata) (err error) {
|
func (d *obfsHTTPDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||||
const (
|
d.md.host = mdutil.GetString(md, "obfs.host", "host")
|
||||||
header = "header"
|
d.md.path = mdutil.GetString(md, "obfs.path", "path")
|
||||||
host = "host"
|
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{}
|
h := http.Header{}
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
h.Add(k, v)
|
h.Add(k, v)
|
||||||
}
|
}
|
||||||
d.md.header = h
|
d.md.header = h
|
||||||
}
|
}
|
||||||
d.md.host = mdutil.GetString(md, host)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func (d *obfsTLSDialer) Dial(ctx context.Context, addr string, opts ...dialer.Di
|
|||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Error(err)
|
d.logger.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func (d *phtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
|||||||
tr := &http.Transport{
|
tr := &http.Transport{
|
||||||
// Proxy: http.ProxyFromEnvironment,
|
// Proxy: http.ProxyFromEnvironment,
|
||||||
DialContext: func(ctx context.Context, network, adr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, adr string) (net.Conn, error) {
|
||||||
return options.NetDialer.Dial(ctx, network, addr)
|
return options.Dialer.Dial(ctx, network, addr)
|
||||||
},
|
},
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
MaxIdleConns: 100,
|
MaxIdleConns: 100,
|
||||||
|
@ -67,7 +67,7 @@ func (d *quicDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
opt(options)
|
opt(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := options.NetDialer.Dial(ctx, "udp", "")
|
c, err := options.Dialer.Dial(ctx, "udp", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func (d *quicDialer) initSession(ctx context.Context, addr net.Addr, conn net.Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
tlsCfg := d.options.TLSConfig
|
tlsCfg := d.options.TLSConfig
|
||||||
tlsCfg.NextProtos = []string{"http/3", "quic/v1"}
|
tlsCfg.NextProtos = []string{"h3", "quic/v1"}
|
||||||
|
|
||||||
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
|
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,7 +31,7 @@ func (d *quicDialer) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
d.md.cipherKey = []byte(key)
|
d.md.cipherKey = []byte(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
|
if md == nil || !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
|
||||||
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
||||||
if d.md.keepAlivePeriod <= 0 {
|
if d.md.keepAlivePeriod <= 0 {
|
||||||
d.md.keepAlivePeriod = 10 * time.Second
|
d.md.keepAlivePeriod = 10 * time.Second
|
||||||
|
@ -64,7 +64,7 @@ func (d *sshDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mdata "github.com/go-gost/core/metadata"
|
mdata "github.com/go-gost/core/metadata"
|
||||||
mdutil "github.com/go-gost/core/metadata/util"
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/zalando/go-keyring"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,21 +23,35 @@ type metadata struct {
|
|||||||
|
|
||||||
func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) {
|
func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||||
const (
|
const (
|
||||||
handshakeTimeout = "handshakeTimeout"
|
handshakeTimeout = "handshakeTimeout"
|
||||||
privateKeyFile = "privateKeyFile"
|
privateKeyFile = "privateKeyFile"
|
||||||
passphrase = "passphrase"
|
passphrase = "passphrase"
|
||||||
|
passphraseFromKeyring = "passphraseFromKeyring"
|
||||||
)
|
)
|
||||||
|
|
||||||
if key := mdutil.GetString(md, privateKeyFile); key != "" {
|
if key := mdutil.GetString(md, privateKeyFile); key != "" {
|
||||||
|
key, err = homedir.Expand(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
data, err := os.ReadFile(key)
|
data, err := os.ReadFile(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if pp := mdutil.GetString(md, passphrase); pp != "" {
|
var pp string
|
||||||
d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp))
|
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 {
|
} else {
|
||||||
|
pp = mdutil.GetString(md, passphrase)
|
||||||
|
}
|
||||||
|
if pp == "" {
|
||||||
d.md.signer, err = ssh.ParsePrivateKey(data)
|
d.md.signer, err = ssh.ParsePrivateKey(data)
|
||||||
|
} else {
|
||||||
|
d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -64,7 +64,7 @@ func (d *sshdDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package sshd
|
package sshd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mdata "github.com/go-gost/core/metadata"
|
mdata "github.com/go-gost/core/metadata"
|
||||||
mdutil "github.com/go-gost/core/metadata/util"
|
mdutil "github.com/go-gost/core/metadata/util"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/zalando/go-keyring"
|
||||||
"golang.org/x/crypto/ssh"
|
"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 != "" {
|
if key := mdutil.GetString(md, privateKeyFile); key != "" {
|
||||||
|
key, err = homedir.Expand(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
data, err := os.ReadFile(key)
|
data, err := os.ReadFile(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 == "" {
|
if pp == "" {
|
||||||
d.md.signer, err = ssh.ParsePrivateKey(data)
|
d.md.signer, err = ssh.ParsePrivateKey(data)
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,7 +40,7 @@ func (d *tcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Error(err)
|
d.logger.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
tls "github.com/refraction-networking/utls"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ func (d *tlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.logger.Error(err)
|
d.logger.Error(err)
|
||||||
}
|
}
|
||||||
@ -57,8 +57,20 @@ func (d *tlsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dia
|
|||||||
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
|
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
|
||||||
defer conn.SetDeadline(time.Time{})
|
defer conn.SetDeadline(time.Time{})
|
||||||
}
|
}
|
||||||
|
tlsConfig := d.options.TLSConfig
|
||||||
tlsConn := tls.Client(conn, d.options.TLSConfig)
|
utlsConf := &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs}
|
||||||
|
if len(tlsConfig.Certificates) > 0 {
|
||||||
|
for _, certificate := range tlsConfig.Certificates {
|
||||||
|
utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{
|
||||||
|
Certificate: certificate.Certificate,
|
||||||
|
PrivateKey: certificate.PrivateKey,
|
||||||
|
OCSPStaple: certificate.OCSPStaple,
|
||||||
|
SignedCertificateTimestamps: certificate.SignedCertificateTimestamps,
|
||||||
|
Leaf: certificate.Leaf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tlsConn := tls.UClient(conn, utlsConf, tls.HelloChrome_Auto)
|
||||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -40,7 +40,7 @@ func (d *udpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := options.NetDialer.Dial(ctx, "udp", addr)
|
c, err := options.Dialer.Dial(ctx, "udp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
48
dialer/wg/dialer.go
Normal file
48
dialer/wg/dialer.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package wg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/go-gost/core/dialer"
|
||||||
|
"github.com/go-gost/core/logger"
|
||||||
|
md "github.com/go-gost/core/metadata"
|
||||||
|
"github.com/go-gost/x/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.DialerRegistry().Register("wg", NewDialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
type wgDialer struct {
|
||||||
|
md metadata
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
||||||
|
options := &dialer.Options{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &wgDialer{
|
||||||
|
logger: options.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *wgDialer) Init(md md.Metadata) (err error) {
|
||||||
|
return d.parseMetadata(md)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *wgDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
|
||||||
|
var options dialer.DialOptions
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
d.logger.Error(err)
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
23
dialer/wg/metadata.go
Normal file
23
dialer/wg/metadata.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package wg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
md "github.com/go-gost/core/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dialTimeout = "dialTimeout"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultDialTimeout = 5 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
dialTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *wgDialer) parseMetadata(md md.Metadata) (err error) {
|
||||||
|
return
|
||||||
|
}
|
@ -2,6 +2,8 @@ package ws
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/go-gost/x/util"
|
||||||
|
tls "github.com/refraction-networking/utls"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
@ -57,7 +59,7 @@ func (d *wsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOpt
|
|||||||
opt(&options)
|
opt(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.options.Logger.Error(err)
|
d.options.Logger.Error(err)
|
||||||
}
|
}
|
||||||
@ -91,13 +93,43 @@ func (d *wsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dial
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
url := url.URL{Scheme: "ws", Host: host, Path: d.md.path}
|
urlObj := url.URL{Scheme: "ws", Host: host, Path: d.md.path}
|
||||||
if d.tlsEnabled {
|
if d.tlsEnabled {
|
||||||
url.Scheme = "wss"
|
urlObj.Scheme = "wss"
|
||||||
dialer.TLSClientConfig = d.options.TLSConfig
|
dialer.TLSClientConfig = d.options.TLSConfig
|
||||||
|
tlsConfig := d.options.TLSConfig
|
||||||
|
dialer.NetDialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
utlsConf := &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs}
|
||||||
|
if len(tlsConfig.Certificates) > 0 {
|
||||||
|
for _, certificate := range tlsConfig.Certificates {
|
||||||
|
utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{
|
||||||
|
Certificate: certificate.Certificate,
|
||||||
|
PrivateKey: certificate.PrivateKey,
|
||||||
|
OCSPStaple: certificate.OCSPStaple,
|
||||||
|
SignedCertificateTimestamps: certificate.SignedCertificateTimestamps,
|
||||||
|
Leaf: certificate.Leaf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var client *tls.UConn
|
||||||
|
if d.md.useH2 {
|
||||||
|
client = tls.UClient(conn, utlsConf, tls.HelloChrome_Auto)
|
||||||
|
} else {
|
||||||
|
client = tls.UClient(conn, utlsConf, tls.HelloCustom)
|
||||||
|
client.ApplyPreset(util.NewWsSpec())
|
||||||
|
}
|
||||||
|
err := client.Handshake()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
urlStr, errUnescape := url.QueryUnescape(urlObj.String())
|
||||||
c, resp, err := dialer.DialContext(ctx, url.String(), d.md.header)
|
if errUnescape != nil {
|
||||||
|
d.options.Logger.Debugf("[ws] URL QueryUnescape Error URL.String() -> %s", urlObj.String())
|
||||||
|
}
|
||||||
|
c, resp, err := dialer.DialContext(ctx, urlStr, d.md.header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user