Compare commits

..

10 Commits

Author SHA1 Message Date
ginuerzh
696d10fc28 add webtransport tunnel 2023-11-10 21:29:11 +08:00
ginuerzh
6bface4581 add path option for hop 2023-11-09 20:34:59 +08:00
ginuerzh
3eca21104a add port filter for matcher 2023-11-09 10:59:32 +08:00
ginuerzh
a697bf2e35 update resolver 2023-11-08 20:51:43 +08:00
ginuerzh
6980fcfb19 improve udp listener 2023-11-07 23:10:44 +08:00
ginuerzh
d464be5fd0 update sd 2023-11-02 22:31:52 +08:00
ginuerzh
68edeb2d59 update sd 2023-11-02 20:52:33 +08:00
ginuerzh
a7166b8206 add sd for tunnel 2023-10-31 22:59:14 +08:00
ginuerzh
e8d5e719a4 add tls options 2023-10-29 10:27:13 +08:00
ginuerzh
cffbecc3c0 update go.mod 2023-10-27 22:14:13 +08:00
46 changed files with 1846 additions and 382 deletions

View File

@ -113,8 +113,13 @@ func (p *localAdmission) Admit(ctx context.Context, addr string, opts ...admissi
matched := p.matched(addr)
return !p.options.whitelist && !matched ||
b := !p.options.whitelist && !matched ||
p.options.whitelist && matched
if !b {
p.options.logger.Debugf("%s is denied", addr)
}
return b
}
func (p *localAdmission) periodReload(ctx context.Context) error {
@ -156,6 +161,10 @@ func (p *localAdmission) reload(ctx context.Context) error {
inets = append(inets, inet)
continue
}
if ipAddr, _ := net.ResolveIPAddr("ip", pattern); ipAddr != nil {
p.options.logger.Debugf("resolve IP: %s -> %s", pattern, ipAddr)
ips = append(ips, ipAddr.IP)
}
}
p.mu.Lock()

View File

