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"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
type httpPluginRequest struct {
|
||||
Addr string `json:"addr"`
|
||||
}
|
176
api/api.go
176
api/api.go
@ -2,6 +2,13 @@ package api
|
||||
|
||||
import (
|
||||
"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 (
|
||||
@ -13,3 +20,172 @@ type Response struct {
|
||||
Code int `json:"code,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"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"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
|
||||
type getConfigRequest struct {
|
||||
// output format, one of yaml|json, default is json.
|
||||
@ -37,6 +44,40 @@ func getConfig(ctx *gin.Context) {
|
||||
var req getConfigRequest
|
||||
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
|
||||
resp.Config = config.Global()
|
||||
|
||||
@ -62,6 +103,9 @@ type saveConfigRequest struct {
|
||||
// output format, one of yaml|json, default is yaml.
|
||||
// in: query
|
||||
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.
|
||||
@ -92,11 +136,15 @@ func saveConfig(ctx *gin.Context) {
|
||||
req.Format = "yaml"
|
||||
}
|
||||
|
||||
if req.Path != "" {
|
||||
file = req.Path
|
||||
}
|
||||
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
writeError(ctx, &Error{
|
||||
statusCode: http.StatusInternalServerError,
|
||||
Code: 40005,
|
||||
Code: ErrCodeSaveConfigFailed,
|
||||
Msg: fmt.Sprintf("create file: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
@ -106,8 +154,8 @@ func saveConfig(ctx *gin.Context) {
|
||||
if err := config.Global().Write(f, req.Format); err != nil {
|
||||
writeError(ctx, &Error{
|
||||
statusCode: http.StatusInternalServerError,
|
||||
Code: 40006,
|
||||
Msg: fmt.Sprintf("write: %s", err.Error()),
|
||||
Code: ErrCodeSaveConfigFailed,
|
||||
Msg: fmt.Sprintf("save config: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,15 +37,22 @@ func createAdmission(ctx *gin.Context) {
|
||||
var req createAdmissionRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "admission name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.AdmissionRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseAdmission(&req.Data)
|
||||
|
||||
if err := registry.AdmissionRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.AdmissionRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,25 +96,27 @@ func updateAdmission(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.AdmissionRegistry().IsRegistered(req.Admission) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Admission)
|
||||
|
||||
if !registry.AdmissionRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Admission
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseAdmission(&req.Data)
|
||||
|
||||
registry.AdmissionRegistry().Unregister(req.Admission)
|
||||
registry.AdmissionRegistry().Unregister(name)
|
||||
|
||||
if err := registry.AdmissionRegistry().Register(req.Admission, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.AdmissionRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Admissions {
|
||||
if c.Admissions[i].Name == req.Admission {
|
||||
if c.Admissions[i].Name == name {
|
||||
c.Admissions[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -145,17 +156,19 @@ func deleteAdmission(ctx *gin.Context) {
|
||||
var req deleteAdmissionRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.AdmissionRegistry().IsRegistered(req.Admission) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Admission)
|
||||
|
||||
if !registry.AdmissionRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.AdmissionRegistry().Unregister(req.Admission)
|
||||
registry.AdmissionRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
admissiones := c.Admissions
|
||||
c.Admissions = nil
|
||||
for _, s := range admissiones {
|
||||
if s.Name == req.Admission {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Admissions = append(c.Admissions, s)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,14 +37,21 @@ func createAuther(ctx *gin.Context) {
|
||||
var req createAutherRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "auther name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.AutherRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseAuther(&req.Data)
|
||||
if err := registry.AutherRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.AutherRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -86,24 +95,26 @@ func updateAuther(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.AutherRegistry().IsRegistered(req.Auther) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Auther)
|
||||
|
||||
if !registry.AutherRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Auther
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseAuther(&req.Data)
|
||||
registry.AutherRegistry().Unregister(req.Auther)
|
||||
registry.AutherRegistry().Unregister(name)
|
||||
|
||||
if err := registry.AutherRegistry().Register(req.Auther, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.AutherRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Authers {
|
||||
if c.Authers[i].Name == req.Auther {
|
||||
if c.Authers[i].Name == name {
|
||||
c.Authers[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -143,17 +154,19 @@ func deleteAuther(ctx *gin.Context) {
|
||||
var req deleteAutherRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.AutherRegistry().IsRegistered(req.Auther) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Auther)
|
||||
|
||||
if !registry.AutherRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.AutherRegistry().Unregister(req.Auther)
|
||||
registry.AutherRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
authers := c.Authers
|
||||
c.Authers = nil
|
||||
for _, s := range authers {
|
||||
if s.Name == req.Auther {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Authers = append(c.Authers, s)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,15 +37,22 @@ func createBypass(ctx *gin.Context) {
|
||||
var req createBypassRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "bypass name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.BypassRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseBypass(&req.Data)
|
||||
|
||||
if err := registry.BypassRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.BypassRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,25 +96,27 @@ func updateBypass(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.BypassRegistry().IsRegistered(req.Bypass) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Bypass)
|
||||
|
||||
if !registry.BypassRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Bypass
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseBypass(&req.Data)
|
||||
|
||||
registry.BypassRegistry().Unregister(req.Bypass)
|
||||
registry.BypassRegistry().Unregister(name)
|
||||
|
||||
if err := registry.BypassRegistry().Register(req.Bypass, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.BypassRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Bypasses {
|
||||
if c.Bypasses[i].Name == req.Bypass {
|
||||
if c.Bypasses[i].Name == name {
|
||||
c.Bypasses[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -145,17 +156,19 @@ func deleteBypass(ctx *gin.Context) {
|
||||
var req deleteBypassRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.BypassRegistry().IsRegistered(req.Bypass) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Bypass)
|
||||
|
||||
if !registry.BypassRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.BypassRegistry().Unregister(req.Bypass)
|
||||
registry.BypassRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
bypasses := c.Bypasses
|
||||
c.Bypasses = nil
|
||||
for _, s := range bypasses {
|
||||
if s.Name == req.Bypass {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Bypasses = append(c.Bypasses, s)
|
||||
|
@ -1,9 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/registry"
|
||||
@ -35,19 +38,26 @@ func createChain(ctx *gin.Context) {
|
||||
var req createChainRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "chain name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ChainRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v, err := parser.ParseChain(&req.Data)
|
||||
v, err := parser.ParseChain(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := registry.ChainRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.ChainRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -92,29 +102,31 @@ func updateChain(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.ChainRegistry().IsRegistered(req.Chain) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Chain)
|
||||
|
||||
if !registry.ChainRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Chain
|
||||
req.Data.Name = name
|
||||
|
||||
v, err := parser.ParseChain(&req.Data)
|
||||
v, err := parser.ParseChain(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
registry.ChainRegistry().Unregister(req.Chain)
|
||||
registry.ChainRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ChainRegistry().Register(req.Chain, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.ChainRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Chains {
|
||||
if c.Chains[i].Name == req.Chain {
|
||||
if c.Chains[i].Name == name {
|
||||
c.Chains[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -154,17 +166,19 @@ func deleteChain(ctx *gin.Context) {
|
||||
var req deleteChainRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.ChainRegistry().IsRegistered(req.Chain) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Chain)
|
||||
|
||||
if !registry.ChainRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.ChainRegistry().Unregister(req.Chain)
|
||||
registry.ChainRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
chains := c.Chains
|
||||
c.Chains = nil
|
||||
for _, s := range chains {
|
||||
if s.Name == req.Chain {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Chains = append(c.Chains, s)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,15 +37,21 @@ func createConnLimiter(ctx *gin.Context) {
|
||||
var req createConnLimiterRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseConnLimiter(&req.Data)
|
||||
|
||||
if err := registry.ConnLimiterRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.ConnLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create limmiter %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,25 +95,27 @@ func updateConnLimiter(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.ConnLimiterRegistry().IsRegistered(req.Limiter) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Limiter
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseConnLimiter(&req.Data)
|
||||
|
||||
registry.ConnLimiterRegistry().Unregister(req.Limiter)
|
||||
registry.ConnLimiterRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ConnLimiterRegistry().Register(req.Limiter, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.ConnLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.CLimiters {
|
||||
if c.CLimiters[i].Name == req.Limiter {
|
||||
if c.CLimiters[i].Name == name {
|
||||
c.CLimiters[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -145,17 +155,19 @@ func deleteConnLimiter(ctx *gin.Context) {
|
||||
var req deleteConnLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.ConnLimiterRegistry().IsRegistered(req.Limiter) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.ConnLimiterRegistry().Unregister(req.Limiter)
|
||||
registry.ConnLimiterRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
limiteres := c.CLimiters
|
||||
c.CLimiters = nil
|
||||
for _, s := range limiteres {
|
||||
if s.Name == req.Limiter {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.CLimiters = append(c.CLimiters, s)
|
||||
|
@ -1,9 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/hop"
|
||||
"github.com/go-gost/x/registry"
|
||||
@ -35,19 +38,26 @@ func createHop(ctx *gin.Context) {
|
||||
var req createHopRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "hop name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.HopRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v, err := parser.ParseHop(&req.Data)
|
||||
v, err := parser.ParseHop(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := registry.HopRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.HopRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -92,29 +102,30 @@ func updateHop(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.HopRegistry().IsRegistered(req.Hop) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Hop)
|
||||
if !registry.HopRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Hop
|
||||
req.Data.Name = name
|
||||
|
||||
v, err := parser.ParseHop(&req.Data)
|
||||
v, err := parser.ParseHop(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
registry.HopRegistry().Unregister(req.Hop)
|
||||
registry.HopRegistry().Unregister(name)
|
||||
|
||||
if err := registry.HopRegistry().Register(req.Hop, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.HopRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Hops {
|
||||
if c.Hops[i].Name == req.Hop {
|
||||
if c.Hops[i].Name == name {
|
||||
c.Hops[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -154,17 +165,19 @@ func deleteHop(ctx *gin.Context) {
|
||||
var req deleteHopRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.HopRegistry().IsRegistered(req.Hop) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Hop)
|
||||
|
||||
if !registry.HopRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.HopRegistry().Unregister(req.Hop)
|
||||
registry.HopRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
hops := c.Hops
|
||||
c.Hops = nil
|
||||
for _, s := range hops {
|
||||
if s.Name == req.Hop {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Hops = append(c.Hops, s)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,15 +37,22 @@ func createHosts(ctx *gin.Context) {
|
||||
var req createHostsRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "hosts name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.HostsRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseHostMapper(&req.Data)
|
||||
|
||||
if err := registry.HostsRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.HostsRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,25 +96,27 @@ func updateHosts(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.HostsRegistry().IsRegistered(req.Hosts) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Hosts)
|
||||
|
||||
if !registry.HostsRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Hosts
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseHostMapper(&req.Data)
|
||||
|
||||
registry.HostsRegistry().Unregister(req.Hosts)
|
||||
registry.HostsRegistry().Unregister(name)
|
||||
|
||||
if err := registry.HostsRegistry().Register(req.Hosts, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.HostsRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Hosts {
|
||||
if c.Hosts[i].Name == req.Hosts {
|
||||
if c.Hosts[i].Name == name {
|
||||
c.Hosts[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -145,17 +156,19 @@ func deleteHosts(ctx *gin.Context) {
|
||||
var req deleteHostsRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.HostsRegistry().IsRegistered(req.Hosts) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Hosts)
|
||||
|
||||
if !registry.HostsRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.HostsRegistry().Unregister(req.Hosts)
|
||||
registry.HostsRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
hosts := c.Hosts
|
||||
c.Hosts = nil
|
||||
for _, s := range hosts {
|
||||
if s.Name == req.Hosts {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Hosts = append(c.Hosts, s)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,15 +37,22 @@ func createIngress(ctx *gin.Context) {
|
||||
var req createIngressRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "ingress name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.IngressRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseIngress(&req.Data)
|
||||
|
||||
if err := registry.IngressRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.IngressRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,25 +96,27 @@ func updateIngress(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.IngressRegistry().IsRegistered(req.Ingress) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Ingress)
|
||||
|
||||
if !registry.IngressRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Ingress
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseIngress(&req.Data)
|
||||
|
||||
registry.IngressRegistry().Unregister(req.Ingress)
|
||||
registry.IngressRegistry().Unregister(name)
|
||||
|
||||
if err := registry.IngressRegistry().Register(req.Ingress, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.IngressRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Ingresses {
|
||||
if c.Ingresses[i].Name == req.Ingress {
|
||||
if c.Ingresses[i].Name == name {
|
||||
c.Ingresses[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -145,17 +156,19 @@ func deleteIngress(ctx *gin.Context) {
|
||||
var req deleteIngressRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.IngressRegistry().IsRegistered(req.Ingress) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Ingress)
|
||||
|
||||
if !registry.IngressRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.IngressRegistry().Unregister(req.Ingress)
|
||||
registry.IngressRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
ingresses := c.Ingresses
|
||||
c.Ingresses = nil
|
||||
for _, s := range ingresses {
|
||||
if s.Name == req.Ingress {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Ingresses = append(c.Ingresses, s)
|
||||
|
@ -1,7 +1,9 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,15 +37,22 @@ func createLimiter(ctx *gin.Context) {
|
||||
var req createLimiterRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseTrafficLimiter(&req.Data)
|
||||
|
||||
if err := registry.TrafficLimiterRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,25 +96,27 @@ func updateLimiter(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.TrafficLimiterRegistry().IsRegistered(req.Limiter) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Limiter
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseTrafficLimiter(&req.Data)
|
||||
|
||||
registry.TrafficLimiterRegistry().Unregister(req.Limiter)
|
||||
registry.TrafficLimiterRegistry().Unregister(name)
|
||||
|
||||
if err := registry.TrafficLimiterRegistry().Register(req.Limiter, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Limiters {
|
||||
if c.Limiters[i].Name == req.Limiter {
|
||||
if c.Limiters[i].Name == name {
|
||||
c.Limiters[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -145,17 +156,19 @@ func deleteLimiter(ctx *gin.Context) {
|
||||
var req deleteLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.TrafficLimiterRegistry().IsRegistered(req.Limiter) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.TrafficLimiterRegistry().Unregister(req.Limiter)
|
||||
registry.TrafficLimiterRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
limiteres := c.Limiters
|
||||
c.Limiters = nil
|
||||
for _, s := range limiteres {
|
||||
if s.Name == req.Limiter {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Limiters = append(c.Limiters, s)
|
||||
|
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,15 +37,22 @@ func createRateLimiter(ctx *gin.Context) {
|
||||
var req createRateLimiterRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.RateLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseRateLimiter(&req.Data)
|
||||
|
||||
if err := registry.RateLimiterRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.RateLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -87,25 +96,27 @@ func updateRateLimiter(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.RateLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Limiter
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseRateLimiter(&req.Data)
|
||||
|
||||
registry.RateLimiterRegistry().Unregister(req.Limiter)
|
||||
registry.RateLimiterRegistry().Unregister(name)
|
||||
|
||||
if err := registry.RateLimiterRegistry().Register(req.Limiter, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.RateLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.RLimiters {
|
||||
if c.RLimiters[i].Name == req.Limiter {
|
||||
if c.RLimiters[i].Name == name {
|
||||
c.RLimiters[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -145,17 +156,19 @@ func deleteRateLimiter(ctx *gin.Context) {
|
||||
var req deleteRateLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.RateLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.RateLimiterRegistry().Unregister(req.Limiter)
|
||||
registry.RateLimiterRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
limiteres := c.RLimiters
|
||||
c.RLimiters = nil
|
||||
for _, s := range limiteres {
|
||||
if s.Name == req.Limiter {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.RLimiters = append(c.RLimiters, s)
|
||||
|
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,19 +37,26 @@ func createResolver(ctx *gin.Context) {
|
||||
var req createResolverRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "resolver name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ResolverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v, err := parser.ParseResolver(&req.Data)
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := registry.ResolverRegistry().Register(req.Data.Name, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.ResolverRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -91,29 +100,31 @@ func updateResolver(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if !registry.ResolverRegistry().IsRegistered(req.Resolver) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Resolver)
|
||||
|
||||
if !registry.ResolverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = req.Resolver
|
||||
req.Data.Name = name
|
||||
|
||||
v, err := parser.ParseResolver(&req.Data)
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
registry.ResolverRegistry().Unregister(req.Resolver)
|
||||
registry.ResolverRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ResolverRegistry().Register(req.Resolver, v); err != nil {
|
||||
writeError(ctx, ErrDup)
|
||||
if err := registry.ResolverRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Resolvers {
|
||||
if c.Resolvers[i].Name == req.Resolver {
|
||||
if c.Resolvers[i].Name == name {
|
||||
c.Resolvers[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -153,17 +164,19 @@ func deleteResolver(ctx *gin.Context) {
|
||||
var req deleteResolverRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
if !registry.ResolverRegistry().IsRegistered(req.Resolver) {
|
||||
writeError(ctx, ErrNotFound)
|
||||
name := strings.TrimSpace(req.Resolver)
|
||||
|
||||
if !registry.ResolverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.ResolverRegistry().Unregister(req.Resolver)
|
||||
registry.ResolverRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
resolvers := c.Resolvers
|
||||
c.Resolvers = nil
|
||||
for _, s := range resolvers {
|
||||
if s.Name == req.Resolver {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Resolvers = append(c.Resolvers, s)
|
||||
|
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
@ -35,25 +37,27 @@ func createService(ctx *gin.Context) {
|
||||
var req createServiceRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
if req.Data.Name == "" {
|
||||
writeError(ctx, ErrInvalid)
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "service name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ServiceRegistry().IsRegistered(req.Data.Name) {
|
||||
writeError(ctx, ErrDup)
|
||||
if registry.ServiceRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
svc, err := parser.ParseService(&req.Data)
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := registry.ServiceRegistry().Register(req.Data.Name, svc); err != nil {
|
||||
if err := registry.ServiceRegistry().Register(name, svc); err != nil {
|
||||
svc.Close()
|
||||
writeError(ctx, ErrDup)
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -99,26 +103,28 @@ func updateService(ctx *gin.Context) {
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
old := registry.ServiceRegistry().Get(req.Service)
|
||||
name := strings.TrimSpace(req.Service)
|
||||
|
||||
old := registry.ServiceRegistry().Get(name)
|
||||
if old == nil {
|
||||
writeError(ctx, ErrNotFound)
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
||||
return
|
||||
}
|
||||
old.Close()
|
||||
|
||||
req.Data.Name = req.Service
|
||||
req.Data.Name = name
|
||||
|
||||
svc, err := parser.ParseService(&req.Data)
|
||||
if err != nil {
|
||||
writeError(ctx, ErrCreate)
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create service %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
registry.ServiceRegistry().Unregister(req.Service)
|
||||
registry.ServiceRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ServiceRegistry().Register(req.Service, svc); err != nil {
|
||||
if err := registry.ServiceRegistry().Register(name, svc); err != nil {
|
||||
svc.Close()
|
||||
writeError(ctx, ErrDup)
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("service %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -126,7 +132,7 @@ func updateService(ctx *gin.Context) {
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Services {
|
||||
if c.Services[i].Name == req.Service {
|
||||
if c.Services[i].Name == name {
|
||||
c.Services[i] = &req.Data
|
||||
break
|
||||
}
|
||||
@ -166,20 +172,22 @@ func deleteService(ctx *gin.Context) {
|
||||
var req deleteServiceRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
svc := registry.ServiceRegistry().Get(req.Service)
|
||||
name := strings.TrimSpace(req.Service)
|
||||
|
||||
svc := registry.ServiceRegistry().Get(name)
|
||||
if svc == nil {
|
||||
writeError(ctx, ErrNotFound)
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("service %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
registry.ServiceRegistry().Unregister(req.Service)
|
||||
registry.ServiceRegistry().Unregister(name)
|
||||
svc.Close()
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
services := c.Services
|
||||
c.Services = nil
|
||||
for _, s := range services {
|
||||
if s.Name == req.Service {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Services = append(c.Services, s)
|
||||
|
18
api/error.go
18
api/error.go
@ -15,6 +15,16 @@ var (
|
||||
ErrSave = &Error{statusCode: http.StatusInternalServerError, Code: 40005, Msg: "save config failed"}
|
||||
)
|
||||
|
||||
type ErrCode int
|
||||
|
||||
const (
|
||||
ErrCodeInvalid = 40001
|
||||
ErrCodeDup = 40002
|
||||
ErrCodeFailed = 40003
|
||||
ErrCodeNotFound = 40004
|
||||
ErrCodeSaveConfigFailed = 40005
|
||||
)
|
||||
|
||||
// Error is an api error.
|
||||
type Error struct {
|
||||
statusCode int
|
||||
@ -22,6 +32,14 @@ type Error struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func NewError(status, code int, msg string) error {
|
||||
return &Error{
|
||||
statusCode: status,
|
||||
Code: code,
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
b, _ := json.Marshal(e)
|
||||
return string(b)
|
||||
|
@ -36,7 +36,12 @@ func mwBasicAuth(auther auth.Authenticator) gin.HandlerFunc {
|
||||
}
|
||||
u, p, _ := c.Request.BasicAuth()
|
||||
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:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisLoader'
|
||||
reload:
|
||||
@ -71,6 +73,8 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisLoader'
|
||||
reload:
|
||||
@ -91,6 +95,8 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisLoader'
|
||||
reload:
|
||||
@ -107,9 +113,6 @@ definitions:
|
||||
ChainConfig:
|
||||
properties:
|
||||
hops:
|
||||
description: |-
|
||||
REMOVED since beta.6
|
||||
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
||||
items:
|
||||
$ref: '#/definitions/HopConfig'
|
||||
type: array
|
||||
@ -185,8 +188,18 @@ definitions:
|
||||
x-go-name: Limiters
|
||||
log:
|
||||
$ref: '#/definitions/LogConfig'
|
||||
loggers:
|
||||
items:
|
||||
$ref: '#/definitions/LoggerConfig'
|
||||
type: array
|
||||
x-go-name: Loggers
|
||||
metrics:
|
||||
$ref: '#/definitions/MetricsConfig'
|
||||
observers:
|
||||
items:
|
||||
$ref: '#/definitions/ObserverConfig'
|
||||
type: array
|
||||
x-go-name: Observers
|
||||
profiling:
|
||||
$ref: '#/definitions/ProfilingConfig'
|
||||
recorders:
|
||||
@ -204,6 +217,16 @@ definitions:
|
||||
$ref: '#/definitions/LimiterConfig'
|
||||
type: array
|
||||
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:
|
||||
items:
|
||||
$ref: '#/definitions/ServiceConfig'
|
||||
@ -273,6 +296,8 @@ definitions:
|
||||
addr:
|
||||
type: string
|
||||
x-go-name: Addr
|
||||
auth:
|
||||
$ref: '#/definitions/AuthConfig'
|
||||
bypass:
|
||||
type: string
|
||||
x-go-name: Bypass
|
||||
@ -281,20 +306,44 @@ definitions:
|
||||
type: string
|
||||
type: array
|
||||
x-go-name: Bypasses
|
||||
filter:
|
||||
$ref: '#/definitions/NodeFilterConfig'
|
||||
host:
|
||||
description: DEPRECATED by filter.host
|
||||
type: string
|
||||
x-go-name: Host
|
||||
http:
|
||||
$ref: '#/definitions/HTTPNodeConfig'
|
||||
metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
x-go-name: Metadata
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
network:
|
||||
type: string
|
||||
x-go-name: Network
|
||||
path:
|
||||
description: DEPRECATED by filter.path
|
||||
type: string
|
||||
x-go-name: Path
|
||||
protocol:
|
||||
description: DEPRECATED by filter.protocol
|
||||
type: string
|
||||
x-go-name: Protocol
|
||||
tls:
|
||||
$ref: '#/definitions/TLSNodeConfig'
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
ForwarderConfig:
|
||||
properties:
|
||||
hop:
|
||||
description: the referenced hop name
|
||||
type: string
|
||||
x-go-name: Hop
|
||||
name:
|
||||
description: DEPRECATED by hop field
|
||||
type: string
|
||||
x-go-name: Name
|
||||
nodes:
|
||||
@ -304,12 +353,6 @@ definitions:
|
||||
x-go-name: Nodes
|
||||
selector:
|
||||
$ref: '#/definitions/SelectorConfig'
|
||||
targets:
|
||||
description: DEPRECATED by nodes since beta.4
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-go-name: Targets
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
HTTPLoader:
|
||||
@ -321,6 +364,42 @@ definitions:
|
||||
x-go-name: URL
|
||||
type: object
|
||||
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:
|
||||
properties:
|
||||
auth:
|
||||
@ -338,13 +417,16 @@ definitions:
|
||||
x-go-name: Chain
|
||||
chainGroup:
|
||||
$ref: '#/definitions/ChainGroupConfig'
|
||||
ingress:
|
||||
limiter:
|
||||
type: string
|
||||
x-go-name: Ingress
|
||||
x-go-name: Limiter
|
||||
metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
x-go-name: Metadata
|
||||
observer:
|
||||
type: string
|
||||
x-go-name: Observer
|
||||
retries:
|
||||
format: int64
|
||||
type: integer
|
||||
@ -366,9 +448,13 @@ definitions:
|
||||
type: string
|
||||
type: array
|
||||
x-go-name: Bypasses
|
||||
file:
|
||||
$ref: '#/definitions/FileLoader'
|
||||
hosts:
|
||||
type: string
|
||||
x-go-name: Hosts
|
||||
http:
|
||||
$ref: '#/definitions/HTTPLoader'
|
||||
interface:
|
||||
type: string
|
||||
x-go-name: Interface
|
||||
@ -380,6 +466,12 @@ definitions:
|
||||
$ref: '#/definitions/NodeConfig'
|
||||
type: array
|
||||
x-go-name: Nodes
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisLoader'
|
||||
reload:
|
||||
$ref: '#/definitions/Duration'
|
||||
resolver:
|
||||
type: string
|
||||
x-go-name: Resolver
|
||||
@ -418,6 +510,8 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisLoader'
|
||||
reload:
|
||||
@ -433,6 +527,8 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisLoader'
|
||||
reload:
|
||||
@ -468,6 +564,8 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisLoader'
|
||||
reload:
|
||||
@ -559,11 +657,25 @@ definitions:
|
||||
x-go-name: MaxSize
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
LoggerConfig:
|
||||
properties:
|
||||
log:
|
||||
$ref: '#/definitions/LogConfig'
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
MetricsConfig:
|
||||
properties:
|
||||
addr:
|
||||
type: string
|
||||
x-go-name: Addr
|
||||
auth:
|
||||
$ref: '#/definitions/AuthConfig'
|
||||
auther:
|
||||
type: string
|
||||
x-go-name: Auther
|
||||
path:
|
||||
type: string
|
||||
x-go-name: Path
|
||||
@ -574,6 +686,9 @@ definitions:
|
||||
addr:
|
||||
type: string
|
||||
x-go-name: Addr
|
||||
async:
|
||||
type: boolean
|
||||
x-go-name: Async
|
||||
chain:
|
||||
type: string
|
||||
x-go-name: Chain
|
||||
@ -583,6 +698,9 @@ definitions:
|
||||
hostname:
|
||||
type: string
|
||||
x-go-name: Hostname
|
||||
only:
|
||||
type: string
|
||||
x-go-name: Only
|
||||
prefer:
|
||||
type: string
|
||||
x-go-name: Prefer
|
||||
@ -609,12 +727,13 @@ definitions:
|
||||
$ref: '#/definitions/ConnectorConfig'
|
||||
dialer:
|
||||
$ref: '#/definitions/DialerConfig'
|
||||
host:
|
||||
type: string
|
||||
x-go-name: Host
|
||||
filter:
|
||||
$ref: '#/definitions/NodeFilterConfig'
|
||||
hosts:
|
||||
type: string
|
||||
x-go-name: Hosts
|
||||
http:
|
||||
$ref: '#/definitions/HTTPNodeConfig'
|
||||
interface:
|
||||
type: string
|
||||
x-go-name: Interface
|
||||
@ -625,14 +744,55 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
protocol:
|
||||
network:
|
||||
type: string
|
||||
x-go-name: Protocol
|
||||
x-go-name: Network
|
||||
resolver:
|
||||
type: string
|
||||
x-go-name: Resolver
|
||||
sockopts:
|
||||
$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
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
ProfilingConfig:
|
||||
@ -646,15 +806,24 @@ definitions:
|
||||
properties:
|
||||
file:
|
||||
$ref: '#/definitions/FileRecorder'
|
||||
http:
|
||||
$ref: '#/definitions/HTTPRecorder'
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
redis:
|
||||
$ref: '#/definitions/RedisRecorder'
|
||||
tcp:
|
||||
$ref: '#/definitions/TCPRecorder'
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
RecorderObject:
|
||||
properties:
|
||||
Metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
@ -713,6 +882,8 @@ definitions:
|
||||
$ref: '#/definitions/NameserverConfig'
|
||||
type: array
|
||||
x-go-name: Nameservers
|
||||
plugin:
|
||||
$ref: '#/definitions/PluginConfig'
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
Response:
|
||||
@ -726,6 +897,47 @@ definitions:
|
||||
x-go-name: Msg
|
||||
type: object
|
||||
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:
|
||||
properties:
|
||||
failTimeout:
|
||||
@ -779,6 +991,14 @@ definitions:
|
||||
x-go-name: Limiter
|
||||
listener:
|
||||
$ref: '#/definitions/ListenerConfig'
|
||||
logger:
|
||||
type: string
|
||||
x-go-name: Logger
|
||||
loggers:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-go-name: Loggers
|
||||
metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
@ -786,6 +1006,9 @@ definitions:
|
||||
name:
|
||||
type: string
|
||||
x-go-name: Name
|
||||
observer:
|
||||
type: string
|
||||
x-go-name: Observer
|
||||
recorders:
|
||||
items:
|
||||
$ref: '#/definitions/RecorderObject'
|
||||
@ -799,6 +1022,61 @@ definitions:
|
||||
x-go-name: RLimiter
|
||||
sockopts:
|
||||
$ref: '#/definitions/SockOptsConfig'
|
||||
status:
|
||||
$ref: '#/definitions/ServiceStatus'
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
ServiceEvent:
|
||||
properties:
|
||||
msg:
|
||||
type: string
|
||||
x-go-name: Msg
|
||||
time:
|
||||
format: int64
|
||||
type: integer
|
||||
x-go-name: Time
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
ServiceStats:
|
||||
properties:
|
||||
currentConns:
|
||||
format: uint64
|
||||
type: integer
|
||||
x-go-name: CurrentConns
|
||||
inputBytes:
|
||||
format: uint64
|
||||
type: integer
|
||||
x-go-name: InputBytes
|
||||
outputBytes:
|
||||
format: uint64
|
||||
type: integer
|
||||
x-go-name: OutputBytes
|
||||
totalConns:
|
||||
format: uint64
|
||||
type: integer
|
||||
x-go-name: TotalConns
|
||||
totalErrs:
|
||||
format: uint64
|
||||
type: integer
|
||||
x-go-name: TotalErrs
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
ServiceStatus:
|
||||
properties:
|
||||
createTime:
|
||||
format: int64
|
||||
type: integer
|
||||
x-go-name: CreateTime
|
||||
events:
|
||||
items:
|
||||
$ref: '#/definitions/ServiceEvent'
|
||||
type: array
|
||||
x-go-name: Events
|
||||
state:
|
||||
type: string
|
||||
x-go-name: State
|
||||
stats:
|
||||
$ref: '#/definitions/ServiceStats'
|
||||
type: object
|
||||
x-go-package: github.com/go-gost/x/config
|
||||
SockOptsConfig:
|
||||
@ -809,6 +1087,15 @@ definitions:
|
||||
x-go-name: Mark
|
||||
type: object
|
||||
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:
|
||||
properties:
|
||||
caFile:
|
||||
@ -823,6 +1110,8 @@ definitions:
|
||||
keyFile:
|
||||
type: string
|
||||
x-go-name: KeyFile
|
||||
options:
|
||||
$ref: '#/definitions/TLSOptions'
|
||||
organization:
|
||||
type: string
|
||||
x-go-name: Organization
|
||||
@ -836,6 +1125,33 @@ definitions:
|
||||
$ref: '#/definitions/Duration'
|
||||
type: object
|
||||
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:
|
||||
title: Documentation of Web API.
|
||||
version: 1.0.0
|
||||
@ -866,6 +1182,11 @@ paths:
|
||||
name: format
|
||||
type: string
|
||||
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:
|
||||
"200":
|
||||
$ref: '#/responses/saveConfigResponse'
|
||||
@ -1397,6 +1718,122 @@ paths:
|
||||
summary: Update limiter by name, the limiter must already exist.
|
||||
tags:
|
||||
- Limiter
|
||||
/config/observers:
|
||||
post:
|
||||
operationId: createObserverRequest
|
||||
parameters:
|
||||
- in: body
|
||||
name: data
|
||||
schema:
|
||||
$ref: '#/definitions/ObserverConfig'
|
||||
x-go-name: Data
|
||||
responses:
|
||||
"200":
|
||||
$ref: '#/responses/createObserverResponse'
|
||||
security:
|
||||
- basicAuth:
|
||||
- '[]'
|
||||
summary: Create a new observer, the name of the observer must be unique in observer list.
|
||||
tags:
|
||||
- Observer
|
||||
/config/observers/{observer}:
|
||||
delete:
|
||||
operationId: deleteObserverRequest
|
||||
parameters:
|
||||
- in: path
|
||||
name: observer
|
||||
required: true
|
||||
type: string
|
||||
x-go-name: Observer
|
||||
responses:
|
||||
"200":
|
||||
$ref: '#/responses/deleteObserverResponse'
|
||||
security:
|
||||
- basicAuth:
|
||||
- '[]'
|
||||
summary: Delete observer by name.
|
||||
tags:
|
||||
- Observer
|
||||
put:
|
||||
operationId: updateObserverRequest
|
||||
parameters:
|
||||
- in: path
|
||||
name: observer
|
||||
required: true
|
||||
type: string
|
||||
x-go-name: Observer
|
||||
- in: body
|
||||
name: data
|
||||
schema:
|
||||
$ref: '#/definitions/ObserverConfig'
|
||||
x-go-name: Data
|
||||
responses:
|
||||
"200":
|
||||
$ref: '#/responses/updateObserverResponse'
|
||||
security:
|
||||
- basicAuth:
|
||||
- '[]'
|
||||
summary: Update observer by name, the observer must already exist.
|
||||
tags:
|
||||
- Observer
|
||||
/config/recorders:
|
||||
post:
|
||||
operationId: createRecorderRequest
|
||||
parameters:
|
||||
- in: body
|
||||
name: data
|
||||
schema:
|
||||
$ref: '#/definitions/RecorderConfig'
|
||||
x-go-name: Data
|
||||
responses:
|
||||
"200":
|
||||
$ref: '#/responses/createRecorderResponse'
|
||||
security:
|
||||
- basicAuth:
|
||||
- '[]'
|
||||
summary: Create a new recorder, the name of the recorder must be unique in recorder list.
|
||||
tags:
|
||||
- Recorder
|
||||
/config/recorders/{recorder}:
|
||||
delete:
|
||||
operationId: deleteRecorderRequest
|
||||
parameters:
|
||||
- in: path
|
||||
name: recorder
|
||||
required: true
|
||||
type: string
|
||||
x-go-name: Recorder
|
||||
responses:
|
||||
"200":
|
||||
$ref: '#/responses/deleteRecorderResponse'
|
||||
security:
|
||||
- basicAuth:
|
||||
- '[]'
|
||||
summary: Delete recorder by name.
|
||||
tags:
|
||||
- Recorder
|
||||
put:
|
||||
operationId: updateRecorderRequest
|
||||
parameters:
|
||||
- in: path
|
||||
name: recorder
|
||||
required: true
|
||||
type: string
|
||||
x-go-name: Recorder
|
||||
- in: body
|
||||
name: data
|
||||
schema:
|
||||
$ref: '#/definitions/RecorderConfig'
|
||||
x-go-name: Data
|
||||
responses:
|
||||
"200":
|
||||
$ref: '#/responses/updateRecorderResponse'
|
||||
security:
|
||||
- basicAuth:
|
||||
- '[]'
|
||||
summary: Update recorder by name, the recorder must already exist.
|
||||
tags:
|
||||
- Recorder
|
||||
/config/resolvers:
|
||||
post:
|
||||
operationId: createResolverRequest
|
||||
@ -1513,6 +1950,122 @@ paths:
|
||||
summary: Update rate limiter by name, the limiter must already exist.
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
operationId: createServiceRequest
|
||||
@ -1628,18 +2181,42 @@ responses:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
createObserverResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
createRateLimiterResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
createRecorderResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
createResolverResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
createRouterResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
createSDResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
createServiceResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
@ -1700,18 +2277,42 @@ responses:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
deleteObserverResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
deleteRateLimiterResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
deleteRecorderResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
deleteResolverResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
deleteRouterResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
deleteSDResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
deleteServiceResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
@ -1784,18 +2385,42 @@ responses:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
updateObserverResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
updateRateLimiterResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
updateRecorderResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
updateResolverResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
updateRouterResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
updateSDResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
Data: {}
|
||||
schema:
|
||||
$ref: '#/definitions/Response'
|
||||
updateServiceResponse:
|
||||
description: successful operation.
|
||||
headers:
|
||||
|
@ -110,7 +110,7 @@ func (p *authenticator) Authenticate(ctx context.Context, user, password string,
|
||||
}
|
||||
|
||||
v, ok := p.kvs[user]
|
||||
return "", ok && (v == "" || password == v)
|
||||
return user, ok && (v == "" || password == v)
|
||||
}
|
||||
|
||||
func (p *authenticator) periodReload(ctx context.Context) error {
|
||||
@ -145,6 +145,8 @@ func (p *authenticator) reload(ctx context.Context) (err error) {
|
||||
kvs[k] = v
|
||||
}
|
||||
|
||||
p.options.logger.Debugf("load items %d", len(m))
|
||||
|
||||
p.mu.Lock()
|
||||
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
|
||||
}
|
||||
|
||||
|
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"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"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"
|
||||
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 {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
@ -118,7 +56,7 @@ func (p *httpPlugin) Authenticate(ctx context.Context, user, password string, op
|
||||
rb := httpPluginRequest{
|
||||
Username: user,
|
||||
Password: password,
|
||||
Client: string(auth_util.ClientAddrFromContext(ctx)),
|
||||
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
|
||||
}
|
||||
v, err := json.Marshal(&rb)
|
||||
if err != nil {
|
@ -130,6 +130,7 @@ func (bp *localBypass) reload(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
patterns := append(bp.options.matchers, v...)
|
||||
bp.options.logger.Debugf("load items %d", len(patterns))
|
||||
|
||||
var addrs []string
|
||||
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
|
||||
}
|
||||
|
||||
@ -235,11 +235,17 @@ func (bp *localBypass) Contains(ctx context.Context, network, addr string, opts
|
||||
b := !bp.options.whitelist && matched ||
|
||||
bp.options.whitelist && !matched
|
||||
if b {
|
||||
bp.options.logger.Debugf("bypass: %s", addr)
|
||||
bp.options.logger.Debugf("bypass: %s, whitelist: %t", addr, bp.options.whitelist)
|
||||
} else {
|
||||
bp.options.logger.Debugf("pass: %s, whitelist: %t", addr, bp.options.whitelist)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *localBypass) IsWhitelist() bool {
|
||||
return p.options.whitelist
|
||||
}
|
||||
|
||||
func (bp *localBypass) parseLine(s string) string {
|
||||
if n := strings.IndexByte(s, '#'); n >= 0 {
|
||||
s = s[:n]
|
||||
|
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"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"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"
|
||||
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 {
|
||||
Network string `json:"network"`
|
||||
Addr string `json:"addr"`
|
||||
@ -129,7 +62,7 @@ func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts ..
|
||||
rb := httpPluginRequest{
|
||||
Network: network,
|
||||
Addr: addr,
|
||||
Client: string(auth_util.IDFromContext(ctx)),
|
||||
Client: string(ctxvalue.ClientIDFromContext(ctx)),
|
||||
Host: options.Host,
|
||||
Path: options.Path,
|
||||
}
|
||||
@ -163,3 +96,7 @@ func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts ..
|
||||
}
|
||||
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
|
||||
node = node.Copy()
|
||||
node.Options().Transport = tr
|
||||
rt = NewRoute()
|
||||
rt = NewRoute(ChainRouteOption(c))
|
||||
}
|
||||
|
||||
rt.addNode(node)
|
||||
|
114
chain/route.go
114
chain/route.go
@ -2,6 +2,8 @@ package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
@ -10,9 +12,86 @@ import (
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/metrics"
|
||||
"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"
|
||||
)
|
||||
|
||||
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 {
|
||||
Chain chain.Chainer
|
||||
}
|
||||
@ -25,12 +104,12 @@ func ChainRouteOption(c chain.Chainer) RouteOption {
|
||||
}
|
||||
}
|
||||
|
||||
type route struct {
|
||||
type chainRoute struct {
|
||||
nodes []*chain.Node
|
||||
options RouteOptions
|
||||
}
|
||||
|
||||
func NewRoute(opts ...RouteOption) *route {
|
||||
func NewRoute(opts ...RouteOption) *chainRoute {
|
||||
var options RouteOptions
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
@ -38,18 +117,18 @@ func NewRoute(opts ...RouteOption) *route {
|
||||
}
|
||||
}
|
||||
|
||||
return &route{
|
||||
return &chainRoute{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *route) addNode(nodes ...*chain.Node) {
|
||||
func (r *chainRoute) addNode(nodes ...*chain.Node) {
|
||||
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 {
|
||||
return chain.DefaultRoute.Dial(ctx, network, address, opts...)
|
||||
return DefaultRoute.Dial(ctx, network, address, opts...)
|
||||
}
|
||||
|
||||
var options chain.DialOptions
|
||||
@ -73,9 +152,9 @@ func (r *route) Dial(ctx context.Context, network, address string, opts ...chain
|
||||
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 {
|
||||
return chain.DefaultRoute.Bind(ctx, network, address, opts...)
|
||||
return DefaultRoute.Bind(ctx, network, address, opts...)
|
||||
}
|
||||
|
||||
var options chain.BindOptions
|
||||
@ -106,7 +185,7 @@ func (r *route) Bind(ctx context.Context, network, address string, opts ...chain
|
||||
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"
|
||||
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 {
|
||||
v.Inc()
|
||||
}
|
||||
} else {
|
||||
if marker != nil {
|
||||
marker.Reset()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
if marker != nil {
|
||||
@ -181,7 +261,7 @@ func (r *route) connect(ctx context.Context, logger logger.Logger) (conn net.Con
|
||||
preNode := node
|
||||
for _, node := range r.nodes[1:] {
|
||||
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 {
|
||||
cn.Close()
|
||||
if marker != nil {
|
||||
@ -217,14 +297,14 @@ func (r *route) connect(ctx context.Context, logger logger.Logger) (conn net.Con
|
||||
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()) {
|
||||
return nil
|
||||
}
|
||||
return r.nodes[index]
|
||||
}
|
||||
|
||||
func (r *route) Nodes() []*chain.Node {
|
||||
func (r *chainRoute) Nodes() []*chain.Node {
|
||||
if r != nil {
|
||||
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"`
|
||||
}
|
||||
|
||||
type LoggerConfig struct {
|
||||
Name string `json:"name"`
|
||||
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
||||
}
|
||||
|
||||
type ProfilingConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
}
|
||||
@ -244,6 +249,21 @@ type SDConfig struct {
|
||||
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 {
|
||||
Name string `json:"name"`
|
||||
File *FileRecorder `yaml:",omitempty" json:"file,omitempty"`
|
||||
@ -277,9 +297,9 @@ type RedisRecorder struct {
|
||||
}
|
||||
|
||||
type RecorderObject struct {
|
||||
Name string `json:"name"`
|
||||
Record string `json:"record"`
|
||||
Metadata map[string]any
|
||||
Name string `json:"name"`
|
||||
Record string `json:"record"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type LimiterConfig struct {
|
||||
@ -289,6 +309,12 @@ type LimiterConfig struct {
|
||||
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 ObserverConfig struct {
|
||||
Name string `json:"name"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type ListenerConfig struct {
|
||||
@ -311,33 +337,69 @@ type HandlerConfig struct {
|
||||
Authers []string `yaml:",omitempty" json:"authers,omitempty"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,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"`
|
||||
}
|
||||
|
||||
type ForwarderConfig struct {
|
||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||
// DEPRECATED by hop field
|
||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||
// the referenced hop name
|
||||
Hop string `yaml:",omitempty" json:"hop,omitempty"`
|
||||
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
||||
Nodes []*ForwardNodeConfig `json:"nodes"`
|
||||
}
|
||||
|
||||
type ForwardNodeConfig struct {
|
||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
// DEPRECATED by filter.protocol
|
||||
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
||||
// DEPRECATED by filter.host
|
||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||
// DEPRECATED by filter.path
|
||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
||||
// DEPRECATED by http.auth
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
|
||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPURLRewriteConfig struct {
|
||||
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 {
|
||||
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"`
|
||||
// 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 {
|
||||
@ -380,11 +442,36 @@ type ServiceConfig struct {
|
||||
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
||||
CLimiter string `yaml:"climiter,omitempty" json:"climiter,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"`
|
||||
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
|
||||
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
|
||||
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,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 {
|
||||
@ -413,27 +500,26 @@ type HopConfig struct {
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
Name string `json:"name"`
|
||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
||||
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
||||
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
||||
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
||||
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
|
||||
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
||||
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
||||
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
|
||||
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
|
||||
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
||||
Netns string `yaml:",omitempty" json:"netns,omitempty"`
|
||||
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
||||
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
|
||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -446,11 +532,14 @@ type Config struct {
|
||||
Resolvers []*ResolverConfig `yaml:",omitempty" json:"resolvers,omitempty"`
|
||||
Hosts []*HostsConfig `yaml:",omitempty" json:"hosts,omitempty"`
|
||||
Ingresses []*IngressConfig `yaml:",omitempty" json:"ingresses,omitempty"`
|
||||
Routers []*RouterConfig `yaml:",omitempty" json:"routers,omitempty"`
|
||||
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
|
||||
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
|
||||
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
|
||||
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,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"`
|
||||
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
||||
Profiling *ProfilingConfig `yaml:",omitempty" json:"profiling,omitempty"`
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/logger"
|
||||
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/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
@ -28,13 +29,13 @@ func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return xadmission.NewHTTPPlugin(
|
||||
return admission_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return xadmission.NewGRPCPlugin(
|
||||
return admission_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/logger"
|
||||
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/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
@ -28,13 +29,13 @@ func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
|
||||
}
|
||||
switch cfg.Plugin.Type {
|
||||
case "http":
|
||||
return xauth.NewHTTPPlugin(
|
||||
return auth_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return xauth.NewGRPCPlugin(
|
||||
return auth_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/go-gost/core/bypass"
|
||||
"github.com/go-gost/core/logger"
|
||||
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/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
@ -28,13 +29,13 @@ func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return xbypass.NewHTTPPlugin(
|
||||
return bypass_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return xbypass.NewGRPCPlugin(
|
||||
return bypass_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
|
@ -12,12 +12,12 @@ import (
|
||||
"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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
chainLogger := logger.Default().WithFields(map[string]any{
|
||||
chainLogger := log.WithFields(map[string]any{
|
||||
"kind": "chain",
|
||||
"chain": cfg.Name,
|
||||
})
|
||||
@ -37,7 +37,7 @@ func ParseChain(cfg *config.ChainConfig) (chain.Chainer, error) {
|
||||
var err error
|
||||
|
||||
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
|
||||
}
|
||||
} else {
|
||||
|
@ -8,16 +8,20 @@ import (
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/hop"
|
||||
"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/parsing"
|
||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||
node_parser "github.com/go-gost/x/config/parsing/node"
|
||||
selector_parser "github.com/go-gost/x/config/parsing/selector"
|
||||
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/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 {
|
||||
return nil, nil
|
||||
}
|
||||
@ -31,14 +35,14 @@ func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return xhop.NewHTTPPlugin(
|
||||
case plugin.HTTP:
|
||||
return hop_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
), nil
|
||||
default:
|
||||
return xhop.NewGRPCPlugin(
|
||||
return hop_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
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
|
||||
for _, v := range cfg.Nodes {
|
||||
if v == nil {
|
||||
@ -59,25 +73,31 @@ func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
||||
v.Hosts = cfg.Hosts
|
||||
}
|
||||
if v.Interface == "" {
|
||||
v.Interface = cfg.Interface
|
||||
v.Interface = ifce
|
||||
}
|
||||
if v.Netns == "" {
|
||||
v.Netns = netns
|
||||
}
|
||||
|
||||
if v.SockOpts == nil {
|
||||
v.SockOpts = cfg.SockOpts
|
||||
}
|
||||
|
||||
if v.Connector == nil {
|
||||
v.Connector = &config.ConnectorConfig{
|
||||
Type: "http",
|
||||
}
|
||||
v.Connector = &config.ConnectorConfig{}
|
||||
}
|
||||
if strings.TrimSpace(v.Connector.Type) == "" {
|
||||
v.Connector.Type = "http"
|
||||
}
|
||||
|
||||
if v.Dialer == nil {
|
||||
v.Dialer = &config.DialerConfig{
|
||||
Type: "tcp",
|
||||
}
|
||||
v.Dialer = &config.DialerConfig{}
|
||||
}
|
||||
if strings.TrimSpace(v.Dialer.Type) == "" {
|
||||
v.Dialer.Type = "tcp"
|
||||
}
|
||||
|
||||
node, err := node_parser.ParseNode(cfg.Name, v)
|
||||
node, err := node_parser.ParseNode(cfg.Name, v, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -97,7 +117,7 @@ func ParseHop(cfg *config.HopConfig) (hop.Hop, error) {
|
||||
xhop.SelectorOption(sel),
|
||||
xhop.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||
xhop.ReloadPeriodOption(cfg.Reload),
|
||||
xhop.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
xhop.LoggerOption(log.WithFields(map[string]any{
|
||||
"kind": "hop",
|
||||
"hop": cfg.Name,
|
||||
})),
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
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/plugin"
|
||||
)
|
||||
@ -28,13 +29,13 @@ func ParseHostMapper(cfg *config.HostsConfig) hosts.HostMapper {
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return xhosts.NewHTTPPlugin(
|
||||
return hosts_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return xhosts.NewGRPCPlugin(
|
||||
return hosts_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
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/plugin"
|
||||
)
|
||||
@ -27,13 +28,13 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return xingress.NewHTTPPlugin(
|
||||
return ingress_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return xingress.NewGRPCPlugin(
|
||||
return ingress_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
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 {
|
||||
if rule.Hostname == "" || rule.Endpoint == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, xingress.Rule{
|
||||
rules = append(rules, &ingress.Rule{
|
||||
Hostname: rule.Hostname,
|
||||
Endpoint: rule.Endpoint,
|
||||
})
|
||||
|
@ -1,15 +1,20 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/limiter/conn"
|
||||
"github.com/go-gost/core/limiter/rate"
|
||||
"github.com/go-gost/core/limiter/traffic"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
xconn "github.com/go-gost/x/limiter/conn"
|
||||
xrate "github.com/go-gost/x/limiter/rate"
|
||||
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) {
|
||||
@ -17,6 +22,30 @@ func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter)
|
||||
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
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/bypass"
|
||||
"github.com/go-gost/core/chain"
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/core/metadata/util"
|
||||
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/parsing"
|
||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||
@ -23,7 +24,7 @@ import (
|
||||
"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 {
|
||||
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,
|
||||
"kind": "node",
|
||||
"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.InterfaceTransportOption(cfg.Interface),
|
||||
chain.NetnsTransportOption(cfg.Netns),
|
||||
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{
|
||||
chain.TransportNodeOption(tr),
|
||||
chain.BypassNodeOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||
chain.ResoloverNodeOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
||||
chain.HostMapperNodeOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
||||
chain.MetadataNodeOption(nm),
|
||||
chain.HostNodeOption(host),
|
||||
chain.ProtocolNodeOption(cfg.Protocol),
|
||||
chain.PathNodeOption(cfg.Path),
|
||||
chain.NetworkNodeOption(cfg.Network),
|
||||
}
|
||||
|
||||
if filter := cfg.Filter; filter != nil {
|
||||
// convert *.example.com to .example.com
|
||||
// convert *example.com to example.com
|
||||
host := filter.Host
|
||||
if strings.HasPrefix(host, "*") {
|
||||
host = host[1:]
|
||||
if !strings.HasPrefix(host, ".") {
|
||||
host = "." + host
|
||||
}
|
||||
}
|
||||
|
||||
settings := &chain.NodeFilterSettings{
|
||||
Protocol: filter.Protocol,
|
||||
Host: host,
|
||||
Path: filter.Path,
|
||||
}
|
||||
opts = append(opts, chain.NodeFilterOption(settings))
|
||||
}
|
||||
|
||||
if cfg.HTTP != nil {
|
||||
opts = append(opts, chain.HTTPNodeOption(&chain.HTTPNodeSettings{
|
||||
settings := &chain.HTTPNodeSettings{
|
||||
Host: cfg.HTTP.Host,
|
||||
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 {
|
||||
tlsCfg := &chain.TLSNodeSettings{
|
||||
ServerName: cfg.TLS.ServerName,
|
||||
@ -185,18 +223,5 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
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"
|
||||
MDKeyPostDown = "postDown"
|
||||
MDKeyIgnoreChain = "ignoreChain"
|
||||
MDKeyEnableStats = "enableStats"
|
||||
|
||||
MDKeyRecorderDirection = "direction"
|
||||
MDKeyRecorderTimestampFormat = "timeStampFormat"
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
xrecorder "github.com/go-gost/x/recorder"
|
||||
recorder_plugin "github.com/go-gost/x/recorder/plugin"
|
||||
)
|
||||
|
||||
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) {
|
||||
case "http":
|
||||
return xrecorder.NewHTTPPlugin(
|
||||
return recorder_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return xrecorder.NewGRPCPlugin(
|
||||
return recorder_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"github.com/go-gost/x/registry"
|
||||
xresolver "github.com/go-gost/x/resolver"
|
||||
resolver_plugin "github.com/go-gost/x/resolver/plugin"
|
||||
)
|
||||
|
||||
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) {
|
||||
case "http":
|
||||
return xresolver.NewHTTPPlugin(
|
||||
return resolver_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
), nil
|
||||
default:
|
||||
return xresolver.NewGRPCPlugin(
|
||||
return resolver_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
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/x/config"
|
||||
"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 {
|
||||
@ -24,13 +24,13 @@ func ParseSD(cfg *config.SDConfig) sd.SD {
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return xsd.NewHTTPPlugin(
|
||||
return sd_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return xsd.NewGRPCPlugin(
|
||||
return sd_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
|
@ -2,6 +2,9 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/auth"
|
||||
@ -12,6 +15,7 @@ import (
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
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/selector"
|
||||
"github.com/go-gost/core/service"
|
||||
@ -22,42 +26,49 @@ import (
|
||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||
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"
|
||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||
"github.com/go-gost/x/metadata"
|
||||
"github.com/go-gost/x/registry"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
if cfg.Listener == nil {
|
||||
cfg.Listener = &config.ListenerConfig{
|
||||
Type: "tcp",
|
||||
}
|
||||
cfg.Listener = &config.ListenerConfig{}
|
||||
}
|
||||
if strings.TrimSpace(cfg.Listener.Type) == "" {
|
||||
cfg.Listener.Type = "tcp"
|
||||
}
|
||||
|
||||
if cfg.Handler == nil {
|
||||
cfg.Handler = &config.HandlerConfig{
|
||||
Type: "auto",
|
||||
}
|
||||
cfg.Handler = &config.HandlerConfig{}
|
||||
}
|
||||
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",
|
||||
"service": cfg.Name,
|
||||
"listener": cfg.Listener.Type,
|
||||
"handler": cfg.Handler.Type,
|
||||
})
|
||||
|
||||
listenerLogger := serviceLogger.WithFields(map[string]any{
|
||||
"kind": "listener",
|
||||
})
|
||||
|
||||
tlsCfg := cfg.Listener.TLS
|
||||
if tlsCfg == nil {
|
||||
tlsCfg = &config.TLSConfig{}
|
||||
}
|
||||
tlsConfig, err := tls_util.LoadServerConfig(tlsCfg)
|
||||
if err != nil {
|
||||
listenerLogger.Error(err)
|
||||
serviceLogger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
@ -88,6 +99,10 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
ifce := cfg.Interface
|
||||
var preUp, preDown, postUp, postDown []string
|
||||
var ignoreChain bool
|
||||
var pStats *stats.Stats
|
||||
var observePeriod time.Duration
|
||||
var netnsIn, netnsOut string
|
||||
var dialTimeout time.Duration
|
||||
if cfg.Metadata != nil {
|
||||
md := metadata.NewMetadata(cfg.Metadata)
|
||||
ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol)
|
||||
@ -104,31 +119,83 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
postUp = mdutil.GetStrings(md, parsing.MDKeyPostUp)
|
||||
postDown = mdutil.GetStrings(md, parsing.MDKeyPostDown)
|
||||
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{
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.RouterOption(xchain.NewRouter(routerOpts...)),
|
||||
listener.AutherOption(auther),
|
||||
listener.AuthOption(auth_parser.Info(cfg.Listener.Auth)),
|
||||
listener.TLSConfigOption(tlsConfig),
|
||||
listener.AdmissionOption(admission.AdmissionGroup(admissions...)),
|
||||
listener.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Limiter)),
|
||||
listener.ConnLimiterOption(registry.ConnLimiterRegistry().Get(cfg.CLimiter)),
|
||||
listener.LoggerOption(listenerLogger),
|
||||
listener.ServiceOption(cfg.Name),
|
||||
listener.ProxyProtocolOption(ppv),
|
||||
listener.StatsOption(pStats),
|
||||
listener.NetnsOption(netnsIn),
|
||||
listener.LoggerOption(listenerLogger),
|
||||
}
|
||||
if !ignoreChain {
|
||||
listenOpts = append(listenOpts,
|
||||
listener.ChainOption(chainGroup(cfg.Listener.Chain, cfg.Listener.ChainGroup)),
|
||||
)
|
||||
|
||||
if netnsIn != "" {
|
||||
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
|
||||
if rf := registry.ListenerRegistry().Get(cfg.Listener.Type); rf != nil {
|
||||
ln = rf(listenOpts...)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unregistered listener: %s", cfg.Listener.Type)
|
||||
return nil, fmt.Errorf("unknown listener: %s", cfg.Listener.Type)
|
||||
}
|
||||
|
||||
if cfg.Listener.Metadata == nil {
|
||||
@ -183,10 +250,11 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
})
|
||||
}
|
||||
|
||||
routerOpts := []chain.RouterOption{
|
||||
routerOpts = []chain.RouterOption{
|
||||
chain.RetriesRouterOption(cfg.Handler.Retries),
|
||||
// chain.TimeoutRouterOption(10*time.Second),
|
||||
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)),
|
||||
@ -198,26 +266,28 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
chain.ChainRouterOption(chainGroup(cfg.Handler.Chain, cfg.Handler.ChainGroup)),
|
||||
)
|
||||
}
|
||||
router := chain.NewRouter(routerOpts...)
|
||||
|
||||
var h handler.Handler
|
||||
if rf := registry.HandlerRegistry().Get(cfg.Handler.Type); rf != nil {
|
||||
h = rf(
|
||||
handler.RouterOption(router),
|
||||
handler.RouterOption(xchain.NewRouter(routerOpts...)),
|
||||
handler.AutherOption(auther),
|
||||
handler.AuthOption(auth_parser.Info(cfg.Handler.Auth)),
|
||||
handler.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||
handler.TLSConfigOption(tlsConfig),
|
||||
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.ServiceOption(cfg.Name),
|
||||
handler.NetnsOption(netnsIn),
|
||||
)
|
||||
} 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 {
|
||||
hop, err := parseForwarder(cfg.Forwarder)
|
||||
hop, err := parseForwarder(cfg.Forwarder, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -240,6 +310,9 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
xservice.PostUpOption(postUp),
|
||||
xservice.PostDownOption(postDown),
|
||||
xservice.RecordersOption(recorders...),
|
||||
xservice.StatsOption(pStats),
|
||||
xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)),
|
||||
xservice.ObservePeriodOption(observePeriod),
|
||||
xservice.LoggerOption(serviceLogger),
|
||||
)
|
||||
|
||||
@ -247,38 +320,61 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
hopName := cfg.Hop
|
||||
if hopName == "" {
|
||||
hopName = cfg.Name
|
||||
}
|
||||
if hopName != "" {
|
||||
return registry.HopRegistry().Get(hopName), nil
|
||||
}
|
||||
|
||||
hc := config.HopConfig{
|
||||
Name: cfg.Name,
|
||||
Selector: cfg.Selector,
|
||||
}
|
||||
for _, node := range cfg.Nodes {
|
||||
if node != nil {
|
||||
hc.Nodes = append(hc.Nodes,
|
||||
&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,
|
||||
},
|
||||
)
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return registry.HopRegistry().Get(hc.Name), nil
|
||||
return hop_parser.ParseHop(&hc, log)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
conn, err := cOpts.NetDialer.Dial(ctx, network, address)
|
||||
conn, err := cOpts.Dialer.Dial(ctx, network, address)
|
||||
if err != nil {
|
||||
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{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"remote": remoteAddr,
|
||||
"local": localAddr,
|
||||
"network": network,
|
||||
"address": address,
|
||||
})
|
||||
|
@ -6,20 +6,16 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gost/core/common/net/udp"
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/relay"
|
||||
"github.com/go-gost/x/internal/net/udp"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
relay_util "github.com/go-gost/x/internal/util/relay"
|
||||
)
|
||||
|
||||
// Bind implements connector.Binder.
|
||||
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{
|
||||
"network": network,
|
||||
"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) {
|
||||
laddr, err := c.bind(conn, relay.CmdBind, network, address)
|
||||
if err != nil {
|
||||
@ -157,7 +73,7 @@ func (c *relayConnector) bindUDP(ctx context.Context, conn net.Conn, network, ad
|
||||
ReadQueueSize: opts.UDPDataQueueSize,
|
||||
ReadBufferSize: opts.UDPDataBufferSize,
|
||||
TTL: opts.UDPConnTTL,
|
||||
KeepAlive: true,
|
||||
Keepalive: true,
|
||||
Logger: log,
|
||||
})
|
||||
|
||||
@ -177,6 +93,14 @@ func (c *relayConnector) bind(conn net.Conn, cmd relay.CmdType, network, address
|
||||
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.ParseFrom(address)
|
||||
req.Features = append(req.Features, fa)
|
||||
|
@ -13,12 +13,14 @@ import (
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
"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
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
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) {
|
||||
n = len(b) // force byte length consistent
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
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())
|
||||
@ -50,6 +56,7 @@ type udpConn struct {
|
||||
net.Conn
|
||||
wbuf *bytes.Buffer
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
||||
var bb [2]byte
|
||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||
@ -119,7 +130,7 @@ func readResponse(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
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 nil
|
||||
@ -213,16 +224,3 @@ func (c *bindUDPConn) RemoteAddr() net.Addr {
|
||||
func (c *bindUDPConn) Metadata() mdata.Metadata {
|
||||
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)
|
||||
}
|
||||
|
||||
if !c.md.tunnelID.IsZero() {
|
||||
req.Features = append(req.Features, &relay.TunnelFeature{
|
||||
ID: c.md.tunnelID.ID(),
|
||||
})
|
||||
}
|
||||
|
||||
if c.md.noDelay {
|
||||
if _, err := req.WriteTo(conn); err != nil {
|
||||
return nil, err
|
||||
|
@ -5,15 +5,12 @@ import (
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/core/metadata/util"
|
||||
"github.com/go-gost/relay"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
noDelay bool
|
||||
tunnelID relay.TunnelID
|
||||
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.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{
|
||||
Version: mdutil.GetInt(md, "mux.version"),
|
||||
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/common/net/udp"
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
"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/socks"
|
||||
)
|
||||
@ -87,7 +87,7 @@ func (c *socks5Connector) bindUDP(ctx context.Context, conn net.Conn, network, a
|
||||
ReadQueueSize: opts.UDPDataQueueSize,
|
||||
ReadBufferSize: opts.UDPDataBufferSize,
|
||||
TTL: opts.UDPConnTTL,
|
||||
KeepAlive: true,
|
||||
Keepalive: true,
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if socksAddr.Host == "" {
|
||||
socksAddr.Type = gosocks5.AddrIPv4
|
||||
socksAddr.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
|
@ -130,6 +130,10 @@ func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, a
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if addr.Host == "" {
|
||||
addr.Type = gosocks5.AddrIPv4
|
||||
addr.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
|
||||
log.Trace(req)
|
||||
@ -201,16 +205,22 @@ func (c *socks5Connector) relayUDP(ctx context.Context, conn net.Conn, addr net.
|
||||
}
|
||||
log.Trace(reply)
|
||||
|
||||
log.Debugf("bind on: %v", reply.Addr)
|
||||
|
||||
if reply.Rep != gosocks5.Succeeded {
|
||||
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 {
|
||||
c.options.Logger.Error(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{
|
||||
udpConn: cc.(*net.UDPConn),
|
||||
|
@ -17,24 +17,19 @@ type metadata struct {
|
||||
noTLS bool
|
||||
relay string
|
||||
udpBufferSize int
|
||||
udpTimeout time.Duration
|
||||
muxCfg *mux.Config
|
||||
}
|
||||
|
||||
func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
connectTimeout = "timeout"
|
||||
noTLS = "notls"
|
||||
relay = "relay"
|
||||
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)
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, "timeout")
|
||||
c.md.noTLS = mdutil.GetBool(md, "notls")
|
||||
c.md.relay = mdutil.GetString(md, "relay")
|
||||
c.md.udpBufferSize = mdutil.GetInt(md, "udp.bufferSize", "udpBufferSize")
|
||||
if c.md.udpBufferSize <= 0 {
|
||||
c.md.udpBufferSize = defaultUDPBufferSize
|
||||
}
|
||||
c.md.udpTimeout = mdutil.GetDuration(md, "udp.timeout")
|
||||
|
||||
c.md.muxCfg = &mux.Config{
|
||||
Version: mdutil.GetInt(md, "mux.version"),
|
||||
|
@ -27,7 +27,8 @@ func (c *tunnelConnector) Bind(ctx context.Context, conn net.Conn, network, addr
|
||||
"endpoint": endpoint,
|
||||
"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)
|
||||
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, &relay.TunnelFeature{
|
||||
ID: c.md.tunnelID.ID(),
|
||||
ID: c.md.tunnelID,
|
||||
})
|
||||
if _, err = req.WriteTo(conn); err != nil {
|
||||
return
|
||||
@ -100,7 +101,7 @@ func (c *tunnelConnector) initTunnel(conn net.Conn, network, address string) (ad
|
||||
}
|
||||
case relay.FeatureTunnel:
|
||||
if feature, _ := f.(*relay.TunnelFeature); feature != nil {
|
||||
cid = relay.NewConnectorID(feature.ID[:])
|
||||
cid = feature.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1,24 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
"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 {
|
||||
net.Conn
|
||||
wbuf *bytes.Buffer
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
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
|
||||
_, err = io.ReadFull(c.Conn, bb[:])
|
||||
if err != nil {
|
||||
@ -88,14 +45,6 @@ func (c *udpConn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
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
|
||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||
@ -119,7 +68,7 @@ func readResponse(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
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 nil
|
||||
|
@ -1,7 +1,6 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -10,7 +9,7 @@ import (
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -74,7 +73,7 @@ func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, a
|
||||
}
|
||||
|
||||
srcAddr := conn.LocalAddr().String()
|
||||
if v := auth_util.ClientAddrFromContext(ctx); v != "" {
|
||||
if v := ctxvalue.ClientAddrFromContext(ctx); 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, &relay.TunnelFeature{
|
||||
ID: c.md.tunnelID.ID(),
|
||||
ID: c.md.tunnelID,
|
||||
})
|
||||
|
||||
if c.md.noDelay {
|
||||
if _, err := req.WriteTo(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// drain the response
|
||||
if err := readResponse(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := req.WriteTo(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// drain the response
|
||||
if err := readResponse(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch network {
|
||||
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":
|
||||
cc := &udpConn{
|
||||
conn = &udpConn{
|
||||
Conn: conn,
|
||||
}
|
||||
if !c.md.noDelay {
|
||||
cc.wbuf = &bytes.Buffer{}
|
||||
if _, err := req.WriteTo(cc.wbuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
conn = cc
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
|
@ -18,13 +18,11 @@ var (
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
tunnelID relay.TunnelID
|
||||
noDelay bool
|
||||
muxCfg *mux.Config
|
||||
}
|
||||
|
||||
func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout")
|
||||
c.md.noDelay = mdutil.GetBool(md, "nodelay")
|
||||
|
||||
if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); 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[:])
|
||||
}
|
||||
|
||||
if weight := mdutil.GetInt(md, "tunnel.weight"); weight > 0 {
|
||||
c.md.tunnelID = c.md.tunnelID.SetWeight(uint8(weight))
|
||||
}
|
||||
|
||||
c.md.muxCfg = &mux.Config{
|
||||
Version: mdutil.GetInt(md, "mux.version"),
|
||||
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)
|
||||
}
|
||||
|
||||
conn, err := options.NetDialer.Dial(ctx, "udp", addr)
|
||||
conn, err := options.Dialer.Dial(ctx, "udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -71,14 +71,13 @@ func (d *grpcDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
||||
grpcOpts := []grpc.DialOption{
|
||||
// grpc.WithBlock(),
|
||||
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.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.DefaultConfig,
|
||||
MinConnectTimeout: d.md.minConnectTimeout,
|
||||
}),
|
||||
grpc.FailOnNonTempDialError(true),
|
||||
}
|
||||
if !d.md.insecure {
|
||||
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 {
|
||||
d.options.Logger.Error(err)
|
||||
return nil, err
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
net_dialer "github.com/go-gost/core/common/net/dialer"
|
||||
"github.com/go-gost/core/dialer"
|
||||
"github.com/go-gost/core/logger"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
net_dialer "github.com/go-gost/x/internal/net/dialer"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
@ -70,28 +70,40 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D
|
||||
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{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: d.options.TLSConfig,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
netd := options.NetDialer
|
||||
netd := options.Dialer
|
||||
if netd == nil {
|
||||
netd = net_dialer.DefaultNetDialer
|
||||
}
|
||||
return netd.Dial(ctx, network, addr)
|
||||
},
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
MaxIdleConns: 16,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
TLSHandshakeTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 15 * time.Second,
|
||||
},
|
||||
}
|
||||
d.clients[address] = client
|
||||
}
|
||||
|
||||
var c net.Conn
|
||||
c = &conn{
|
||||
var c net.Conn = &conn{
|
||||
localAddr: &net.TCPAddr{},
|
||||
remoteAddr: raddr,
|
||||
onClose: func() {
|
||||
|
@ -94,14 +94,14 @@ func (d *h2Dialer) Dial(ctx context.Context, address string, opts ...dialer.Dial
|
||||
client.Transport = &http2.Transport{
|
||||
AllowHTTP: true,
|
||||
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 {
|
||||
client.Transport = &http.Transport{
|
||||
TLSClientConfig: d.options.TLSConfig,
|
||||
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,
|
||||
MaxIdleConns: 100,
|
||||
|
@ -79,14 +79,14 @@ func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.Dial
|
||||
return nil, err
|
||||
}
|
||||
|
||||
udpConn, err := options.NetDialer.Dial(ctx, "udp", "")
|
||||
udpConn, err := options.Dialer.Dial(ctx, "udp", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return quic.DialEarly(context.Background(), udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
|
||||
},
|
||||
QuicConfig: &quic.Config{
|
||||
QUICConfig: &quic.Config{
|
||||
KeepAlivePeriod: d.md.keepAlivePeriod,
|
||||
HandshakeIdleTimeout: d.md.handshakeTimeout,
|
||||
MaxIdleTimeout: d.md.maxIdleTimeout,
|
||||
|
@ -50,7 +50,7 @@ func (d *http3Dialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
}
|
||||
|
||||
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)
|
||||
if d.md.keepAlivePeriod <= 0 {
|
||||
d.md.keepAlivePeriod = 10 * time.Second
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/x/registry"
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
wt "github.com/quic-go/webtransport-go"
|
||||
)
|
||||
|
||||
@ -74,33 +73,32 @@ func (d *wtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOpt
|
||||
path: d.md.path,
|
||||
header: d.md.header,
|
||||
dialer: &wt.Dialer{
|
||||
RoundTripper: &http3.RoundTripper{
|
||||
TLSClientConfig: d.options.TLSConfig,
|
||||
Dial: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
// d.options.Logger.Infof("dial: %s, %s, %s", addr, adr, host)
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
TLSClientConfig: d.options.TLSConfig,
|
||||
DialAddr: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
// d.options.Logger.Infof("dial: %s, %s, %s", addr, adr, host)
|
||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
udpConn, err := options.NetDialer.Dial(ctx, "udp", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
udpConn, err := options.Dialer.Dial(ctx, "udp", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
|
||||
},
|
||||
QuicConfig: &quic.Config{
|
||||
KeepAlivePeriod: d.md.keepAlivePeriod,
|
||||
HandshakeIdleTimeout: d.md.handshakeTimeout,
|
||||
MaxIdleTimeout: d.md.maxIdleTimeout,
|
||||
/*
|
||||
Versions: []quic.VersionNumber{
|
||||
quic.Version1,
|
||||
},
|
||||
*/
|
||||
MaxIncomingStreams: int64(d.md.maxStreams),
|
||||
},
|
||||
return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
|
||||
},
|
||||
QUICConfig: &quic.Config{
|
||||
KeepAlivePeriod: d.md.keepAlivePeriod,
|
||||
HandshakeIdleTimeout: d.md.handshakeTimeout,
|
||||
MaxIdleTimeout: d.md.maxIdleTimeout,
|
||||
/*
|
||||
Versions: []quic.VersionNumber{
|
||||
quic.Version1,
|
||||
},
|
||||
*/
|
||||
MaxIncomingStreams: int64(d.md.maxStreams),
|
||||
EnableDatagrams: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func (d *wtDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
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)
|
||||
if d.md.keepAlivePeriod <= 0 {
|
||||
d.md.keepAlivePeriod = 10 * time.Second
|
||||
|
@ -19,9 +19,11 @@ import (
|
||||
|
||||
func init() {
|
||||
registry.DialerRegistry().Register("icmp", NewDialer)
|
||||
registry.DialerRegistry().Register("icmp6", NewDialer6)
|
||||
}
|
||||
|
||||
type icmpDialer struct {
|
||||
ip6 bool
|
||||
sessions map[string]*quicSession
|
||||
sessionMutex sync.Mutex
|
||||
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) {
|
||||
if err = d.parseMetadata(md); err != nil {
|
||||
return
|
||||
@ -71,7 +86,11 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
raddr.Port = id
|
||||
}
|
||||
pc = icmp_pkg.ClientConn(pc, id)
|
||||
pc = icmp_pkg.ClientConn(d.ip6, pc, id)
|
||||
|
||||
session, err = d.initSession(ctx, raddr, pc)
|
||||
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.NextProtos = []string{"http/3", "quic/v1"}
|
||||
tlsCfg.NextProtos = []string{"h3", "quic/v1"}
|
||||
|
||||
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
|
||||
if err != nil {
|
||||
|
@ -14,21 +14,14 @@ type metadata struct {
|
||||
}
|
||||
|
||||
func (d *icmpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
keepAlive = "keepAlive"
|
||||
keepAlivePeriod = "ttl"
|
||||
handshakeTimeout = "handshakeTimeout"
|
||||
maxIdleTimeout = "maxIdleTimeout"
|
||||
)
|
||||
|
||||
if mdutil.GetBool(md, keepAlive) {
|
||||
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
|
||||
if mdutil.GetBool(md, "keepalive") {
|
||||
d.md.keepAlivePeriod = mdutil.GetDuration(md, "ttl")
|
||||
if d.md.keepAlivePeriod <= 0 {
|
||||
d.md.keepAlivePeriod = 10 * time.Second
|
||||
}
|
||||
}
|
||||
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
|
||||
d.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
|
||||
d.md.handshakeTimeout = mdutil.GetDuration(md, "handshakeTimeout")
|
||||
d.md.maxIdleTimeout = mdutil.GetDuration(md, "maxIdleTimeout")
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ func (d *kcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
||||
PacketConn: pc,
|
||||
}
|
||||
} else {
|
||||
c, err := options.NetDialer.Dial(ctx, "udp", "")
|
||||
c, err := options.Dialer.Dial(ctx, "udp", "")
|
||||
if err != nil {
|
||||
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.MaxReceiveBuffer = config.SmuxBuf
|
||||
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
|
||||
if !config.NoComp {
|
||||
cc = kcp_util.CompStreamConn(kcpconn)
|
||||
|
@ -21,14 +21,14 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -42,6 +42,19 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
if d.md.config == nil {
|
||||
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)
|
||||
return
|
||||
|
@ -67,7 +67,7 @@ func (d *mtcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ package mtls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
@ -68,7 +68,7 @@ func (d *mtlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
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) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package mws
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/go-gost/x/util"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
@ -82,7 +84,7 @@ func (d *mwsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -158,6 +160,33 @@ func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn,
|
||||
if d.tlsEnabled {
|
||||
url.Scheme = "wss"
|
||||
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 {
|
||||
|
@ -27,6 +27,9 @@ type metadata struct {
|
||||
header http.Header
|
||||
keepaliveInterval time.Duration
|
||||
muxCfg *mux.Config
|
||||
|
||||
//Evan Enhanced
|
||||
useH2 bool
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
type obfsHTTPConn struct {
|
||||
net.Conn
|
||||
host string
|
||||
path string
|
||||
rbuf bytes.Buffer
|
||||
wbuf bytes.Buffer
|
||||
headerDrained bool
|
||||
|
@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/dialer"
|
||||
@ -12,11 +13,13 @@ import (
|
||||
|
||||
func init() {
|
||||
registry.DialerRegistry().Register("ohttp", NewDialer)
|
||||
registry.DialerRegistry().Register("ohttps", NewDialer)
|
||||
}
|
||||
|
||||
type obfsHTTPDialer struct {
|
||||
md metadata
|
||||
logger logger.Logger
|
||||
tlsEnabled bool
|
||||
md metadata
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
||||
@ -30,6 +33,18 @@ func NewDialer(opts ...dialer.Option) dialer.Dialer {
|
||||
}
|
||||
}
|
||||
|
||||
func NewTLSDialer(opts ...dialer.Option) dialer.Dialer {
|
||||
options := &dialer.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
return &obfsHTTPDialer{
|
||||
tlsEnabled: true,
|
||||
logger: options.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *obfsHTTPDialer) Init(md md.Metadata) (err error) {
|
||||
return d.parseMetadata(md)
|
||||
}
|
||||
@ -40,7 +55,7 @@ func (d *obfsHTTPDialer) Dial(ctx context.Context, addr string, opts ...dialer.D
|
||||
opt(options)
|
||||
}
|
||||
|
||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
d.logger.Error(err)
|
||||
}
|
||||
@ -59,9 +74,16 @@ func (d *obfsHTTPDialer) Handshake(ctx context.Context, conn net.Conn, options .
|
||||
host = opts.Addr
|
||||
}
|
||||
|
||||
if d.tlsEnabled {
|
||||
conn = tls.Client(conn, &tls.Config{
|
||||
ServerName: host,
|
||||
})
|
||||
}
|
||||
|
||||
return &obfsHTTPConn{
|
||||
Conn: conn,
|
||||
host: host,
|
||||
path: d.md.path,
|
||||
header: d.md.header,
|
||||
logger: d.logger,
|
||||
}, nil
|
||||
|
@ -7,24 +7,29 @@ import (
|
||||
mdutil "github.com/go-gost/core/metadata/util"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPath = "/"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
host string
|
||||
path string
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (d *obfsHTTPDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
header = "header"
|
||||
host = "host"
|
||||
)
|
||||
d.md.host = mdutil.GetString(md, "obfs.host", "host")
|
||||
d.md.path = mdutil.GetString(md, "obfs.path", "path")
|
||||
if d.md.path == "" {
|
||||
d.md.path = defaultPath
|
||||
}
|
||||
|
||||
if m := mdutil.GetStringMapString(md, header); len(m) > 0 {
|
||||
if m := mdutil.GetStringMapString(md, "obfs.header", "header"); len(m) > 0 {
|
||||
h := http.Header{}
|
||||
for k, v := range m {
|
||||
h.Add(k, v)
|
||||
}
|
||||
d.md.header = h
|
||||
}
|
||||
d.md.host = mdutil.GetString(md, host)
|
||||
return
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func (d *obfsTLSDialer) Dial(ctx context.Context, addr string, opts ...dialer.Di
|
||||
opt(options)
|
||||
}
|
||||
|
||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
d.logger.Error(err)
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func (d *phtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
||||
tr := &http.Transport{
|
||||
// Proxy: http.ProxyFromEnvironment,
|
||||
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,
|
||||
MaxIdleConns: 100,
|
||||
|
@ -67,7 +67,7 @@ func (d *quicDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
||||
opt(options)
|
||||
}
|
||||
|
||||
c, err := options.NetDialer.Dial(ctx, "udp", "")
|
||||
c, err := options.Dialer.Dial(ctx, "udp", "")
|
||||
if err != nil {
|
||||
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.NextProtos = []string{"http/3", "quic/v1"}
|
||||
tlsCfg.NextProtos = []string{"h3", "quic/v1"}
|
||||
|
||||
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
|
||||
if err != nil {
|
||||
|
@ -31,7 +31,7 @@ func (d *quicDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
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)
|
||||
if d.md.keepAlivePeriod <= 0 {
|
||||
d.md.keepAlivePeriod = 10 * time.Second
|
||||
|
@ -64,7 +64,7 @@ func (d *sshDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/core/metadata/util"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zalando/go-keyring"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@ -20,21 +23,35 @@ type metadata struct {
|
||||
|
||||
func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
handshakeTimeout = "handshakeTimeout"
|
||||
privateKeyFile = "privateKeyFile"
|
||||
passphrase = "passphrase"
|
||||
handshakeTimeout = "handshakeTimeout"
|
||||
privateKeyFile = "privateKeyFile"
|
||||
passphrase = "passphrase"
|
||||
passphraseFromKeyring = "passphraseFromKeyring"
|
||||
)
|
||||
|
||||
if key := mdutil.GetString(md, privateKeyFile); key != "" {
|
||||
key, err = homedir.Expand(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := os.ReadFile(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pp := mdutil.GetString(md, passphrase); pp != "" {
|
||||
d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp))
|
||||
var pp string
|
||||
if mdutil.GetBool(md, passphraseFromKeyring) {
|
||||
pp, err = keyring.Get(fmt.Sprintf("SSH %s", key), d.options.Auth.Username())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get secret(%s) from keyring: %w", key, err)
|
||||
}
|
||||
} else {
|
||||
pp = mdutil.GetString(md, passphrase)
|
||||
}
|
||||
if pp == "" {
|
||||
d.md.signer, err = ssh.ParsePrivateKey(data)
|
||||
} else {
|
||||
d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -64,7 +64,7 @@ func (d *sshdDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err = options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package sshd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/core/metadata/util"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zalando/go-keyring"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@ -26,12 +29,24 @@ func (d *sshdDialer) parseMetadata(md mdata.Metadata) (err error) {
|
||||
)
|
||||
|
||||
if key := mdutil.GetString(md, privateKeyFile); key != "" {
|
||||
key, err = homedir.Expand(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := os.ReadFile(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pp := mdutil.GetString(md, passphrase)
|
||||
var pp string
|
||||
if mdutil.GetBool(md, "passphraseFromKeyring") {
|
||||
pp, err = keyring.Get(fmt.Sprintf("SSH %s", key), key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get secret(%s) from keyring: %w", key, err)
|
||||
}
|
||||
} else {
|
||||
pp = mdutil.GetString(md, passphrase)
|
||||
}
|
||||
if pp == "" {
|
||||
d.md.signer, err = ssh.ParsePrivateKey(data)
|
||||
} else {
|
||||
|
@ -40,7 +40,7 @@ func (d *tcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
d.logger.Error(err)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
@ -44,7 +44,7 @@ func (d *tlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
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))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(conn, d.options.TLSConfig)
|
||||
tlsConfig := 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 {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
|
@ -40,7 +40,7 @@ func (d *udpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOp
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
c, err := options.NetDialer.Dial(ctx, "udp", addr)
|
||||
c, err := options.Dialer.Dial(ctx, "udp", addr)
|
||||
if err != nil {
|
||||
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 (
|
||||
"context"
|
||||
"github.com/go-gost/x/util"
|
||||
tls "github.com/refraction-networking/utls"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
@ -57,7 +59,7 @@ func (d *wsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOpt
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
|
||||
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
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 {
|
||||
url.Scheme = "wss"
|
||||
urlObj.Scheme = "wss"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
c, resp, err := dialer.DialContext(ctx, url.String(), d.md.header)
|
||||
urlStr, errUnescape := url.QueryUnescape(urlObj.String())
|
||||
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 {
|
||||
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