Compare commits

..

110 Commits
master ... dev

Author SHA1 Message Date
wenyifan
490e6b40f5 Merge branch 'refs/heads/master' into dev 2024-08-07 09:40:23 +08:00
ginuerzh
bc0d6953bc fix timeout in router 2024-08-06 21:11:27 +08:00
ginuerzh
22e522e933 fix udp connection timeout 2024-08-06 18:33:29 +08:00
ginuerzh
5e8a8a4b4d compatible with HTTP/1.0 2024-08-06 18:31:29 +08:00
wenyifan
fa16373d66 Merge branch 'refs/heads/master' into dev 2024-08-02 09:32:12 +08:00
ginuerzh
1a776dc759 fix connection state in tunnel entrypoint 2024-08-01 20:52:08 +08:00
ginuerzh
12ef82e41f fix ssu handler port exhaustion 2024-07-31 20:55:24 +08:00
ginuerzh
3656ba9315 add http body rewrite for forward handler 2024-07-19 20:45:04 +08:00
wenyifan
f73960ad36 Merge branch 'refs/heads/master' into dev 2024-07-17 15:17:00 +08:00
ginuerzh
c0a80400d2 add support for icmpv6 2024-07-15 20:34:59 +08:00
ginuerzh
2a75be91b0 fix ipv6 for udp tproxy 2024-07-11 22:27:02 +08:00
ginuerzh
4a4c64cc66 fix host parsing 2024-07-10 22:57:49 +08:00
ginuerzh
f2e32080e4 fix cutHost with auth info 2024-07-10 22:16:47 +08:00
ginuerzh
59b99a5b44 v0.1.0 2024-07-08 22:44:29 +08:00
ginuerzh
c1d0887a9b add port range support for service 2024-07-08 22:38:21 +08:00
ginuerzh
96f4d7bf5c netns: fix network namespaces for listeners 2024-07-08 10:59:32 +08:00
ginuerzh
949c98adc0 netns: add support for specifying network namespace by path 2024-07-08 10:59:03 +08:00
dependabot[bot]
13c9e3ba97 Bump github.com/gin-contrib/cors from 1.5.0 to 1.6.0
Bumps [github.com/gin-contrib/cors](https://github.com/gin-contrib/cors) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/gin-contrib/cors/releases)
- [Changelog](https://github.com/gin-contrib/cors/blob/master/.goreleaser.yaml)
- [Commits](https://github.com/gin-contrib/cors/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/gin-contrib/cors
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-08 00:02:17 +08:00
wenyifan
3c1985e980 Merge branch 'refs/heads/master' into dev 2024-07-05 13:45:23 +08:00
ginuerzh
22537ff0d2 mv observer/stats to core 2024-07-04 23:05:35 +08:00
ginuerzh
b583e29a56 Bump github.com/go-gost/gosocks5 from 0.3.1 to 0.4.1 2024-07-04 09:34:35 +08:00
Wankko Ree
ba2a83a51d fix QUIC error "CRYPTO_ERROR 0x178 (remote): tls: no application protocol" 2024-07-03 22:06:17 +08:00
ginuerzh
74dc03bd66 fix http proxy response 2024-06-25 22:09:00 +08:00
ginuerzh
b99292bed8 add dialTimeout option for service 2024-06-25 20:40:38 +08:00
ginuerzh
f9bfca76ed fix netns for socks5 and relay handler 2024-06-24 21:18:04 +08:00
ginuerzh
2ae0462822 add support for linux network namespace 2024-06-21 23:38:18 +08:00
wenyifan
423dd1e35d Merge branch 'master' into dev
# Conflicts:
#	go.mod
#	go.sum
2024-06-19 10:10:55 +08:00
ginuerzh
15f28c667a fix wt dialer 2024-06-13 22:24:50 +08:00
ginuerzh
9bae597cbb Bump deps 2024-06-13 21:53:14 +08:00
ginuerzh
6d819a0c06 add observePeriod option for observer 2024-06-13 21:22:29 +08:00
ginuerzh
784e4b2b01 http: fix non-connect method request handler 2024-06-11 21:50:11 +08:00
ginuerzh
e793b2743b api: fix object name 2024-06-11 21:32:48 +08:00
ginuerzh
ce60160cd7 fix marker for selector 2024-06-07 20:57:47 +08:00
ginuerzh
118ee91c95 fix error response for web APIs 2024-06-04 22:05:13 +08:00
ginuerzh
754b2fdeac fix redis loader for hop 2024-05-08 21:26:15 +08:00
ginuerzh
40f709880d add node filter config 2024-05-08 21:25:14 +08:00
ginuerzh
332a3a1cd0 add p2p option for tun handler 2024-04-26 20:56:56 +08:00
ginuerzh
f2a5089c29 update api doc 2024-04-25 21:45:11 +08:00
ginuerzh
55058573d6 add sd and observer web APIs 2024-04-25 21:31:26 +08:00
ginuerzh
41b5e62207 added ohttps dialer 2024-04-25 14:59:01 +08:00
ginuerzh
766ce7fdaa merger origin/master 2024-04-25 09:44:49 +08:00
ginuerzh
254875cd30 update go.mod 2024-04-24 23:38:50 +08:00
dependabot[bot]
66104cd079 Bump golang.org/x/net from 0.19.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.19.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.19.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 23:20:51 +08:00
ginuerzh
871afeeb6d
Merge pull request #27 from cgroschupp/feature/whitelist-change
feat: add whitelist support
2024-04-24 23:13:29 +08:00
ginuerzh
c3b133a2de
Merge pull request #26 from cgroschupp/feature/darwin_redirect
feat: add redirect darwin support
2024-04-24 23:09:07 +08:00
ginuerzh
68f9690494
Merge pull request #25 from cgroschupp/feature/homedir-expand-ssh
feat: expand homedir of ssh private key
2024-04-24 23:07:50 +08:00
ginuerzh
c35a79b2c9
Merge branch 'master' into feature/homedir-expand-ssh 2024-04-24 23:07:38 +08:00
ginuerzh
a2ab48c423
Merge pull request #24 from cgroschupp/feature/keyring-ssh-passphrase
feat: allow to read ssh passphrase from keyring
2024-04-24 22:46:00 +08:00
ginuerzh
902e24e7e8
Merge branch 'master' into feature/keyring-ssh-passphrase 2024-04-24 22:45:29 +08:00
dependabot[bot]
d26bf4f05c Bump github.com/quic-go/quic-go from 0.40.1 to 0.42.0
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.40.1 to 0.42.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.40.1...v0.42.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 22:35:54 +08:00
dependabot[bot]
5ea88aa5cf Bump google.golang.org/protobuf from 1.31.0 to 1.33.0
Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 22:26:00 +08:00
Christian Groschupp
77a8f28edc feat: add whitelist support 2024-04-16 21:06:14 +02:00
Christian Groschupp
7bf0537243 feat: add redirect darwin support 2024-04-16 20:56:21 +02:00
Christian Groschupp
6ba22b0935 feat: expand homedir of ssh private key 2024-04-16 16:43:47 +02:00
Christian Groschupp
7da8b2a710 feat: allow to read ssh passphrase from keyring 2024-04-16 16:42:50 +02:00
ginuerzh
d9b7585856 add more options for kcp dialer/listener 2024-03-09 20:58:22 +08:00
wenyifan
63ad7f2354 Merge branch 'master' into dev 2024-02-02 14:08:43 +08:00
ginuerzh
25dcf536c6 added url path rewrite for forwarder node 2024-01-31 23:18:42 +08:00
ginuerzh
3d2a7b7d3b move x/internal/ctx to x/ctx 2024-01-30 18:19:39 +08:00
wenyifan
5cc2c3de82 Merge branch 'master' into dev 2024-01-29 09:21:54 +08:00
ginuerzh
5ee7746aab fix tunnel connector selection 2024-01-28 18:56:19 +08:00
ginuerzh
3616a0d8a4 fix tunnel weight 2024-01-28 18:33:54 +08:00
ginuerzh
b5b39de62c add weight for tunnel connector 2024-01-27 23:31:23 +08:00
ginuerzh
43d37d0a5f fix hop http plugin 2024-01-27 21:29:45 +08:00
ginuerzh
8bdd7ee172 added auther option for node http settings 2024-01-27 21:29:45 +08:00
ginuerzh
a618998b36
Merge pull request #20 from go-gost/dependabot/go_modules/github.com/quic-go/quic-go-0.40.1
Bump github.com/quic-go/quic-go from 0.40.0 to 0.40.1
2024-01-18 17:42:34 +08:00
wenyifan
bb1a9908d4 Merge branch 'master' into dev 2024-01-18 09:24:27 +08:00
ginuerzh
01168e9846 fix deadlock in websocket client conn 2024-01-12 23:46:22 +08:00
dependabot[bot]
53aab11764
Bump github.com/quic-go/quic-go from 0.40.0 to 0.40.1
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.40.0 to 0.40.1.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Changelog](https://github.com/quic-go/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.40.0...v0.40.1)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 15:09:36 +00:00
ginuerzh
c04c28e1fd add protocol based node selection for hop 2024-01-08 21:22:20 +08:00
ginuerzh
936954ecf2 fix dns handler panic 2024-01-08 21:20:56 +08:00
ginuerzh
262ac0e9a5 add header for basic auth 2024-01-07 19:40:42 +08:00
ginuerzh
c959fc2f73 add observer 2024-01-03 20:55:06 +08:00
wenyifan
4e1a70ec6d Merge branch 'master' into dev 2023-12-28 17:33:48 +08:00
ginuerzh
e1ae379048 fix tls handshake sniffing 2023-12-27 19:24:31 +08:00
ginuerzh
1117723913 fix kcp option 2023-12-25 22:08:41 +08:00
wenyifan
590a6ae6ff Merge branch 'master' into dev 2023-12-21 17:47:19 +08:00
ginuerzh
9fa95cc8b3 bump github.com/spf13/viper 2023-12-20 21:57:29 +08:00
ginuerzh
40e9a8ce7b fix panic 2023-12-20 19:24:02 +08:00
ginuerzh
4a1b225d2c add logger group for service 2023-12-19 21:28:19 +08:00
ginuerzh
c4b95b180e
Merge pull request #19 from go-gost/dependabot/go_modules/golang.org/x/crypto-0.17.0
Bump golang.org/x/crypto from 0.16.0 to 0.17.0
2023-12-19 21:15:42 +08:00
dependabot[bot]
cc07ccb276
Bump golang.org/x/crypto from 0.16.0 to 0.17.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 00:02:48 +00:00
ginuerzh
f847fa533e fix auth for file handler 2023-12-16 14:28:58 +08:00
ginuerzh
b1390dda1c router: update system routes for linux 2023-11-30 19:39:37 +08:00
ginuerzh
8ef341dc88
Merge pull request #16 from gost-dev/bump-version
Bump deps
2023-11-30 16:19:22 +08:00
Charles Xu
94f8afdf45 Merge remote-tracking branch 'upstream/master' into bump-version 2023-11-30 00:31:50 +00:00
汪航洋
6ea815eb36 api allows private networking 2023-11-29 18:52:44 +08:00
Charles Xu
ee80eedac3 clean deps 2023-11-23 03:39:24 +00:00
Charles Xu
7cc1ef436f bump versions 2023-11-23 03:39:06 +00:00
wenyifan
116c7b51fe Merge branch 'master' into dev 2023-11-21 10:20:05 +08:00
ginuerzh
9be710dc19 fix tun config 2023-11-20 20:42:29 +08:00
ginuerzh
e8be8d625a update go.mod 2023-11-19 20:00:14 +08:00
ginuerzh
c87faa2017 add logger component 2023-11-19 19:55:48 +08:00
ginuerzh
79c15f2c37 update swagger.yaml 2023-11-19 16:59:22 +08:00
ginuerzh
44064e4dd1 add api config for router 2023-11-19 16:16:12 +08:00
ginuerzh
c95edd6ed3 fix crash for tun 2023-11-19 14:39:24 +08:00
ginuerzh
74639e9c4e add router component 2023-11-19 14:23:21 +08:00
ginuerzh
88cc6ff4d5 add traffic limiter for proxy handler 2023-11-18 18:28:09 +08:00
ginuerzh
330631fd79 add path option for saveConfig API 2023-11-17 22:24:50 +08:00
wenyifan
42a4ccb24c Merge branch 'master' into dev 2023-11-17 13:25:11 +08:00
ginuerzh
2c82233b4f add more config options for kcp 2023-11-16 21:42:11 +08:00
ginuerzh
a465210bd6 fix forwarding node address parsing 2023-11-16 20:38:16 +08:00
ginuerzh
f5a20fd0fc remove nodelay option for tunnel 2023-11-16 20:36:17 +08:00
wenyifan
34b9e3b16e utls 2023-11-15 17:49:38 +08:00
wenyifan
3038eb66d8 utls 2023-11-15 15:45:09 +08:00
wenyifan
52aa2027d0 Merge branch 'master' into dev 2023-11-15 10:35:36 +08:00
ginuerzh
9584bdbf4c rm tunnel from relay 2023-11-14 22:35:46 +08:00
ginuerzh
d7b7ac6357 add range port support for forwarder node 2023-11-14 19:41:57 +08:00
wenyifan
6108000cce utls 2023-11-14 00:32:42 +08:00
ginuerzh
ca1f44d93c update ingress interface 2023-11-13 20:40:42 +08:00
275 changed files with 10215 additions and 4054 deletions

67
admission/plugin/grpc.go Normal file
View 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
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
View 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",
})
}