@ -70,9 +70,8 @@ func LoggerOption(logger logger.Logger) Option {
}
type localBypass struct {
ipMatcher matcher.Matcher
cidrMatcher matcher.Matcher
domainMatcher matcher.Matcher
addrMatcher matcher.Matcher
wildcardMatcher matcher.Matcher
cancelFunc context.CancelFunc
options options
@ -132,15 +131,10 @@ func (bp *localBypass) reload(ctx context.Context) error {
}
patterns := append(bp.options.matchers, v...)
var ips []net.IP
var addrs []string
var inets []*net.IPNet
var domains []string
var wildcards []string
for _, pattern := range patterns {
if ip := net.ParseIP(pattern); ip != nil {
ips = append(ips, ip)
continue
}
if _, inet, err := net.ParseCIDR(pattern); err == nil {
inets = append(inets, inet)
continue
@ -149,15 +143,14 @@ func (bp *localBypass) reload(ctx context.Context) error {
wildcards = append(wildcards, pattern)
continue
}
domains = append(domains, pattern)
addrs = append(addrs, pattern)
}
bp.mu.Lock()
defer bp.mu.Unlock()
bp.ipMatcher = matcher.IPMatcher(ips)
bp.cidrMatcher = matcher.CIDRMatcher(inets)
bp.domainMatcher = matcher.DomainMatcher(domains)
bp.addrMatcher = matcher.AddrMatcher(addrs)
bp.wildcardMatcher = matcher.WildcardMatcher(wildcards)
return nil
@ -237,11 +230,6 @@ func (bp *localBypass) Contains(ctx context.Context, network, addr string, opts
return false
}
// try to strip the port
if host, _, _ := net.SplitHostPort(addr); host != "" {
addr = host
}
matched := bp.matched(addr)
b := !bp.options.whitelist && matched ||
@ -263,13 +251,20 @@ func (bp *localBypass) matched(addr string) bool {
bp.mu.RLock()
defer bp.mu.RUnlock()
if ip := net.ParseIP(addr); ip != nil {
return bp.ipMatcher.Match(addr) ||
bp.cidrMatcher.Match(addr)
if bp.addrMatcher.Match(addr) {
return true
}
return bp.domainMatcher.Match(addr) ||
bp.wildcardMatcher.Match(addr)
host, _, _ := net.SplitHostPort(addr)
if host == "" {
host = addr
}
if ip := net.ParseIP(host); ip != nil {
return bp.cidrMatcher.Match(host)
}
return bp.wildcardMatcher.Match(addr)
}
func (bp *localBypass) Close() error {

View File

@ -99,11 +99,12 @@ type MetricsConfig struct {
}
type TLSConfig struct {
CertFile string `yaml:"certFile,omitempty" json:"certFile,omitempty"`
KeyFile string `yaml:"keyFile,omitempty" json:"keyFile,omitempty"`
CAFile string `yaml:"caFile,omitempty" json:"caFile,omitempty"`
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
CertFile string `yaml:"certFile,omitempty" json:"certFile,omitempty"`
KeyFile string `yaml:"keyFile,omitempty" json:"keyFile,omitempty"`
CAFile string `yaml:"caFile,omitempty" json:"caFile,omitempty"`
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
Options *TLSOptions `yaml:",omitempty" json:"options,omitempty"`
// for auto-generated default certificate.
Validity time.Duration `yaml:",omitempty" json:"validity,omitempty"`
@ -111,6 +112,12 @@ type TLSConfig struct {
Organization string `yaml:",omitempty" json:"organization,omitempty"`
}
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"`
}
type PluginConfig struct {
Type string `json:"type"`
Addr string `json:"addr"`
@ -191,6 +198,8 @@ type NameserverConfig struct {
Hostname string `yaml:",omitempty" json:"hostname,omitempty"`
TTL time.Duration `yaml:",omitempty" json:"ttl,omitempty"`
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
Async bool `yaml:",omitempty" json:"async,omitempty"`
Only string `yaml:",omitempty" json:"only,omitempty"`
}
type ResolverConfig struct {
@ -230,6 +239,11 @@ type IngressConfig struct {
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
}
type SDConfig struct {
Name string `json:"name"`
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
}
type RecorderConfig struct {
Name string `json:"name"`
File *FileRecorder `yaml:",omitempty" json:"file,omitempty"`
@ -313,6 +327,7 @@ type ForwardNodeConfig struct {
Host string `yaml:",omitempty" json:"host,omitempty"`
Network string `yaml:",omitempty" json:"network,omitempty"`
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
Path string `yaml:",omitempty" json:"path,omitempty"`
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
@ -326,8 +341,9 @@ type HTTPNodeConfig struct {
}
type TLSNodeConfig struct {
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
Options *TLSOptions `yaml:",omitempty" json:"options,omitempty"`
}
type DialerConfig struct {
@ -405,6 +421,7 @@ type NodeConfig struct {
Host string `yaml:",omitempty" json:"host,omitempty"`
Network string `yaml:",omitempty" json:"network,omitempty"`
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
Path string `yaml:",omitempty" json:"path,omitempty"`
Interface string `yaml:",omitempty" json:"interface,omitempty"`
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
@ -429,6 +446,7 @@ type Config struct {
Resolvers []*ResolverConfig `yaml:",omitempty" json:"resolvers,omitempty"`
Hosts []*HostsConfig `yaml:",omitempty" json:"hosts,omitempty"`
Ingresses []*IngressConfig `yaml:",omitempty" json:"ingresses,omitempty"`
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`

View File

@ -57,9 +57,7 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
if tlsCfg.ServerName == "" {
tlsCfg.ServerName = serverName
}
tlsConfig, err := tls_util.LoadClientConfig(
tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile,
tlsCfg.Secure, tlsCfg.ServerName)
tlsConfig, err := tls_util.LoadClientConfig(tlsCfg)
if err != nil {
nodeLogger.Error(err)
return nil, err
@ -99,9 +97,7 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
if tlsCfg.ServerName == "" {
tlsCfg.ServerName = serverName
}
tlsConfig, err = tls_util.LoadClientConfig(
tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile,
tlsCfg.Secure, tlsCfg.ServerName)
tlsConfig, err = tls_util.LoadClientConfig(tlsCfg)
if err != nil {
nodeLogger.Error(err)
return nil, err
@ -168,6 +164,7 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
chain.MetadataNodeOption(nm),
chain.HostNodeOption(host),
chain.ProtocolNodeOption(cfg.Protocol),
chain.PathNodeOption(cfg.Path),
chain.NetworkNodeOption(cfg.Network),
}
if cfg.HTTP != nil {
@ -177,10 +174,16 @@ func ParseNode(hop string, cfg *config.NodeConfig) (*chain.Node, error) {
}))
}
if cfg.TLS != nil {
opts = append(opts, chain.TLSNodeOption(&chain.TLSNodeSettings{
tlsCfg := &chain.TLSNodeSettings{
ServerName: cfg.TLS.ServerName,
Secure: cfg.TLS.Secure,
}))
}
if o := cfg.TLS.Options; o != nil {
tlsCfg.Options.MinVersion = o.MinVersion
tlsCfg.Options.MaxVersion = o.MaxVersion
tlsCfg.Options.CipherSuites = o.CipherSuites
}
opts = append(opts, chain.TLSNodeOption(tlsCfg))
}
if cfg.Auth != nil {
opts = append(opts, chain.AutherNodeOption(

View File

@ -52,6 +52,8 @@ func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
ClientIP: net.ParseIP(server.ClientIP),
Prefer: server.Prefer,
Hostname: server.Hostname,
Async: server.Async,
Only: server.Only,
})
}

View File

@ -0,0 +1,39 @@
package sd
import (
"crypto/tls"
"strings"
"github.com/go-gost/core/sd"
"github.com/go-gost/x/config"
"github.com/go-gost/x/internal/plugin"
xsd "github.com/go-gost/x/sd"
)
func ParseSD(cfg *config.SDConfig) sd.SD {
if cfg == nil || cfg.Plugin == nil {
return nil
}
var tlsCfg *tls.Config
if cfg.Plugin.TLS != nil {
tlsCfg = &tls.Config{
ServerName: cfg.Plugin.TLS.ServerName,
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
}
}
switch strings.ToLower(cfg.Plugin.Type) {
case "http":
return xsd.NewHTTPPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TLSConfigOption(tlsCfg),
plugin.TimeoutOption(cfg.Plugin.Timeout),
)
default:
return xsd.NewGRPCPlugin(
cfg.Name, cfg.Plugin.Addr,
plugin.TokenOption(cfg.Plugin.Token),
plugin.TLSConfigOption(tlsCfg),
)
}
}

View File

@ -55,8 +55,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
if tlsCfg == nil {
tlsCfg = &config.TLSConfig{}
}
tlsConfig, err := tls_util.LoadServerConfig(
tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile)
tlsConfig, err := tls_util.LoadServerConfig(tlsCfg)
if err != nil {
listenerLogger.Error(err)
return nil, err
@ -149,8 +148,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
if tlsCfg == nil {
tlsCfg = &config.TLSConfig{}
}
tlsConfig, err = tls_util.LoadServerConfig(
tlsCfg.CertFile, tlsCfg.KeyFile, tlsCfg.CAFile)
tlsConfig, err = tls_util.LoadServerConfig(tlsCfg)
if err != nil {
handlerLogger.Error(err)
return nil, err
@ -267,6 +265,7 @@ func parseForwarder(cfg *config.ForwarderConfig) (hop.Hop, error) {
Host: node.Host,
Network: node.Network,
Protocol: node.Protocol,
Path: node.Path,
Bypass: node.Bypass,
Bypasses: node.Bypasses,
HTTP: node.HTTP,

View File

@ -50,6 +50,9 @@ func (c *tunnelConnector) initTunnel(conn net.Conn, network, address string) (ad
if network == "udp" {
req.Cmd |= relay.FUDP
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUDP,
})
}
if c.options.Auth != nil {

View File

@ -29,32 +29,27 @@ type metadata struct {
func (d *http3Dialer) parseMetadata(md mdata.Metadata) (err error) {
const (
authorizePath = "authorizePath"
pushPath = "pushPath"
pullPath = "pullPath"
host = "host"
keepAlive = "keepAlive"
keepAlive = "keepalive"
keepAlivePeriod = "ttl"
handshakeTimeout = "handshakeTimeout"
maxIdleTimeout = "maxIdleTimeout"
maxStreams = "maxStreams"
)
d.md.authorizePath = mdutil.GetString(md, authorizePath)
d.md.authorizePath = mdutil.GetString(md, "pht.authorizePath", "authorizePath")
if !strings.HasPrefix(d.md.authorizePath, "/") {
d.md.authorizePath = defaultAuthorizePath
}
d.md.pushPath = mdutil.GetString(md, pushPath)
d.md.pushPath = mdutil.GetString(md, "pht.pushPath", "pushPath")
if !strings.HasPrefix(d.md.pushPath, "/") {
d.md.pushPath = defaultPushPath
}
d.md.pullPath = mdutil.GetString(md, pullPath)
d.md.pullPath = mdutil.GetString(md, "pht.pullPath", "pullPath")
if !strings.HasPrefix(d.md.pullPath, "/") {
d.md.pullPath = defaultPullPath
}
d.md.host = mdutil.GetString(md, host)
d.md.host = mdutil.GetString(md, "host")
if !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
if d.md.keepAlivePeriod <= 0 {

58
dialer/http3/wt/client.go Normal file
View File

@ -0,0 +1,58 @@
package wt
import (
"context"
"net"
"net/http"
"net/http/httputil"
"net/url"
"github.com/go-gost/core/logger"
wt_util "github.com/go-gost/x/internal/util/wt"
wt "github.com/quic-go/webtransport-go"
)
type Client struct {
host string
path string
header http.Header
dialer *wt.Dialer
session *wt.Session
log logger.Logger
}
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
ok := false
if c.session != nil {
select {
case <-c.session.Context().Done():
default:
ok = true
}
}
if !ok {
url := url.URL{
Scheme: "https",
Host: c.host,
Path: c.path,
}
resp, session, err := c.dialer.Dial(ctx, url.String(), c.header)
if err != nil {
return nil, err
}
if c.log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
c.log.Trace(string(dump))
}
c.session = session
}
stream, err := c.session.OpenStream()
if err != nil {
return nil, err
}
return wt_util.Conn(c.session, stream), nil
}

111
dialer/http3/wt/dialer.go Normal file
View File

@ -0,0 +1,111 @@
package wt
import (
"context"
"crypto/tls"
"net"
"sync"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
wt "github.com/quic-go/webtransport-go"
)
func init() {
registry.DialerRegistry().Register("wt", NewDialer)
}
type wtDialer struct {
clients map[string]*Client
clientMutex sync.Mutex
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &wtDialer{
clients: make(map[string]*Client),
options: options,
}
}
func (d *wtDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return
}
// Multiplex implements dialer.Multiplexer interface.
func (d *wtDialer) Multiplex() bool {
return true
}
func (d *wtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
d.clientMutex.Lock()
defer d.clientMutex.Unlock()
client := d.clients[addr]
if client == nil {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
host := d.md.host
if host == "" {
host = options.Host
}
if h, _, _ := net.SplitHostPort(host); h != "" {
host = h
}
client = &Client{
log: d.options.Logger,
host: host,
path: d.md.path,
header: d.md.header,
dialer: &wt.Dialer{
RoundTripper: &http3.RoundTripper{
TLSClientConfig: d.options.TLSConfig,
Dial: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
// d.options.Logger.Infof("dial: %s, %s, %s", addr, adr, host)
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
udpConn, err := options.NetDialer.Dial(ctx, "udp", "")
if err != nil {
return nil, err
}
return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
},
QuicConfig: &quic.Config{
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
/*
Versions: []quic.VersionNumber{
quic.Version1,
},
*/
MaxIncomingStreams: int64(d.md.maxStreams),
},
},
},
}
d.clients[addr] = client
}
return client.Dial(ctx, addr)
}

View File

@ -0,0 +1,62 @@
package wt
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
)
const (
defaultPath = "/wt"
defaultKeepalivePeriod = 15 * time.Second
)
type metadata struct {
host string
path string
header http.Header
// QUIC config options
keepAlivePeriod time.Duration
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
maxStreams int
}
func (d *wtDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
keepAlive = "keepalive"
keepAlivePeriod = "ttl"
handshakeTimeout = "handshakeTimeout"
maxIdleTimeout = "maxIdleTimeout"
maxStreams = "maxStreams"
)
d.md.host = mdutil.GetString(md, "wt.host", "host")
d.md.path = mdutil.GetString(md, "wt.path", "path")
if d.md.path == "" {
d.md.path = defaultPath
}
if !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
if d.md.keepAlivePeriod <= 0 {
d.md.keepAlivePeriod = 10 * time.Second
}
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
d.md.maxStreams = mdutil.GetInt(md, maxStreams)
if m := mdutil.GetStringMapString(md, "wt.header", "header"); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
return
}

View File

@ -30,22 +30,8 @@ type metadata struct {
}
func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
host = "host"
path = "path"
handshakeTimeout = "handshakeTimeout"
readHeaderTimeout = "readHeaderTimeout"
readBufferSize = "readBufferSize"
writeBufferSize = "writeBufferSize"
enableCompression = "enableCompression"
header = "header"
)
d.md.host = mdutil.GetString(md, host)
d.md.path = mdutil.GetString(md, path)
d.md.host = mdutil.GetString(md, "ws.host", "host")
d.md.path = mdutil.GetString(md, "ws.path", "path")
if d.md.path == "" {
d.md.path = defaultPath
}
@ -60,13 +46,13 @@ func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
d.md.readHeaderTimeout = mdutil.GetDuration(md, readHeaderTimeout)
d.md.readBufferSize = mdutil.GetInt(md, readBufferSize)
d.md.writeBufferSize = mdutil.GetInt(md, writeBufferSize)
d.md.enableCompression = mdutil.GetBool(md, enableCompression)
d.md.handshakeTimeout = mdutil.GetDuration(md, "ws.handshakeTimeout", "handshakeTimeout")
d.md.readHeaderTimeout = mdutil.GetDuration(md, "ws.readHeaderTimeout", "readHeaderTimeout")
d.md.readBufferSize = mdutil.GetInt(md, "ws.readBufferSize", "readBufferSize")
d.md.writeBufferSize = mdutil.GetInt(md, "ws.writeBufferSize", "writeBufferSize")
d.md.enableCompression = mdutil.GetBool(md, "ws.enableCompression", "enableCompression")
if m := mdutil.GetStringMapString(md, header); len(m) > 0 {
if m := mdutil.GetStringMapString(md, "ws.header", "header"); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
@ -74,7 +60,7 @@ func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.header = h
}
if mdutil.GetBool(md, "keepalive") {
if mdutil.GetBool(md, "ws.keepalive", "keepalive") {
d.md.keepaliveInterval = mdutil.GetDuration(md, "ttl", "keepalive.interval")
if d.md.keepaliveInterval <= 0 {
d.md.keepaliveInterval = defaultKeepalivePeriod

View File

@ -28,33 +28,20 @@ type metadata struct {
}
func (d *wsDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
host = "host"
path = "path"
d.md.host = mdutil.GetString(md, "ws.host", "host")
handshakeTimeout = "handshakeTimeout"
readHeaderTimeout = "readHeaderTimeout"
readBufferSize = "readBufferSize"
writeBufferSize = "writeBufferSize"
enableCompression = "enableCompression"
header = "header"
)
d.md.host = mdutil.GetString(md, host)
d.md.path = mdutil.GetString(md, path)
d.md.path = mdutil.GetString(md, "ws.path", "path")
if d.md.path == "" {
d.md.path = defaultPath
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
d.md.readHeaderTimeout = mdutil.GetDuration(md, readHeaderTimeout)
d.md.readBufferSize = mdutil.GetInt(md, readBufferSize)
d.md.writeBufferSize = mdutil.GetInt(md, writeBufferSize)
d.md.enableCompression = mdutil.GetBool(md, enableCompression)
d.md.handshakeTimeout = mdutil.GetDuration(md, "ws.handshakeTimeout", "handshakeTimeout")
d.md.readHeaderTimeout = mdutil.GetDuration(md, "ws.readHeaderTimeout", "readHeaderTimeout")
d.md.readBufferSize = mdutil.GetInt(md, "ws.readBufferSize", "readBufferSize")
d.md.writeBufferSize = mdutil.GetInt(md, "ws.writeBufferSize", "writeBufferSize")
d.md.enableCompression = mdutil.GetBool(md, "ws.enableCompression", "enableCompression")
if m := mdutil.GetStringMapString(md, header); len(m) > 0 {
if m := mdutil.GetStringMapString(md, "ws.header", "header"); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
@ -62,7 +49,7 @@ func (d *wsDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.header = h
}
if mdutil.GetBool(md, "keepalive") {
if mdutil.GetBool(md, "ws.keepalive", "keepalive") {
d.md.keepaliveInterval = mdutil.GetDuration(md, "ttl", "keepalive.interval")
if d.md.keepaliveInterval <= 0 {
d.md.keepaliveInterval = defaultKeepalivePeriod

15
go.mod
View File

@ -7,23 +7,24 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.9.1
github.com/go-gost/core v0.0.0-20231027140845-d975ec3c7477
github.com/go-gost/core v0.0.0-20231109123312-8e4fc06cf1b7
github.com/go-gost/gosocks4 v0.0.1
github.com/go-gost/gosocks5 v0.4.0
github.com/go-gost/plugin v0.0.0-20231020155519-e190e1c74d78
github.com/go-gost/plugin v0.0.0-20231109123346-0ae4157b9d25
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451
github.com/go-redis/redis/v8 v8.11.5
github.com/gobwas/glob v0.2.3
github.com/golang/snappy v0.0.4
github.com/google/uuid v1.3.0
github.com/google/uuid v1.4.0
github.com/gorilla/websocket v1.5.0
github.com/miekg/dns v1.1.56
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pion/dtls/v2 v2.2.6
github.com/pires/go-proxyproto v0.7.0
github.com/prometheus/client_golang v1.17.0
github.com/quic-go/quic-go v0.38.1
github.com/quic-go/quic-go v0.39.0
github.com/quic-go/webtransport-go v0.6.0
github.com/rs/xid v1.3.0
github.com/shadowsocks/go-shadowsocks2 v0.1.5
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601
@ -40,7 +41,7 @@ require (
golang.org/x/sys v0.13.0
golang.org/x/time v0.3.0
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
google.golang.org/grpc v1.58.3
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
)
@ -61,7 +62,6 @@ require (
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect
@ -102,13 +102,14 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
go.uber.org/mock v0.3.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.13.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

41
go.sum
View File

@ -75,6 +75,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
@ -91,16 +93,14 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SU
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gost/core v0.0.0-20231026142046-9e767d674527 h1:BLhpnK+J9A3vugXCJrC+BNjz2Q4qdEE8IWIlWr7VOaw=
github.com/go-gost/core v0.0.0-20231026142046-9e767d674527/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
github.com/go-gost/core v0.0.0-20231027140845-d975ec3c7477 h1:a49XfrB4mgbw7z7oN/WTovx0X7SbxdfoANsEDTy9CqI=
github.com/go-gost/core v0.0.0-20231027140845-d975ec3c7477/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
github.com/go-gost/core v0.0.0-20231109123312-8e4fc06cf1b7 h1:sDsPtmP51qf8zN/RbZZj/3vNLCoH0sdvpIRwV6TfzvY=
github.com/go-gost/core v0.0.0-20231109123312-8e4fc06cf1b7/go.mod h1:ndkgWVYRLwupVaFFWv8ML1Nr8tD3xhHK245PLpUDg4E=
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
github.com/go-gost/gosocks5 v0.4.0 h1:EIrOEkpJez4gwHrMa33frA+hHXJyevjp47thpMQsJzI=
github.com/go-gost/gosocks5 v0.4.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
github.com/go-gost/plugin v0.0.0-20231020155519-e190e1c74d78 h1:amM4Qg1/g2NC5fCysD7YnFpqRHZ9svNIeSDhBBM6pmo=
github.com/go-gost/plugin v0.0.0-20231020155519-e190e1c74d78/go.mod h1:mM/RLNsVy2nz5PiOijuqLYR3LhMzyQ9Kh/p0rXybJoo=
github.com/go-gost/plugin v0.0.0-20231109123346-0ae4157b9d25 h1:sOarC0xAJij4VtEhkJRng5okZW23KlXprxhb5XFZ+pw=
github.com/go-gost/plugin v0.0.0-20231109123346-0ae4157b9d25/go.mod h1:qXr2Zm9Ex2ATqnWuNUzVZqySPMnuIihvblYZt4MlZLw=
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7 h1:qAG1OyjvdA5h221CfFSS3J359V3d2E7dJWyP29QoDSI=
github.com/go-gost/relay v0.4.1-0.20230916134211-828f314ddfe7/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
github.com/go-gost/tls-dissector v0.0.2-0.20220408131628-aac992c27451 h1:xj8gUZGYO3nb5+6Bjw9+tsFkA9sYynrOvDvvC4uDV2I=
@ -136,8 +136,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -191,8 +189,8 @@ github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 h1:gpptm606MZYGaMHMsB
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
@ -290,8 +288,10 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY=
github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -365,7 +365,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@ -373,6 +372,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
@ -426,7 +427,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@ -461,7 +461,6 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@ -486,7 +485,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@ -526,9 +524,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -611,7 +607,6 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
@ -685,8 +680,8 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -703,8 +698,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@ -20,10 +20,12 @@ import (
"github.com/go-gost/core/hop"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/config"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net"
auth_util "github.com/go-gost/x/internal/util/auth"
"github.com/go-gost/x/internal/util/forward"
tls_util "github.com/go-gost/x/internal/util/tls"
"github.com/go-gost/x/registry"
)
@ -232,6 +234,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot
target = h.hop.Select(ctx,
hop.HostSelectOption(req.Host),
hop.ProtocolSelectOption(forward.ProtoHTTP),
hop.PathSelectOption(req.URL.Path),
)
}
if target == nil {
@ -284,10 +287,16 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot
log.Debugf("connection to node %s(%s)", target.Name, target.Addr)
if tlsSettings := target.Options().TLS; tlsSettings != nil {
cc = tls.Client(cc, &tls.Config{
cfg := &tls.Config{
ServerName: tlsSettings.ServerName,
InsecureSkipVerify: !tlsSettings.Secure,
}
tls_util.SetTLSOptions(cfg, &config.TLSOptions{
MinVersion: tlsSettings.Options.MinVersion,
MaxVersion: tlsSettings.Options.MaxVersion,
CipherSuites: tlsSettings.Options.CipherSuites,
})
cc = tls.Client(cc, cfg)
}
if err := req.Write(cc); err != nil {

View File

@ -21,11 +21,13 @@ import (
"github.com/go-gost/core/logger"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
"github.com/go-gost/x/config"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/net/proxyproto"
auth_util "github.com/go-gost/x/internal/util/auth"
"github.com/go-gost/x/internal/util/forward"
tls_util "github.com/go-gost/x/internal/util/tls"
"github.com/go-gost/x/registry"
)
@ -233,6 +235,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot
target = h.hop.Select(ctx,
hop.HostSelectOption(req.Host),
hop.ProtocolSelectOption(forward.ProtoHTTP),
hop.PathSelectOption(req.URL.Path),
)
}
if target == nil {
@ -285,10 +288,16 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, remot
log.Debugf("new connection to node %s(%s)", target.Name, target.Addr)
if tlsSettings := target.Options().TLS; tlsSettings != nil {
cc = tls.Client(cc, &tls.Config{
cfg := &tls.Config{
ServerName: tlsSettings.ServerName,
InsecureSkipVerify: !tlsSettings.Secure,
}
tls_util.SetTLSOptions(cfg, &config.TLSOptions{
MinVersion: tlsSettings.Options.MinVersion,
MaxVersion: tlsSettings.Options.MaxVersion,
CipherSuites: tlsSettings.Options.CipherSuites,
})
cc = tls.Client(cc, cfg)
}
cc = proxyproto.WrapClientConn(h.md.proxyProtocol, remoteAddr, localAddr, cc)

View File

@ -4,11 +4,10 @@ import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"net"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
"github.com/go-gost/core/sd"
"github.com/go-gost/relay"
"github.com/go-gost/x/internal/util/mux"
"github.com/google/uuid"
@ -55,19 +54,21 @@ func (h *tunnelHandler) handleBind(ctx context.Context, conn net.Conn, network,
return
}
h.pool.Add(tunnelID, NewConnector(connectorID, session))
h.pool.Add(tunnelID, NewConnector(connectorID, tunnelID, h.id, session, h.md.sd), h.md.tunnelTTL)
if h.md.ingress != nil {
h.md.ingress.Set(ctx, addr, tunnelID.String())
}
if h.recorder != nil {
h.recorder.Record(ctx,
[]byte(fmt.Sprintf("%s:%s", tunnelID, connectorID)),
recorder.MetadataReocrdOption(connectorMetadata{
Op: "add",
Network: network,
Server: conn.LocalAddr().String(),
}),
)
if h.md.sd != nil {
err := h.md.sd.Register(ctx, &sd.Service{
ID: connectorID.String(),
Name: tunnelID.String(),
Node: h.id,
Network: network,
Address: h.md.entryPoint,
})
if err != nil {
h.log.Error(err)
}
}
log.Debugf("%s/%s: tunnel=%s, connector=%s established", addr, network, tunnelID, connectorID)

View File

@ -11,7 +11,7 @@ import (
xnet "github.com/go-gost/x/internal/net"
)
func (h *tunnelHandler) handleConnect(ctx context.Context, conn net.Conn, network, srcAddr string, dstAddr string, tunnelID relay.TunnelID, log logger.Logger) error {
func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, conn net.Conn, network, srcAddr string, dstAddr string, tunnelID relay.TunnelID, log logger.Logger) error {
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", dstAddr, network),
"cmd": "connect",
@ -33,58 +33,68 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, conn net.Conn, networ
host, _, _ := net.SplitHostPort(dstAddr)
// client is a public entrypoint.
if tunnelID.Equal(h.md.entryPointID) && !h.md.entryPointID.IsZero() {
if tunnelID.Equal(h.md.entryPointID) {
resp.WriteTo(conn)
return h.ep.handle(ctx, conn)
}
var tid relay.TunnelID
if ingress := h.md.ingress; ingress != nil && host != "" {
tid = parseTunnelID(ingress.Get(ctx, host))
if !h.md.directTunnel {
var tid relay.TunnelID
if ingress := h.md.ingress; ingress != nil && host != "" {
tid = parseTunnelID(ingress.Get(ctx, host))
}
if !tid.Equal(tunnelID) {
resp.Status = relay.StatusHostUnreachable
resp.WriteTo(conn)
err := fmt.Errorf("no route to host %s", host)
log.Error(err)
return err
}
}
// direct routing
if h.md.directTunnel {
tid = tunnelID
} else if !tid.Equal(tunnelID) {
resp.Status = relay.StatusHostUnreachable
resp.WriteTo(conn)
err := fmt.Errorf("no route to host %s", host)
log.Error(err)
return err
d := Dialer{
node: h.id,
pool: h.pool,
sd: h.md.sd,
retry: 3,
timeout: 15 * time.Second,
log: log,
}
cc, _, err := getTunnelConn(network, h.pool, tid, 3, log)
cc, node, cid, err := d.Dial(ctx, network, tunnelID.String())
if err != nil {
log.Error(err)
resp.Status = relay.StatusServiceUnavailable
resp.WriteTo(conn)
log.Error(err)
return err
}
defer cc.Close()
log.Debugf("%s >> %s", conn.RemoteAddr(), cc.RemoteAddr())
log.Debugf("new connection to tunnel: %s, connector: %s", tunnelID, cid)
if _, err := resp.WriteTo(conn); err != nil {
log.Error(err)
return err
if node == h.id {
if _, err := resp.WriteTo(conn); err != nil {
log.Error(err)
return err
}
resp = relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
}
af := &relay.AddrFeature{}
af.ParseFrom(srcAddr)
resp.Features = append(resp.Features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(dstAddr)
resp.Features = append(resp.Features, af) // dst address
resp.WriteTo(cc)
} else {
req.WriteTo(cc)
}
resp = relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
}
af := &relay.AddrFeature{}
af.ParseFrom(srcAddr)
resp.Features = append(resp.Features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(dstAddr)
resp.Features = append(resp.Features, af) // dst address
resp.WriteTo(cc)
t := time.Now()
log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.RemoteAddr())
xnet.Transport(conn, cc)

78
handler/tunnel/dialer.go Normal file
View File

@ -0,0 +1,78 @@
package tunnel
import (
"context"
"net"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/sd"
)
type Dialer struct {
node string
pool *ConnectorPool
sd sd.SD
retry int
timeout time.Duration
log logger.Logger
}
func (d *Dialer) Dial(ctx context.Context, network string, tid string) (conn net.Conn, node string, cid string, err error) {
retry := d.retry
if retry <= 0 {
retry = 1
}
for i := 0; i < retry; i++ {
c := d.pool.Get(network, tid)
if c == nil {
break
}
conn, err = c.Session().GetConn()
if err != nil {
d.log.Error(err)
continue
}
node = d.node
cid = c.id.String()
break
}
if conn != nil || err != nil {
return
}
if d.sd == nil {
err = ErrTunnelNotAvailable
return
}
ss, err := d.sd.Get(ctx, tid)
if err != nil {
return
}
var service *sd.Service
for _, s := range ss {
d.log.Debugf("%+v", s)
if s.Name != d.node && s.Network == network {
service = s
break
}
}
if service == nil || service.Address == "" {
err = ErrTunnelNotAvailable
return
}
node = service.Node
cid = service.Name
dialer := net.Dialer{
Timeout: d.timeout,
}
conn, err = dialer.DialContext(ctx, network, service.Address)
return
}

View File

@ -17,6 +17,7 @@ import (
"github.com/go-gost/core/listener"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/sd"
"github.com/go-gost/relay"
admission "github.com/go-gost/x/admission/wrapper"
xio "github.com/go-gost/x/internal/io"
@ -28,8 +29,10 @@ import (
)
type entrypoint struct {
node string
pool *ConnectorPool
ingress ingress.Ingress
sd sd.SD
log logger.Logger
}
@ -51,6 +54,14 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
br := bufio.NewReader(conn)
v, err := br.Peek(1)
if err != nil {
return err
}
if v[0] == relay.Version1 {
return ep.handleConnect(ctx, xnet.NewBufferReaderConn(conn, br), log)
}
var cc net.Conn
for {
resp := &http.Response{
@ -102,32 +113,42 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
remoteAddr = addr
}
cc, cid, err := getTunnelConn("tcp", ep.pool, tunnelID, 3, log)
d := &Dialer{
node: ep.node,
pool: ep.pool,
sd: ep.sd,
retry: 3,
timeout: 15 * time.Second,
log: log,
}
cc, node, cid, err := d.Dial(ctx, "tcp", tunnelID.String())
if err != nil {
log.Error(err)
return resp.Write(conn)
}
log.Debugf("new connection to tunnel: %s, connector: %s", tunnelID, cid)
var features []relay.Feature
af := &relay.AddrFeature{}
af.ParseFrom(remoteAddr.String())
features = append(features, af) // src address
host := req.Host
if h, _, _ := net.SplitHostPort(host); h == "" {
host = net.JoinHostPort(host, "80")
}
af = &relay.AddrFeature{}
af.ParseFrom(host)
features = append(features, af) // dst address
(&relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
Features: features,
}).WriteTo(cc)
if node == ep.node {
var features []relay.Feature
af := &relay.AddrFeature{}
af.ParseFrom(remoteAddr.String())
features = append(features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(host)
features = append(features, af) // dst address
(&relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
Features: features,
}).WriteTo(cc)
}
if err := req.Write(cc); err != nil {
cc.Close()
@ -186,6 +207,90 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
return nil
}
func (ep *entrypoint) handleConnect(ctx context.Context, conn net.Conn, log logger.Logger) error {
req := relay.Request{}
if _, err := req.ReadFrom(conn); err != nil {
return err
}
resp := relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
}
var srcAddr, dstAddr string
network := "tcp"
var tunnelID relay.TunnelID
for _, f := range req.Features {
switch f.Type() {
case relay.FeatureAddr:
if feature, _ := f.(*relay.AddrFeature); feature != nil {
v := net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port)))
if srcAddr != "" {
dstAddr = v
} else {
srcAddr = v
}
}
case relay.FeatureTunnel:
if feature, _ := f.(*relay.TunnelFeature); feature != nil {
tunnelID = relay.NewTunnelID(feature.ID[:])
}
case relay.FeatureNetwork:
if feature, _ := f.(*relay.NetworkFeature); feature != nil {
network = feature.Network.String()
}
}
}
if tunnelID.IsZero() {
resp.Status = relay.StatusBadRequest
resp.WriteTo(conn)
return ErrTunnelID
}
d := Dialer{
pool: ep.pool,
retry: 3,
timeout: 15 * time.Second,
log: log,
}
cc, _, cid, err := d.Dial(ctx, network, tunnelID.String())
if err != nil {
log.Error(err)
resp.Status = relay.StatusServiceUnavailable
resp.WriteTo(conn)
return err
}
defer cc.Close()
log.Debugf("new connection to tunnel: %s, connector: %s", tunnelID, cid)
if _, err := resp.WriteTo(conn); err != nil {
log.Error(err)
return err
}
af := &relay.AddrFeature{}
af.ParseFrom(srcAddr)
resp.Features = append(resp.Features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(dstAddr)
resp.Features = append(resp.Features, af) // dst address
resp.WriteTo(cc)
t := time.Now()
log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.RemoteAddr())
xnet.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr())
return nil
}
func (ep *entrypoint) getRealClientAddr(req *http.Request, raddr net.Addr) net.Addr {
if req == nil {
return nil

View File

@ -8,9 +8,9 @@ import (
"strconv"
"time"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/listener"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
"github.com/go-gost/core/service"
@ -20,14 +20,16 @@ import (
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry"
xservice "github.com/go-gost/x/service"
"github.com/google/uuid"
)
var (
ErrBadVersion = errors.New("relay: bad version")
ErrUnknownCmd = errors.New("relay: unknown command")
ErrTunnelID = errors.New("tunnel: invalid tunnel ID")
ErrUnauthorized = errors.New("relay: unauthorized")
ErrRateLimit = errors.New("relay: rate limiting exceeded")
ErrBadVersion = errors.New("bad version")
ErrUnknownCmd = errors.New("unknown command")
ErrTunnelID = errors.New("invalid tunnel ID")
ErrTunnelNotAvailable = errors.New("tunnel not available")
ErrUnauthorized = errors.New("unauthorized")
ErrRateLimit = errors.New("rate limiting exceeded")
)
func init() {
@ -35,13 +37,14 @@ func init() {
}
type tunnelHandler struct {
router *chain.Router
md metadata
id string
options handler.Options
pool *ConnectorPool
recorder recorder.Recorder
epSvc service.Service
ep *entrypoint
md metadata
log logger.Logger
}
func NewHandler(opts ...handler.Option) handler.Handler {
@ -60,26 +63,33 @@ func (h *tunnelHandler) Init(md md.Metadata) (err error) {
return err
}
h.router = h.options.Router
if h.router == nil {
h.router = chain.NewRouter(chain.LoggerRouterOption(h.options.Logger))
uuid, err := uuid.NewRandom()
if err != nil {
return err
}
h.id = uuid.String()
if opts := h.router.Options(); opts != nil {
h.log = h.options.Logger.WithFields(map[string]any{
"node": h.id,
})
if opts := h.options.Router.Options(); opts != nil {
for _, ro := range opts.Recorders {
if ro.Record == xrecorder.RecorderServiceHandlerTunnelConnector {
if ro.Record == xrecorder.RecorderServiceHandlerTunnel {
h.recorder = ro.Recorder
break
}
}
}
h.pool = NewConnectorPool()
h.pool.WithRecorder(h.recorder)
h.pool = NewConnectorPool(h.id, h.md.sd)
h.ep = &entrypoint{
node: h.id,
pool: h.pool,
ingress: h.md.ingress,
log: h.options.Logger.WithFields(map[string]any{
sd: h.md.sd,
log: h.log.WithFields(map[string]any{
"kind": "entrypoint",
}),
}
@ -102,12 +112,12 @@ func (h *tunnelHandler) initEntrypoint() (err error) {
ln, err := net.Listen(network, h.md.entryPoint)
if err != nil {
h.options.Logger.Error(err)
h.log.Error(err)
return
}
serviceName := fmt.Sprintf("%s-ep-%s", h.options.Service, ln.Addr())
log := h.options.Logger.WithFields(map[string]any{
log := h.log.WithFields(map[string]any{
"service": serviceName,
"listener": "tcp",
"handler": "tunnel-ep",
@ -143,7 +153,7 @@ func (h *tunnelHandler) initEntrypoint() (err error) {
func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
start := time.Now()
log := h.options.Logger.WithFields(map[string]any{
log := h.log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
@ -189,7 +199,7 @@ func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
var user, pass string
var srcAddr, dstAddr string
var networkID relay.NetworkID
network := "tcp"
var tunnelID relay.TunnelID
for _, f := range req.Features {
switch f.Type() {
@ -212,7 +222,7 @@ func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
}
case relay.FeatureNetwork:
if feature, _ := f.(*relay.NetworkFeature); feature != nil {
networkID = feature.Network
network = feature.Network.String()
}
}
}
@ -237,17 +247,13 @@ func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
ctx = auth_util.ContextWithID(ctx, auth_util.ID(id))
}
network := networkID.String()
if (req.Cmd & relay.FUDP) == relay.FUDP {
network = "udp"
}
switch req.Cmd & relay.CmdMask {
case relay.CmdConnect:
defer conn.Close()
log.Debugf("connect: %s >> %s/%s", srcAddr, dstAddr, network)
return h.handleConnect(ctx, conn, network, srcAddr, dstAddr, tunnelID, log)
return h.handleConnect(ctx, &req, conn, network, srcAddr, dstAddr, tunnelID, log)
case relay.CmdBind:
log.Debugf("bind: %s >> %s/%s", srcAddr, dstAddr, network)
return h.handleBind(ctx, conn, network, dstAddr, tunnelID, log)

View File

@ -8,25 +8,36 @@ import (
"github.com/go-gost/core/logger"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
"github.com/go-gost/core/sd"
"github.com/go-gost/relay"
xingress "github.com/go-gost/x/ingress"
"github.com/go-gost/x/internal/util/mux"
"github.com/go-gost/x/registry"
)
const (
defaultTTL = 15 * time.Second
)
type metadata struct {
readTimeout time.Duration
entryPoint string
entryPointID relay.TunnelID
entryPointProxyProtocol int
directTunnel bool
tunnelTTL time.Duration
ingress ingress.Ingress
sd sd.SD
muxCfg *mux.Config
}
func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.readTimeout = mdutil.GetDuration(md, "readTimeout")
h.md.tunnelTTL = mdutil.GetDuration(md, "tunnel.ttl")
if h.md.tunnelTTL <= 0 {
h.md.tunnelTTL = defaultTTL
}
h.md.directTunnel = mdutil.GetBool(md, "tunnel.direct")
h.md.entryPoint = mdutil.GetString(md, "entrypoint")
h.md.entryPointID = parseTunnelID(mdutil.GetString(md, "entrypoint.id"))
@ -54,6 +65,7 @@ func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) {
)
}
}
h.md.sd = registry.SDRegistry().Get(mdutil.GetString(md, "sd"))
h.md.muxCfg = &mux.Config{
Version: mdutil.GetInt(md, "mux.version"),

View File

@ -2,36 +2,34 @@ package tunnel
import (
"context"
"fmt"
"net"
"sync"
"sync/atomic"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder"
"github.com/go-gost/core/sd"
"github.com/go-gost/relay"
"github.com/go-gost/x/internal/util/mux"
"github.com/google/uuid"
)
type connectorMetadata struct {
Op string
Network string
Server string
}
type Connector struct {
id relay.ConnectorID
t time.Time
s *mux.Session
id relay.ConnectorID
tid relay.TunnelID
node string
sd sd.SD
t time.Time
s *mux.Session
}
func NewConnector(id relay.ConnectorID, s *mux.Session) *Connector {
func NewConnector(id relay.ConnectorID, tid relay.TunnelID, node string, s *mux.Session, sd sd.SD) *Connector {
c := &Connector{
id: id,
t: time.Now(),
s: s,
id: id,
tid: tid,
node: node,
sd: sd,
t: time.Now(),
s: s,
}
go c.accept()
return c
@ -43,6 +41,13 @@ func (c *Connector) accept() {
if err != nil {
logger.Default().Errorf("connector %s: %v", c.id, err)
c.s.Close()
if c.sd != nil {
c.sd.Deregister(context.Background(), &sd.Service{
ID: c.id.String(),
Name: c.tid.String(),
Node: c.node,
})
}
return
}
conn.Close()
@ -58,27 +63,34 @@ func (c *Connector) Session() *mux.Session {
}
type Tunnel struct {
node string
id relay.TunnelID
connectors []*Connector
t time.Time
n uint64
close chan struct{}
mu sync.RWMutex
recorder recorder.Recorder
sd sd.SD
ttl time.Duration
}
func NewTunnel(id relay.TunnelID) *Tunnel {
func NewTunnel(node string, tid relay.TunnelID, ttl time.Duration) *Tunnel {
t := &Tunnel{
id: id,
node: node,
id: tid,
t: time.Now(),
close: make(chan struct{}),
ttl: ttl,
}
if t.ttl <= 0 {
t.ttl = defaultTTL
}
go t.clean()
return t
}
func (t *Tunnel) WithRecorder(recorder recorder.Recorder) {
t.recorder = recorder
func (t *Tunnel) WithSD(sd sd.SD) {
t.sd = sd
}
func (t *Tunnel) ID() relay.TunnelID {
@ -133,7 +145,7 @@ func (t *Tunnel) CloseOnIdle() bool {
}
func (t *Tunnel) clean() {
ticker := time.NewTicker(1 * time.Minute)
ticker := time.NewTicker(t.ttl)
defer ticker.Stop()
for {
@ -142,30 +154,29 @@ func (t *Tunnel) clean() {
t.mu.Lock()
if len(t.connectors) == 0 {
t.mu.Unlock()
break
}
var connectors []*Connector
for _, c := range t.connectors {
if c.Session().IsClosed() {
logger.Default().Debugf("remove tunnel: %s, connector: %s", t.id, c.id)
if t.recorder != nil {
t.recorder.Record(context.Background(),
[]byte(fmt.Sprintf("%s:%s", t.id, c.id)),
recorder.MetadataReocrdOption(connectorMetadata{
Op: "del",
}),
)
if t.sd != nil {
t.sd.Deregister(context.Background(), &sd.Service{
ID: c.id.String(),
Name: t.id.String(),
Node: t.node,
})
}
continue
}
connectors = append(connectors, c)
if t.recorder != nil {
t.recorder.Record(context.Background(),
[]byte(fmt.Sprintf("%s:%s", t.id, c.id)),
recorder.MetadataReocrdOption(connectorMetadata{
Op: "set",
}),
)
if t.sd != nil {
t.sd.Renew(context.Background(), &sd.Service{
ID: c.id.String(),
Name: t.id.String(),
Node: t.node,
})
}
}
if len(connectors) != len(t.connectors) {
@ -179,24 +190,23 @@ func (t *Tunnel) clean() {
}
type ConnectorPool struct {
tunnels map[string]*Tunnel
mu sync.RWMutex
recorder recorder.Recorder
node string
sd sd.SD
tunnels map[string]*Tunnel
mu sync.RWMutex
}
func NewConnectorPool() *ConnectorPool {
func NewConnectorPool(node string, sd sd.SD) *ConnectorPool {
p := &ConnectorPool{
node: node,
sd: sd,
tunnels: make(map[string]*Tunnel),
}
go p.closeIdles()
return p
}
func (p *ConnectorPool) WithRecorder(recorder recorder.Recorder) {
p.recorder = recorder
}
func (p *ConnectorPool) Add(tid relay.TunnelID, c *Connector) {
func (p *ConnectorPool) Add(tid relay.TunnelID, c *Connector, ttl time.Duration) {
p.mu.Lock()
defer p.mu.Unlock()
@ -204,15 +214,15 @@ func (p *ConnectorPool) Add(tid relay.TunnelID, c *Connector) {
t := p.tunnels[s]
if t == nil {
t = NewTunnel(tid)
t.WithRecorder(p.recorder)
t = NewTunnel(p.node, tid, ttl)
t.WithSD(p.sd)
p.tunnels[s] = t
}
t.AddConnector(c)
}
func (p *ConnectorPool) Get(network string, tid relay.TunnelID) *Connector {
func (p *ConnectorPool) Get(network string, tid string) *Connector {
if p == nil {
return nil
}
@ -220,7 +230,7 @@ func (p *ConnectorPool) Get(network string, tid relay.TunnelID) *Connector {
p.mu.RLock()
defer p.mu.RUnlock()
t := p.tunnels[tid.String()]
t := p.tunnels[tid]
if t == nil {
return nil
}
@ -260,31 +270,3 @@ func parseTunnelID(s string) (tid relay.TunnelID) {
}
return relay.NewTunnelID(uuid[:])
}
func getTunnelConn(network string, pool *ConnectorPool, tid relay.TunnelID, retry int, log logger.Logger) (conn net.Conn, cid relay.ConnectorID, err error) {
if tid.IsZero() {
err = ErrTunnelID
return
}
if retry <= 0 {
retry = 1
}
for i := 0; i < retry; i++ {
c := pool.Get(network, tid)
if c == nil {
err = fmt.Errorf("tunnel %s not available", tid.String())
break
}
conn, err = c.Session().GetConn()
if err != nil {
log.Error(err)
continue
}
cid = c.id
break
}
return
}

View File

@ -6,6 +6,7 @@ import (
"encoding/json"
"io"
"net"
"sort"
"strings"
"sync"
"time"
@ -179,6 +180,26 @@ func (p *chainHop) Select(ctx context.Context, opts ...hop.SelectOption) *chain.
}
}
// filter by path
if path := options.Path; path != "" {
p.options.logger.Debugf("filter by path: %s", path)
sort.SliceStable(filters, func(i, j int) bool {
return len(filters[i].Options().Path) > len(filters[j].Options().Path)
})
var nodes []*chain.Node
for _, node := range filters {
if node.Options().Path == "" {
nodes = append(nodes, node)
continue
}
if strings.HasPrefix(path, node.Options().Path) {
nodes = append(nodes, node)
break
}
}
filters = nodes
}
var nodes []*chain.Node
for _, node := range filters {
if node == nil {

View File

@ -67,6 +67,7 @@ func (p *grpcPlugin) Select(ctx context.Context, opts ...hop.SelectOption) *chai
Network: options.Network,
Addr: options.Addr,
Host: options.Host,
Path: options.Path,
Client: string(auth_util.IDFromContext(ctx)),
})
if err != nil {
@ -103,6 +104,7 @@ type httpPluginRequest struct {
Network string `json:"network"`
Addr string `json:"addr"`
Host string `json:"host"`
Path string `json:"path"`
Client string `json:"client"`
}
@ -151,6 +153,7 @@ func (p *httpPlugin) Select(ctx context.Context, opts ...hop.SelectOption) *chai
Network: options.Network,
Addr: options.Addr,
Host: options.Host,
Path: options.Path,
Client: string(auth_util.IDFromContext(ctx)),
}
v, err := json.Marshal(&rb)

View File

@ -126,23 +126,19 @@ func (p *httpPlugin) Get(ctx context.Context, host string, opts ...ingress.GetOp
return
}
rb := httpPluginGetRequest{
Host: host,
}
v, err := json.Marshal(&rb)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.url, nil)
if err != nil {
return
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
if err != nil {
return
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
q := req.URL.Query()
q.Set("host", host)
req.URL.RawQuery = q.Encode()
resp, err := p.client.Do(req)
if err != nil {
return

View File

@ -1,7 +1,9 @@
package matcher
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/gobwas/glob"
@ -37,6 +39,69 @@ func (m *ipMatcher) Match(ip string) bool {
return ok
}
type addrMatcher struct {
addrs map[string]*PortRange
}
// AddrMatcher creates a Matcher with a list of HOST:PORT addresses.
// the host can be an IP (e.g. 192.168.1.1) address, a plain domain such as 'example.com',
// or a special pattern '.example.com' that matches 'example.com'
// and any subdomain 'abc.example.com', 'def.abc.example.com' etc.
// The PORT can be a single port number or port range MIN-MAX(e.g. 0-65535).
func AddrMatcher(addrs []string) Matcher {
matcher := &addrMatcher{
addrs: make(map[string]*PortRange),
}
for _, addr := range addrs {
host, port, _ := net.SplitHostPort(addr)
if host == "" {
matcher.addrs[addr] = nil
continue
}
pr, _ := parsePortRange(port)
matcher.addrs[host] = pr
}
return matcher
}
func (m *addrMatcher) Match(addr string) bool {
if m == nil || len(m.addrs) == 0 {
return false
}
host, sp, _ := net.SplitHostPort(addr)
if host == "" {
host = addr
}
port, _ := strconv.Atoi(sp)
if pr, ok := m.addrs[host]; ok {
if pr == nil || pr.contains(port) {
return true
}
}
if pr, ok := m.addrs["."+host]; ok {
if pr == nil || pr.contains(port) {
return true
}
}
for {
if index := strings.IndexByte(host, '.'); index > 0 {
if pr, ok := m.addrs[host[index:]]; ok {
if pr == nil || pr.contains(port) {
return true
}
}
host = host[index+1:]
continue
}
break
}
return false
}
type cidrMatcher struct {
ranger cidranger.Ranger
}
@ -106,37 +171,90 @@ func (m *domainMatcher) Match(domain string) bool {
}
type wildcardMatcherPattern struct {
pattern string
glob glob.Glob
glob glob.Glob
pr *PortRange
}
type wildcardMatcher struct {
patterns []wildcardMatcherPattern
}
// WildcardMatcher creates a Matcher for a specific wildcard domain pattern,
// the pattern should be a wildcard such as '*.exmaple.com'.
// the pattern can be a wildcard such as '*.exmaple.com', '*.example.com:80', or '*.example.com:0-65535'
func WildcardMatcher(patterns []string) Matcher {
matcher := &wildcardMatcher{}
for _, pattern := range patterns {
host, port, _ := net.SplitHostPort(pattern)
if host == "" {
host = pattern
}
pr, _ := parsePortRange(port)
matcher.patterns = append(matcher.patterns, wildcardMatcherPattern{
pattern: pattern,
glob: glob.MustCompile(pattern),
glob: glob.MustCompile(host),
pr: pr,
})
}
return matcher
}
func (m *wildcardMatcher) Match(domain string) bool {
func (m *wildcardMatcher) Match(addr string) bool {
if m == nil || len(m.patterns) == 0 {
return false
}
host, sp, _ := net.SplitHostPort(addr)
if host == "" {
host = addr
}
port, _ := strconv.Atoi(sp)
for _, pattern := range m.patterns {
if pattern.glob.Match(domain) {
return true
if pattern.glob.Match(addr) {
if pattern.pr == nil || pattern.pr.contains(port) {
return true
}
}
}
return false
}
type PortRange struct {
Min int
Max int
}
// ParsePortRange parses the s to a PortRange.
// The s can be a single port number and will be converted to port range port-port.
func parsePortRange(s string) (*PortRange, error) {
minmax := strings.Split(s, "-")
switch len(minmax) {
case 1:
port, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
if port < 0 || port > 65535 {
return nil, fmt.Errorf("invalid port: %s", s)
}
return &PortRange{Min: port, Max: port}, nil
case 2:
min, err := strconv.Atoi(minmax[0])
if err != nil {
return nil, err
}
max, err := strconv.Atoi(minmax[1])
if err != nil {
return nil, err
}
return &PortRange{Min: min, Max: max}, nil
default:
return nil, fmt.Errorf("invalid range: %s", s)
}
}
func (pr *PortRange) contains(port int) bool {
return port >= pr.Min && port <= pr.Max
}

View File

@ -66,7 +66,7 @@ func (c *Cache) Load(key CacheKey) (msg *dns.Msg, ttl time.Duration) {
}
ttl = item.ttl - time.Since(item.ts)
c.logger.Debugf("hit resolver cache: %s, ttl: %v", key, ttl)
c.logger.Debugf("resolver cache hit: %s, ttl: %v", key, ttl)
return
}

View File

@ -6,9 +6,43 @@ import (
"errors"
"net"
"os"
"strings"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
)
const (
VersionTLS10 = "VersionTLS10"
VersionTLS11 = "VersionTLS11"
VersionTLS12 = "VersionTLS12"
VersionTLS13 = "VersionTLS13"
)
const (
TLS_RSA_WITH_RC4_128_SHA = "TLS_RSA_WITH_RC4_128_SHA"
TLS_RSA_WITH_3DES_EDE_CBC_SHA = "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
TLS_RSA_WITH_AES_128_CBC_SHA = "TLS_RSA_WITH_AES_128_CBC_SHA"
TLS_RSA_WITH_AES_256_CBC_SHA = "TLS_RSA_WITH_AES_256_CBC_SHA"
TLS_RSA_WITH_AES_128_CBC_SHA256 = "TLS_RSA_WITH_AES_128_CBC_SHA256"
TLS_RSA_WITH_AES_128_GCM_SHA256 = "TLS_RSA_WITH_AES_128_GCM_SHA256"
TLS_RSA_WITH_AES_256_GCM_SHA384 = "TLS_RSA_WITH_AES_256_GCM_SHA384"
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
TLS_ECDHE_RSA_WITH_RC4_128_SHA = "TLS_ECDHE_RSA_WITH_RC4_128_SHA"
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
)
// LoadDefaultConfig loads the certificate from cert & key files and optional CA file.
@ -33,19 +67,19 @@ func LoadDefaultConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
}
// LoadServerConfig loads the certificate from cert & key files and client CA file.
func LoadServerConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
if certFile == "" && keyFile == "" {
func LoadServerConfig(config *config.TLSConfig) (*tls.Config, error) {
if config.CertFile == "" && config.KeyFile == "" {
return nil, nil
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
if err != nil {
return nil, err
}
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
pool, err := loadCA(caFile)
pool, err := loadCA(config.CAFile)
if err != nil {
return nil, err
}
@ -54,17 +88,19 @@ func LoadServerConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
cfg.ClientAuth = tls.RequireAndVerifyClientCert
}
SetTLSOptions(cfg, config.Options)
return cfg, nil
}
// LoadClientConfig loads the certificate from cert & key files and CA file.
func LoadClientConfig(certFile, keyFile, caFile string, verify bool, serverName string) (*tls.Config, error) {
func LoadClientConfig(config *config.TLSConfig) (*tls.Config, error) {
var cfg *tls.Config
if certFile == "" && keyFile == "" {
if config.CertFile == "" && config.KeyFile == "" {
cfg = &tls.Config{}
} else {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
if err != nil {
return nil, err
}
@ -74,17 +110,21 @@ func LoadClientConfig(certFile, keyFile, caFile string, verify bool, serverName
}
}
rootCAs, err := loadCA(caFile)
rootCAs, err := loadCA(config.CAFile)
if err != nil {
return nil, err
}
cfg.RootCAs = rootCAs
cfg.ServerName = serverName
cfg.InsecureSkipVerify = !verify
cfg.ServerName = config.ServerName
cfg.InsecureSkipVerify = !config.Secure
if config.Options != nil {
SetTLSOptions(cfg, config.Options)
}
// If the root ca is given, but skip verify, we verify the certificate manually.
if cfg.RootCAs != nil && !verify {
if cfg.RootCAs != nil && !config.Secure {
cfg.VerifyConnection = func(state tls.ConnectionState) error {
opts := x509.VerifyOptions{
Roots: cfg.RootCAs,
@ -109,6 +149,81 @@ func LoadClientConfig(certFile, keyFile, caFile string, verify bool, serverName
return cfg, nil
}
func SetTLSOptions(cfg *tls.Config, opts *config.TLSOptions) {
if cfg == nil || opts == nil {
return
}
switch strings.ToLower(opts.MinVersion) {
case strings.ToLower(VersionTLS10):
cfg.MinVersion = tls.VersionTLS10
case strings.ToLower(VersionTLS11):
cfg.MinVersion = tls.VersionTLS11
case strings.ToLower(VersionTLS12):
cfg.MinVersion = tls.VersionTLS12
case strings.ToLower(VersionTLS13):
cfg.MinVersion = tls.VersionTLS13
}
switch strings.ToLower(opts.MaxVersion) {
case strings.ToLower(VersionTLS10):
cfg.MaxVersion = tls.VersionTLS10
case strings.ToLower(VersionTLS11):
cfg.MaxVersion = tls.VersionTLS11
case strings.ToLower(VersionTLS12):
cfg.MaxVersion = tls.VersionTLS12
case strings.ToLower(VersionTLS13):
cfg.MaxVersion = tls.VersionTLS13
}
for _, v := range opts.CipherSuites {
switch strings.ToLower(v) {
case strings.ToLower(TLS_RSA_WITH_RC4_128_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_RC4_128_SHA)
case strings.ToLower(TLS_RSA_WITH_3DES_EDE_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
case strings.ToLower(TLS_RSA_WITH_AES_128_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
case strings.ToLower(TLS_RSA_WITH_AES_256_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
case strings.ToLower(TLS_RSA_WITH_AES_128_CBC_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
case strings.ToLower(TLS_RSA_WITH_AES_128_GCM_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
case strings.ToLower(TLS_RSA_WITH_AES_256_GCM_SHA384):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
case strings.ToLower(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
case strings.ToLower(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
case strings.ToLower(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
case strings.ToLower(TLS_ECDHE_RSA_WITH_RC4_128_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
case strings.ToLower(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
case strings.ToLower(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
case strings.ToLower(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
case strings.ToLower(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
case strings.ToLower(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
case strings.ToLower(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
case strings.ToLower(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
case strings.ToLower(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
case strings.ToLower(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
case strings.ToLower(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
case strings.ToLower(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256):
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
}
}
}
func loadCA(caFile string) (cp *x509.CertPool, err error) {
if caFile == "" {
return

View File

@ -49,8 +49,17 @@ func (c *websocketConn) WriteMessage(messageType int, data []byte) error {
}
func (c *websocketConn) SetDeadline(t time.Time) error {
c.mux.Lock()
defer c.mux.Unlock()
if err := c.SetReadDeadline(t); err != nil {
return err
}
return c.SetWriteDeadline(t)
}
func (c *websocketConn) SetWriteDeadline(t time.Time) error {
c.mux.Lock()
defer c.mux.Unlock()
return c.Conn.SetWriteDeadline(t)
}

52
internal/util/wt/conn.go Normal file
View File

@ -0,0 +1,52 @@
package wt
import (
"net"
"time"
wt "github.com/quic-go/webtransport-go"
)
type conn struct {
session *wt.Session
stream wt.Stream
}
func Conn(session *wt.Session, stream wt.Stream) net.Conn {
return &conn{
session: session,
stream: stream,
}
}
func (c *conn) Read(b []byte) (n int, err error) {
return c.stream.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
return c.stream.Write(b)
}
func (c *conn) Close() error {
return c.stream.Close()
}
func (c *conn) LocalAddr() net.Addr {
return c.session.LocalAddr()
}
func (c *conn) RemoteAddr() net.Addr {
return c.session.RemoteAddr()
}
func (c *conn) SetDeadline(t time.Time) error {
return c.stream.SetDeadline(t)
}
func (c *conn) SetReadDeadline(t time.Time) error {
return c.stream.SetReadDeadline(t)
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return c.stream.SetWriteDeadline(t)
}

View File

@ -30,11 +30,7 @@ type metadata struct {
func (l *http3Listener) parseMetadata(md mdata.Metadata) (err error) {
const (
authorizePath = "authorizePath"
pushPath = "pushPath"
pullPath = "pullPath"
keepAlive = "keepAlive"
keepAlive = "keepalive"
keepAlivePeriod = "ttl"
handshakeTimeout = "handshakeTimeout"
maxIdleTimeout = "maxIdleTimeout"
@ -43,15 +39,15 @@ func (l *http3Listener) parseMetadata(md mdata.Metadata) (err error) {
backlog = "backlog"
)
l.md.authorizePath = mdutil.GetString(md, authorizePath)
l.md.authorizePath = mdutil.GetString(md, "pht.authorizePath", "authorizePath")
if !strings.HasPrefix(l.md.authorizePath, "/") {
l.md.authorizePath = defaultAuthorizePath
}
l.md.pushPath = mdutil.GetString(md, pushPath)
l.md.pushPath = mdutil.GetString(md, "pht.pushPath", "pushPath")
if !strings.HasPrefix(l.md.pushPath, "/") {
l.md.pushPath = defaultPushPath
}
l.md.pullPath = mdutil.GetString(md, pullPath)
l.md.pullPath = mdutil.GetString(md, "pht.pullPath", "pullPath")
if !strings.HasPrefix(l.md.pullPath, "/") {
l.md.pullPath = defaultPullPath
}

View File

@ -0,0 +1,158 @@
package wt
import (
"net"
"net/http"
"net/http/httputil"
"github.com/go-gost/core/listener"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
xnet "github.com/go-gost/x/internal/net"
wt_util "github.com/go-gost/x/internal/util/wt"
"github.com/go-gost/x/registry"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
wt "github.com/quic-go/webtransport-go"
)
func init() {
registry.ListenerRegistry().Register("wt", NewListener)
}
type wtListener struct {
addr net.Addr
srv *wt.Server
cqueue chan net.Conn
errChan chan error
logger logger.Logger
md metadata
options listener.Options
}
func NewListener(opts ...listener.Option) listener.Listener {
options := listener.Options{}
for _, opt := range opts {
opt(&options)
}
return &wtListener{
logger: options.Logger,
options: options,
}
}
func (l *wtListener) Init(md md.Metadata) (err error) {
if err = l.parseMetadata(md); err != nil {
return
}
network := "udp"
if xnet.IsIPv4(l.options.Addr) {
network = "udp4"
}
l.addr, err = net.ResolveUDPAddr(network, l.options.Addr)
if err != nil {
return
}
mux := http.NewServeMux()
mux.Handle(l.md.path, http.HandlerFunc(l.upgrade))
l.srv = &wt.Server{
H3: http3.Server{
Addr: l.options.Addr,
TLSConfig: l.options.TLSConfig,
QuicConfig: &quic.Config{
KeepAlivePeriod: l.md.keepAlivePeriod,
HandshakeIdleTimeout: l.md.handshakeTimeout,
MaxIdleTimeout: l.md.maxIdleTimeout,
/*
Versions: []quic.VersionNumber{
quic.Version1,
quic.Version2,
},
*/
MaxIncomingStreams: int64(l.md.maxStreams),
},
Handler: mux,
},
CheckOrigin: func(r *http.Request) bool { return true },
}
l.cqueue = make(chan net.Conn, l.md.backlog)
l.errChan = make(chan error, 1)
go func() {
if err := l.srv.ListenAndServe(); err != nil {
l.logger.Error(err)
}
}()
return
}
func (l *wtListener) Accept() (conn net.Conn, err error) {
var ok bool
select {
case conn = <-l.cqueue:
case err, ok = <-l.errChan:
if !ok {
err = listener.ErrClosed
}
}
return
}
func (l *wtListener) Addr() net.Addr {
return l.addr
}
func (l *wtListener) Close() (err error) {
return l.srv.Close()
}
func (l *wtListener) upgrade(w http.ResponseWriter, r *http.Request) {
log := l.logger.WithFields(map[string]any{
"local": l.addr.String(),
"remote": r.RemoteAddr,
})
if l.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
log.Trace(string(dump))
}
s, err := l.srv.Upgrade(w, r)
if err != nil {
l.logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}
l.mux(s, log)
}
func (l *wtListener) mux(s *wt.Session, log logger.Logger) (err error) {
defer func() {
if err != nil {
s.CloseWithError(1, err.Error())
} else {
s.CloseWithError(0, "")
}
}()
for {
var stream wt.Stream
stream, err = s.AcceptStream(s.Context())
if err != nil {
log.Errorf("accept stream: %v", err)
return
}
select {
case l.cqueue <- wt_util.Conn(s, stream):
default:
stream.Close()
l.logger.Warnf("connection queue is full, stream %v discarded", stream.StreamID())
}
}
}

View File

@ -0,0 +1,58 @@
package wt
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util"
)
const (
defaultPath = "/wt"
defaultBacklog = 128
)
type metadata struct {
path string
backlog int
// QUIC config options
keepAlivePeriod time.Duration
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
maxStreams int
}
func (l *wtListener) parseMetadata(md mdata.Metadata) (err error) {
const (
keepAlive = "keepalive"
keepAlivePeriod = "ttl"
handshakeTimeout = "handshakeTimeout"
maxIdleTimeout = "maxIdleTimeout"
maxStreams = "maxStreams"
backlog = "backlog"
)
l.md.path = mdutil.GetString(md, "wt.path", "path")
if l.md.path == "" {
l.md.path = defaultPath
}
l.md.backlog = mdutil.GetInt(md, backlog)
if l.md.backlog <= 0 {
l.md.backlog = defaultBacklog
}
if mdutil.GetBool(md, keepAlive) {
l.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
if l.md.keepAlivePeriod <= 0 {
l.md.keepAlivePeriod = 10 * time.Second
}
}
l.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
l.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
l.md.maxStreams = mdutil.GetInt(md, maxStreams)
return
}

View File

@ -160,6 +160,7 @@ func (l *mwsListener) upgrade(w http.ResponseWriter, r *http.Request) {
conn, err := l.upgrader.Upgrade(w, r, l.md.header)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Error(err)
return
}

View File

@ -31,33 +31,21 @@ type metadata struct {
}
func (l *mwsListener) parseMetadata(md mdata.Metadata) (err error) {
const (
path = "path"
backlog = "backlog"
header = "header"
handshakeTimeout = "handshakeTimeout"
readHeaderTimeout = "readHeaderTimeout"
readBufferSize = "readBufferSize"
writeBufferSize = "writeBufferSize"
enableCompression = "enableCompression"
)
l.md.path = mdutil.GetString(md, path)
l.md.path = mdutil.GetString(md, "ws.path", "path")
if l.md.path == "" {
l.md.path = defaultPath
}
l.md.backlog = mdutil.GetInt(md, backlog)
l.md.backlog = mdutil.GetInt(md, "ws.backlog", "backlog")
if l.md.backlog <= 0 {
l.md.backlog = defaultBacklog
}
l.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
l.md.readHeaderTimeout = mdutil.GetDuration(md, readHeaderTimeout)
l.md.readBufferSize = mdutil.GetInt(md, readBufferSize)
l.md.writeBufferSize = mdutil.GetInt(md, writeBufferSize)
l.md.enableCompression = mdutil.GetBool(md, enableCompression)
l.md.handshakeTimeout = mdutil.GetDuration(md, "ws.handshakeTimeout", "handshakeTimeout")
l.md.readHeaderTimeout = mdutil.GetDuration(md, "ws.readHeaderTimeout", "readHeaderTimeout")
l.md.readBufferSize = mdutil.GetInt(md, "ws.readBufferSize", "readBufferSize")
l.md.writeBufferSize = mdutil.GetInt(md, "ws.writeBufferSize", "writeBufferSize")
l.md.enableCompression = mdutil.GetBool(md, "ws.enableCompression", "enableCompression")
l.md.muxCfg = &mux.Config{
Version: mdutil.GetInt(md, "mux.version"),
@ -69,7 +57,7 @@ func (l *mwsListener) parseMetadata(md mdata.Metadata) (err error) {
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
if mm := mdutil.GetStringMapString(md, header); len(mm) > 0 {
if mm := mdutil.GetStringMapString(md, "ws.header", "header"); len(mm) > 0 {
hd := http.Header{}
for k, v := range mm {
hd.Add(k, v)

View File

@ -9,8 +9,8 @@ import (
const (
defaultTTL = 5 * time.Second
defaultReadBufferSize = 4096
defaultReadQueueSize = 1024
defaultReadBufferSize = 1024
defaultReadQueueSize = 128
defaultBacklog = 128
)

View File

@ -156,6 +156,7 @@ func (l *wsListener) upgrade(w http.ResponseWriter, r *http.Request) {
conn, err := l.upgrader.Upgrade(w, r, l.md.header)
if err != nil {
l.logger.Error(err)
w.WriteHeader(http.StatusInternalServerError)
return
}

View File

@ -28,36 +28,23 @@ type metadata struct {
}
func (l *wsListener) parseMetadata(md mdata.Metadata) (err error) {
const (
path = "path"
backlog = "backlog"
handshakeTimeout = "handshakeTimeout"
readHeaderTimeout = "readHeaderTimeout"
readBufferSize = "readBufferSize"
writeBufferSize = "writeBufferSize"
enableCompression = "enableCompression"
header = "header"
)
l.md.path = mdutil.GetString(md, path)
l.md.path = mdutil.GetString(md, "ws.path", "path")
if l.md.path == "" {
l.md.path = defaultPath
}
l.md.backlog = mdutil.GetInt(md, backlog)
l.md.backlog = mdutil.GetInt(md, "ws.backlog", "backlog")
if l.md.backlog <= 0 {
l.md.backlog = defaultBacklog
}
l.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
l.md.readHeaderTimeout = mdutil.GetDuration(md, readHeaderTimeout)
l.md.readBufferSize = mdutil.GetInt(md, readBufferSize)
l.md.writeBufferSize = mdutil.GetInt(md, writeBufferSize)
l.md.enableCompression = mdutil.GetBool(md, enableCompression)
l.md.handshakeTimeout = mdutil.GetDuration(md, "ws.handshakeTimeout", "handshakeTimeout")
l.md.readHeaderTimeout = mdutil.GetDuration(md, "ws.readHeaderTimeout", "readHeaderTimeout")
l.md.readBufferSize = mdutil.GetInt(md, "ws.readBufferSize", "readBufferSize")
l.md.writeBufferSize = mdutil.GetInt(md, "ws.writeBufferSize", "writeBufferSize")
l.md.enableCompression = mdutil.GetBool(md, "ws.enableCompression", "enableCompression")
if mm := mdutil.GetStringMapString(md, header); len(mm) > 0 {
if mm := mdutil.GetStringMapString(md, "ws.header", "header"); len(mm) > 0 {
hd := http.Header{}
for k, v := range mm {
hd.Add(k, v)

View File

@ -1,6 +1,6 @@
package recorder
const (
RecorderServiceHandlerSerial = "recorder.service.handler.serial"
RecorderServiceHandlerTunnelConnector = "recorder.service.handler.tunnel.connector"
RecorderServiceHandlerSerial = "recorder.service.handler.serial"
RecorderServiceHandlerTunnel = "recorder.service.handler.tunnel"
)

View File

@ -18,6 +18,7 @@ import (
"github.com/go-gost/core/recorder"
reg "github.com/go-gost/core/registry"
"github.com/go-gost/core/resolver"
"github.com/go-gost/core/sd"
"github.com/go-gost/core/service"
)
@ -32,7 +33,7 @@ var (
connectorReg reg.Registry[NewConnector] = new(connectorRegistry)
serviceReg reg.Registry[service.Service] = new(serviceRegistry)
chainReg reg.Registry[chain.Chainer] = new(chainRegistry)
hopReg reg.Registry[hop.Hop] = new(hopRegistry)
hopReg reg.Registry[hop.Hop] = new(hopRegistry)
autherReg reg.Registry[auth.Authenticator] = new(autherRegistry)
admissionReg reg.Registry[admission.Admission] = new(admissionRegistry)
bypassReg reg.Registry[bypass.Bypass] = new(bypassRegistry)
@ -45,6 +46,7 @@ var (
rateLimiterReg reg.Registry[rate.RateLimiter] = new(rateLimiterRegistry)
ingressReg reg.Registry[ingress.Ingress] = new(ingressRegistry)
sdReg reg.Registry[sd.SD] = new(sdRegistry)
)
type registry[T any] struct {
@ -163,3 +165,7 @@ func RateLimiterRegistry() reg.Registry[rate.RateLimiter] {
func IngressRegistry() reg.Registry[ingress.Ingress] {
return ingressReg
}
func SDRegistry() reg.Registry[sd.SD] {
return sdReg
}

66
registry/sd.go Normal file
View File

@ -0,0 +1,66 @@
package registry
import (
"context"
"github.com/go-gost/core/sd"
)
type sdRegistry struct {
registry[sd.SD]
}
func (r *sdRegistry) Register(name string, v sd.SD) error {
return r.registry.Register(name, v)
}
func (r *sdRegistry) Get(name string) sd.SD {
if name != "" {
return &sdWrapper{name: name, r: r}
}
return nil
}
func (r *sdRegistry) get(name string) sd.SD {
return r.registry.Get(name)
}
type sdWrapper struct {
name string
r *sdRegistry
}
func (w *sdWrapper) Register(ctx context.Context, service *sd.Service, opts ...sd.Option) error {
v := w.r.get(w.name)
if v == nil {
return nil
}
return v.Register(ctx, service, opts...)
}
func (w *sdWrapper) Deregister(ctx context.Context, service *sd.Service) error {
v := w.r.get(w.name)
if v == nil {
return nil
}
return v.Deregister(ctx, service)
}
func (w *sdWrapper) Renew(ctx context.Context, service *sd.Service) error {
v := w.r.get(w.name)
if v == nil {
return nil
}
return v.Renew(ctx, service)
}
func (w *sdWrapper) Get(ctx context.Context, name string) ([]*sd.Service, error) {
v := w.r.get(w.name)
if v == nil {
return nil, nil
}
return v.Get(ctx, name)
}

View File

@ -22,6 +22,8 @@ type NameServer struct {
ClientIP net.IP
Prefer string
Hostname string // for TLS handshake verification
Async bool
Only string
exchanger exchanger.Exchanger
}
@ -79,6 +81,16 @@ func NewResolver(nameservers []NameServer, opts ...Option) (resolver.Resolver, e
}
server.exchanger = ex
switch server.Only {
case "ip4", "ipv4", "ip6", "ipv6":
server.Prefer = server.Only
default:
server.Only = ""
}
if server.TTL < 0 {
server.Async = false
}
servers = append(servers, server)
}
cache := resolver_util.NewCache().
@ -102,7 +114,11 @@ func (r *localResolver) Resolve(ctx context.Context, network, host string, opts
}
for _, server := range r.servers {
ips, err = r.resolve(ctx, &server, host)
if server.Async {
ips, err = r.resolveAsync(ctx, &server, host)
} else {
ips, err = r.resolve(ctx, &server, host)
}
if err != nil {
r.options.logger.Error(err)
continue
@ -124,18 +140,70 @@ func (r *localResolver) resolve(ctx context.Context, server *NameServer, host st
}
if server.Prefer == "ipv6" { // prefer ipv6
if ips, err = r.resolve6(ctx, server, host); len(ips) > 0 {
if ips, err = r.resolve6(ctx, server, host); len(ips) > 0 || server.Only == "ipv6" {
return
}
return r.resolve4(ctx, server, host)
}
if ips, err = r.resolve4(ctx, server, host); len(ips) > 0 {
if ips, err = r.resolve4(ctx, server, host); len(ips) > 0 || server.Only == "ipv4" {
return
}
return r.resolve6(ctx, server, host)
}
func (r *localResolver) resolveAsync(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
ips, ttl, ok := r.lookupCache(ctx, server, host)
if !ok {
return r.resolve(ctx, server, host)
}
if ttl <= 0 {
r.options.logger.Debugf("async resolve %s via %s", host, server.exchanger.String())
go r.resolve(ctx, server, host)
}
return
}
func (r *localResolver) lookupCache(ctx context.Context, server *NameServer, host string) (ips []net.IP, ttl time.Duration, ok bool) {
lookup := func(t uint16, host string) (ips []net.IP, ttl time.Duration, ok bool) {
mq := dns.Msg{}
mq.SetQuestion(dns.Fqdn(host), t)
mr, ttl := r.cache.Load(resolver_util.NewCacheKey(&mq.Question[0]))
if mr == nil {
return
}
ok = true
for _, ans := range mr.Answer {
if ar, _ := ans.(*dns.AAAA); ar != nil {
ips = append(ips, ar.AAAA)
}
if ar, _ := ans.(*dns.A); ar != nil {
ips = append(ips, ar.A)
}
}
return
}
if server.Prefer == "ipv6" {
ips, ttl, ok = lookup(dns.TypeAAAA, host)
if len(ips) > 0 || server.Only == "ipv6" {
return
}
ips, ttl, ok = lookup(dns.TypeA, host)
return
}
ips, ttl, ok = lookup(dns.TypeA, host)
if len(ips) > 0 || server.Only == "ipv4" {
return
}
return lookup(dns.TypeAAAA, host)
}
func (r *localResolver) resolve4(ctx context.Context, server *NameServer, host string) (ips []net.IP, err error) {
mq := dns.Msg{}
mq.SetQuestion(dns.Fqdn(host), dns.TypeA)
@ -149,6 +217,10 @@ func (r *localResolver) resolve6(ctx context.Context, server *NameServer, host s
}
func (r *localResolver) resolveIPs(ctx context.Context, server *NameServer, mq *dns.Msg) (ips []net.IP, err error) {
if r.options.logger.IsLevelEnabled(logger.TraceLevel) {
r.options.logger.Trace(mq.String())
}
key := resolver_util.NewCacheKey(&mq.Question[0])
mr, ttl := r.cache.Load(key)
if ttl <= 0 {
@ -158,6 +230,10 @@ func (r *localResolver) resolveIPs(ctx context.Context, server *NameServer, mq *
return
}
r.cache.Store(key, mr, server.TTL)
if r.options.logger.IsLevelEnabled(logger.TraceLevel) {
r.options.logger.Trace(mr.String())
}
}
for _, ans := range mr.Answer {

338
sd/plugin.go Normal file
View File

@ -0,0 +1,338 @@
package ingress
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/sd"
"github.com/go-gost/plugin/sd/proto"
"github.com/go-gost/x/internal/plugin"
"google.golang.org/grpc"
)
type grpcPlugin struct {
conn grpc.ClientConnInterface
client proto.SDClient
log logger.Logger
}
// NewGRPCPlugin creates an SD plugin based on gRPC.
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) sd.SD {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
log := logger.Default().WithFields(map[string]any{
"kind": "sd",
"sd": name,
})
conn, err := plugin.NewGRPCConn(addr, &options)
if err != nil {
log.Error(err)
}
p := &grpcPlugin{
conn: conn,
log: log,
}
if conn != nil {
p.client = proto.NewSDClient(conn)
}
return p
}
func (p *grpcPlugin) Register(ctx context.Context, service *sd.Service, opts ...sd.Option) error {
if p.client == nil {
return nil
}
_, err := p.client.Register(ctx,
&proto.RegisterRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
if err != nil {
p.log.Error(err)
return err
}
return nil
}
func (p *grpcPlugin) Deregister(ctx context.Context, service *sd.Service) error {
if p.client == nil {
return nil
}
_, err := p.client.Deregister(ctx, &proto.DeregisterRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
return err
}
func (p *grpcPlugin) Renew(ctx context.Context, service *sd.Service) error {
if p.client == nil {
return nil
}
_, err := p.client.Renew(ctx, &proto.RenewRequest{
Service: &proto.Service{
Id: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
},
})
return err
}
func (p *grpcPlugin) Get(ctx context.Context, name string) ([]*sd.Service, error) {
if p.client == nil {
return nil, nil
}
r, err := p.client.Get(ctx, &proto.GetServiceRequest{
Name: name,
})
if err != nil {
return nil, err
}
var services []*sd.Service
for _, v := range r.Services {
if v == nil {
continue
}
services = append(services, &sd.Service{
Node: v.Node,
Name: v.Name,
Network: v.Network,
Address: v.Address,
})
}
return services, nil
}
func (p *grpcPlugin) Close() error {
if closer, ok := p.conn.(io.Closer); ok {
return closer.Close()
}
return nil
}
type sdService struct {
ID string `json:"id"`
Name string `json:"name"`
Node string `json:"node"`
Network string `json:"network"`
Address string `json:"address"`
}
type httpGetResponse struct {
Services []*sdService `json:"services"`
}
type httpPlugin struct {
url string
client *http.Client
header http.Header
log logger.Logger
}
// NewHTTPPlugin creates an SD plugin based on HTTP.
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) sd.SD {
var options plugin.Options
for _, opt := range opts {
opt(&options)
}
return &httpPlugin{
url: url,
client: plugin.NewHTTPClient(&options),
header: options.Header,
log: logger.Default().WithFields(map[string]any{
"kind": "sd",
"sd": name,
}),
}
}
func (p *httpPlugin) Register(ctx context.Context, service *sd.Service, opts ...sd.Option) error {
if p.client == nil || service == nil {
return nil
}
v, err := json.Marshal(sdService{
ID: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
})
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
if err != nil {
return err
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf(resp.Status)
}
return nil
}
func (p *httpPlugin) Deregister(ctx context.Context, service *sd.Service) error {
if p.client == nil || service == nil {
return nil
}
v, err := json.Marshal(sdService{
ID: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
})
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, p.url, bytes.NewReader(v))
if err != nil {
return err
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf(resp.Status)
}
return nil
}
func (p *httpPlugin) Renew(ctx context.Context, service *sd.Service) error {
if p.client == nil {
return nil
}
v, err := json.Marshal(sdService{
ID: service.ID,
Name: service.Name,
Node: service.Node,
Network: service.Network,
Address: service.Address,
})
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPut, p.url, bytes.NewReader(v))
if err != nil {
return err
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
resp, err := p.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf(resp.Status)
}
return nil
}
func (p *httpPlugin) Get(ctx context.Context, name string) (services []*sd.Service, err error) {
if p.client == nil {
return
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.url, nil)
if err != nil {
return
}
if p.header != nil {
req.Header = p.header.Clone()
}
req.Header.Set("Content-Type", "application/json")
q := req.URL.Query()
q.Set("name", name)
req.URL.RawQuery = q.Encode()
resp, err := p.client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(resp.Status)
}
res := &httpGetResponse{}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil, err
}
for _, v := range res.Services {
if v == nil {
continue
}
services = append(services, &sd.Service{
ID: v.ID,
Name: v.Name,
Node: v.Node,
Network: v.Network,
Address: v.Address,
})
}
return
}