Compare commits

...
132 Commits
Author SHA1 Message Date
dependabot[bot] 07ca57055a Bump google.golang.org/grpc from 1.67.1 to 1.79.3
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.67.1 to 1.79.3.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.67.1...v1.79.3)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.79.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 21:37:46 +08:00
ginuerzh 2acc669976 fix webtransport listener 2026-04-21 21:34:50 +08:00
misakydrip 6c32dda058 fix a udp bug
fix udp bug error: use of closed network connection
2026-04-21 21:07:00 +08:00
RMT 2a2643a6b7 WIP 2026-04-21 21:05:43 +08:00
Evsyukov Denis 802943bd28 fix(ss): propagate cipher errors and decode SIP002 base64 auth
- Return err instead of nil in SS TCP/UDP connector Init when
  NewClientConfig fails, preventing nil pointer dereference in WrapConn
- Initialize tcpClient in UDP connector for UDP-over-TCP path
- Add decodeSIP002Auth to handle ss://BASE64(method:password)@host:port
  URL format with RawURLEncoding and StdEncoding fallback
- Update buildServiceConfig and buildNodeConfig to decode SIP002 auth
  for ss* schemes before falling back to standard userinfo parsing
- Support RawURLEncoding in parseAuthFromCmd with StdEncoding fallback
- Add tests for decodeSIP002Auth, parseAuthFromCmd, and integration
  tests for buildNodeConfig/buildServiceConfig
2026-04-21 20:55:45 +08:00
pak.wzq f373f2f4b9 fix 2026-04-21 20:53:10 +08:00
Vasileios Papadimas 96f35bd7aa feat: add custom header support to PHT protocol
- Add Header field to PHT Client and clientConn structs
- Support custom headers in authorize, push, and pull requests
- Add metadata parsing for header configuration as map[string]string
- Enables PHT usage with header-based authentication (e.g., Cloudflare Access)

Backwards compatible - headers are optional.
2026-04-21 20:51:17 +08:00
Jason Lyu cd7bf9521f fix sshd connection type 2026-04-21 20:49:28 +08:00
21paradox 180145189f add SetReadDeadline in Pipe, to prevent memory not auto release in mws/mtcp 2026-04-21 20:02:32 +08:00
David Manouchehri 7625973ca1 Add MASQUE/CONNECT-UDP support (RFC 9298)
Implement UDP tunneling over HTTP/3 using HTTP Datagrams (RFC 9297):
- Add masque handler for server-side CONNECT-UDP
- Add masque connector and h3-masque dialer for client-side
- Add enableDatagrams option to HTTP/3 listener
- Add shared utilities for datagram connections and path parsing

Resource management and connection caching:
- Add deferred stream cleanup in connector on error paths
- Add IsClosed() and Close() methods to Client for proper session management
- Clean up stale cached clients in dialer before reuse
- Close underlying stream when DatagramConn is closed
- Move RequestStream opening from connector to dialer to enable dead
  connection detection and cache invalidation (follows QUIC dialer pattern)