View File

@ -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
View 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",
})
}

View File

@ -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
View 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
View 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",
})
}

View File

@ -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)

View File

@ -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)

View File

@ -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()
}
}
}

View File

@ -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)
}

View File

@ -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:

View File

@ -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
View 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
}

View File

@ -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 {

View File

@ -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
View 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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
View 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
View 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
View 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,
}
}

View File

@ -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"`

View File

@ -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),

View File

@ -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),

View File

@ -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),

View File

@ -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 {

View File

@ -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,
})),

View File

@ -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),

View File

@ -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,
})

View File

@ -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 != "" {

View 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
}

View File

@ -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
}

View 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),
)
}
}

View File

@ -10,6 +10,7 @@ const (
MDKeyPostUp = "postUp"
MDKeyPostDown = "postDown"
MDKeyIgnoreChain = "ignoreChain"
MDKeyEnableStats = "enableStats"
MDKeyRecorderDirection = "direction"
MDKeyRecorderTimestampFormat = "timeStampFormat"

View File

@ -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),

View File

@ -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),

View 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...)
}

View File

@ -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),

View File

@ -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 {

View File

@ -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,
})

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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"),

View File

@ -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,
})

View File

@ -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,

View File

@ -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),

View File

@ -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"),

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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
View 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
}

View File

@ -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
}

View File

@ -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

View File

@ -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() {

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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,
},
},
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -18,6 +18,7 @@ import (
type obfsHTTPConn struct {
net.Conn
host string
path string
rbuf bytes.Buffer
wbuf bytes.Buffer
headerDrained bool

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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

View File

@ -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
View 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
View 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
}

View File

@ -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