2026-04-21 19:53:07 +08:00
Rehtt b3b5986b63 fix command line resolution path-based protocols (such as unix socket) address resolution problem 2026-04-21 19:47:38 +08:00
dependabot[bot] 254699e176 Bump github.com/quic-go/quic-go from 0.54.1 to 0.57.0
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.54.1 to 0.57.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.54.1...v0.57.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 19:42:58 +08:00
RMT 6389378610 Remove usage of ShadowConn for ssHandler and ssConnector (#68)
The new ss library has integrated the function of ShadowConn.
2025-12-29 16:22:42 +08:00
dependabot[bot] 77eda4ce00 Bump golang.org/x/crypto from 0.40.0 to 0.45.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.40.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.40.0...v0.45.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-22 22:33:55 +08:00
RMT a280193dac Bump github.com/go-gost/go-shadowsocks2 from 0.1.0 to 0.1.1 2025-11-22 22:33:24 +08:00
RMT 0c97db3c05 Adapt new shadowsocks library.
1. Modify ss/ssu handler and connector
2. Add users to metadata of ss and ssu handler
2025-11-11 12:37:33 +08:00
Simon Hang d2cc07d3ac Pass username to new redis client 2025-10-30 13:26:11 +08:00
dependabot[bot] a0c46cec6e Bump github.com/quic-go/quic-go from 0.53.0 to 0.54.1
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.53.0 to 0.54.1.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.53.0...v0.54.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-11 22:02:16 +08:00
eWloYW8 eae322c7ba feat: support custom ComponentID for Windows TAP device configuration 2025-10-11 22:01:24 +08:00
ginuerzh 1c1f092a14 fix metrics wrapper 2025-10-11 21:54:16 +08:00
ginuerzh a69a759e17 tunnel: add multiple entrypoints 2025-10-09 22:32:03 +08:00
ginuerzh 007ff0bae9 fix panic for channel close 2025-09-07 14:20:05 +08:00
ginuerzh e2072399dd fix sniffing for websocket 2025-09-04 21:30:49 +08:00
ginuerzh c7d16962ec add service option for plugin 2025-08-29 23:36:31 +08:00
Denis Galeev a12ed27dfa fix handler/socks5: UDP IP address filtering 2025-08-28 16:54:03 +08:00
Denis Galeev bf70f07419 fix recorder: writes order 2025-08-25 10:03:26 +08:00
ginuerzh a65b7a059a try to fix http2 panic (#54) 2025-08-21 22:12:43 +08:00
Denis Galeev d5683e9138 prevent panic 2025-08-21 21:21:23 +08:00
ginuerzh 52289bea6c add network param for sniffing 2025-08-13 21:25:35 +08:00
ginuerzh d0ff5358df fix panic for nil pointer 2025-08-13 21:24:05 +08:00
ginuerzh 0e5c285eb7 fix empty hop nodes 2025-08-10 18:36:43 +08:00
ginuerzh a5f66b0615 node: add bypass and admission matcher 2025-08-10 17:59:17 +08:00
ginuerzh f3d3d2231e fix tun route for ipv6 2025-08-09 19:19:04 +08:00
ginuerzh 4eb006f1b0 tungo: debug exec.Command output 2025-08-09 16:34:04 +08:00
ginuerzh 2b9f795972 logger: fix panic when ouput is none 2025-08-09 14:03:26 +08:00
ginuerzh 589c185053 forwarder: connect to sni host for empty node 2025-08-09 12:43:09 +08:00
ginuerzh 404aaae5f1 without cancel the context 2025-08-05 20:24:00 +08:00
ginuerzh fd9dc6408a fix ws listener 2025-08-05 00:13:48 +08:00
ginuerzh b597467858 add context for conn 2025-08-04 19:29:38 +08:00
ginuerzh ad5cf6fd61 add proxyProtocol support for dialer 2025-08-03 15:36:29 +08:00
ginuerzh f71351f5ef add interface xnet.SrcAddr/DstAddr 2025-08-03 10:15:53 +08:00
ginuerzh 79203e407c add scoped options for cmd 2025-08-02 12:07:18 +08:00
ginuerzh db21de831a fix xnet.Pipe 2025-08-01 23:00:50 +08:00
ginuerzh a5309eee97 add buffer size option for udp connection 2025-07-30 21:40:53 +08:00
ginuerzh b64e5901b6 http: fix response for connect request 2025-07-30 21:40:31 +08:00
ginuerzh 87e454a6f1 tungo: additional metadata options 2025-07-30 10:07:18 +08:00
ginuerzh 3bbc10796c http2: added non-connect request support 2025-07-30 10:06:52 +08:00
ginuerzh 208d18125c replace xnet.Transport by xnet.Pipe 2025-07-28 20:52:15 +08:00
ginuerzh 35a049fb03 rename vtun to tungo 2025-07-26 16:17:04 +08:00
ginuerzh 281295d02f fix tun device 2025-07-23 22:44:02 +08:00
ginuerzh acae52ab89 add vtun listener and handler 2025-07-23 21:30:21 +08:00
ginuerzh afcf4a5252 parse real client IP 2025-07-15 19:59:10 +08:00
ginuerzh d9a2f44a78 add src field for logger and recorder 2025-07-02 21:35:02 +08:00
ginuerzh 97ed5080a6 service: reset tempDelay when retrying after accept error #52 2025-06-28 18:11:13 +08:00
ginuerzh c8d905dea5 read config from stdin 2025-06-28 18:09:04 +08:00
ginuerzh fe096d6e74 add clientIP for ws logger #51 2025-06-28 18:02:21 +08:00
ginuerzh 3ec2f9d487 update quic 2025-06-25 21:06:23 +08:00
ginuerzh ce3c28baf5 rm deprecated library shadowsocks-go/shadowsocks 2025-06-25 21:05:39 +08:00
ginuerzh 11d982e062 add new APIs 2025-06-25 21:02:55 +08:00
ginuerzh 6caad5e37e add file option for AuthConfig 2025-06-24 21:16:17 +08:00
ginuerzh d819fd3041 support http-100 continue for sniffer 2025-06-24 21:15:38 +08:00
ginuerzh 8cacfad9eb fix metrics for listener 2025-06-24 21:13:30 +08:00
dependabot[bot]andginuerzh a71b9d79fd Bump golang.org/x/net from 0.33.0 to 0.38.0 (#50)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.38.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: ginuerzh <ginuerzh@users.noreply.github.com>
2025-06-24 11:21:19 +08:00
dependabot[bot] 9a380d3fd8 Bump golang.org/x/crypto from 0.31.0 to 0.35.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.31.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.31.0...v0.35.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-24 11:01:46 +08:00
Denis Galeev 4c99e11d81 fix parse 2025-06-24 11:01:26 +08:00
Denis Galeev 1eaf08c9c1 fix recorders 2025-06-24 11:01:26 +08:00
Denis Galeev 0e59ccc64b add new metric 2025-06-24 11:01:26 +08:00
ginuerzh 04d1587a77 set dns for tun 2025-02-25 20:39:33 +08:00
ginuerzh 69a0aa8853 use fixed buffer size 2025-02-18 21:04:04 +08:00
ginuerzh e0915affda use fixed buffer size 2025-02-18 17:38:27 +08:00
ginuerzh d610e2dc01 add traffic limiter for relay bind 2025-02-18 17:37:13 +08:00
ginuerzh 2495b13053 fix traffic logging for socks5 udp 2025-02-18 17:30:36 +08:00
wzxjohn 11bca5bc3d fix(http): set Proxy-Agent header after authenticate to avoid leak #42 2025-02-18 15:30:56 +08:00
Denis Galeev e4b1c3c2b5 feat recorder: add rotate options 2025-02-18 15:30:42 +08:00
ginuerzh 75a106ee84 fix tun device 2025-02-11 23:12:46 +08:00
ginuerzh 2132268780 Bump golang.zx2c4.com/wireguard 2025-02-11 22:13:57 +08:00
ginuerzh ef440e8b4a router: router.id is optional 2025-02-11 20:54:52 +08:00
ginuerzh fc12e33786 router handler: add cache for sd service 2025-02-09 16:16:44 +08:00
ginuerzh 08ad260606 add entrypoint for router handler 2025-02-08 23:07:54 +08:00
ginuerzh 9cd03b6b59 update go.mod 2025-02-07 15:45:14 +08:00
ginuerzh 355c72477a add router handler & connector 2025-02-07 15:44:31 +08:00
ginuerzh 379d9a73ad service: add limiter.scope option 2025-01-20 23:22:58 +08:00
ginuerzh bf36bdc680 update go.mod 2025-01-10 21:48:05 +08:00
ginuerzh aa7528fc1e add reload for web api 2025-01-10 21:39:21 +08:00
ginuerzh e480039691 update go.mod 2025-01-08 23:12:56 +08:00
ginuerzh 489a762811 service graceful shutdown 2025-01-08 23:12:06 +08:00
ginuerzh eef3bd7f25 fix panic for stats wrapper 2024-12-27 20:42:20 +08:00
ginuerzh 06d993023f add traffic reset for stats 2024-12-24 20:11:22 +08:00
TimWhite 914a4622fd Update dialer.go 2024-12-24 17:22:13 +08:00
ginuerzh a5d4774682 add ip range matcher for bypass 2024-12-23 21:06:55 +08:00
ginuerzh be8e0555e5 handle tls for tunnel entrypoint 2024-12-23 21:05:55 +08:00
ginuerzh 2693ba61c4 forwarder: fix ip matcher for tls 2024-12-18 19:05:19 +08:00
ginuerzh fcb54bd576 fix observer for http2 2024-12-16 20:48:11 +08:00
dependabot[bot] 5206d1bd58 Bump golang.org/x/crypto from 0.30.0 to 0.31.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 19:09:33 +08:00
ginuerzh 98f5a5f7eb fix webtransport 2024-12-11 22:25:51 +08:00
zhangchunyun ff6807815f consider byte order for IPv6 original dst address 2024-12-11 22:00:03 +08:00
dependabot[bot] 8d962398ab Bump github.com/quic-go/quic-go from 0.45.0 to 0.48.2
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.45.0 to 0.48.2.
- [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.45.0...v0.48.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-11 21:57:24 +08:00
ginuerzh c1e6164a83 add unix domain socket support for api & metrics services 2024-11-22 22:49:10 +08:00
ginuerzh 6f001a779a tunnel: fix tunnel routing for public entrypoint 2024-11-22 22:04:44 +08:00
ginuerzh 2e8a5d6f51 fix http keepalive for forwarding 2024-11-17 16:16:20 +08:00
ginuerzh c2aedbabd9 add routing rule for node 2024-11-15 20:28:47 +08:00
ginuerzh 79b6b9138e add limiterRefreshInterval option for limiter 2024-11-14 20:16:09 +08:00
ginuerzh 3db20563d2 add custom http response header for reverse proxy 2024-11-07 11:04:42 +08:00
ginuerzh a12870e766 add compression flag option for http request 2024-11-02 20:41:23 +08:00
ginuerzh edb9a992e2 http: fix http/1.0 connection close 2024-11-01 18:17:50 +08:00
ginuerzh f4d9a5f79b tunnel: checking closed connector 2024-11-01 15:25:45 +08:00
ginuerzh 88ba01b3dd add redirect for handler recorder object 2024-10-31 19:14:21 +08:00
ginuerzh d8126e8e30 update go.mod 2024-10-22 22:54:04 +08:00
ginuerzh a97ac1f30a fix http body recorder 2024-10-22 22:51:33 +08:00
ginuerzh 3b4fd643de fix udp port forwarding 2024-10-22 22:50:40 +08:00
ginuerzh 3ca253537b fix race condition for sniffing 2024-10-20 19:19:16 +08:00
ginuerzh 41f527aa46 sniffing: full websocket frame recording 2024-10-19 20:40:07 +08:00
ginuerzh 6b932e35bf sniffing websocket frame 2024-10-19 19:36:06 +08:00
ginuerzh a0cbee8817 use http.transport for non-connect http tunnel 2024-10-17 21:57:20 +08:00
ginuerzh 7e51404ae5 fix handler sniffing 2024-10-16 23:01:12 +08:00
ginuerzh 618d042001 add input/ouput fields for log 2024-10-16 22:16:34 +08:00
ginuerzh 24547b4332 add ClientAddr for websocket conn 2024-10-16 20:46:45 +08:00
ginuerzh ea179a0ee6 tunnel: fix http switching protocols 2024-10-15 19:36:20 +08:00
ginuerzh 51ca9f620a add transport for tunnel entrypoint 2024-10-15 18:47:36 +08:00
ginuerzh 5bba004324 add keepalive option for tunnel entrypoint 2024-10-14 23:09:07 +08:00
ginuerzh 0ffed81601 add sniffer for forwarding handler 2024-10-14 21:18:38 +08:00
ginuerzh 27242d0b66 recorder: add input/output traffic stats for handler recorder object 2024-10-11 20:32:56 +08:00
ginuerzh 8f994ab632 add handle options for sniffer 2024-10-10 22:49:56 +08:00
ginuerzh 9a56c8fba3 add keepalive option for http handler 2024-10-10 16:52:01 +08:00
ginuerzh c42a44abb6 add sniffer utility 2024-10-02 22:51:23 +08:00
ginuerzh 3c8add4b82 add traffic sniffing for handler 2024-09-27 15:55:28 +08:00
ginuerzh dfb6cb95d0 add username for redis 2024-09-25 22:05:19 +08:00
ginuerzh e22aa91571 add route for recorder 2024-09-24 20:20:15 +08:00
ginuerzh dd179bc951 bypass: wildcard matcher for ip 2024-09-24 20:19:48 +08:00
ginuerzh 8934cb6b1c add tls handshake for recorder 2024-09-20 21:26:21 +08:00
ginuerzh e09ec08881 add sid for connector log 2024-09-20 18:20:57 +08:00
ginuerzh e37213ac01 add http body for handler recorder 2024-09-16 19:44:15 +08:00
393 changed files with 20588 additions and 4875 deletions
+3 -1
View File
@@ -1 +1,3 @@
# The extended (may be experimental) features outside the main gost tree.
# The extended (may be experimental) features outside the main gost tree.
+47 -18
View File
@@ -13,6 +13,7 @@ import (
"github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/matcher"
xlogger "github.com/go-gost/x/logger"
)
type options struct {
@@ -75,6 +76,7 @@ type localAdmission struct {
mu sync.RWMutex
cancelFunc context.CancelFunc
options options
logger logger.Logger
}
// NewAdmission creates and initializes a new Admission using matcher patterns as its match rules.
@@ -85,18 +87,19 @@ func NewAdmission(opts ...Option) admission.Admission {
opt(&options)
}
ctx, cancel := context.WithCancel(context.TODO())
ctx, cancel := context.WithCancel(context.Background())
p := &localAdmission{
cancelFunc: cancel,
options: options,
ipMatcher: matcher.NopMatcher(),
cidrMatcher: matcher.NopMatcher(),
cancelFunc: cancel,
options: options,
logger: options.logger,
}
if p.logger == nil {
p.logger = xlogger.Nop()
}
if err := p.reload(ctx); err != nil {
options.logger.Warnf("reload: %v", err)
}
if p.options.period > 0 {
go p.periodReload(ctx)
}
go p.periodReload(ctx)
return p
}
@@ -117,13 +120,20 @@ func (p *localAdmission) Admit(ctx context.Context, addr string, opts ...admissi
p.options.whitelist && matched
if !b {
p.options.logger.Debugf("%s is denied", addr)
p.logger.Debugf("%s is denied", addr)
}
return b
}
func (p *localAdmission) periodReload(ctx context.Context) error {
if err := p.reload(ctx); err != nil {
p.logger.Warnf("reload: %v", err)
}
period := p.options.period
if period <= 0 {
return nil
}
if period < time.Second {
period = time.Second
}
@@ -134,7 +144,7 @@ func (p *localAdmission) periodReload(ctx context.Context) error {
select {
case <-ticker.C:
if err := p.reload(ctx); err != nil {
p.options.logger.Warnf("reload: %v", err)
p.logger.Warnf("reload: %v", err)
// return err
}
case <-ctx.Done():
@@ -162,7 +172,7 @@ func (p *localAdmission) reload(ctx context.Context) error {
continue
}
if ipAddr, _ := net.ResolveIPAddr("ip", pattern); ipAddr != nil {
p.options.logger.Debugf("resolve IP: %s -> %s", pattern, ipAddr)
p.logger.Debugf("resolve IP: %s -> %s", pattern, ipAddr)
ips = append(ips, ipAddr.IP)
}
}
@@ -181,7 +191,7 @@ func (p *localAdmission) load(ctx context.Context) (patterns []string, err error
if lister, ok := p.options.fileLoader.(loader.Lister); ok {
list, er := lister.List(ctx)
if er != nil {
p.options.logger.Warnf("file loader: %v", er)
p.logger.Warnf("file loader: %v", er)
}
for _, s := range list {
if line := p.parseLine(s); line != "" {
@@ -191,7 +201,7 @@ func (p *localAdmission) load(ctx context.Context) (patterns []string, err error
} else {
r, er := p.options.fileLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("file loader: %v", er)
p.logger.Warnf("file loader: %v", er)
}
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
@@ -202,13 +212,13 @@ func (p *localAdmission) load(ctx context.Context) (patterns []string, err error
if lister, ok := p.options.redisLoader.(loader.Lister); ok {
list, er := lister.List(ctx)
if er != nil {
p.options.logger.Warnf("redis loader: %v", er)
p.logger.Warnf("redis loader: %v", er)
}
patterns = append(patterns, list...)
} else {
r, er := p.options.redisLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("redis loader: %v", er)
p.logger.Warnf("redis loader: %v", er)
}
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
@@ -219,14 +229,14 @@ func (p *localAdmission) load(ctx context.Context) (patterns []string, err error
if p.options.httpLoader != nil {
r, er := p.options.httpLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("http loader: %v", er)
p.logger.Warnf("http loader: %v", er)
}
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
}
}
p.options.logger.Debugf("load items %d", len(patterns))
p.logger.Debugf("load items %d", len(patterns))
return
}
@@ -271,3 +281,22 @@ func (p *localAdmission) Close() error {
}
return nil
}
type admissionGroup struct {
admissions []admission.Admission
}
func AdmissionGroup(admissions ...admission.Admission) admission.Admission {
return &admissionGroup{
admissions: admissions,
}
}
func (p *admissionGroup) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
for _, admission := range p.admissions {
if admission != nil && !admission.Admit(ctx, addr, opts...) {
return false
}
}
return true
}
+7 -1
View File
@@ -48,9 +48,15 @@ func (p *grpcPlugin) Admit(ctx context.Context, addr string, opts ...admission.O
return false
}
var options admission.Options
for _, opt := range opts {
opt(&options)
}
r, err := p.client.Admit(ctx,
&proto.AdmissionRequest{
Addr: addr,
Service: options.Service,
Addr: addr,
})
if err != nil {
p.log.Error(err)
+9 -2
View File
@@ -12,7 +12,8 @@ import (
)
type httpPluginRequest struct {
Addr string `json:"addr"`
Service string `json:"service"`
Addr string `json:"addr"`
}
type httpPluginResponse struct {
@@ -49,8 +50,14 @@ func (p *httpPlugin) Admit(ctx context.Context, addr string, opts ...admission.O
return
}
var options admission.Options
for _, opt := range opts {
opt(&options)
}
rb := httpPluginRequest{
Addr: addr,
Service: options.Service,
Addr: addr,
}
v, err := json.Marshal(&rb)
if err != nil {
+25 -10
View File
@@ -8,7 +8,8 @@ import (
"syscall"
"github.com/go-gost/core/admission"
"github.com/go-gost/core/metadata"
"github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/net/udp"
)
@@ -50,9 +51,23 @@ func (c *serverConn) SyscallConn() (rc syscall.RawConn, err error) {
return
}
func (c *serverConn) Metadata() metadata.Metadata {
if md, ok := c.Conn.(metadata.Metadatable); ok {
return md.Metadata()
func (c *serverConn) CloseRead() error {
if sc, ok := c.Conn.(xio.CloseRead); ok {
return sc.CloseRead()
}
return xio.ErrUnsupported
}
func (c *serverConn) CloseWrite() error {
if sc, ok := c.Conn.(xio.CloseWrite); ok {
return sc.CloseWrite()
}
return xio.ErrUnsupported
}
func (c *serverConn) Context() context.Context {
if innerCtx, ok := c.Conn.(ctx.Context); ok {
return innerCtx.Context()
}
return nil
}
@@ -88,9 +103,9 @@ func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
}
}
func (c *packetConn) Metadata() metadata.Metadata {
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
return md.Metadata()
func (c *packetConn) Context() context.Context {
if innerCtx, ok := c.PacketConn.(ctx.Context); ok {
return innerCtx.Context()
}
return nil
}
@@ -234,9 +249,9 @@ func (c *udpConn) SetDSCP(n int) error {
return nil
}
func (c *udpConn) Metadata() metadata.Metadata {
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
return md.Metadata()
func (c *udpConn) Context() context.Context {
if innerCtx, ok := c.PacketConn.(ctx.Context); ok {
return innerCtx.Context()
}
return nil
}
+18 -2
View File
@@ -6,19 +6,22 @@ import (
"github.com/go-gost/core/admission"
"github.com/go-gost/core/logger"
xctx "github.com/go-gost/x/ctx"
)
type listener struct {
service string
net.Listener
admission admission.Admission
log logger.Logger
}
func WrapListener(admission admission.Admission, ln net.Listener) net.Listener {
func WrapListener(service string, admission admission.Admission, ln net.Listener) net.Listener {
if admission == nil {
return ln
}
return &listener{
service: service,
Listener: ln,
admission: admission,
}
@@ -30,8 +33,21 @@ func (ln *listener) Accept() (net.Conn, error) {
if err != nil {
return nil, err
}
ctx := context.Background()
if cc, ok := c.(xctx.Context); ok {
if cv := cc.Context(); cv != nil {
ctx = cv
}
}
clientAddr := c.RemoteAddr()
if addr := xctx.SrcAddrFromContext(ctx); addr != nil {
clientAddr = addr
}
if ln.admission != nil &&
!ln.admission.Admit(context.Background(), c.RemoteAddr().String()) {
!ln.admission.Admit(ctx, clientAddr.String(), admission.WithService(ln.service)) {
c.Close()
continue
}
+50 -82
View File
@@ -1,14 +1,14 @@
// swagger generate spec -o swagger.yaml
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 (
@@ -17,56 +17,22 @@ var (
)
type Response struct {
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"`
Code int `json:"code,omitempty"`
Msg string `json:"msg,omitempty"`
Data interface{} `json:"data,omitempty"`
}
type options struct {
accessLog bool
pathPrefix string
auther auth.Authenticator
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
func Register(r *gin.Engine, opts *Options) {
if opts == nil {
opts = &Options{}
}
var options options
for _, opt := range opts {
opt(&options)
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(
cors.New((cors.Config{
AllowAllOrigins: true,
@@ -76,115 +42,117 @@ func NewService(addr string, opts ...Option) (service.Service, error) {
})),
gin.Recovery(),
)
if options.accessLog {
if opts.AccessLog {
r.Use(mwLogger())
}
router := r.Group("")
if options.pathPrefix != "" {
router = router.Group(options.pathPrefix)
if opts.PathPrefix != "" {
router = router.Group(opts.PathPrefix)
}
router.StaticFS("/docs", http.FS(swaggerDoc))
config := router.Group("/config")
config.Use(mwBasicAuth(options.auther))
registerConfig(config)
config.Use(mwBasicAuth(opts.Auther))
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("/reload", reloadConfig)
config.GET("/services", getServiceList)
config.GET("/services/:service", getService)
config.POST("/services", createService)
config.PUT("/services/:service", updateService)
config.DELETE("/services/:service", deleteService)
config.GET("/chains", getChainList)
config.GET("/chains/:chain", getChain)
config.POST("/chains", createChain)
config.PUT("/chains/:chain", updateChain)
config.DELETE("/chains/:chain", deleteChain)
config.GET("/hops", getHopList)
config.GET("/hops/:hop", getHop)
config.POST("/hops", createHop)
config.PUT("/hops/:hop", updateHop)
config.DELETE("/hops/:hop", deleteHop)
config.GET("/authers", getAutherList)
config.GET("/authers/:auther", getAuther)
config.POST("/authers", createAuther)
config.PUT("/authers/:auther", updateAuther)
config.DELETE("/authers/:auther", deleteAuther)
config.GET("/admissions", getAdmissionList)
config.GET("/admissions/:admission", getAdmission)
config.POST("/admissions", createAdmission)
config.PUT("/admissions/:admission", updateAdmission)
config.DELETE("/admissions/:admission", deleteAdmission)
config.GET("/bypasses", getBypassList)
config.GET("/bypasses/:bypass", getBypass)
config.POST("/bypasses", createBypass)
config.PUT("/bypasses/:bypass", updateBypass)
config.DELETE("/bypasses/:bypass", deleteBypass)
config.GET("/resolvers", getResolverList)
config.GET("/resolvers/:resolver", getResolver)
config.POST("/resolvers", createResolver)
config.PUT("/resolvers/:resolver", updateResolver)
config.DELETE("/resolvers/:resolver", deleteResolver)
config.GET("/hosts", getHostsList)
config.GET("/hosts/:hosts", getHosts)
config.POST("/hosts", createHosts)
config.PUT("/hosts/:hosts", updateHosts)
config.DELETE("/hosts/:hosts", deleteHosts)
config.GET("/ingresses", getIngressList)
config.GET("/ingresses/:ingress", getIngress)
config.POST("/ingresses", createIngress)
config.PUT("/ingresses/:ingress", updateIngress)
config.DELETE("/ingresses/:ingress", deleteIngress)
config.GET("/routers", getRouterList)
config.GET("/routers/:router", getRouter)
config.POST("/routers", createRouter)
config.PUT("/routers/:router", updateRouter)
config.DELETE("/routers/:router", deleteRouter)
config.GET("/observers", getObserverList)
config.GET("/observers/:observer", getObserver)
config.POST("/observers", createObserver)
config.PUT("/observers/:observer", updateObserver)
config.DELETE("/observers/:observer", deleteObserver)
config.GET("/recorders", getRecorderList)
config.GET("/recorders/:recorder", getRecorder)
config.POST("/recorders", createRecorder)
config.PUT("/recorders/:recorder", updateRecorder)
config.DELETE("/recorders/:recorder", deleteRecorder)
config.GET("/sds", getSDList)
config.GET("/sds/:sd", getSD)
config.POST("/sds", createSD)
config.PUT("/sds/:sd", updateSD)
config.DELETE("/sds/:sd", deleteSD)
config.GET("/limiters", getLimiterList)
config.GET("/limiters/:limiter", getLimiter)
config.POST("/limiters", createLimiter)
config.PUT("/limiters/:limiter", updateLimiter)
config.DELETE("/limiters/:limiter", deleteLimiter)
config.GET("/climiters", getConnLimiterList)
config.GET("/climiters/:limiter", getConnLimiter)
config.POST("/climiters", createConnLimiter)
config.PUT("/climiters/:limiter", updateConnLimiter)
config.DELETE("/climiters/:limiter", deleteConnLimiter)
config.GET("/rlimiters", getRateLimiterList)
config.GET("/rlimiters/:limiter", getRateLimiter)
config.POST("/rlimiters", createRateLimiter)
config.PUT("/rlimiters/:limiter", updateRateLimiter)
config.DELETE("/rlimiters/:limiter", deleteRateLimiter)
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getAdmissionListRequest
type getAdmissionListRequest struct {
}
// successful operation.
// swagger:response getAdmissionListResponse
type getAdmissionListResponse struct {
// in: body
Data admissionList
}
type admissionList struct {
Count int `json:"count"`
List []*config.AdmissionConfig `json:"list"`
}
func getAdmissionList(ctx *gin.Context) {
// swagger:route GET /config/admissions Admission getAdmissionListRequest
//
// Get admission list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getAdmissionListResponse
var req getAdmissionListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Admissions
var resp getAdmissionListResponse
resp.Data = admissionList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getAdmissionRequest
type getAdmissionRequest struct {
// in: path
// required: true
Admission string `uri:"admission" json:"admission"`
}
// successful operation.
// swagger:response getAdmissionResponse
type getAdmissionResponse struct {
// in: body
Data *config.AdmissionConfig
}
func getAdmission(ctx *gin.Context) {
// swagger:route GET /config/admissions/{admission} Admission getAdmissionRequest
//
// Get admission.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getAdmissionResponse
var req getAdmissionRequest
ctx.ShouldBindUri(&req)
var resp getAdmissionResponse
for _, admission := range config.Global().Admissions {
if admission == nil {
continue
}
if admission.Name == req.Admission {
resp.Data = admission
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createAdmissionRequest
type createAdmissionRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getAutherListRequest
type getAutherListRequest struct {
}
// successful operation.
// swagger:response getAutherListResponse
type getAutherListResponse struct {
// in: body
Data autherList
}
type autherList struct {
Count int `json:"count"`
List []*config.AutherConfig `json:"list"`
}
func getAutherList(ctx *gin.Context) {
// swagger:route GET /config/authers Auther getAutherListRequest
//
// Get auther list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getAutherListResponse
var req getAutherListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Authers
var resp getAutherListResponse
resp.Data = autherList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getAutherRequest
type getAutherRequest struct {
// in: path
// required: true
Auther string `uri:"auther" json:"auther"`
}
// successful operation.
// swagger:response getAutherResponse
type getAutherResponse struct {
// in: body
Data *config.AutherConfig
}
func getAuther(ctx *gin.Context) {
// swagger:route GET /config/authers/{auther} Auther getAutherRequest
//
// Get auther.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getAutherResponse
var req getAutherRequest
ctx.ShouldBindUri(&req)
var resp getAutherResponse
for _, auther := range config.Global().Authers {
if auther == nil {
continue
}
if auther.Name == req.Auther {
resp.Data = auther
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createAutherRequest
type createAutherRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getBypassListRequest
type getBypassListRequest struct {
}
// successful operation.
// swagger:response getBypassListResponse
type getBypassListResponse struct {
// in: body
Data bypassList
}
type bypassList struct {
Count int `json:"count"`
List []*config.BypassConfig `json:"list"`
}
func getBypassList(ctx *gin.Context) {
// swagger:route GET /config/bypasses Bypass getBypassListRequest
//
// Get bypass list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getBypassListResponse
var req getBypassListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Bypasses
var resp getBypassListResponse
resp.Data = bypassList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getBypassRequest
type getBypassRequest struct {
// in: path
// required: true
Bypass string `uri:"bypass" json:"bypass"`
}
// successful operation.
// swagger:response getBypassResponse
type getBypassResponse struct {
// in: body
Data *config.BypassConfig
}
func getBypass(ctx *gin.Context) {
// swagger:route GET /config/bypasses/{bypass} Bypass getBypassRequest
//
// Get bypass.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getBypassResponse
var req getBypassRequest
ctx.ShouldBindUri(&req)
var resp getBypassResponse
for _, bypass := range config.Global().Bypasses {
if bypass == nil {
continue
}
if bypass.Name == req.Bypass {
resp.Data = bypass
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createBypassRequest
type createBypassRequest struct {
// in: body
+87
View File
@@ -12,6 +12,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getChainListRequest
type getChainListRequest struct {
}
// successful operation.
// swagger:response getChainListResponse
type getChainListResponse struct {
// in: body
Data chainList
}
type chainList struct {
Count int `json:"count"`
List []*config.ChainConfig `json:"list"`
}
func getChainList(ctx *gin.Context) {
// swagger:route GET /config/chains Chain getChainListRequest
//
// Get chain list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getChainListResponse
var req getChainListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Chains
var resp getChainListResponse
resp.Data = chainList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getChainRequest
type getChainRequest struct {
// in: path
// required: true
Chain string `uri:"chain" json:"chain"`
}
// successful operation.
// swagger:response getChainResponse
type getChainResponse struct {
// in: body
Data *config.ChainConfig
}
func getChain(ctx *gin.Context) {
// swagger:route GET /config/chains/{chain} Chain getChainRequest
//
// Get chain.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getChainResponse
var req getChainRequest
ctx.ShouldBindUri(&req)
var resp getChainResponse
for _, chain := range config.Global().Chains {
if chain == nil {
continue
}
if chain.Name == req.Chain {
resp.Data = chain
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createChainRequest
type createChainRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getConnLimiterListRequest
type getConnLimiterListRequest struct {
}
// successful operation.
// swagger:response getConnLimiterListResponse
type getConnLimiterListResponse struct {
// in: body
Data connLimiterList
}
type connLimiterList struct {
Count int `json:"count"`
List []*config.LimiterConfig `json:"list"`
}
func getConnLimiterList(ctx *gin.Context) {
// swagger:route GET /config/climiters Limiter getConnLimiterListRequest
//
// Get conn limiter list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getConnLimiterListResponse
var req getConnLimiterListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().CLimiters
var resp getConnLimiterListResponse
resp.Data = connLimiterList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getConnLimiterRequest
type getConnLimiterRequest struct {
// in: path
// required: true
Limiter string `uri:"limiter" json:"limiter"`
}
// successful operation.
// swagger:response getConnLimiterResponse
type getConnLimiterResponse struct {
// in: body
Data *config.LimiterConfig
}
func getConnLimiter(ctx *gin.Context) {
// swagger:route GET /config/climiters/{limiter} Limiter getConnLimiterRequest
//
// Get conn limiter.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getConnLimiterResponse
var req getConnLimiterRequest
ctx.ShouldBindUri(&req)
var resp getConnLimiterResponse
for _, limiter := range config.Global().CLimiters{
if limiter == nil {
continue
}
if limiter.Name == req.Limiter {
resp.Data = limiter
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createConnLimiterRequest
type createConnLimiterRequest struct {
// in: body
+87
View File
@@ -12,6 +12,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getHopListRequest
type getHopListRequest struct {
}
// successful operation.
// swagger:response getHopListResponse
type getHopListResponse struct {
// in: body
Data hopList
}
type hopList struct {
Count int `json:"count"`
List []*config.HopConfig `json:"list"`
}
func getHopList(ctx *gin.Context) {
// swagger:route GET /config/hops Hop getHopListRequest
//
// Get hop list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getHopListResponse
var req getHopListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Hops
var resp getHopListResponse
resp.Data = hopList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getHopRequest
type getHopRequest struct {
// in: path
// required: true
Hop string `uri:"hop" json:"hop"`
}
// successful operation.
// swagger:response getHopResponse
type getHopResponse struct {
// in: body
Data *config.HopConfig
}
func getHop(ctx *gin.Context) {
// swagger:route GET /config/hops/{hop} Hop getHopRequest
//
// Get hop.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getHopResponse
var req getHopRequest
ctx.ShouldBindUri(&req)
var resp getHopResponse
for _, hop := range config.Global().Hops {
if hop == nil {
continue
}
if hop.Name == req.Hop {
resp.Data = hop
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createHopRequest
type createHopRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getHostsListRequest
type getHostsListRequest struct {
}
// successful operation.
// swagger:response getHostsListResponse
type getHostsListResponse struct {
// in: body
Data hostsList
}
type hostsList struct {
Count int `json:"count"`
List []*config.HostsConfig `json:"list"`
}
func getHostsList(ctx *gin.Context) {
// swagger:route GET /config/hosts Hosts getHostsListRequest
//
// Get hosts list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getHostsListResponse
var req getHostsListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Hosts
var resp getHostsListResponse
resp.Data = hostsList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getHostsRequest
type getHostsRequest struct {
// in: path
// required: true
Hosts string `uri:"hosts" json:"hosts"`
}
// successful operation.
// swagger:response getHostsResponse
type getHostsResponse struct {
// in: body
Data *config.HostsConfig
}
func getHosts(ctx *gin.Context) {
// swagger:route GET /config/hosts/{hosts} Hosts getHostsRequest
//
// Get hosts.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getHostsResponse
var req getHostsRequest
ctx.ShouldBindUri(&req)
var resp getHostsResponse
for _, hosts := range config.Global().Hosts {
if hosts == nil {
continue
}
if hosts.Name == req.Hosts {
resp.Data = hosts
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createHostsRequest
type createHostsRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getIngressListRequest
type getIngressListRequest struct {
}
// successful operation.
// swagger:response getIngressListResponse
type getIngressListResponse struct {
// in: body
Data ingressList
}
type ingressList struct {
Count int `json:"count"`
List []*config.IngressConfig `json:"list"`
}
func getIngressList(ctx *gin.Context) {
// swagger:route GET /config/ingresses Ingress getIngressListRequest
//
// Get ingress list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getIngressListResponse
var req getIngressListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Ingresses
var resp getIngressListResponse
resp.Data = ingressList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getIngressRequest
type getIngressRequest struct {
// in: path
// required: true
Ingress string `uri:"ingress" json:"ingress"`
}
// successful operation.
// swagger:response getIngressResponse
type getIngressResponse struct {
// in: body
Data *config.IngressConfig
}
func getIngress(ctx *gin.Context) {
// swagger:route GET /config/ingresses/{ingress} Ingress getIngressRequest
//
// Get ingress.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getIngressResponse
var req getIngressRequest
ctx.ShouldBindUri(&req)
var resp getIngressResponse
for _, ingress := range config.Global().Ingresses {
if ingress == nil {
continue
}
if ingress.Name == req.Ingress {
resp.Data = ingress
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createIngressRequest
type createIngressRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getLimiterListRequest
type getLimiterListRequest struct {
}
// successful operation.
// swagger:response getLimiterListResponse
type getLimiterListResponse struct {
// in: body
Data limiterList
}
type limiterList struct {
Count int `json:"count"`
List []*config.LimiterConfig `json:"list"`
}
func getLimiterList(ctx *gin.Context) {
// swagger:route GET /config/limiters Limiter getLimiterListRequest
//
// Get limiter list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getLimiterListResponse
var req getLimiterListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Limiters
var resp getLimiterListResponse
resp.Data = limiterList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getLimiterRequest
type getLimiterRequest struct {
// in: path
// required: true
Limiter string `uri:"limiter" json:"limiter"`
}
// successful operation.
// swagger:response getLimiterResponse
type getLimiterResponse struct {
// in: body
Data *config.LimiterConfig
}
func getLimiter(ctx *gin.Context) {
// swagger:route GET /config/limiters/{limiter} Limiter getLimiterRequest
//
// Get limiter.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getLimiterResponse
var req getLimiterRequest
ctx.ShouldBindUri(&req)
var resp getLimiterResponse
for _, limiter := range config.Global().Limiters {
if limiter == nil {
continue
}
if limiter.Name == req.Limiter {
resp.Data = limiter
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createLimiterRequest
type createLimiterRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getObserverListRequest
type getObserverListRequest struct {
}
// successful operation.
// swagger:response getObserverListResponse
type getObserverListResponse struct {
// in: body
Data observerList
}
type observerList struct {
Count int `json:"count"`
List []*config.ObserverConfig `json:"list"`
}
func getObserverList(ctx *gin.Context) {
// swagger:route GET /config/observers Observer getObserverListRequest
//
// Get observer list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getObserverListResponse
var req getObserverListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Observers
var resp getObserverListResponse
resp.Data = observerList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getObserverRequest
type getObserverRequest struct {
// in: path
// required: true
Observer string `uri:"observer" json:"observer"`
}
// successful operation.
// swagger:response getObserverResponse
type getObserverResponse struct {
// in: body
Data *config.ObserverConfig
}
func getObserver(ctx *gin.Context) {
// swagger:route GET /config/observers/{observer} Observer getObserverRequest
//
// Get observer.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getObserverResponse
var req getObserverRequest
ctx.ShouldBindUri(&req)
var resp getObserverResponse
for _, observer := range config.Global().Observers {
if observer == nil {
continue
}
if observer.Name == req.Observer {
resp.Data = observer
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createObserverRequest
type createObserverRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getRateLimiterListRequest
type getRateLimiterListRequest struct {
}
// successful operation.
// swagger:response getRateLimiterListResponse
type getRateLimiterListResponse struct {
// in: body
Data rateLimiterList
}
type rateLimiterList struct {
Count int `json:"count"`
List []*config.LimiterConfig `json:"list"`
}
func getRateLimiterList(ctx *gin.Context) {
// swagger:route GET /config/rlimiters Limiter getRateLimiterListRequest
//
// Get rate limiter list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getRateLimiterListResponse
var req getRateLimiterListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().CLimiters
var resp getRateLimiterListResponse
resp.Data = rateLimiterList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getRateLimiterRequest
type getRateLimiterRequest struct {
// in: path
// required: true
Limiter string `uri:"limiter" json:"limiter"`
}
// successful operation.
// swagger:response getRateLimiterResponse
type getRateLimiterResponse struct {
// in: body
Data *config.LimiterConfig
}
func getRateLimiter(ctx *gin.Context) {
// swagger:route GET /config/rlimiters/{limiter} Limiter getRateLimiterRequest
//
// Get conn limiter.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getRateLimiterResponse
var req getRateLimiterRequest
ctx.ShouldBindUri(&req)
var resp getRateLimiterResponse
for _, limiter := range config.Global().CLimiters{
if limiter == nil {
continue
}
if limiter.Name == req.Limiter {
resp.Data = limiter
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createRateLimiterRequest
type createRateLimiterRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getRecorderListRequest
type getRecorderListRequest struct {
}
// successful operation.
// swagger:response getRecorderListResponse
type getRecorderListResponse struct {
// in: body
Data recorderList
}
type recorderList struct {
Count int `json:"count"`
List []*config.RecorderConfig `json:"list"`
}
func getRecorderList(ctx *gin.Context) {
// swagger:route GET /config/recorders Recorder getRecorderListRequest
//
// Get recorder list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getRecorderListResponse
var req getRecorderListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Recorders
var resp getRecorderListResponse
resp.Data = recorderList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getRecorderRequest
type getRecorderRequest struct {
// in: path
// required: true
Recorder string `uri:"recorder" json:"recorder"`
}
// successful operation.
// swagger:response getRecorderResponse
type getRecorderResponse struct {
// in: body
Data *config.RecorderConfig
}
func getRecorder(ctx *gin.Context) {
// swagger:route GET /config/recorders/{recorder} Recorder getRecorderRequest
//
// Get recorder.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getRecorderResponse
var req getRecorderRequest
ctx.ShouldBindUri(&req)
var resp getRecorderResponse
for _, recorder := range config.Global().Recorders {
if recorder == nil {
continue
}
if recorder.Name == req.Recorder {
resp.Data = recorder
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createRecorderRequest
type createRecorderRequest struct {
// in: body
+56
View File
@@ -0,0 +1,56 @@
package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/loader"
"github.com/go-gost/x/config/parsing/parser"
"github.com/go-gost/x/registry"
)
// swagger:parameters reloadConfigRequest
type reloadConfigRequest struct{}
// successful operation.
// swagger:response reloadConfigResponse
type reloadConfigResponse struct {
Data Response
}
func reloadConfig(ctx *gin.Context) {
// swagger:route POST /config/reload Reload reloadConfigRequest
//
// Hot reload config.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: reloadConfigResponse
cfg, err := parser.Parse()
if err != nil {
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, err.Error()))
return
}
config.Set(cfg)
if err := loader.Load(cfg); err != nil {
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, err.Error()))
return
}
for _, svc := range registry.ServiceRegistry().GetAll() {
svc := svc
go func() {
svc.Serve()
}()
}
ctx.JSON(http.StatusOK, Response{
Msg: "OK",
})
}
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getResolverListRequest
type getResolverListRequest struct {
}
// successful operation.
// swagger:response getResolverListResponse
type getResolverListResponse struct {
// in: body
Data resolverList
}
type resolverList struct {
Count int `json:"count"`
List []*config.ResolverConfig `json:"list"`
}
func getResolverList(ctx *gin.Context) {
// swagger:route GET /config/resolvers Resolver getResolverListRequest
//
// Get resolver list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getResolverListResponse
var req getResolverListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Resolvers
var resp getResolverListResponse
resp.Data = resolverList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getResolverRequest
type getResolverRequest struct {
// in: path
// required: true
Resolver string `uri:"resolver" json:"resolver"`
}
// successful operation.
// swagger:response getResolverResponse
type getResolverResponse struct {
// in: body
Data *config.ResolverConfig
}
func getResolver(ctx *gin.Context) {
// swagger:route GET /config/resolvers/{resolver} Resolver getResolverRequest
//
// Get resolver.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getResolverResponse
var req getResolverRequest
ctx.ShouldBindUri(&req)
var resp getResolverResponse
for _, resolver := range config.Global().Resolvers {
if resolver == nil {
continue
}
if resolver.Name == req.Resolver {
resp.Data = resolver
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createResolverRequest
type createResolverRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getRouterListRequest
type getRouterListRequest struct {
}
// successful operation.
// swagger:response getRouterListResponse
type getRouterListResponse struct {
// in: body
Data routerList
}
type routerList struct {
Count int `json:"count"`
List []*config.RouterConfig `json:"list"`
}
func getRouterList(ctx *gin.Context) {
// swagger:route GET /config/routers Router getRouterListRequest
//
// Get router list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getRouterListResponse
var req getRouterListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Routers
var resp getRouterListResponse
resp.Data = routerList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getRouterRequest
type getRouterRequest struct {
// in: path
// required: true
Router string `uri:"router" json:"router"`
}
// successful operation.
// swagger:response getRouterResponse
type getRouterResponse struct {
// in: body
Data *config.RouterConfig
}
func getRouter(ctx *gin.Context) {
// swagger:route GET /config/routers/{router} Router getRouterRequest
//
// Get router.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getRouterResponse
var req getRouterRequest
ctx.ShouldBindUri(&req)
var resp getRouterResponse
for _, router := range config.Global().Routers {
if router == nil {
continue
}
if router.Name == req.Router {
resp.Data = router
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createRouterRequest
type createRouterRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getSDListRequest
type getSDListRequest struct {
}
// successful operation.
// swagger:response getSDListResponse
type getSDListResponse struct {
// in: body
Data sdList
}
type sdList struct {
Count int `json:"count"`
List []*config.SDConfig `json:"list"`
}
func getSDList(ctx *gin.Context) {
// swagger:route GET /config/sds SD getSDListRequest
//
// Get sd list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getSDListResponse
var req getSDListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().SDs
var resp getSDListResponse
resp.Data = sdList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getSDRequest
type getSDRequest struct {
// in: path
// required: true
SD string `uri:"sd" json:"sd"`
}
// successful operation.
// swagger:response getSDResponse
type getSDResponse struct {
// in: body
Data *config.SDConfig
}
func getSD(ctx *gin.Context) {
// swagger:route GET /config/sds/{sd} SD getSDRequest
//
// Get sd.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getSDResponse
var req getSDRequest
ctx.ShouldBindUri(&req)
var resp getSDResponse
for _, sd := range config.Global().SDs {
if sd == nil {
continue
}
if sd.Name == req.SD {
resp.Data = sd
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createSDRequest
type createSDRequest struct {
// in: body
+87
View File
@@ -11,6 +11,93 @@ import (
"github.com/go-gost/x/registry"
)
// swagger:parameters getServiceListRequest
type getServiceListRequest struct {
}
// successful operation.
// swagger:response getServiceListResponse
type getServiceListResponse struct {
// in: body
Data serviceList
}
type serviceList struct {
Count int `json:"count"`
List []*config.ServiceConfig `json:"list"`
}
func getServiceList(ctx *gin.Context) {
// swagger:route GET /config/services Service getServiceListRequest
//
// Get service list.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getServiceListResponse
var req getServiceListRequest
ctx.ShouldBindQuery(&req)
list := config.Global().Services
var resp getServiceListResponse
resp.Data = serviceList{
Count: len(list),
List: list,
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters getServiceRequest
type getServiceRequest struct {
// in: path
// required: true
Service string `uri:"service" json:"service"`
}
// successful operation.
// swagger:response getServiceResponse
type getServiceResponse struct {
// in: body
Data *config.ServiceConfig
}
func getService(ctx *gin.Context) {
// swagger:route GET /config/services/{service} Service getServiceRequest
//
// Get service.
//
// Security:
// basicAuth: []
//
// Responses:
// 200: getServiceResponse
var req getServiceRequest
ctx.ShouldBindUri(&req)
var resp getServiceResponse
for _, service := range config.Global().Services {
if service == nil {
continue
}
if service.Name == req.Service {
resp.Data = service
}
}
ctx.JSON(http.StatusOK, Response{
Data: resp.Data,
})
}
// swagger:parameters createServiceRequest
type createServiceRequest struct {
// in: body
+1 -1
View File
@@ -35,7 +35,7 @@ func mwBasicAuth(auther auth.Authenticator) gin.HandlerFunc {
return
}
u, p, _ := c.Request.BasicAuth()
if _, ok := auther.Authenticate(c, u, p); !ok {
if _, ok := auther.Authenticate(c, u, p, auth.WithService("@api")); !ok {
c.Writer.Header().Set("WWW-Authenticate", "Basic")
c.JSON(http.StatusUnauthorized, Response{
Code: http.StatusUnauthorized,
+96
View File
@@ -0,0 +1,96 @@
package service
import (
"net"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/service"
"github.com/go-gost/x/api"
)
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(network, addr string, opts ...Option) (service.Service, error) {
if network == "" {
network = "tcp"
}
ln, err := net.Listen(network, addr)
if err != nil {
return nil, err
}
var options options
for _, opt := range opts {
opt(&options)
}
gin.SetMode(gin.ReleaseMode)
r := gin.New()
api.Register(r, &api.Options{
AccessLog: options.accessLog,
PathPrefix: options.pathPrefix,
Auther: options.auther,
})
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
}
}
+898 -8
View File
File diff suppressed because it is too large Load Diff
+47 -17
View File
@@ -67,6 +67,7 @@ type authenticator struct {
mu sync.RWMutex
cancelFunc context.CancelFunc
options options
logger logger.Logger
}
// NewAuthenticator creates an Authenticator that authenticates client by pre-defined user mapping.
@@ -75,23 +76,19 @@ func NewAuthenticator(opts ...Option) auth.Authenticator {
for _, opt := range opts {
opt(&options)
}
if options.logger == nil {
options.logger = xlogger.Nop()
}
ctx, cancel := context.WithCancel(context.TODO())
ctx, cancel := context.WithCancel(context.Background())
p := &authenticator{
kvs: make(map[string]string),
cancelFunc: cancel,
options: options,
logger: options.logger,
}
if p.logger == nil {
p.logger = xlogger.Nop()
}
if err := p.reload(ctx); err != nil {
options.logger.Warnf("reload: %v", err)
}
if p.options.period > 0 {
go p.periodReload(ctx)
}
go p.periodReload(ctx)
return p
}
@@ -114,7 +111,14 @@ func (p *authenticator) Authenticate(ctx context.Context, user, password string,
}
func (p *authenticator) periodReload(ctx context.Context) error {
if err := p.reload(ctx); err != nil {
p.logger.Warnf("reload: %v", err)
}
period := p.options.period
if period <= 0 {
return nil
}
if period < time.Second {
period = time.Second
}
@@ -125,7 +129,7 @@ func (p *authenticator) periodReload(ctx context.Context) error {
select {
case <-ticker.C:
if err := p.reload(ctx); err != nil {
p.options.logger.Warnf("reload: %v", err)
p.logger.Warnf("reload: %v", err)
// return err
}
case <-ctx.Done():
@@ -145,7 +149,7 @@ func (p *authenticator) reload(ctx context.Context) (err error) {
kvs[k] = v
}
p.options.logger.Debugf("load items %d", len(m))
p.logger.Debugf("load items %d", len(m))
p.mu.Lock()
defer p.mu.Unlock()
@@ -162,13 +166,13 @@ func (p *authenticator) load(ctx context.Context) (m map[string]string, err erro
if mapper, ok := p.options.fileLoader.(loader.Mapper); ok {
auths, er := mapper.Map(ctx)
if er != nil {
p.options.logger.Warnf("file loader: %v", er)
p.logger.Warnf("file loader: %v", er)
}
m = auths
} else {
r, er := p.options.fileLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("file loader: %v", er)
p.logger.Warnf("file loader: %v", er)
}
if auths, _ := p.parseAuths(r); auths != nil {
m = auths
@@ -179,7 +183,7 @@ func (p *authenticator) load(ctx context.Context) (m map[string]string, err erro
if mapper, ok := p.options.fileLoader.(loader.Mapper); ok {
auths, er := mapper.Map(ctx)
if er != nil {
p.options.logger.Warnf("file loader: %v", er)
p.logger.Warnf("file loader: %v", er)
}
for k, v := range auths {
m[k] = v
@@ -187,7 +191,7 @@ func (p *authenticator) load(ctx context.Context) (m map[string]string, err erro
} else {
r, er := p.options.redisLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("redis loader: %v", er)
p.logger.Warnf("redis loader: %v", er)
}
if auths, _ := p.parseAuths(r); auths != nil {
for k, v := range auths {
@@ -199,7 +203,7 @@ func (p *authenticator) load(ctx context.Context) (m map[string]string, err erro
if p.options.httpLoader != nil {
r, er := p.options.httpLoader.Load(ctx)
if er != nil {
p.options.logger.Warnf("http loader: %v", er)
p.logger.Warnf("http loader: %v", er)
}
if auths, _ := p.parseAuths(r); auths != nil {
for k, v := range auths {
@@ -252,3 +256,29 @@ func (p *authenticator) Close() error {
}
return nil
}
type authenticatorGroup struct {
authers []auth.Authenticator
}
func AuthenticatorGroup(authers ...auth.Authenticator) auth.Authenticator {
return &authenticatorGroup{
authers: authers,
}
}
func (p *authenticatorGroup) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
if len(p.authers) == 0 {
return "", false
}
for _, auther := range p.authers {
if auther == nil {
continue
}
if id, ok := auther.Authenticate(ctx, user, password, opts...); ok {
return id, ok
}
}
return "", false
}
+12 -2
View File
@@ -7,7 +7,7 @@ import (
"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"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
@@ -51,11 +51,21 @@ func (p *grpcPlugin) Authenticate(ctx context.Context, user, password string, op
return "", false
}
var options auth.Options
for _, opt := range opts {
opt(&options)
}
var clientAddr string
if v := xctx.SrcAddrFromContext(ctx); v != nil {
clientAddr = v.String()
}
r, err := p.client.Authenticate(ctx,
&proto.AuthenticateRequest{
Service: options.Service,
Username: user,
Password: password,
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
Client: clientAddr,
})
if err != nil {
p.log.Error(err)
+14 -2
View File
@@ -8,11 +8,12 @@ import (
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
ctxvalue "github.com/go-gost/x/ctx"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/plugin"
)
type httpPluginRequest struct {
Service string `json:"service"`
Username string `json:"username"`
Password string `json:"password"`
Client string `json:"client"`
@@ -53,10 +54,21 @@ func (p *httpPlugin) Authenticate(ctx context.Context, user, password string, op
return
}
var options auth.Options
for _, opt := range opts {
opt(&options)
}
var clientAddr string
if v := xctx.SrcAddrFromContext(ctx); v != nil {
clientAddr = v.String()
}
rb := httpPluginRequest{
Service: options.Service,
Username: user,
Password: password,
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
Client: clientAddr,
}
v, err := json.Marshal(&rb)
if err != nil {
+148 -67
View File
@@ -6,14 +6,19 @@ import (
"errors"
"io"
"net"
"slices"
"strings"
"sync"
"time"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/matcher"
xnet "github.com/go-gost/x/internal/net"
xlogger "github.com/go-gost/x/logger"
"github.com/gobwas/glob"
)
var (
@@ -78,9 +83,11 @@ type localBypass struct {
cidrMatcher matcher.Matcher
addrMatcher matcher.Matcher
wildcardMatcher matcher.Matcher
cancelFunc context.CancelFunc
ipRangeMatcher matcher.Matcher
options options
logger logger.Logger
mu sync.RWMutex
cancelFunc context.CancelFunc
}
// NewBypass creates and initializes a new Bypass.
@@ -91,36 +98,47 @@ func NewBypass(opts ...Option) bypass.Bypass {
opt(&options)
}
ctx, cancel := context.WithCancel(context.TODO())
ctx, cancel := context.WithCancel(context.Background())
bp := &localBypass{
cancelFunc: cancel,
options: options,
p := &localBypass{
cidrMatcher: matcher.NopMatcher(),
addrMatcher: matcher.NopMatcher(),
wildcardMatcher: matcher.NopMatcher(),
ipRangeMatcher: matcher.NopMatcher(),
cancelFunc: cancel,
options: options,
logger: options.logger,
}
if p.logger == nil {
p.logger = xlogger.Nop()
}
if err := bp.reload(ctx); err != nil {
options.logger.Warnf("reload: %v", err)
}
if bp.options.period > 0 {
go bp.periodReload(ctx)
}
go p.periodReload(ctx)
return bp
return p
}
func (bp *localBypass) periodReload(ctx context.Context) error {
period := bp.options.period
func (p *localBypass) periodReload(ctx context.Context) error {
if err := p.reload(ctx); err != nil {
p.logger.Warnf("reload: %v", err)
}
period := p.options.period
if period <= 0 {
return nil
}
if period < time.Second {
period = time.Second
}
ticker := time.NewTicker(period)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := bp.reload(ctx); err != nil {
bp.options.logger.Warnf("reload: %v", err)
if err := p.reload(ctx); err != nil {
p.logger.Warnf("reload: %v", err)
// return err
}
case <-ctx.Done():
@@ -129,84 +147,96 @@ func (bp *localBypass) periodReload(ctx context.Context) error {
}
}
func (bp *localBypass) reload(ctx context.Context) error {
v, err := bp.load(ctx)
func (p *localBypass) reload(ctx context.Context) error {
v, err := p.load(ctx)
if err != nil {
return err
}
patterns := append(bp.options.matchers, v...)
bp.options.logger.Debugf("load items %d", len(patterns))
patterns := append(p.options.matchers, v...)
p.logger.Debugf("load items %d", len(patterns))
var addrs []string
var inets []*net.IPNet
var wildcards []string
var ipRanges []xnet.IPRange
for _, pattern := range patterns {
if _, inet, err := net.ParseCIDR(pattern); err == nil {
inets = append(inets, inet)
continue
}
if strings.ContainsAny(pattern, "*?") {
wildcards = append(wildcards, pattern)
if _, err := glob.Compile(pattern); err == nil {
wildcards = append(wildcards, pattern)
continue
}
}
r := xnet.IPRange{}
if err := r.Parse(pattern); err == nil {
ipRanges = append(ipRanges, r)
continue
}
addrs = append(addrs, pattern)
}
bp.mu.Lock()
defer bp.mu.Unlock()
p.mu.Lock()
defer p.mu.Unlock()
bp.cidrMatcher = matcher.CIDRMatcher(inets)
bp.addrMatcher = matcher.AddrMatcher(addrs)
bp.wildcardMatcher = matcher.WildcardMatcher(wildcards)
p.cidrMatcher = matcher.CIDRMatcher(inets)
p.addrMatcher = matcher.AddrMatcher(addrs)
p.wildcardMatcher = matcher.WildcardMatcher(wildcards)
p.ipRangeMatcher = matcher.IPRangeMatcher(ipRanges)
return nil
}
func (bp *localBypass) load(ctx context.Context) (patterns []string, err error) {
if bp.options.fileLoader != nil {
if lister, ok := bp.options.fileLoader.(loader.Lister); ok {
func (p *localBypass) load(ctx context.Context) (patterns []string, err error) {
if p.options.fileLoader != nil {
if lister, ok := p.options.fileLoader.(loader.Lister); ok {
list, er := lister.List(ctx)
if er != nil {
bp.options.logger.Warnf("file loader: %v", er)
p.logger.Warnf("file loader: %v", er)
}
for _, s := range list {
if line := bp.parseLine(s); line != "" {
if line := p.parseLine(s); line != "" {
patterns = append(patterns, line)
}
}
} else {
r, er := bp.options.fileLoader.Load(ctx)
r, er := p.options.fileLoader.Load(ctx)
if er != nil {
bp.options.logger.Warnf("file loader: %v", er)
p.logger.Warnf("file loader: %v", er)
}
if v, _ := bp.parsePatterns(r); v != nil {
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
}
}
}
if bp.options.redisLoader != nil {
if lister, ok := bp.options.redisLoader.(loader.Lister); ok {
if p.options.redisLoader != nil {
if lister, ok := p.options.redisLoader.(loader.Lister); ok {
list, er := lister.List(ctx)
if er != nil {
bp.options.logger.Warnf("redis loader: %v", er)
p.logger.Warnf("redis loader: %v", er)
}
patterns = append(patterns, list...)
} else {
r, er := bp.options.redisLoader.Load(ctx)
r, er := p.options.redisLoader.Load(ctx)
if er != nil {
bp.options.logger.Warnf("redis loader: %v", er)
p.logger.Warnf("redis loader: %v", er)
}
if v, _ := bp.parsePatterns(r); v != nil {
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
}
}
}
if bp.options.httpLoader != nil {
r, er := bp.options.httpLoader.Load(ctx)
if p.options.httpLoader != nil {
r, er := p.options.httpLoader.Load(ctx)
if er != nil {
bp.options.logger.Warnf("http loader: %v", er)
p.logger.Warnf("http loader: %v", er)
}
if v, _ := bp.parsePatterns(r); v != nil {
if v, _ := p.parsePatterns(r); v != nil {
patterns = append(patterns, v...)
}
}
@@ -214,14 +244,14 @@ func (bp *localBypass) load(ctx context.Context) (patterns []string, err error)
return
}
func (bp *localBypass) parsePatterns(r io.Reader) (patterns []string, err error) {
func (p *localBypass) parsePatterns(r io.Reader) (patterns []string, err error) {
if r == nil {
return
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
if line := bp.parseLine(scanner.Text()); line != "" {
if line := p.parseLine(scanner.Text()); line != "" {
patterns = append(patterns, line)
}
}
@@ -230,19 +260,24 @@ func (bp *localBypass) parsePatterns(r io.Reader) (patterns []string, err error)
return
}
func (bp *localBypass) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
if addr == "" || bp == nil {
func (p *localBypass) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
if addr == "" || p == nil {
return false
}
matched := bp.matched(addr)
matched := p.matched(addr)
b := !p.options.whitelist && matched ||
p.options.whitelist && !matched
log := p.logger.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
})
b := !bp.options.whitelist && matched ||
bp.options.whitelist && !matched
if b {
bp.options.logger.Debugf("bypass: %s, whitelist: %t", addr, bp.options.whitelist)
log.Debugf("bypass: %s, whitelist: %t", addr, p.options.whitelist)
} else {
bp.options.logger.Debugf("pass: %s, whitelist: %t", addr, bp.options.whitelist)
log.Debugf("pass: %s, whitelist: %t", addr, p.options.whitelist)
}
return b
}
@@ -251,18 +286,22 @@ func (p *localBypass) IsWhitelist() bool {
return p.options.whitelist
}
func (bp *localBypass) parseLine(s string) string {
func (p *localBypass) parseLine(s string) string {
if n := strings.IndexByte(s, '#'); n >= 0 {
s = s[:n]
}
return strings.TrimSpace(s)
}
func (bp *localBypass) matched(addr string) bool {
bp.mu.RLock()
defer bp.mu.RUnlock()
func (p *localBypass) matched(addr string) bool {
p.mu.RLock()
defer p.mu.RUnlock()
if bp.addrMatcher.Match(addr) {
if p.ipRangeMatcher.Match(addr) {
return true
}
if p.addrMatcher.Match(addr) {
return true
}
@@ -271,20 +310,62 @@ func (bp *localBypass) matched(addr string) bool {
host = addr
}
if ip := net.ParseIP(host); ip != nil {
return bp.cidrMatcher.Match(host)
if ip := net.ParseIP(host); ip != nil && p.cidrMatcher.Match(host) {
return true
}
return bp.wildcardMatcher.Match(addr)
return p.wildcardMatcher.Match(addr)
}
func (bp *localBypass) Close() error {
bp.cancelFunc()
if bp.options.fileLoader != nil {
bp.options.fileLoader.Close()
func (p *localBypass) Close() error {
p.cancelFunc()
if p.options.fileLoader != nil {
p.options.fileLoader.Close()
}
if bp.options.redisLoader != nil {
bp.options.redisLoader.Close()
if p.options.redisLoader != nil {
p.options.redisLoader.Close()
}
return nil
}
type bypassGroup struct {
bypasses []bypass.Bypass
}
func BypassGroup(bypasses ...bypass.Bypass) bypass.Bypass {
return &bypassGroup{
bypasses: bypasses,
}
}
func (p *bypassGroup) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
var whitelist, blacklist []bool
for _, bypass := range p.bypasses {
result := bypass.Contains(ctx, network, addr, opts...)
if bypass.IsWhitelist() {
whitelist = append(whitelist, result)
} else {
blacklist = append(blacklist, result)
}
}
status := false
if len(whitelist) > 0 {
if slices.Contains(whitelist, false) {
status = false
} else {
status = true
}
}
if !status && len(blacklist) > 0 {
if slices.Contains(blacklist, true) {
status = true
} else {
status = false
}
}
return status
}
func (p *bypassGroup) IsWhitelist() bool {
return false
}
+1
View File
@@ -56,6 +56,7 @@ func (p *grpcPlugin) Contains(ctx context.Context, network, addr string, opts ..
r, err := p.client.Bypass(ctx,
&proto.BypassRequest{
Service: options.Service,
Network: network,
Addr: addr,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
+2
View File
@@ -13,6 +13,7 @@ import (
)
type httpPluginRequest struct {
Service string `json:"service"`
Network string `json:"network"`
Addr string `json:"addr"`
Client string `json:"client"`
@@ -60,6 +61,7 @@ func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts ..
}
rb := httpPluginRequest{
Service: options.Service,
Network: network,
Addr: addr,
Client: string(ctxvalue.ClientIDFromContext(ctx)),
+1 -1
View File
@@ -38,7 +38,7 @@ func (*defaultRoute) Dial(ctx context.Context, network, address string, opts ...
netd := dialer.Dialer{
Interface: options.Interface,
Netns: options.Netns,
Logger: options.Logger,
Log: options.Logger,
}
if options.SockOpts != nil {
netd.Mark = options.SockOpts.Mark
+20 -10
View File
@@ -10,7 +10,8 @@ import (
"github.com/go-gost/core/chain"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
xctx "github.com/go-gost/x/ctx"
ictx "github.com/go-gost/x/internal/ctx"
xnet "github.com/go-gost/x/internal/net"
)
@@ -50,7 +51,7 @@ func (r *Router) Dial(ctx context.Context, network, address string) (conn net.Co
r.record(ctx, recorder.RecorderServiceRouterDialAddress, []byte(host))
log := r.options.Logger.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
"sid": xctx.SidFromContext(ctx),
})
conn, err = r.dial(ctx, network, address, log)
@@ -96,6 +97,11 @@ func (r *Router) dial(ctx context.Context, network, address string, log logger.L
defer cancel()
}
buf := ictx.BufferFromContext(ctx)
if buf != nil {
buf.Reset()
}
var ipAddr string
ipAddr, err = xnet.Resolve(ctx, "ip", address, r.options.Resolver, r.options.HostMapper, log)
if err != nil {
@@ -103,19 +109,23 @@ func (r *Router) dial(ctx context.Context, network, address string, log logger.L
break
}
if buf != nil {
buf.Reset()
}
var route chain.Route
if r.options.Chain != nil {
route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address))
}
if log.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)
log.Debugf("route(retry=%d) %s", i, buf.String())
if buf == nil {
buf = &bytes.Buffer{}
}
for _, node := range routePath(route) {
fmt.Fprintf(buf, "%s@%s > ", node.Name, node.Addr)
}
fmt.Fprintf(buf, "%s", ipAddr)
log.Debugf("route(retry=%d) %s", i, buf.String())
if route == nil {
route = DefaultRoute
@@ -142,7 +152,7 @@ func (r *Router) Bind(ctx context.Context, network, address string, opts ...chai
}
log := r.options.Logger.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
"sid": xctx.SidFromContext(ctx),
})
log.Debugf("bind on %s/%s", address, network)
+214 -57
View File
@@ -10,12 +10,12 @@ import (
"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"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry"
)
@@ -42,24 +42,6 @@ func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error)
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 {
@@ -71,8 +53,6 @@ func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error)
hopConfig := &config.HopConfig{
Name: fmt.Sprintf("%shop-%d", namePrefix, i),
Selector: parseSelector(m),
Nodes: nodes,
Metadata: m,
}
if v := mdutil.GetString(md, "bypass"); v != "" {
@@ -135,15 +115,53 @@ func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error)
}
if v := mdutil.GetString(md, "interface"); v != "" {
hopConfig.Interface = v
m["hop.interface"] = v
delete(m, "interface")
}
if v := mdutil.GetInt(md, "so_mark"); v > 0 {
hopConfig.SockOpts = &config.SockOptsConfig{
Mark: v,
}
m["hop.so_mark"] = v
delete(m, "so_mark")
}
if v := mdutil.GetInt(md, "proxyProtocol"); v > 0 {
m["hop.proxyProtocol"] = v
delete(m, "proxyProtocol")
}
hopMd := map[string]any{}
for k, v := range m {
if strings.HasPrefix(k, "node.") ||
strings.HasPrefix(k, "connector.") ||
strings.HasPrefix(k, "dialer.") {
continue
}
if s, found := strings.CutPrefix(k, "hop."); found {
hopMd[s] = v
delete(m, k)
continue
}
hopMd[k] = v
}
hopConfig.Metadata = hopMd
nodeConfig, err := buildNodeConfig(url, m)
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)
}
hopConfig.Nodes = nodes
chain.Hops = append(chain.Hops, hopConfig)
}
@@ -279,7 +297,7 @@ func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error)
}
if in != "" || out != "" {
limiter.Limits = append(limiter.Limits,
fmt.Sprintf("%s %s %s", traffic.GlobalLimitKey, in, out))
fmt.Sprintf("%s %s %s", traffic.ServiceLimitKey, in, out))
}
if cin != "" || cout != "" {
limiter.Limits = append(limiter.Limits,
@@ -358,9 +376,24 @@ func buildServiceConfig(url *url.URL) ([]*config.ServiceConfig, error) {
listener = schemes[1]
}
addrs := xnet.AddrPortRange(url.Host).Addrs()
if len(addrs) == 0 {
addrs = append(addrs, url.Host)
// For path-based protocols (unix socket, serial), use path as address
isPathBasedProtocol := listener == "unix" || listener == "serial"
var addrs []string
if isPathBasedProtocol {
path := url.EscapedPath()
if url.Host != "" {
addrs = append(addrs, url.Host+strings.TrimPrefix(path, "/"))
} else if path != "" {
addrs = append(addrs, path)
} else {
addrs = append(addrs, url.Host)
}
} else {
addrs = xnet.AddrPortRange(url.Host).Addrs()
if len(addrs) == 0 {
addrs = append(addrs, url.Host)
}
}
var services []*config.ServiceConfig
@@ -382,7 +415,8 @@ func buildServiceConfig(url *url.URL) ([]*config.ServiceConfig, error) {
var nodes []*config.ForwardNodeConfig
// forward mode
if remotes := strings.Trim(url.EscapedPath(), "/"); remotes != "" {
// For path-based protocols, path is address not forward target
if remotes := strings.Trim(url.EscapedPath(), "/"); remotes != "" && !isPathBasedProtocol {
i := 0
for _, addr := range strings.Split(remotes, ",") {
addrs := xnet.AddrPortRange(addr).Addrs()
@@ -435,10 +469,15 @@ func buildServiceConfig(url *url.URL) ([]*config.ServiceConfig, error) {
var auth *config.AuthConfig
if url.User != nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
if strings.HasPrefix(url.Scheme, "ss") {
auth = decodeSIP002Auth(url)
}
if auth == nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
}
auth.Password, _ = url.User.Password()
}
auth.Password, _ = url.User.Password()
}
m := map[string]any{}
@@ -479,19 +518,63 @@ func buildServiceConfig(url *url.URL) ([]*config.ServiceConfig, error) {
}
if v := mdutil.GetString(md, "dns"); v != "" {
md.Set("dns", strings.Split(v, ","))
m["dns"] = strings.Split(v, ",")
}
if v := mdutil.GetString(md, "interface"); v != "" {
m["service.interface"] = v
delete(m, "interface")
}
if v := mdutil.GetInt(md, "so_mark"); v > 0 {
m["service.so_mark"] = v
delete(m, "so_mark")
}
if v := mdutil.GetInt(md, "proxyProtocol"); v > 0 {
m["service.proxyProtocol"] = v
delete(m, "proxyProtocol")
}
if v := mdutil.GetString(md, "netns"); v != "" {
m["service.netns"] = v
delete(m, "netns")
}
if v := mdutil.GetString(md, "netns.out"); v != "" {
m["service.netns.out"] = v
delete(m, "netns.out")
}
selector := parseSelector(m)
serviceMd := map[string]any{}
handleMd := map[string]any{}
listenerMd := map[string]any{}
for k, v := range m {
if s, found := strings.CutPrefix(k, "service."); found {
serviceMd[s] = v
continue
}
if s, found := strings.CutPrefix(k, "handler."); found {
handleMd[s] = v
continue
}
if s, found := strings.CutPrefix(k, "listener."); found {
listenerMd[s] = v
continue
}
handleMd[k] = v
listenerMd[k] = v
serviceMd[k] = v
}
handlerCfg := &config.HandlerConfig{
Type: handler,
Auth: auth,
Metadata: m,
Metadata: handleMd,
}
listenerCfg := &config.ListenerConfig{
Type: listener,
TLS: tlsConfig,
Metadata: m,
Metadata: listenerMd,
}
if listenerCfg.Type == "ssh" || listenerCfg.Type == "sshd" {
handlerCfg.Auth = nil
@@ -504,13 +587,13 @@ func buildServiceConfig(url *url.URL) ([]*config.ServiceConfig, error) {
}
svc.Handler = handlerCfg
svc.Listener = listenerCfg
svc.Metadata = m
svc.Metadata = serviceMd
}
return services, nil
}
func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
func buildNodeConfig(url *url.URL, m map[string]any) (*config.NodeConfig, error) {
var connector, dialer string
schemes := strings.Split(url.Scheme, "+")
if len(schemes) == 1 {
@@ -522,19 +605,8 @@ func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
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"
}
@@ -547,10 +619,15 @@ func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
var auth *config.AuthConfig
if url.User != nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
if strings.HasPrefix(url.Scheme, "ss") {
auth = decodeSIP002Auth(url)
}
if auth == nil {
auth = &config.AuthConfig{
Username: url.User.Username(),
}
auth.Password, _ = url.User.Password()
}
auth.Password, _ = url.User.Password()
}
if sauth := mdutil.GetString(md, "auth"); sauth != "" && auth == nil {
@@ -591,15 +668,59 @@ func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
tlsConfig = nil
}
nodeMd := map[string]any{}
connectorMd := map[string]any{}
dialerMd := map[string]any{}
for k, v := range m {
if s, found := strings.CutPrefix(k, "node."); found {
nodeMd[s] = v
continue
}
if s, found := strings.CutPrefix(k, "connector."); found {
connectorMd[s] = v
continue
}
if s, found := strings.CutPrefix(k, "dialer."); found {
dialerMd[s] = v
continue
}
connectorMd[k] = v
dialerMd[k] = v
nodeMd[k] = v
}
// For path-based protocols (unix socket, serial), use path as address
var nodeAddr string
isPathBasedProtocol := dialer == "unix" || dialer == "serial"
if isPathBasedProtocol {
path := url.EscapedPath()
if url.Host != "" {
// Two slashes: host + path = relative path
nodeAddr = url.Host + strings.TrimPrefix(path, "/")
} else if path != "" {
// Three slashes: path is absolute (keep leading slash)
nodeAddr = path
} else {
nodeAddr = url.Host
}
} else {
nodeAddr = url.Host
}
node := &config.NodeConfig{
Addr: nodeAddr,
Metadata: nodeMd,
}
node.Connector = &config.ConnectorConfig{
Type: connector,
Auth: auth,
Metadata: m,
Metadata: connectorMd,
}
node.Dialer = &config.DialerConfig{
Type: dialer,
TLS: tlsConfig,
Metadata: m,
Metadata: dialerMd,
}
if node.Dialer.Type == "ssh" || node.Dialer.Type == "sshd" {
@@ -631,10 +752,46 @@ func Norm(s string) (*url.URL, error) {
return url, nil
}
func parseAuthFromCmd(sa string) (*config.AuthConfig, error) {
v, err := base64.StdEncoding.DecodeString(sa)
func decodeSIP002Auth(u *url.URL) *config.AuthConfig {
if u.User == nil {
return nil
}
_, hasPassword := u.User.Password()
if hasPassword {
return nil
}
username := u.User.Username()
if username == "" {
return nil
}
decoded, err := base64.RawURLEncoding.DecodeString(username)
if err != nil {
return nil, err
decoded, err = base64.StdEncoding.DecodeString(username)
if err != nil {
return nil
}
}
cs := string(decoded)
n := strings.IndexByte(cs, ':')
if n < 0 {
return nil
}
return &config.AuthConfig{
Username: cs[:n],
Password: cs[n+1:],
}
}
func parseAuthFromCmd(sa string) (*config.AuthConfig, error) {
v, err := base64.RawURLEncoding.DecodeString(sa)
if err != nil {
v, err = base64.StdEncoding.DecodeString(sa)
if err != nil {
return nil, err
}
}
cs := string(v)
n := strings.IndexByte(cs, ':')
+229
View File
@@ -0,0 +1,229 @@
package cmd
import (
"encoding/base64"
"net/url"
"testing"
)
func TestDecodeSIP002Auth(t *testing.T) {
tests := []struct {
name string
rawURL string
wantUsername string
wantPassword string
wantNil bool
}{
{
name: "SIP002 URL-safe base64 without padding",
rawURL: "ss://" + base64.RawURLEncoding.EncodeToString([]byte("aes-256-gcm:password")) + "@server:8388",
wantUsername: "aes-256-gcm",
wantPassword: "password",
},
{
name: "SIP002 standard base64 with padding",
rawURL: "ss://" + base64.StdEncoding.EncodeToString([]byte("aes-256-gcm:pass")) + "@server:8388",
wantUsername: "aes-256-gcm",
wantPassword: "pass",
},
{
name: "standard format with password (not SIP002)",
rawURL: "ss://aes-256-gcm:password@server:8388",
wantNil: true,
},
{
name: "no userinfo",
rawURL: "ss://server:8388",
wantNil: true,
},
{
name: "invalid base64",
rawURL: "ss://not-valid!!!@server:8388",
wantNil: true,
},
{
name: "base64 without colon in decoded",
rawURL: "ss://" + base64.RawURLEncoding.EncodeToString([]byte("nocolon")) + "@server:8388",
wantNil: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u, err := url.Parse(tt.rawURL)
if err != nil {
t.Fatalf("url.Parse(%q): %v", tt.rawURL, err)
}
got := decodeSIP002Auth(u)
if tt.wantNil {
if got != nil {
t.Errorf("decodeSIP002Auth() = %+v, want nil", got)
}
return
}
if got == nil {
t.Fatal("decodeSIP002Auth() = nil, want non-nil")
}
if got.Username != tt.wantUsername {
t.Errorf("Username = %q, want %q", got.Username, tt.wantUsername)
}
if got.Password != tt.wantPassword {
t.Errorf("Password = %q, want %q", got.Password, tt.wantPassword)
}
})
}
}
func TestParseAuthFromCmd(t *testing.T) {
tests := []struct {
name string
input string
wantUsername string
wantPassword string
wantErr bool
}{
{
name: "StdEncoding with padding",
input: base64.StdEncoding.EncodeToString([]byte("user:pass")),
wantUsername: "user",
wantPassword: "pass",
},
{
name: "RawURLEncoding without padding",
input: base64.RawURLEncoding.EncodeToString([]byte("user:pass")),
wantUsername: "user",
wantPassword: "pass",
},
{
name: "invalid base64",
input: "not-valid!!!",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseAuthFromCmd(tt.input)
if tt.wantErr {
if err == nil {
t.Error("parseAuthFromCmd() error = nil, want error")
}
return
}
if err != nil {
t.Fatalf("parseAuthFromCmd() error = %v", err)
}
if got.Username != tt.wantUsername {
t.Errorf("Username = %q, want %q", got.Username, tt.wantUsername)
}
if got.Password != tt.wantPassword {
t.Errorf("Password = %q, want %q", got.Password, tt.wantPassword)
}
})
}
}
func TestBuildNodeConfigSIP002(t *testing.T) {
encoded := base64.RawURLEncoding.EncodeToString([]byte("aes-256-gcm:password"))
rawURL := "ss://" + encoded + "@server:8388"
u, err := url.Parse(rawURL)
if err != nil {
t.Fatalf("url.Parse: %v", err)
}
node, err := buildNodeConfig(u, map[string]any{})
if err != nil {
t.Fatalf("buildNodeConfig: %v", err)
}
if node.Connector == nil || node.Connector.Auth == nil {
t.Fatal("node.Connector.Auth is nil")
}
if node.Connector.Auth.Username != "aes-256-gcm" {
t.Errorf("Username = %q, want %q", node.Connector.Auth.Username, "aes-256-gcm")
}
if node.Connector.Auth.Password != "password" {
t.Errorf("Password = %q, want %q", node.Connector.Auth.Password, "password")
}
}
func TestBuildNodeConfigStandardAuth(t *testing.T) {
rawURL := "http://user:pass@server:8080"
u, err := url.Parse(rawURL)
if err != nil {
t.Fatalf("url.Parse: %v", err)
}
node, err := buildNodeConfig(u, map[string]any{})
if err != nil {
t.Fatalf("buildNodeConfig: %v", err)
}
if node.Connector == nil || node.Connector.Auth == nil {
t.Fatal("node.Connector.Auth is nil")
}
if node.Connector.Auth.Username != "user" {
t.Errorf("Username = %q, want %q", node.Connector.Auth.Username, "user")
}
if node.Connector.Auth.Password != "pass" {
t.Errorf("Password = %q, want %q", node.Connector.Auth.Password, "pass")
}
}
func TestBuildServiceConfigSIP002(t *testing.T) {
encoded := base64.RawURLEncoding.EncodeToString([]byte("aes-256-gcm:password"))
rawURL := "ss://" + encoded + "@:8388"
u, err := url.Parse(rawURL)
if err != nil {
t.Fatalf("url.Parse: %v", err)
}
svcs, err := buildServiceConfig(u)
if err != nil {
t.Fatalf("buildServiceConfig: %v", err)
}
if len(svcs) == 0 {
t.Fatal("no services returned")
}
auth := svcs[0].Handler.Auth
if auth == nil {
t.Fatal("Handler.Auth is nil")
}
if auth.Username != "aes-256-gcm" {
t.Errorf("Username = %q, want %q", auth.Username, "aes-256-gcm")
}
if auth.Password != "password" {
t.Errorf("Password = %q, want %q", auth.Password, "password")
}
}
func TestBuildServiceConfigStandardAuth(t *testing.T) {
rawURL := "http://user:pass@:8080"
u, err := url.Parse(rawURL)
if err != nil {
t.Fatalf("url.Parse: %v", err)
}
svcs, err := buildServiceConfig(u)
if err != nil {
t.Fatalf("buildServiceConfig: %v", err)
}
if len(svcs) == 0 {
t.Fatal("no services returned")
}
auth := svcs[0].Handler.Auth
if auth == nil {
t.Fatal("Handler.Auth is nil")
}
if auth.Username != "user" {
t.Errorf("Username = %q, want %q", auth.Username, "user")
}
if auth.Password != "pass" {
t.Errorf("Password = %q, want %q", auth.Password, "pass")
}
}
+80 -52
View File
@@ -121,6 +121,7 @@ type TLSOptions struct {
MinVersion string `yaml:"minVersion,omitempty" json:"minVersion,omitempty"`
MaxVersion string `yaml:"maxVersion,omitempty" json:"maxVersion,omitempty"`
CipherSuites []string `yaml:"cipherSuites,omitempty" json:"cipherSuites,omitempty"`
ALPN []string `yaml:"alpn,omitempty" json:"alpn,omitempty"`
}
type PluginConfig struct {
@@ -142,8 +143,9 @@ type AutherConfig struct {
}
type AuthConfig struct {
Username string `json:"username"`
Username string `yaml:",omitempty" json:"username,omitempty"`
Password string `yaml:",omitempty" json:"password,omitempty"`
File string `yaml:",omitempty" json:"file,omitempty"`
}
type SelectorConfig struct {
@@ -154,7 +156,7 @@ type SelectorConfig struct {
type AdmissionConfig struct {
Name string `json:"name"`
// DEPRECATED by whitelist since beta.4
// Deprecated: use whitelist instead
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
@@ -167,7 +169,7 @@ type AdmissionConfig struct {
type BypassConfig struct {
Name string `json:"name"`
// DEPRECATED by whitelist since beta.4
// Deprecated: use whitelist instead
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
@@ -185,6 +187,7 @@ type FileLoader struct {
type RedisLoader struct {
Addr string `json:"addr"`
DB int `yaml:",omitempty" json:"db,omitempty"`
Username string `yaml:",omitempty" json:"username,omitempty"`
Password string `yaml:",omitempty" json:"password,omitempty"`
Key string `yaml:",omitempty" json:"key,omitempty"`
Type string `yaml:",omitempty" json:"type,omitempty"`
@@ -250,7 +253,9 @@ type SDConfig struct {
}
type RouterRouteConfig struct {
Net string `json:"net"`
// Deprecated: use dst instead
Net string `yaml:",omitempty" json:"net,omitempty"`
Dst string `yaml:",omitempty" json:"dst,omitempty"`
Gateway string `json:"gateway"`
}
@@ -274,8 +279,9 @@ type RecorderConfig struct {
}
type FileRecorder struct {
Path string `json:"path"`
Sep string `yaml:",omitempty" json:"sep,omitempty"`
Path string `json:"path"`
Sep string `yaml:",omitempty" json:"sep,omitempty"`
Rotation *LogRotationConfig `yaml:",omitempty" json:"rotation,omitempty"`
}
type TCPRecorder struct {
@@ -292,6 +298,7 @@ type HTTPRecorder struct {
type RedisRecorder struct {
Addr string `json:"addr"`
DB int `yaml:",omitempty" json:"db,omitempty"`
Username string `yaml:",omitempty" json:"username,omitempty"`
Password string `yaml:",omitempty" json:"password,omitempty"`
Key string `yaml:",omitempty" json:"key,omitempty"`
Type string `yaml:",omitempty" json:"type,omitempty"`
@@ -344,7 +351,7 @@ type HandlerConfig struct {
}
type ForwarderConfig struct {
// DEPRECATED by hop field
// Deprecated: use hop instead
Name string `yaml:",omitempty" json:"name,omitempty"`
// the referenced hop name
Hop string `yaml:",omitempty" json:"hop,omitempty"`
@@ -358,18 +365,20 @@ type ForwardNodeConfig struct {
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
// Deprecated: use matcher instead
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
// DEPRECATED by filter.host
// Deprecated: use matcher instead
Host string `yaml:",omitempty" json:"host,omitempty"`
// DEPRECATED by filter.path
// Deprecated: use matcher instead
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"`
// Deprecated: use matcher instead
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
// Deprecated: use http.auth instead
Auth *AuthConfig `yaml:",omitempty" json:"auth,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 {
@@ -390,13 +399,24 @@ type NodeFilterConfig struct {
Path string `yaml:",omitempty" json:"path,omitempty"`
}
type NodeMatcherConfig struct {
Rule string `yaml:",omitempty" json:"rule,omitempty"`
Priority int `yaml:",omitempty" json:"priority,omitempty"`
}
type HTTPNodeConfig struct {
// rewrite host header
Host string `yaml:",omitempty" json:"host,omitempty"`
// additional request header
// Deprecated: use requestHeader instead
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
// rewrite URL
// additional request header
RequestHeader map[string]string `yaml:"requestHeader,omitempty" json:"requestHeader,omitempty"`
// additional response header
ResponseHeader map[string]string `yaml:"responseHeader,omitempty" json:"responseHeader,omitempty"`
// Deprecated: use rewriteURL instead
Rewrite []HTTPURLRewriteConfig `yaml:",omitempty" json:"rewrite,omitempty"`
// rewrite URL
RewriteURL []HTTPURLRewriteConfig `yaml:"rewriteURL,omitempty" json:"rewriteURL,omitempty"`
// rewrite response body
RewriteBody []HTTPBodyRewriteConfig `yaml:"rewriteBody,omitempty" json:"rewriteBody,omitempty"`
// HTTP basic auth
@@ -430,9 +450,9 @@ type SockOptsConfig struct {
type ServiceConfig struct {
Name string `json:"name"`
Addr string `yaml:",omitempty" json:"addr,omitempty"`
// DEPRECATED by metadata.interface since beta.5
// Deprecated: use metadata.interface instead
Interface string `yaml:",omitempty" json:"interface,omitempty"`
// DEPRECATED by metadata.so_mark since beta.5
// Deprecated: use metadata.so_mark instead
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
Admission string `yaml:",omitempty" json:"admission,omitempty"`
Admissions []string `yaml:",omitempty" json:"admissions,omitempty"`
@@ -487,40 +507,47 @@ type ChainGroupConfig struct {
}
type HopConfig struct {
Name string `json:"name"`
Interface string `yaml:",omitempty" json:"interface,omitempty"`
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
Selector *SelectorConfig `yaml:",omitempty" json:"selector,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"`
Nodes []*NodeConfig `yaml:",omitempty" json:"nodes,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"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
Name string `json:"name"`
// Deprecated: use metadata.interface instead
Interface string `yaml:",omitempty" json:"interface,omitempty"`
// Deprecated: use metadata.so_mark instead
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
Selector *SelectorConfig `yaml:",omitempty" json:"selector,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"`
Nodes []*NodeConfig `yaml:",omitempty" json:"nodes,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"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
}
type NodeConfig struct {
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"`
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"`
// Deprecated: use metadata.interface instead
Interface string `yaml:",omitempty" json:"interface,omitempty"`
// Deprecated: use metadata.netns instead
Netns string `yaml:",omitempty" json:"netns,omitempty"`
// Deprecated: use metadata.so_mark instead
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
// Deprecated: use matcher instead
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,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 {
@@ -556,7 +583,8 @@ func (c *Config) Load() error {
return v.Unmarshal(c)
}
func (c *Config) Read(r io.Reader) error {
func (c *Config) Read(r io.Reader, configType string) error {
v.SetConfigType(configType)
if err := v.ReadConfig(r); err != nil {
return err
}
+232
View File
@@ -0,0 +1,232 @@
package loader
import (
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
admission_parser "github.com/go-gost/x/config/parsing/admission"
auth_parser "github.com/go-gost/x/config/parsing/auth"
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
chain_parser "github.com/go-gost/x/config/parsing/chain"
hop_parser "github.com/go-gost/x/config/parsing/hop"
hosts_parser "github.com/go-gost/x/config/parsing/hosts"
ingress_parser "github.com/go-gost/x/config/parsing/ingress"
limiter_parser "github.com/go-gost/x/config/parsing/limiter"
logger_parser "github.com/go-gost/x/config/parsing/logger"
observer_parser "github.com/go-gost/x/config/parsing/observer"
recorder_parser "github.com/go-gost/x/config/parsing/recorder"
resolver_parser "github.com/go-gost/x/config/parsing/resolver"
router_parser "github.com/go-gost/x/config/parsing/router"
sd_parser "github.com/go-gost/x/config/parsing/sd"
service_parser "github.com/go-gost/x/config/parsing/service"
"github.com/go-gost/x/registry"
)
var (
defaultLoader *loader = &loader{}
)
func Load(cfg *config.Config) error {
return defaultLoader.Load(cfg)
}
type loader struct{}
func (l *loader) Load(cfg *config.Config) error {
logCfg := cfg.Log
if logCfg == nil {
logCfg = &config.LogConfig{}
}
logger.SetDefault(logger_parser.ParseLogger(&config.LoggerConfig{Log: logCfg}))
tlsCfg, err := parsing.BuildDefaultTLSConfig(cfg.TLS)
if err != nil {
return err
}
parsing.SetDefaultTLSConfig(tlsCfg)
if err := register(cfg); err != nil {
return err
}
return nil
}
func register(cfg *config.Config) error {
if cfg == nil {
return nil
}
for name := range registry.LoggerRegistry().GetAll() {
registry.LoggerRegistry().Unregister(name)
}
for _, loggerCfg := range cfg.Loggers {
if err := registry.LoggerRegistry().Register(loggerCfg.Name, logger_parser.ParseLogger(loggerCfg)); err != nil {
return err
}
}
for name := range registry.AutherRegistry().GetAll() {
registry.AutherRegistry().Unregister(name)
}
for _, autherCfg := range cfg.Authers {
if err := registry.AutherRegistry().Register(autherCfg.Name, auth_parser.ParseAuther(autherCfg)); err != nil {
return err
}
}
for name := range registry.AdmissionRegistry().GetAll() {
registry.AdmissionRegistry().Unregister(name)
}
for _, admissionCfg := range cfg.Admissions {
if err := registry.AdmissionRegistry().Register(admissionCfg.Name, admission_parser.ParseAdmission(admissionCfg)); err != nil {
return err
}
}
for name := range registry.BypassRegistry().GetAll() {
registry.BypassRegistry().Unregister(name)
}
for _, bypassCfg := range cfg.Bypasses {
if err := registry.BypassRegistry().Register(bypassCfg.Name, bypass_parser.ParseBypass(bypassCfg)); err != nil {
return err
}
}
for name := range registry.ResolverRegistry().GetAll() {
registry.ResolverRegistry().Unregister(name)
}
for _, resolverCfg := range cfg.Resolvers {
r, err := resolver_parser.ParseResolver(resolverCfg)
if err != nil {
return err
}
if err := registry.ResolverRegistry().Register(resolverCfg.Name, r); err != nil {
return err
}
}
for name := range registry.HostsRegistry().GetAll() {
registry.HostsRegistry().Unregister(name)
}
for _, hostsCfg := range cfg.Hosts {
if err := registry.HostsRegistry().Register(hostsCfg.Name, hosts_parser.ParseHostMapper(hostsCfg)); err != nil {
return err
}
}
for name := range registry.IngressRegistry().GetAll() {
registry.IngressRegistry().Unregister(name)
}
for _, ingressCfg := range cfg.Ingresses {
if err := registry.IngressRegistry().Register(ingressCfg.Name, ingress_parser.ParseIngress(ingressCfg)); err != nil {
return err
}
}
for name := range registry.RouterRegistry().GetAll() {
registry.RouterRegistry().Unregister(name)
}
for _, routerCfg := range cfg.Routers {
if err := registry.RouterRegistry().Register(routerCfg.Name, router_parser.ParseRouter(routerCfg)); err != nil {
return err
}
}
for name := range registry.SDRegistry().GetAll() {
registry.SDRegistry().Unregister(name)
}
for _, sdCfg := range cfg.SDs {
if err := registry.SDRegistry().Register(sdCfg.Name, sd_parser.ParseSD(sdCfg)); err != nil {
return err
}
}
for name := range registry.ObserverRegistry().GetAll() {
registry.ObserverRegistry().Unregister(name)
}
for _, observerCfg := range cfg.Observers {
if err := registry.ObserverRegistry().Register(observerCfg.Name, observer_parser.ParseObserver(observerCfg)); err != nil {
return err
}
}
for name := range registry.RecorderRegistry().GetAll() {
registry.RecorderRegistry().Unregister(name)
}
for _, recorderCfg := range cfg.Recorders {
if err := registry.RecorderRegistry().Register(recorderCfg.Name, recorder_parser.ParseRecorder(recorderCfg)); err != nil {
return err
}
}
for name := range registry.TrafficLimiterRegistry().GetAll() {
registry.TrafficLimiterRegistry().Unregister(name)
}
for _, limiterCfg := range cfg.Limiters {
if err := registry.TrafficLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseTrafficLimiter(limiterCfg)); err != nil {
return err
}
}
for name := range registry.ConnLimiterRegistry().GetAll() {
registry.ConnLimiterRegistry().Unregister(name)
}
for _, limiterCfg := range cfg.CLimiters {
if err := registry.ConnLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseConnLimiter(limiterCfg)); err != nil {
return err
}
}
for name := range registry.RateLimiterRegistry().GetAll() {
registry.RateLimiterRegistry().Unregister(name)
}
for _, limiterCfg := range cfg.RLimiters {
if err := registry.RateLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseRateLimiter(limiterCfg)); err != nil {
return err
}
}
for name := range registry.HopRegistry().GetAll() {
registry.HopRegistry().Unregister(name)
}
for _, hopCfg := range cfg.Hops {
hop, err := hop_parser.ParseHop(hopCfg, logger.Default())
if err != nil {
return err
}
if err := registry.HopRegistry().Register(hopCfg.Name, hop); err != nil {
return err
}
}
for name := range registry.ChainRegistry().GetAll() {
registry.ChainRegistry().Unregister(name)
}
for _, chainCfg := range cfg.Chains {
c, err := chain_parser.ParseChain(chainCfg, logger.Default())
if err != nil {
return err
}
if err := registry.ChainRegistry().Register(chainCfg.Name, c); err != nil {
return err
}
}
for name := range registry.ServiceRegistry().GetAll() {
registry.ServiceRegistry().Unregister(name)
}
for _, svcCfg := range cfg.Services {
svc, err := service_parser.ParseService(svcCfg)
if err != nil {
return err
}
if svc != nil {
if err := registry.ServiceRegistry().Register(svcCfg.Name, svc); err != nil {
return err
}
}
}
return nil
}
+1
View File
@@ -59,6 +59,7 @@ func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
opts = append(opts, xadmission.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
+52 -1
View File
@@ -1,8 +1,12 @@
package auth
import (
"bufio"
"crypto/tls"
"io"
"net/url"
"os"
"strings"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/logger"
@@ -67,6 +71,7 @@ func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
opts = append(opts, xauth.RedisLoaderOption(loader.RedisHashLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -97,7 +102,20 @@ func ParseAutherFromAuth(au *config.AuthConfig) auth.Authenticator {
}
func Info(cfg *config.AuthConfig) *url.Userinfo {
if cfg == nil || cfg.Username == "" {
if cfg == nil {
return nil
}
if cfg.File != "" {
if f, _ := os.Open(cfg.File); f != nil {
defer f.Close()
if infos, _ := parseInfo(f, 1); len(infos) > 0 {
return infos[0]
}
}
}
if cfg.Username == "" {
return nil
}
@@ -107,6 +125,39 @@ func Info(cfg *config.AuthConfig) *url.Userinfo {
return url.UserPassword(cfg.Username, cfg.Password)
}
func parseInfo(r io.Reader, max int) (infos []*url.Userinfo, err error) {
if r == nil {
return
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
// line := strings.Replace(scanner.Text(), "\t", " ", -1)
line := strings.TrimSpace(scanner.Text())
if n := strings.IndexByte(line, '#'); n == 0 {
continue
}
sp := strings.SplitN(line, " ", 2)
if len(sp) == 1 {
if k := strings.TrimSpace(sp[0]); k != "" {
infos = append(infos, url.User(k))
}
}
if len(sp) == 2 {
if k := strings.TrimSpace(sp[0]); k != "" {
infos = append(infos, url.UserPassword(k, strings.TrimSpace(sp[1])))
}
}
if max > 0 && len(infos) >= max {
break
}
}
err = scanner.Err()
return
}
func List(name string, names ...string) []auth.Authenticator {
var authers []auth.Authenticator
if auther := registry.AutherRegistry().Get(name); auther != nil {
+1
View File
@@ -59,6 +59,7 @@ func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
opts = append(opts, xbypass.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
+55 -12
View File
@@ -4,11 +4,10 @@ import (
"crypto/tls"
"strings"
"github.com/go-gost/core/bypass"
"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"
xbypass "github.com/go-gost/x/bypass"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
@@ -19,6 +18,7 @@ import (
"github.com/go-gost/x/internal/loader"
"github.com/go-gost/x/internal/plugin"
"github.com/go-gost/x/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
@@ -50,6 +50,8 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
}
}
var ppv int
var soMark int
ifce := cfg.Interface
var netns string
if cfg.Metadata != nil {
@@ -57,7 +59,15 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
if v := mdutil.GetString(md, parsing.MDKeyInterface); v != "" {
ifce = v
}
netns = mdutil.GetString(md, "netns")
if cfg.SockOpts != nil {
soMark = cfg.SockOpts.Mark
}
if v := mdutil.GetInt(md, parsing.MDKeySoMark); v > 0 {
soMark = v
}
ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol)
netns = mdutil.GetString(md, parsing.MDKeyNetns)
}
var nodes []*chain.Node
@@ -66,21 +76,53 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
continue
}
m := v.Metadata
if m == nil {
m = map[string]any{}
v.Metadata = m
}
md := metadata.NewMetadata(m)
if v.Resolver == "" {
v.Resolver = cfg.Resolver
}
if v.Hosts == "" {
v.Hosts = cfg.Hosts
}
if v.Interface == "" {
v.Interface = ifce
}
if v.Netns == "" {
v.Netns = netns
}
if v.SockOpts == nil {
v.SockOpts = cfg.SockOpts
if !md.IsExists(parsing.MDKeyInterface) {
// inherit from hop
if ifce != "" {
m[parsing.MDKeyInterface] = ifce
}
// node level
if v.Interface != "" {
m[parsing.MDKeyInterface] = v.Interface
}
}
if !md.IsExists(parsing.MDKeySoMark) {
// inherit from hop
if soMark != 0 {
m[parsing.MDKeySoMark] = soMark
}
// node level
if v.SockOpts != nil && v.SockOpts.Mark != 0 {
m[parsing.MDKeySoMark] = v.SockOpts.Mark
}
}
if !md.IsExists(parsing.MDKeyProxyProtocol) && ppv > 0 {
// inherit from hop
m[parsing.MDKeyProxyProtocol] = ppv
}
if !md.IsExists(parsing.MDKeyNetns) {
// inherit from hop
if netns != "" {
m[parsing.MDKeyNetns] = netns
}
// node level
if v.Netns != "" {
m[parsing.MDKeyNetns] = v.Name
}
}
if v.Connector == nil {
@@ -115,7 +157,7 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
xhop.NameOption(cfg.Name),
xhop.NodeOption(nodes...),
xhop.SelectorOption(sel),
xhop.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
xhop.BypassOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
xhop.ReloadPeriodOption(cfg.Reload),
xhop.LoggerOption(log.WithFields(map[string]any{
"kind": "hop",
@@ -130,6 +172,7 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
opts = append(opts, xhop.RedisLoaderOption(loader.RedisStringLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
+2
View File
@@ -75,6 +75,7 @@ func ParseHostMapper(cfg *config.HostsConfig) hosts.HostMapper {
opts = append(opts, xhosts.RedisLoaderOption(loader.RedisListLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -82,6 +83,7 @@ func ParseHostMapper(cfg *config.HostsConfig) hosts.HostMapper {
opts = append(opts, xhosts.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
+2
View File
@@ -70,6 +70,7 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
opts = append(opts, xingress.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -77,6 +78,7 @@ func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
opts = append(opts, xingress.RedisLoaderOption(loader.RedisHashLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
+6
View File
@@ -57,6 +57,7 @@ func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter)
opts = append(opts, xtraffic.RedisLoaderOption(loader.RedisListLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -64,6 +65,7 @@ func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter)
opts = append(opts, xtraffic.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -103,6 +105,7 @@ func ParseConnLimiter(cfg *config.LimiterConfig) (lim conn.ConnLimiter) {
opts = append(opts, xconn.RedisLoaderOption(loader.RedisListLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -110,6 +113,7 @@ func ParseConnLimiter(cfg *config.LimiterConfig) (lim conn.ConnLimiter) {
opts = append(opts, xconn.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -149,6 +153,7 @@ func ParseRateLimiter(cfg *config.LimiterConfig) (lim rate.RateLimiter) {
opts = append(opts, xrate.RedisLoaderOption(loader.RedisListLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -156,6 +161,7 @@ func ParseRateLimiter(cfg *config.LimiterConfig) (lim rate.RateLimiter) {
opts = append(opts, xrate.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
+1 -1
View File
@@ -25,7 +25,7 @@ func ParseLogger(cfg *config.LoggerConfig) logger.Logger {
var out io.Writer = os.Stderr
switch cfg.Log.Output {
case "none", "null":
return xlogger.Nop()
out = io.Discard
case "stdout":
out = os.Stdout
case "stderr", "":
+44 -23
View File
@@ -6,14 +6,12 @@ import (
"regexp"
"strings"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
xauth "github.com/go-gost/x/auth"
xbypass "github.com/go-gost/x/bypass"
xchain "github.com/go-gost/x/chain"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
@@ -21,7 +19,9 @@ import (
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
tls_util "github.com/go-gost/x/internal/util/tls"
mdx "github.com/go-gost/x/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry"
"github.com/go-gost/x/routing"
)
func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.Node, error) {
@@ -64,11 +64,6 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
return nil, err
}
var nm metadata.Metadata
if cfg.Metadata != nil {
nm = mdx.NewMetadata(cfg.Metadata)
}
connectorLogger := nodeLogger.WithFields(map[string]any{
"kind": "connector",
})
@@ -104,10 +99,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
return nil, err
}
var ppv int
if nm != nil {
ppv = mdutil.GetInt(nm, parsing.MDKeyProxyProtocol)
}
md := mdx.NewMetadata(cfg.Metadata)
dialerLogger := nodeLogger.WithFields(map[string]any{
"kind": "dialer",
@@ -119,7 +111,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
dialer.AuthOption(auth_parser.Info(cfg.Dialer.Auth)),
dialer.TLSConfigOption(tlsConfig),
dialer.LoggerOption(dialerLogger),
dialer.ProxyProtocolOption(ppv),
dialer.ProxyProtocolOption(mdutil.GetInt(md, parsing.MDKeyProxyProtocol)),
)
} else {
return nil, fmt.Errorf("unregistered dialer: %s", cfg.Dialer.Type)
@@ -134,25 +126,25 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
}
var sockOpts *chain.SockOpts
if cfg.SockOpts != nil {
if v := mdutil.GetInt(md, parsing.MDKeySoMark); v != 0 {
sockOpts = &chain.SockOpts{
Mark: cfg.SockOpts.Mark,
Mark: v,
}
}
tr := xchain.NewTransport(d, cr,
chain.AddrTransportOption(cfg.Addr),
chain.InterfaceTransportOption(cfg.Interface),
chain.NetnsTransportOption(cfg.Netns),
chain.InterfaceTransportOption(mdutil.GetString(md, parsing.MDKeyInterface)),
chain.NetnsTransportOption(mdutil.GetString(md, parsing.MDKeyNetns)),
chain.SockOptsTransportOption(sockOpts),
)
opts := []chain.NodeOption{
chain.TransportNodeOption(tr),
chain.BypassNodeOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
chain.BypassNodeOption(xbypass.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.MetadataNodeOption(md),
chain.NetworkNodeOption(cfg.Network),
}
@@ -175,10 +167,33 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
opts = append(opts, chain.NodeFilterOption(settings))
}
if cfg.Matcher != nil {
priority := cfg.Matcher.Priority
if rule := strings.TrimSpace(cfg.Matcher.Rule); rule != "" {
if matcher, err := routing.NewMatcher(rule); err == nil {
log.Debugf("new matcher for node %s with rule %s", cfg.Name, cfg.Matcher.Rule)
if priority == 0 {
priority = len(cfg.Matcher.Rule)
}
opts = append(opts, chain.MatcherNodeOption(matcher))
} else {
log.Error(err)
priority = -1
}
}
opts = append(opts, chain.PriorityNodeOption(priority))
}
if cfg.HTTP != nil {
settings := &chain.HTTPNodeSettings{
Host: cfg.HTTP.Host,
Header: cfg.HTTP.Header,
Host: cfg.HTTP.Host,
RequestHeader: cfg.HTTP.RequestHeader,
ResponseHeader: cfg.HTTP.ResponseHeader,
}
if settings.RequestHeader == nil {
settings.RequestHeader = cfg.HTTP.Header
}
if auth := cfg.HTTP.Auth; auth != nil && auth.Username != "" {
@@ -191,7 +206,12 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
})),
)
}
for _, v := range cfg.HTTP.Rewrite {
rewriteURL := cfg.HTTP.RewriteURL
if rewriteURL == nil {
rewriteURL = cfg.HTTP.Rewrite
}
for _, v := range rewriteURL {
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
settings.RewriteURL = append(settings.RewriteURL, chain.HTTPURLRewriteSetting{
Pattern: pattern,
@@ -201,7 +221,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
}
for _, v := range cfg.HTTP.RewriteBody {
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
settings.RewriteBody = append(settings.RewriteBody, chain.HTTPBodyRewriteSettings{
settings.RewriteResponseBody = append(settings.RewriteResponseBody, chain.HTTPBodyRewriteSettings{
Type: v.Type,
Pattern: pattern,
Replacement: []byte(v.Replacement),
@@ -220,6 +240,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
tlsCfg.Options.MinVersion = o.MinVersion
tlsCfg.Options.MaxVersion = o.MaxVersion
tlsCfg.Options.CipherSuites = o.CipherSuites
tlsCfg.Options.ALPN = o.ALPN
}
opts = append(opts, chain.TLSNodeOption(tlsCfg))
}
+14
View File
@@ -15,4 +15,18 @@ const (
MDKeyRecorderDirection = "direction"
MDKeyRecorderTimestampFormat = "timeStampFormat"
MDKeyRecorderHexdump = "hexdump"
MDKeyRecorderHTTPBody = "http.body"
MDKeyRecorderHTTPMaxBodySize = "http.maxBodySize"
MDKeyLimiterRefreshInterval = "limiter.refreshInterval"
MDKeyLimiterCleanupInterval = "limiter.cleanupInterval"
MDKeyLimiterScope = "limiter.scope"
MDKeyObserverResetTraffic = "observer.resetTraffic"
MDKeyObserverPeriod = "observer.period"
MDKeyNetns = "netns"
MDKeyNetnsOut = "netns.out"
MDKeyDialTimeout = "dialTimeout"
)
+220
View File
@@ -0,0 +1,220 @@
package parser
import (
"bufio"
"encoding/json"
"os"
"strings"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/cmd"
xmd "github.com/go-gost/x/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
var (
defaultParser = &parser{}
)
func Init(args Args) {
defaultParser = &parser{
args: args,
}
}
func Parse() (*config.Config, error) {
return defaultParser.Parse()
}
type Args struct {
CfgFile string
Services []string
Nodes []string
Debug bool
Trace bool
ApiAddr string
MetricsAddr string
}
type parser struct {
args Args
}
func (p *parser) Parse() (*config.Config, error) {
cfg := &config.Config{}
if cfgFile := strings.TrimSpace(p.args.CfgFile); cfgFile != "" {
if cfgFile == "-" { // stdin
br := bufio.NewReader(os.Stdin)
b, err := br.Peek(1)
if err != nil {
return nil, err
}
if b[0] == '{' {
if err := cfg.Read(br, "json"); err != nil {
return nil, err
}
} else {
if err := cfg.Read(br, "yaml"); err != nil {
return nil, err
}
}
} else if strings.HasPrefix(cfgFile, "{") && strings.HasSuffix(cfgFile, "}") { // inline
if err := json.Unmarshal([]byte(cfgFile), cfg); err != nil {
return nil, err
}
} else {
if err := cfg.ReadFile(cfgFile); err != nil { // file
return nil, err
}
}
}
cmdCfg, err := cmd.BuildConfigFromCmd(p.args.Services, p.args.Nodes)
if err != nil {
return nil, err
}
cfg = mergeConfig(cfg, cmdCfg)
if len(cfg.Services) == 0 && p.args.ApiAddr == "" && cfg.API == nil {
if err := cfg.Load(); err != nil {
return nil, err
}
}
if v := os.Getenv("GOST_LOGGER_LEVEL"); v != "" {
cfg.Log = &config.LogConfig{
Level: v,
}
}
if v := os.Getenv("GOST_API"); v != "" {
cfg.API = &config.APIConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_METRICS"); v != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: v,
}
}
if v := os.Getenv("GOST_PROFILING"); v != "" {
cfg.Profiling = &config.ProfilingConfig{
Addr: v,
}
}
if p.args.Debug || p.args.Trace {
if cfg.Log == nil {
cfg.Log = &config.LogConfig{}
}
cfg.Log.Level = string(logger.DebugLevel)
if p.args.Trace {
cfg.Log.Level = string(logger.TraceLevel)
}
}
if p.args.ApiAddr != "" {
cfg.API = &config.APIConfig{
Addr: p.args.ApiAddr,
}
if url, _ := cmd.Norm(p.args.ApiAddr); url != nil {
cfg.API.Addr = url.Host
if url.User != nil {
username := url.User.Username()
password, _ := url.User.Password()
cfg.API.Auth = &config.AuthConfig{
Username: username,
Password: password,
}
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := xmd.NewMetadata(m)
cfg.API.PathPrefix = mdutil.GetString(md, "pathPrefix")
cfg.API.AccessLog = mdutil.GetBool(md, "accesslog")
}
}
if p.args.MetricsAddr != "" {
cfg.Metrics = &config.MetricsConfig{
Addr: p.args.MetricsAddr,
}
if url, _ := cmd.Norm(p.args.MetricsAddr); url != nil {
cfg.Metrics.Addr = url.Host
if url.User != nil {
username := url.User.Username()
password, _ := url.User.Password()
cfg.Metrics.Auth = &config.AuthConfig{
Username: username,
Password: password,
}
}
m := map[string]any{}
for k, v := range url.Query() {
if len(v) > 0 {
m[k] = v[0]
}
}
md := xmd.NewMetadata(m)
cfg.Metrics.Path = mdutil.GetString(md, "path")
}
}
return cfg, nil
}
func mergeConfig(cfg1, cfg2 *config.Config) *config.Config {
if cfg1 == nil {
return cfg2
}
if cfg2 == nil {
return cfg1
}
cfg := &config.Config{
Services: append(cfg1.Services, cfg2.Services...),
Chains: append(cfg1.Chains, cfg2.Chains...),
Hops: append(cfg1.Hops, cfg2.Hops...),
Authers: append(cfg1.Authers, cfg2.Authers...),
Admissions: append(cfg1.Admissions, cfg2.Admissions...),
Bypasses: append(cfg1.Bypasses, cfg2.Bypasses...),
Resolvers: append(cfg1.Resolvers, cfg2.Resolvers...),
Hosts: append(cfg1.Hosts, cfg2.Hosts...),
Ingresses: append(cfg1.Ingresses, cfg2.Ingresses...),
SDs: append(cfg1.SDs, cfg2.SDs...),
Recorders: append(cfg1.Recorders, cfg2.Recorders...),
Limiters: append(cfg1.Limiters, cfg2.Limiters...),
CLimiters: append(cfg1.CLimiters, cfg2.CLimiters...),
RLimiters: append(cfg1.RLimiters, cfg2.RLimiters...),
Loggers: append(cfg1.Loggers, cfg2.Loggers...),
Routers: append(cfg1.Routers, cfg2.Routers...),
Observers: append(cfg1.Observers, cfg2.Observers...),
TLS: cfg1.TLS,
Log: cfg1.Log,
API: cfg1.API,
Metrics: cfg1.Metrics,
Profiling: cfg1.Profiling,
}
if cfg2.TLS != nil {
cfg.TLS = cfg2.TLS
}
if cfg2.Log != nil {
cfg.Log = cfg2.Log
}
if cfg2.API != nil {
cfg.API = cfg2.API
}
if cfg2.Metrics != nil {
cfg.Metrics = cfg2.Metrics
}
if cfg2.Profiling != nil {
cfg.Profiling = cfg2.Profiling
}
return cfg
}
+45 -3
View File
@@ -2,16 +2,26 @@ package recorder
import (
"crypto/tls"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
"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"
"gopkg.in/natefinch/lumberjack.v2"
)
type discardCloser struct{}
func (discardCloser) Write(p []byte) (n int, err error) { return len(p), nil }
func (discardCloser) Close() error { return nil }
func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
if cfg == nil {
return nil
@@ -42,13 +52,38 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
}
if cfg.File != nil && cfg.File.Path != "" {
return xrecorder.FileRecorder(cfg.File.Path,
xrecorder.SepRecorderOption(cfg.File.Sep),
var out io.WriteCloser = discardCloser{}
if cfg.File.Rotation != nil {
out = &lumberjack.Logger{
Filename: cfg.File.Path,
MaxSize: cfg.File.Rotation.MaxSize,
MaxAge: cfg.File.Rotation.MaxAge,
MaxBackups: cfg.File.Rotation.MaxBackups,
LocalTime: cfg.File.Rotation.LocalTime,
Compress: cfg.File.Rotation.Compress,
}
} else {
os.MkdirAll(filepath.Dir(cfg.File.Path), 0755)
f, err := os.OpenFile(cfg.File.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logger.Default().Warn(err)
} else {
out = f
}
}
return xrecorder.FileRecorder(out,
xrecorder.RecorderFileRecorderOption(cfg.Name),
xrecorder.SepFileRecorderOption(cfg.File.Sep),
)
}
if cfg.TCP != nil && cfg.TCP.Addr != "" {
return xrecorder.TCPRecorder(cfg.TCP.Addr, xrecorder.TimeoutTCPRecorderOption(cfg.TCP.Timeout))
return xrecorder.TCPRecorder(cfg.TCP.Addr,
xrecorder.RecorderTCPRecorderOption(cfg.Name),
xrecorder.TimeoutTCPRecorderOption(cfg.TCP.Timeout),
)
}
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
@@ -57,6 +92,7 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
h.Add(k, v)
}
return xrecorder.HTTPRecorder(cfg.HTTP.URL,
xrecorder.RecorderHTTPRecorderOption(cfg.Name),
xrecorder.TimeoutHTTPRecorderOption(cfg.HTTP.Timeout),
xrecorder.HeaderHTTPRecorderOption(h),
)
@@ -68,20 +104,26 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
switch cfg.Redis.Type {
case "list": // redis list
return xrecorder.RedisListRecorder(cfg.Redis.Addr,
xrecorder.RecorderRedisRecorderOption(cfg.Name),
xrecorder.DBRedisRecorderOption(cfg.Redis.DB),
xrecorder.KeyRedisRecorderOption(cfg.Redis.Key),
xrecorder.UsernameRedisRecorderOption(cfg.Redis.Username),
xrecorder.PasswordRedisRecorderOption(cfg.Redis.Password),
)
case "sset": // sorted set
return xrecorder.RedisSortedSetRecorder(cfg.Redis.Addr,
xrecorder.RecorderRedisRecorderOption(cfg.Name),
xrecorder.DBRedisRecorderOption(cfg.Redis.DB),
xrecorder.KeyRedisRecorderOption(cfg.Redis.Key),
xrecorder.UsernameRedisRecorderOption(cfg.Redis.Username),
xrecorder.PasswordRedisRecorderOption(cfg.Redis.Password),
)
default: // redis set
return xrecorder.RedisSetRecorder(cfg.Redis.Addr,
xrecorder.RecorderRedisRecorderOption(cfg.Name),
xrecorder.DBRedisRecorderOption(cfg.Redis.DB),
xrecorder.KeyRedisRecorderOption(cfg.Redis.Key),
xrecorder.UsernameRedisRecorderOption(cfg.Redis.Username),
xrecorder.PasswordRedisRecorderOption(cfg.Redis.Password),
)
}
+17 -5
View File
@@ -45,18 +45,27 @@ func ParseRouter(cfg *config.RouterConfig) router.Router {
var routes []*router.Route
for _, route := range cfg.Routes {
_, ipNet, _ := net.ParseCIDR(route.Net)
if ipNet == nil {
if route == nil {
continue
}
gw := net.ParseIP(route.Gateway)
if gw == nil {
_, ipNet, _ := net.ParseCIDR(route.Net)
dst := route.Dst
if dst != "" {
_, ipNet, _ = net.ParseCIDR(dst)
} else {
if ipNet != nil {
dst = ipNet.String()
}
}
if dst == "" || route.Gateway == "" {
continue
}
routes = append(routes, &router.Route{
Net: ipNet,
Gateway: gw,
Dst: dst,
Gateway: route.Gateway,
})
}
opts := []xrouter.Option{
@@ -76,6 +85,7 @@ func ParseRouter(cfg *config.RouterConfig) router.Router {
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisListLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -83,6 +93,7 @@ func ParseRouter(cfg *config.RouterConfig) router.Router {
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisSetLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
@@ -90,6 +101,7 @@ func ParseRouter(cfg *config.RouterConfig) router.Router {
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisHashLoader(
cfg.Redis.Addr,
loader.DBRedisLoaderOption(cfg.Redis.DB),
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
loader.KeyRedisLoaderOption(cfg.Redis.Key),
)))
+2
View File
@@ -47,6 +47,8 @@ func ParseNodeSelector(cfg *config.SelectorConfig) selector.Selector[*chain.Node
strategy = xs.FIFOStrategy[*chain.Node]()
case "hash":
strategy = xs.HashStrategy[*chain.Node]()
case "parallel":
strategy = xs.ParallelStrategy[*chain.Node]()
default:
strategy = xs.RoundRobinStrategy[*chain.Node]()
}
+43 -17
View File
@@ -6,19 +6,19 @@ import (
"strings"
"time"
"github.com/go-gost/core/admission"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/hop"
"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"
xadmission "github.com/go-gost/x/admission"
xauth "github.com/go-gost/x/auth"
xbypass "github.com/go-gost/x/bypass"
xchain "github.com/go-gost/x/chain"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
@@ -29,7 +29,10 @@ import (
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"
cache_limiter "github.com/go-gost/x/limiter/traffic/cache"
"github.com/go-gost/x/metadata"
mdutil "github.com/go-gost/x/metadata/util"
xstats "github.com/go-gost/x/observer/stats"
"github.com/go-gost/x/registry"
xservice "github.com/go-gost/x/service"
"github.com/vishvananda/netns"
@@ -73,6 +76,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
}
if tlsConfig == nil {
tlsConfig = parsing.DefaultTLSConfig().Clone()
tls_util.SetTLSOptions(tlsConfig, tlsCfg.Options)
}
authers := auth_parser.List(cfg.Listener.Auther, cfg.Listener.Authers...)
@@ -83,7 +87,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
}
var auther auth.Authenticator
if len(authers) > 0 {
auther = auth.AuthenticatorGroup(authers...)
auther = xauth.AuthenticatorGroup(authers...)
}
admissions := admission_parser.List(cfg.Admission, cfg.Admissions...)
@@ -99,10 +103,15 @@ 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 pStats stats.Stats
var observerPeriod time.Duration
var netnsIn, netnsOut string
var dialTimeout time.Duration
var limiterRefreshInterval time.Duration
var limiterCleanupInterval time.Duration
var limiterScope string
if cfg.Metadata != nil {
md := metadata.NewMetadata(cfg.Metadata)
ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol)
@@ -121,12 +130,18 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
ignoreChain = mdutil.GetBool(md, parsing.MDKeyIgnoreChain)
if mdutil.GetBool(md, parsing.MDKeyEnableStats) {
pStats = &stats.Stats{}
pStats = xstats.NewStats(mdutil.GetBool(md, parsing.MDKeyObserverResetTraffic))
}
observePeriod = mdutil.GetDuration(md, "observePeriod")
netnsIn = mdutil.GetString(md, "netns")
netnsOut = mdutil.GetString(md, "netns.out")
dialTimeout = mdutil.GetDuration(md, "dialTimeout")
observerPeriod = mdutil.GetDuration(md, parsing.MDKeyObserverPeriod, "observePeriod")
netnsIn = mdutil.GetString(md, parsing.MDKeyNetns)
netnsOut = mdutil.GetString(md, parsing.MDKeyNetnsOut)
dialTimeout = mdutil.GetDuration(md, parsing.MDKeyDialTimeout)
limiterRefreshInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterRefreshInterval)
limiterCleanupInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterCleanupInterval)
limiterScope = mdutil.GetString(md, parsing.MDKeyLimiterScope)
}
listenerLogger := serviceLogger.WithFields(map[string]any{
@@ -154,8 +169,15 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
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.AdmissionOption(xadmission.AdmissionGroup(admissions...)),
listener.TrafficLimiterOption(
cache_limiter.NewCachedTrafficLimiter(
registry.TrafficLimiterRegistry().Get(cfg.Limiter),
cache_limiter.RefreshIntervalOption(limiterRefreshInterval),
cache_limiter.CleanupIntervalOption(limiterCleanupInterval),
cache_limiter.ScopeOption(limiterScope),
),
),
listener.ConnLimiterOption(registry.ConnLimiterRegistry().Get(cfg.CLimiter)),
listener.ServiceOption(cfg.Name),
listener.ProxyProtocolOption(ppv),
@@ -222,6 +244,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
}
if tlsConfig == nil {
tlsConfig = parsing.DefaultTLSConfig().Clone()
tls_util.SetTLSOptions(tlsConfig, tlsCfg.Options)
}
authers = auth_parser.List(cfg.Handler.Auther, cfg.Handler.Authers...)
@@ -233,7 +256,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
auther = nil
if len(authers) > 0 {
auther = auth.AuthenticatorGroup(authers...)
auther = xauth.AuthenticatorGroup(authers...)
}
var recorders []recorder.RecorderObject
@@ -246,6 +269,8 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
Direction: mdutil.GetBool(md, parsing.MDKeyRecorderDirection),
TimestampFormat: mdutil.GetString(md, parsing.MDKeyRecorderTimestampFormat),
Hexdump: mdutil.GetBool(md, parsing.MDKeyRecorderHexdump),
HTTPBody: mdutil.GetBool(md, parsing.MDKeyRecorderHTTPBody),
MaxBodySize: mdutil.GetInt(md, parsing.MDKeyRecorderHTTPMaxBodySize),
},
})
}
@@ -273,7 +298,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
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.BypassOption(xbypass.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)),
@@ -305,7 +330,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
}
s := xservice.NewService(cfg.Name, ln, h,
xservice.AdmissionOption(admission.AdmissionGroup(admissions...)),
xservice.AdmissionOption(xadmission.AdmissionGroup(admissions...)),
xservice.PreUpOption(preUp),
xservice.PreDownOption(preDown),
xservice.PostUpOption(postUp),
@@ -313,7 +338,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
xservice.RecordersOption(recorders...),
xservice.StatsOption(pStats),
xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)),
xservice.ObservePeriodOption(observePeriod),
xservice.ObserverPeriodOption(observerPeriod),
xservice.LoggerOption(serviceLogger),
)
@@ -370,6 +395,7 @@ func parseForwarder(cfg *config.ForwarderConfig, log logger.Logger) (hop.Hop, er
Bypass: node.Bypass,
Bypasses: node.Bypasses,
Filter: filter,
Matcher: node.Matcher,
HTTP: httpCfg,
TLS: node.TLS,
Metadata: node.Metadata,
+12 -5
View File
@@ -12,6 +12,7 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"sync/atomic"
"time"
"github.com/go-gost/core/logger"
@@ -20,14 +21,19 @@ import (
)
var (
defaultTLSConfig *tls.Config
defaultTLSConfig atomic.Value
)
func DefaultTLSConfig() *tls.Config {
return defaultTLSConfig
v, _ := defaultTLSConfig.Load().(*tls.Config)
return v
}
func BuildDefaultTLSConfig(cfg *config.TLSConfig) {
func SetDefaultTLSConfig(cfg *tls.Config) {
defaultTLSConfig.Store(cfg)
}
func BuildDefaultTLSConfig(cfg *config.TLSConfig) (*tls.Config, error) {
log := logger.Default()
if cfg == nil {
@@ -43,7 +49,7 @@ func BuildDefaultTLSConfig(cfg *config.TLSConfig) {
// generate random self-signed certificate.
cert, err := genCertificate(cfg.Validity, cfg.Organization, cfg.CommonName)
if err != nil {
log.Fatal(err)
return nil, err
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
@@ -52,7 +58,8 @@ func BuildDefaultTLSConfig(cfg *config.TLSConfig) {
} else {
log.Debug("load global TLS certificate files OK")
}
defaultTLSConfig = tlsConfig
return tlsConfig, nil
}
func genCertificate(validity time.Duration, org string, cn string) (cert tls.Certificate, err error) {
+41
View File
@@ -0,0 +1,41 @@
package direct
import (
"io"
"net"
"time"
)
type conn struct{}
func (c *conn) Close() error {
return nil
}
func (c *conn) Read(b []byte) (n int, err error) {
return 0, io.EOF
}
func (c *conn) Write(b []byte) (n int, err error) {
return 0, io.ErrClosedPipe
}
func (c *conn) LocalAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) RemoteAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) SetDeadline(t time.Time) error {
return nil
}
func (c *conn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return nil
}
+9 -2
View File
@@ -1,4 +1,4 @@
package forward
package direct
import (
"context"
@@ -6,6 +6,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -15,6 +16,7 @@ func init() {
}
type directConnector struct {
md metadata
options connector.Options
}
@@ -30,7 +32,7 @@ func NewConnector(opts ...connector.Option) connector.Connector {
}
func (c *directConnector) Init(md md.Metadata) (err error) {
return nil
return c.parseMetadata(md)
}
func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
@@ -39,6 +41,10 @@ func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, addr
opt(&cOpts)
}
if c.md.action == "reject" {
return &conn{}, nil
}
conn, err := cOpts.Dialer.Dial(ctx, network, address)
if err != nil {
return nil, err
@@ -57,6 +63,7 @@ func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, addr
"local": localAddr,
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+17
View File
@@ -0,0 +1,17 @@
package direct
import (
"strings"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
action string
}
func (c *directConnector) parseMetadata(md mdata.Metadata) (err error) {
c.md.action = strings.ToLower(mdutil.GetString(md, "action"))
return
}
+2
View File
@@ -6,6 +6,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -38,6 +39,7 @@ func (c *forwardConnector) Connect(ctx context.Context, conn net.Conn, network,
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+2
View File
@@ -14,6 +14,7 @@ import (
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
@@ -48,6 +49,7 @@ func (c *httpConnector) Connect(ctx context.Context, conn net.Conn, network, add
"remote": conn.RemoteAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
+11 -5
View File
@@ -15,6 +15,8 @@ import (
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
xctx "github.com/go-gost/x/ctx"
ictx "github.com/go-gost/x/internal/ctx"
"github.com/go-gost/x/registry"
)
@@ -48,11 +50,17 @@ func (c *http2Connector) Connect(ctx context.Context, conn net.Conn, network, ad
"remote": conn.RemoteAddr().String(),
"network": network,
"address": address,
"sid": string(xctx.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
v, _ := conn.(md.Metadatable)
if v == nil {
var client *http.Client
if cc, ok := conn.(xctx.Context); ok {
if md := ictx.MetadataFromContext(cc.Context()); md != nil {
client, _ = md.Get("client").(*http.Client)
}
}
if client == nil {
err := errors.New("http2: wrong connection type")
log.Error(err)
return nil, err
@@ -67,7 +75,6 @@ func (c *http2Connector) Connect(ctx context.Context, conn net.Conn, network, ad
ProtoMinor: 0,
Header: c.md.header,
Body: pr,
// ContentLength: -1,
}
if req.Header == nil {
req.Header = make(http.Header)
@@ -90,8 +97,7 @@ func (c *http2Connector) Connect(ctx context.Context, conn net.Conn, network, ad
defer conn.SetDeadline(time.Time{})
}
client := v.Metadata().Get("client").(*http.Client)
resp, err := client.Do(req)
resp, err := client.Do(req.WithContext(context.WithoutCancel(ctx)))
if err != nil {
log.Error(err)
conn.Close()
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
+180
View File
@@ -0,0 +1,180 @@
package masque
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
masque_dialer "github.com/go-gost/x/dialer/http3/masque"
masque_util "github.com/go-gost/x/internal/util/masque"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("masque", NewConnector)
}
var (
ErrInvalidConnection = errors.New("masque: invalid connection type, expected MasqueConn")
ErrConnectFailed = errors.New("masque: CONNECT-UDP failed")
ErrCapsuleRequired = errors.New("masque: server did not confirm capsule-protocol")
)
type masqueConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &masqueConnector{
options: options,
}
}
func (c *masqueConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *masqueConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
// Only support UDP
if !strings.HasPrefix(network, "udp") {
return nil, fmt.Errorf("masque connector only supports UDP, got %s", network)
}
log.Debugf("connect-udp %s/%s", address, network)
// Get the MasqueConn from the dialer
masqueConn, ok := conn.(*masque_dialer.MasqueConn)
if !ok {
log.Error(ErrInvalidConnection)
return nil, ErrInvalidConnection
}
// Get pre-opened stream from dialer (stream opening happens there for dead connection detection)
reqStream := masqueConn.GetRequestStream()
proxyHost := masqueConn.GetHost()
// Apply connect timeout to the actual stream
if c.md.connectTimeout > 0 {
reqStream.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer reqStream.SetDeadline(time.Time{})
}
// Ensure stream is closed on any error
success := false
defer func() {
if !success {
reqStream.Close()
}
}()
// Build CONNECT-UDP path
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, fmt.Errorf("masque: invalid address %s: %w", address, err)
}
path := masque_util.BuildMasquePath(host, mustAtoi(port))
// Create CONNECT-UDP request
// For Extended CONNECT (RFC 9220), set Proto to the :protocol value
// quic-go translates req.Proto to the :protocol pseudo-header
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Path: path},
Host: proxyHost,
Header: http.Header{
"Capsule-Protocol": []string{"?1"},
},
Proto: "connect-udp", // This becomes the :protocol pseudo-header
ProtoMajor: 3,
}
// Add proxy authentication if configured
// Use Proxy-Authorization header (not Authorization) for proxy auth
if c.options.Auth != nil {
u := c.options.Auth.Username()
p, _ := c.options.Auth.Password()
auth := u + ":" + p
encoded := base64.StdEncoding.EncodeToString([]byte(auth))
req.Header.Set("Proxy-Authorization", "Basic "+encoded)
}
if log.IsLevelEnabled(logger.TraceLevel) {
log.Tracef("CONNECT-UDP request: %s %s Host=%s", req.Method, path, proxyHost)
}
log.Debugf("sending CONNECT-UDP request to %s", path)
// Send request headers
if err := reqStream.SendRequestHeader(req); err != nil {
log.Error("masque: failed to send request:", err)
return nil, err
}
// Read response
resp, err := reqStream.ReadResponse()
if err != nil {
log.Error("masque: failed to read response:", err)
return nil, err
}
if log.IsLevelEnabled(logger.TraceLevel) {
log.Tracef("CONNECT-UDP response: %d %s", resp.StatusCode, resp.Status)
}
if resp.StatusCode != http.StatusOK {
log.Errorf("masque: proxy returned status %d", resp.StatusCode)
return nil, fmt.Errorf("%w: status %d", ErrConnectFailed, resp.StatusCode)
}
if resp.Header.Get("Capsule-Protocol") != "?1" {
log.Error(ErrCapsuleRequired)
return nil, ErrCapsuleRequired
}
log.Debugf("CONNECT-UDP established to %s", address)
// Get the underlying HTTP/3 stream for datagrams
stream := reqStream
// Resolve target address
raddr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
return nil, err
}
// Create datagram connection wrapping the request stream
datagramConn := masque_util.NewDatagramConnFromRequestStream(stream, conn.LocalAddr(), raddr)
success = true // Prevent defer from closing stream - datagramConn now owns it
return datagramConn, nil
}
func mustAtoi(s string) int {
n, _ := strconv.Atoi(s)
return n
}
+17
View File
@@ -0,0 +1,17 @@
package masque
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
connectTimeout time.Duration
}
func (c *masqueConnector) parseMetadata(md mdata.Metadata) (err error) {
c.md.connectTimeout = mdutil.GetDuration(md, "timeout", "connectTimeout")
return nil
}
+2 -2
View File
@@ -44,7 +44,7 @@ func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, ad
if err != nil {
return nil, err
}
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
log.Infof("bind on %s/%s OK", laddr, laddr.Network())
session, err := mux.ServerSession(conn, c.md.muxCfg)
if err != nil {
@@ -63,7 +63,7 @@ func (c *relayConnector) bindUDP(ctx context.Context, conn net.Conn, network, ad
if err != nil {
return nil, err
}
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
log.Infof("bind on %s/%s OK", laddr, laddr.Network())
ln := udp.NewListener(
relay_util.UDPTunClientPacketConn(conn),
+7 -8
View File
@@ -192,14 +192,13 @@ func (c *bindUDPConn) Write(b []byte) (n int, err error) {
return
}
// 2-byte data length header
var bh [2]byte
binary.BigEndian.PutUint16(bh[:], uint16(len(b)))
_, err = c.Conn.Write(bh[:])
if err != nil {
return
}
return c.Conn.Write(b)
buf := bufpool.Get(len(b) + 2)
defer bufpool.Put(buf)
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
n = copy(buf[2:], b)
return c.Conn.Write(buf)
}
func (c *bindUDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
+2
View File
@@ -10,6 +10,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/ctx"
relay_util "github.com/go-gost/x/internal/util/relay"
"github.com/go-gost/x/registry"
)
@@ -44,6 +45,7 @@ func (c *relayConnector) Connect(ctx context.Context, conn net.Conn, network, ad
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
)
+73
View File
@@ -0,0 +1,73 @@
package tunnel
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/relay"
xrelay "github.com/go-gost/x/internal/util/relay"
)
type packetConn struct {
net.Conn
}
func (c *packetConn) Read(b []byte) (n int, err error) {
var bb [2]byte
_, err = io.ReadFull(c.Conn, bb[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bb[:]))
if len(b) >= dlen {
return io.ReadFull(c.Conn, b[:dlen])
}
buf := bufpool.Get(dlen)
defer bufpool.Put(buf)
n, err = io.ReadFull(c.Conn, buf)
copy(b, buf[:n])
return
}
func (c *packetConn) Write(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
buf := bufpool.Get(len(b) + 2)
defer bufpool.Put(buf)
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
n = copy(buf[2:], b)
return c.Conn.Write(buf)
}
func readResponse(r io.Reader) (err error) {
resp := relay.Response{}
_, err = resp.ReadFrom(r)
if err != nil {
return
}
if resp.Version != relay.Version1 {
err = relay.ErrBadVersion
return
}
if resp.Status != relay.StatusOK {
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
return
}
return nil
}
+120
View File
@@ -0,0 +1,120 @@
package tunnel
import (
"context"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("router", NewConnector)
}
type routerConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &routerConnector{
options: options,
}
}
func (c *routerConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *routerConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"network": network,
"address": address,
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"sid": xctx.SidFromContext(ctx).String(),
})
log.Debugf("connect %s/%s", address, network)
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
req := relay.Request{
Version: relay.Version1,
Cmd: relay.CmdAssociate,
}
if c.options.Auth != nil {
pwd, _ := c.options.Auth.Password()
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: c.options.Auth.Username(),
Password: pwd,
})
}
switch network {
case "udp":
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUDP,
})
case "ip":
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkIP,
})
}
srcAddr := conn.RemoteAddr().String()
if v := xctx.SrcAddrFromContext(ctx); v != nil {
srcAddr = v.String()
}
af := &relay.AddrFeature{}
af.ParseFrom(srcAddr)
req.Features = append(req.Features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(address)
req.Features = append(req.Features, af) // dst address
if !c.md.routerID.IsZero() {
req.Features = append(req.Features, &relay.TunnelFeature{
ID: c.md.routerID,
})
}
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 "udp", "ip":
conn = &packetConn{
Conn: conn,
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
log.Debugf("connect to router %s@%s OK", address, c.md.routerID)
return conn, nil
}
+34
View File
@@ -0,0 +1,34 @@
package tunnel
import (
"errors"
"time"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/google/uuid"
)
var (
ErrInvalidRouterID = errors.New("router: invalid router ID")
)
type metadata struct {
connectTimeout time.Duration
routerID relay.TunnelID
}
func (c *routerConnector) parseMetadata(md mdata.Metadata) (err error) {
c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout")
if s := mdutil.GetString(md, "router.id"); s != "" {
uuid, err := uuid.Parse(s)
if err != nil {
return err
}
c.md.routerID = relay.NewTunnelID(uuid[:])
}
return
}
+2
View File
@@ -6,6 +6,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -38,6 +39,7 @@ func (c *serialConnector) Connect(ctx context.Context, conn net.Conn, network, a
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+2
View File
@@ -6,6 +6,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -39,6 +40,7 @@ func (c *sniConnector) Connect(ctx context.Context, conn net.Conn, network, addr
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
+2
View File
@@ -11,6 +11,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks4"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -45,6 +46,7 @@ func (c *socks4Connector) Connect(ctx context.Context, conn net.Conn, network, a
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
+2
View File
@@ -12,6 +12,7 @@ import (
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
@@ -97,6 +98,7 @@ func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, a
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+1 -1
View File
@@ -4,8 +4,8 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
"github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
+20 -12
View File
@@ -9,10 +9,12 @@ import (
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/go-shadowsocks2/core"
"github.com/go-gost/go-shadowsocks2/socks"
"github.com/go-gost/go-shadowsocks2/utils"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/ss"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core"
)
func init() {
@@ -20,7 +22,7 @@ func init() {
}
type ssConnector struct {
cipher core.Cipher
client core.TCPClient
md metadata
options connector.Options
}
@@ -44,7 +46,13 @@ func (c *ssConnector) Init(md md.Metadata) (err error) {
if c.options.Auth != nil {
method := c.options.Auth.Username()
password, _ := c.options.Auth.Password()
c.cipher, err = ss.ShadowCipher(method, password, c.md.key)
clientConfig, err := utils.NewClientConfig(method, password)
if err != nil {
return err
}
c.client = core.NewTCPClient(clientConfig)
}
return
@@ -56,6 +64,7 @@ func (c *ssConnector) Connect(ctx context.Context, conn net.Conn, network, addre
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
@@ -91,21 +100,20 @@ func (c *ssConnector) Connect(ctx context.Context, conn net.Conn, network, addre
defer conn.SetDeadline(time.Time{})
}
if c.cipher != nil {
conn = c.cipher.StreamConn(conn)
target := socks.Addr(rawaddr[:n])
conn, err = c.client.WrapConn(conn, target)
if err != nil {
return nil, err
}
var sc net.Conn
if c.md.noDelay {
sc = ss.ShadowConn(conn, nil)
// write the addr at once.
if _, err := sc.Write(rawaddr[:n]); err != nil {
err := conn.(core.TCPConn).ClientFirstWrite()
if err != nil {
return nil, err
}
} else {
// cache the header
sc = ss.ShadowConn(conn, rawaddr[:n])
}
sc = conn
return sc, nil
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
+22 -12
View File
@@ -4,14 +4,18 @@ import (
"context"
"fmt"
"net"
"sync"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/go-shadowsocks2/core"
"github.com/go-gost/go-shadowsocks2/socks"
"github.com/go-gost/go-shadowsocks2/utils"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/relay"
"github.com/go-gost/x/internal/util/ss"
"github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core"
)
func init() {
@@ -19,9 +23,11 @@ func init() {
}
type ssuConnector struct {
cipher core.Cipher
md metadata
options connector.Options
client core.UDPClient
tcpClient core.TCPClient
md metadata
options connector.Options
sessionMap sync.Map
}
func NewConnector(opts ...connector.Option) connector.Connector {
@@ -43,7 +49,12 @@ func (c *ssuConnector) Init(md md.Metadata) (err error) {
if c.options.Auth != nil {
method := c.options.Auth.Username()
password, _ := c.options.Auth.Password()
c.cipher, err = ss.ShadowCipher(method, password, c.md.key)
clientConfig, err := utils.NewClientConfig(method, password)
if err != nil {
return err
}
c.client = core.NewUDPClient(clientConfig, 60)
c.tcpClient = core.NewTCPClient(clientConfig)
}
return
@@ -55,6 +66,7 @@ func (c *ssuConnector) Connect(ctx context.Context, conn net.Conn, network, addr
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
@@ -78,16 +90,14 @@ func (c *ssuConnector) Connect(ctx context.Context, conn net.Conn, network, addr
pc, ok := conn.(net.PacketConn)
if ok {
if c.cipher != nil {
pc = c.cipher.PacketConn(pc)
}
// standard UDP relay
return ss.UDPClientConn(pc, conn.RemoteAddr(), taddr, c.md.bufferSize), nil
return ss.UDPClientConn(pc, conn.RemoteAddr(), taddr, c.md.udpBufferSize, &c.client, &c.sessionMap), nil
}
if c.cipher != nil {
conn = ss.ShadowConn(c.cipher.StreamConn(conn), nil)
target := socks.ParseAddr(taddr.String())
conn, err := c.tcpClient.WrapConn(conn, target)
if err != nil {
return nil, err
}
// UDP over TCP
+5 -17
View File
@@ -1,34 +1,22 @@
package ss
import (
"math"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
key string
connectTimeout time.Duration
bufferSize int
udpBufferSize int
}
func (c *ssuConnector) parseMetadata(md mdata.Metadata) (err error) {
const (
key = "key"
connectTimeout = "timeout"
bufferSize = "bufferSize" // udp buffer size
)
c.md.key = mdutil.GetString(md, key)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
if bs := mdutil.GetInt(md, bufferSize); bs > 0 {
c.md.bufferSize = int(math.Min(math.Max(float64(bs), 512), 64*1024))
} else {
c.md.bufferSize = 4096
}
c.md.key = mdutil.GetString(md, "key")
c.md.connectTimeout = mdutil.GetDuration(md, "timeout", "connectTimeout")
c.md.udpBufferSize = mdutil.GetInt(md, "udpBufferSize", "udp.bufferSize")
return
}
+2
View File
@@ -7,6 +7,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
ssh_util "github.com/go-gost/x/internal/util/ssh"
"github.com/go-gost/x/registry"
)
@@ -40,6 +41,7 @@ func (c *sshdConnector) Connect(ctx context.Context, conn net.Conn, network, add
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+2
View File
@@ -6,6 +6,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -38,6 +39,7 @@ func (c *tcpConnector) Connect(ctx context.Context, conn net.Conn, network, addr
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+7 -8
View File
@@ -130,14 +130,13 @@ func (c *bindUDPConn) Write(b []byte) (n int, err error) {
return
}
// 2-byte data length header
var bh [2]byte
binary.BigEndian.PutUint16(bh[:], uint16(len(b)))
_, err = c.Conn.Write(bh[:])
if err != nil {
return
}
return c.Conn.Write(b)
buf := bufpool.Get(len(b) + 2)
defer bufpool.Put(buf)
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
n = copy(buf[2:], b)
return c.Conn.Write(buf)
}
func (c *bindUDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
+7 -6
View File
@@ -9,7 +9,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/ctx"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -39,10 +39,11 @@ func (c *tunnelConnector) Init(md md.Metadata) (err error) {
func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"sid": xctx.SidFromContext(ctx).String(),
})
log.Debugf("connect %s/%s", address, network)
@@ -72,9 +73,9 @@ func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, a
})
}
srcAddr := conn.LocalAddr().String()
if v := ctxvalue.ClientAddrFromContext(ctx); v != "" {
srcAddr = string(v)
srcAddr := conn.RemoteAddr().String()
if v := xctx.SrcAddrFromContext(ctx); v != nil {
srcAddr = v.String()
}
af := &relay.AddrFeature{}
+4 -1
View File
@@ -5,9 +5,9 @@ import (
"time"
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"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/google/uuid"
)
@@ -56,6 +56,9 @@ func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) {
if c.md.muxCfg.Version == 0 {
c.md.muxCfg.Version = 2
}
if c.md.muxCfg.MaxStreamBuffer == 0 {
c.md.muxCfg.MaxStreamBuffer = 1048576
}
return
}
+2
View File
@@ -6,6 +6,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
@@ -38,6 +39,7 @@ func (c *unixConnector) Connect(ctx context.Context, conn net.Conn, network, add
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
+54 -38
View File
@@ -1,76 +1,92 @@
package ctx
import "context"
// clientAddrKey saves the client address.
type clientAddrKey struct{}
type ClientAddr string
var (
keyClientAddr clientAddrKey
import (
"context"
"net"
)
func ContextWithClientAddr(ctx context.Context, addr ClientAddr) context.Context {
return context.WithValue(ctx, keyClientAddr, addr)
type Context interface {
Context() context.Context
}
func ClientAddrFromContext(ctx context.Context) ClientAddr {
v, _ := ctx.Value(keyClientAddr).(ClientAddr)
type WithContext interface {
WithContext(ctx context.Context)
}
type srcAddrKey struct{}
func ContextWithSrcAddr(ctx context.Context, addr net.Addr) context.Context {
return context.WithValue(ctx, srcAddrKey{}, addr)
}
func SrcAddrFromContext(ctx context.Context) net.Addr {
v, _ := ctx.Value(srcAddrKey{}).(net.Addr)
return v
}
// sidKey saves the session ID.
type sidKey struct{}
type Sid string
type dstAddrKey struct{}
var (
keySid sidKey
func ContextWithDstAddr(ctx context.Context, addr net.Addr) context.Context {
return context.WithValue(ctx, dstAddrKey{}, addr)
}
func DstAddrFromContext(ctx context.Context) net.Addr {
v, _ := ctx.Value(dstAddrKey{}).(net.Addr)
return v
}
type (
Sid string
// sidKey saves the session ID.
sidKey struct{}
)
func (s Sid) String() string {
return string(s)
}
func ContextWithSid(ctx context.Context, sid Sid) context.Context {
return context.WithValue(ctx, keySid, sid)
return context.WithValue(ctx, sidKey{}, sid)
}
func SidFromContext(ctx context.Context) Sid {
v, _ := ctx.Value(keySid).(Sid)
v, _ := ctx.Value(sidKey{}).(Sid)
return v
}
// hashKey saves the hash source for Selector.
type hashKey struct{}
type Hash struct {
Source string
}
var (
clientHashKey = &hashKey{}
type (
// hashKey saves the hash source for Selector.
hashKey struct{}
Hash struct {
Source string
}
)
func ContextWithHash(ctx context.Context, hash *Hash) context.Context {
return context.WithValue(ctx, clientHashKey, hash)
return context.WithValue(ctx, hashKey{}, hash)
}
func HashFromContext(ctx context.Context) *Hash {
if v, _ := ctx.Value(clientHashKey).(*Hash); v != nil {
if v, _ := ctx.Value(hashKey{}).(*Hash); v != nil {
return v
}
return nil
}
type clientIDKey struct{}
type ClientID string
var (
keyClientID = &clientIDKey{}
type (
ClientID string
clientIDKey struct{}
)
func (s ClientID) String() string {
return string(s)
}
func ContextWithClientID(ctx context.Context, clientID ClientID) context.Context {
return context.WithValue(ctx, keyClientID, clientID)
return context.WithValue(ctx, clientIDKey{}, clientID)
}
func ClientIDFromContext(ctx context.Context) ClientID {
v, _ := ctx.Value(keyClientID).(ClientID)
v, _ := ctx.Value(clientIDKey{}).(ClientID)
return v
}
+8
View File
@@ -8,6 +8,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/net/proxyproto"
xdtls "github.com/go-gost/x/internal/util/dtls"
"github.com/go-gost/x/registry"
"github.com/pion/dtls/v2"
@@ -50,6 +52,12 @@ func (d *dtlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
return nil, err
}
conn = proxyproto.WrapClientConn(
d.options.ProxyProtocol,
xctx.SrcAddrFromContext(ctx),
xctx.DstAddrFromContext(ctx),
conn)
tlsCfg := d.options.TLSConfig
if tlsCfg == nil {
tlsCfg = &tls.Config{
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
+14 -1
View File
@@ -7,6 +7,8 @@ import (
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/net/proxyproto"
pb "github.com/go-gost/x/internal/util/grpc/proto"
"github.com/go-gost/x/registry"
"google.golang.org/grpc"
@@ -71,7 +73,18 @@ 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.Dialer.Dial(c, "tcp", s)
conn, err := options.Dialer.Dial(c, "tcp", s)
if err != nil {
return nil, err
}
conn = proxyproto.WrapClientConn(
d.options.ProxyProtocol,
xctx.SrcAddrFromContext(ctx),
xctx.DstAddrFromContext(ctx),
conn)
return conn, nil
}),
grpc.WithAuthority(host),
grpc.WithConnectParams(grpc.ConnectParams{
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
+4 -6
View File
@@ -1,11 +1,10 @@
package http2
import (
"context"
"errors"
"net"
"time"
mdata "github.com/go-gost/core/metadata"
)
// a dummy HTTP2 client conn used by HTTP2 client connector
@@ -13,7 +12,7 @@ type conn struct {
localAddr net.Addr
remoteAddr net.Addr
onClose func()
md mdata.Metadata
ctx context.Context
}
func (c *conn) Close() error {
@@ -51,7 +50,6 @@ func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
// Metadata implements metadata.Metadatable interface.
func (c *conn) Metadata() mdata.Metadata {
return c.md
func (c *conn) Context() context.Context {
return c.ctx
}
+18 -7
View File
@@ -10,7 +10,10 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
xctx "github.com/go-gost/x/ctx"
ictx "github.com/go-gost/x/internal/ctx"
net_dialer "github.com/go-gost/x/internal/net/dialer"
"github.com/go-gost/x/internal/net/proxyproto"
mdx "github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
)
@@ -23,7 +26,6 @@ type http2Dialer struct {
clients map[string]*http.Client
clientMutex sync.Mutex
logger logger.Logger
md metadata
options dialer.Options
}
@@ -91,7 +93,18 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D
if netd == nil {
netd = net_dialer.DefaultNetDialer
}
return netd.Dial(ctx, network, addr)
conn, err := netd.Dial(ctx, network, addr)
if err != nil {
return nil, err
}
conn = proxyproto.WrapClientConn(
d.options.ProxyProtocol,
xctx.SrcAddrFromContext(ctx),
xctx.DstAddrFromContext(ctx),
conn)
return conn, nil
},
ForceAttemptHTTP2: true,
MaxIdleConns: 16,
@@ -103,7 +116,7 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D
d.clients[address] = client
}
var c net.Conn = &conn{
return &conn{
localAddr: &net.TCPAddr{},
remoteAddr: raddr,
onClose: func() {
@@ -111,8 +124,6 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D
defer d.clientMutex.Unlock()
delete(d.clients, address)
},
md: mdx.NewMetadata(map[string]any{"client": client}),
}
return c, nil
ctx: ictx.ContextWithMetadata(ctx, mdx.NewMetadata(map[string]any{"client": client})),
}, nil
}
+33 -12
View File
@@ -15,6 +15,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
xctx "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/net/proxyproto"
"github.com/go-gost/x/registry"
"golang.org/x/net/http2"
)
@@ -93,15 +95,37 @@ func (d *h2Dialer) Dial(ctx context.Context, address string, opts ...dialer.Dial
if d.h2c {
client.Transport = &http2.Transport{
AllowHTTP: true,
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return options.Dialer.Dial(ctx, network, addr)
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
conn, err := options.Dialer.Dial(ctx, network, addr)
if err != nil {
return nil, err
}
conn = proxyproto.WrapClientConn(
d.options.ProxyProtocol,
xctx.SrcAddrFromContext(ctx),
xctx.DstAddrFromContext(ctx),
conn)
return conn, nil
},
}
} else {
client.Transport = &http.Transport{
TLSClientConfig: d.options.TLSConfig,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return options.Dialer.Dial(ctx, network, addr)
conn, err := options.Dialer.Dial(ctx, network, addr)
if err != nil {
return nil, err
}
conn = proxyproto.WrapClientConn(
d.options.ProxyProtocol,
xctx.SrcAddrFromContext(ctx),
xctx.DstAddrFromContext(ctx),
conn)
return conn, nil
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
@@ -126,14 +150,11 @@ func (d *h2Dialer) Dial(ctx context.Context, address string, opts ...dialer.Dial
}
pr, pw := io.Pipe()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Scheme: scheme, Host: host},
Header: d.md.header,
ProtoMajor: 2,
ProtoMinor: 0,
Body: pr,
Host: host,
// ContentLength: -1,
Method: http.MethodConnect,
URL: &url.URL{Scheme: scheme, Host: host},
Header: d.md.header,
Body: pr,
Host: host,
}
if req.Header == nil {
req.Header = make(http.Header)
@@ -148,7 +169,7 @@ func (d *h2Dialer) Dial(ctx context.Context, address string, opts ...dialer.Dial
d.logger.Trace(string(dump))
}
resp, err := client.Do(req)
resp, err := client.Do(req.WithContext(context.WithoutCancel(ctx)))
if err != nil {
return nil, err
}
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"net/http"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
+3 -3
View File
@@ -70,9 +70,9 @@ func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.Dial
Host: host,
Client: &http.Client{
// Timeout: 60 * time.Second,
Transport: &http3.RoundTripper{
Transport: &http3.Transport{
TLSClientConfig: d.options.TLSConfig,
Dial: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
Dial: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
// d.options.Logger.Infof("dial: %s/%s, %s", addr, network, host)
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
@@ -90,7 +90,7 @@ func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.Dial
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
},
MaxIncomingStreams: int64(d.md.maxStreams),
+150
View File
@@ -0,0 +1,150 @@
package masque
import (
"context"
"crypto/tls"
"net"
"time"
"github.com/go-gost/core/logger"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)
// Client manages HTTP/3 connections for MASQUE proxying.
// It wraps a QUIC connection and HTTP/3 client, following the session pattern
// used by other gost dialers (ssh, quic, mws, etc.).
type Client struct {
host string
addr string
transport *http3.Transport
quicConn *quic.Conn
clientConn *http3.ClientConn
dialer func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error)
log logger.Logger
}
// IsClosed returns true if the underlying connection is closed.
// This follows the pattern used by other gost session wrappers.
func (c *Client) IsClosed() bool {
if c.clientConn == nil {
return true
}
select {
case <-c.clientConn.Context().Done():
return true
default:
}
return false
}
// Close closes the client connection.
func (c *Client) Close() error {
if c.quicConn != nil {
return (*c.quicConn).CloseWithError(0, "closed")
}
return nil
}
// Dial establishes an HTTP/3 connection to the proxy server.
// It returns a MasqueConn that can be used by the MASQUE connector.
// Following the QUIC dialer pattern, this opens a request stream immediately
// to detect dead connections and allow cache invalidation.
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
// Create connection if we don't have one (caller checks IsClosed first)
if c.clientConn == nil {
// Use the transport's Dial function to establish QUIC connection
quicConn, err := c.dialer(ctx, addr, c.transport.TLSClientConfig, c.transport.QUICConfig)
if err != nil {
return nil, err
}
c.quicConn = quicConn
// Create HTTP/3 client connection on top of QUIC
c.clientConn = c.transport.NewClientConn(quicConn)
}
// Open request stream NOW (following QUIC dialer pattern).
// This allows dead connection detection - if this fails, caller can invalidate cache.
reqStream, err := c.clientConn.OpenRequestStream(ctx)
if err != nil {
// Connection is dead - clear our state so next dial creates fresh connection
c.clientConn = nil
c.quicConn = nil
return nil, err
}
// Return a connection wrapper with pre-opened stream
return &MasqueConn{
clientConn: c.clientConn,
reqStream: reqStream,
host: c.host,
log: c.log,
}, nil
}
// MasqueConn wraps an HTTP/3 client connection for use with the MASQUE connector.
// It implements net.Conn for compatibility with the gost connector interface,
// but the actual data transfer happens via datagrams through the pre-opened RequestStream.
type MasqueConn struct {
clientConn *http3.ClientConn
reqStream *http3.RequestStream // Pre-opened stream for CONNECT-UDP
host string
log logger.Logger
}
// GetRequestStream returns the pre-opened HTTP/3 request stream.
// The connector uses this for the CONNECT-UDP handshake.
func (c *MasqueConn) GetRequestStream() *http3.RequestStream {
return c.reqStream
}
// GetHost returns the proxy host.
func (c *MasqueConn) GetHost() string {
return c.host
}
// Read implements net.Conn but is not used for MASQUE.
// The actual data transfer happens via datagrams.
func (c *MasqueConn) Read(b []byte) (n int, err error) {
// This should not be called - datagrams are used for data transfer
return 0, nil
}
// Write implements net.Conn but is not used for MASQUE.
// The actual data transfer happens via datagrams.
func (c *MasqueConn) Write(b []byte) (n int, err error) {
// This should not be called - datagrams are used for data transfer
return len(b), nil
}
// Close closes the connection.
func (c *MasqueConn) Close() error {
// Don't close the underlying clientConn as it's shared/pooled
return nil
}
// LocalAddr returns the local network address.
func (c *MasqueConn) LocalAddr() net.Addr {
return &net.UDPAddr{}
}
// RemoteAddr returns the remote network address.
func (c *MasqueConn) RemoteAddr() net.Addr {
return &net.UDPAddr{}
}
// SetDeadline sets the read and write deadlines.
func (c *MasqueConn) SetDeadline(t time.Time) error {
return nil
}
// SetReadDeadline sets the read deadline.
func (c *MasqueConn) SetReadDeadline(t time.Time) error {
return nil
}
// SetWriteDeadline sets the write deadline.
func (c *MasqueConn) SetWriteDeadline(t time.Time) error {
return nil
}

Some files were not shown because too many files have changed in this diff Show More