Compare commits

...
17 Commits
128 changed files with 3538 additions and 941 deletions
+19
View File
@@ -271,3 +271,22 @@ func (p *localAdmission) Close() error {
}
return nil
}
type admissionGroup struct {
admissions []admission.Admission
}
func AdmissionGroup(admissions ...admission.Admission) admission.Admission {
return &admissionGroup{
admissions: admissions,
}
}
func (p *admissionGroup) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
for _, admission := range p.admissions {
if admission != nil && !admission.Admit(ctx, addr, opts...) {
return false
}
}
return true
}
+5 -2
View File
@@ -53,8 +53,11 @@ type server struct {
cclose chan struct{}
}
func NewService(addr string, opts ...Option) (service.Service, error) {
ln, err := net.Listen("tcp", addr)
func NewService(network, addr string, opts ...Option) (service.Service, error) {
if network == "" {
network = "tcp"
}
ln, err := net.Listen(network, addr)
if err != nil {
return nil, err
}
+26
View File
@@ -252,3 +252,29 @@ func (p *authenticator) Close() error {
}
return nil
}
type authenticatorGroup struct {
authers []auth.Authenticator
}
func AuthenticatorGroup(authers ...auth.Authenticator) auth.Authenticator {
return &authenticatorGroup{
authers: authers,
}
}
func (p *authenticatorGroup) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
if len(p.authers) == 0 {
return "", false
}
for _, auther := range p.authers {
if auther == nil {
continue
}
if id, ok := auther.Authenticate(ctx, user, password, opts...); ok {
return id, ok
}
}
return "", false
}
+43
View File
@@ -6,6 +6,7 @@ import (
"errors"
"io"
"net"
"slices"
"strings"
"sync"
"time"
@@ -288,3 +289,45 @@ func (bp *localBypass) Close() error {
}
return nil
}
type bypassGroup struct {
bypasses []bypass.Bypass
}
func BypassGroup(bypasses ...bypass.Bypass) bypass.Bypass {
return &bypassGroup{
bypasses: bypasses,
}
}
func (p *bypassGroup) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
var whitelist, blacklist []bool
for _, bypass := range p.bypasses {
result := bypass.Contains(ctx, network, addr, opts...)
if bypass.IsWhitelist() {
whitelist = append(whitelist, result)
} else {
blacklist = append(blacklist, result)
}
}
status := false
if len(whitelist) > 0 {
if slices.Contains(whitelist, false) {
status = false
} else {
status = true
}
}
if !status && len(blacklist) > 0 {
if slices.Contains(blacklist, true) {
status = true
} else {
status = false
}
}
return status
}
func (p *bypassGroup) IsWhitelist() bool {
return false
}
+47 -32
View File
@@ -155,7 +155,7 @@ type SelectorConfig struct {
type AdmissionConfig struct {
Name string `json:"name"`
// DEPRECATED by whitelist since beta.4
// Deprecated: use whitelist instead
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
@@ -168,7 +168,7 @@ type AdmissionConfig struct {
type BypassConfig struct {
Name string `json:"name"`
// DEPRECATED by whitelist since beta.4
// Deprecated: use whitelist instead
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
@@ -347,7 +347,7 @@ type HandlerConfig struct {
}
type ForwarderConfig struct {
// DEPRECATED by hop field
// Deprecated: use hop instead
Name string `yaml:",omitempty" json:"name,omitempty"`
// the referenced hop name
Hop string `yaml:",omitempty" json:"hop,omitempty"`
@@ -361,18 +361,20 @@ type ForwardNodeConfig struct {
Network string `yaml:",omitempty" json:"network,omitempty"`
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
// DEPRECATED by filter.protocol
// Deprecated: use matcher instead
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
// DEPRECATED by filter.host
// Deprecated: use matcher instead
Host string `yaml:",omitempty" json:"host,omitempty"`
// DEPRECATED by filter.path
// Deprecated: use matcher instead
Path string `yaml:",omitempty" json:"path,omitempty"`
// DEPRECATED by http.auth
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
// Deprecated: use matcher instead
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
// Deprecated: use http.auth instead
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
}
type HTTPURLRewriteConfig struct {
@@ -393,13 +395,24 @@ type NodeFilterConfig struct {
Path string `yaml:",omitempty" json:"path,omitempty"`
}
type NodeMatcherConfig struct {
Rule string `yaml:",omitempty" json:"rule,omitempty"`
Priority int `yaml:",omitempty" json:"priority,omitempty"`
}
type HTTPNodeConfig struct {
// rewrite host header
Host string `yaml:",omitempty" json:"host,omitempty"`
// additional request header
// Deprecated: use requestHeader instead
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
// rewrite URL
// additional request header
RequestHeader map[string]string `yaml:"requestHeader,omitempty" json:"requestHeader,omitempty"`
// additional response header
ResponseHeader map[string]string `yaml:"responseHeader,omitempty" json:"responseHeader,omitempty"`
// Deprecated: use rewriteURL instead
Rewrite []HTTPURLRewriteConfig `yaml:",omitempty" json:"rewrite,omitempty"`
// rewrite URL
RewriteURL []HTTPURLRewriteConfig `yaml:"rewriteURL,omitempty" json:"rewriteURL,omitempty"`
// rewrite response body
RewriteBody []HTTPBodyRewriteConfig `yaml:"rewriteBody,omitempty" json:"rewriteBody,omitempty"`
// HTTP basic auth
@@ -433,9 +446,9 @@ type SockOptsConfig struct {
type ServiceConfig struct {
Name string `json:"name"`
Addr string `yaml:",omitempty" json:"addr,omitempty"`
// DEPRECATED by metadata.interface since beta.5
// Deprecated: use metadata.interface instead
Interface string `yaml:",omitempty" json:"interface,omitempty"`
// DEPRECATED by metadata.so_mark since beta.5
// Deprecated: use metadata.so_mark instead
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
Admission string `yaml:",omitempty" json:"admission,omitempty"`
Admissions []string `yaml:",omitempty" json:"admissions,omitempty"`
@@ -508,22 +521,24 @@ type HopConfig struct {
}
type NodeConfig struct {
Name string `json:"name"`
Addr string `yaml:",omitempty" json:"addr,omitempty"`
Network string `yaml:",omitempty" json:"network,omitempty"`
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
Interface string `yaml:",omitempty" json:"interface,omitempty"`
Netns string `yaml:",omitempty" json:"netns,omitempty"`
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
Name string `json:"name"`
Addr string `yaml:",omitempty" json:"addr,omitempty"`
Network string `yaml:",omitempty" json:"network,omitempty"`
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
Interface string `yaml:",omitempty" json:"interface,omitempty"`
Netns string `yaml:",omitempty" json:"netns,omitempty"`
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
// Deprecated: use matcher instead
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
}
type Config struct {
+2 -2
View File
@@ -4,10 +4,10 @@ import (
"crypto/tls"
"strings"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/hop"
"github.com/go-gost/core/logger"
xbypass "github.com/go-gost/x/bypass"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
@@ -115,7 +115,7 @@ func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
xhop.NameOption(cfg.Name),
xhop.NodeOption(nodes...),
xhop.SelectorOption(sel),
xhop.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
xhop.BypassOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
xhop.ReloadPeriodOption(cfg.Reload),
xhop.LoggerOption(log.WithFields(map[string]any{
"kind": "hop",
+36 -7
View File
@@ -6,14 +6,13 @@ import (
"regexp"
"strings"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
xauth "github.com/go-gost/x/auth"
xbypass "github.com/go-gost/x/bypass"
xchain "github.com/go-gost/x/chain"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
@@ -21,7 +20,9 @@ import (
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
tls_util "github.com/go-gost/x/internal/util/tls"
mdx "github.com/go-gost/x/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry"
"github.com/go-gost/x/routing"
)
func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.Node, error) {
@@ -149,7 +150,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
opts := []chain.NodeOption{
chain.TransportNodeOption(tr),
chain.BypassNodeOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
chain.BypassNodeOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
chain.ResoloverNodeOption(registry.ResolverRegistry().Get(cfg.Resolver)),
chain.HostMapperNodeOption(registry.HostsRegistry().Get(cfg.Hosts)),
chain.MetadataNodeOption(nm),
@@ -175,10 +176,33 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
opts = append(opts, chain.NodeFilterOption(settings))
}
if cfg.Matcher != nil {
priority := cfg.Matcher.Priority
if rule := strings.TrimSpace(cfg.Matcher.Rule); rule != "" {
if matcher, err := routing.NewMatcher(rule); err == nil {
log.Debugf("new matcher for node %s with rule %s", cfg.Name, cfg.Matcher.Rule)
if priority == 0 {
priority = len(cfg.Matcher.Rule)
}
opts = append(opts, chain.MatcherNodeOption(matcher))
} else {
log.Error(err)
priority = -1
}
}
opts = append(opts, chain.PriorityNodeOption(priority))
}
if cfg.HTTP != nil {
settings := &chain.HTTPNodeSettings{
Host: cfg.HTTP.Host,
Header: cfg.HTTP.Header,
Host: cfg.HTTP.Host,
RequestHeader: cfg.HTTP.RequestHeader,
ResponseHeader: cfg.HTTP.ResponseHeader,
}
if settings.RequestHeader == nil {
settings.RequestHeader = cfg.HTTP.Header
}
if auth := cfg.HTTP.Auth; auth != nil && auth.Username != "" {
@@ -191,7 +215,12 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
})),
)
}
for _, v := range cfg.HTTP.Rewrite {
rewriteURL := cfg.HTTP.RewriteURL
if rewriteURL == nil {
rewriteURL = cfg.HTTP.Rewrite
}
for _, v := range rewriteURL {
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
settings.RewriteURL = append(settings.RewriteURL, chain.HTTPURLRewriteSetting{
Pattern: pattern,
@@ -201,7 +230,7 @@ func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.No
}
for _, v := range cfg.HTTP.RewriteBody {
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
settings.RewriteBody = append(settings.RewriteBody, chain.HTTPBodyRewriteSettings{
settings.RewriteResponseBody = append(settings.RewriteResponseBody, chain.HTTPBodyRewriteSettings{
Type: v.Type,
Pattern: pattern,
Replacement: []byte(v.Replacement),
+9 -7
View File
@@ -6,9 +6,7 @@ import (
"strings"
"time"
"github.com/go-gost/core/admission"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/hop"
@@ -18,6 +16,9 @@ import (
"github.com/go-gost/core/recorder"
"github.com/go-gost/core/selector"
"github.com/go-gost/core/service"
xadmission "github.com/go-gost/x/admission"
xauth "github.com/go-gost/x/auth"
xbypass "github.com/go-gost/x/bypass"
xchain "github.com/go-gost/x/chain"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
@@ -84,7 +85,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
}
var auther auth.Authenticator
if len(authers) > 0 {
auther = auth.AuthenticatorGroup(authers...)
auther = xauth.AuthenticatorGroup(authers...)
}
admissions := admission_parser.List(cfg.Admission, cfg.Admissions...)
@@ -155,7 +156,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
listener.AutherOption(auther),
listener.AuthOption(auth_parser.Info(cfg.Listener.Auth)),
listener.TLSConfigOption(tlsConfig),
listener.AdmissionOption(admission.AdmissionGroup(admissions...)),
listener.AdmissionOption(xadmission.AdmissionGroup(admissions...)),
listener.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Limiter)),
listener.ConnLimiterOption(registry.ConnLimiterRegistry().Get(cfg.CLimiter)),
listener.ServiceOption(cfg.Name),
@@ -235,7 +236,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
auther = nil
if len(authers) > 0 {
auther = auth.AuthenticatorGroup(authers...)
auther = xauth.AuthenticatorGroup(authers...)
}
var recorders []recorder.RecorderObject
@@ -277,7 +278,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
handler.RouterOption(xchain.NewRouter(routerOpts...)),
handler.AutherOption(auther),
handler.AuthOption(auth_parser.Info(cfg.Handler.Auth)),
handler.BypassOption(bypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
handler.BypassOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
handler.TLSConfigOption(tlsConfig),
handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)),
handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)),
@@ -309,7 +310,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
}
s := xservice.NewService(cfg.Name, ln, h,
xservice.AdmissionOption(admission.AdmissionGroup(admissions...)),
xservice.AdmissionOption(xadmission.AdmissionGroup(admissions...)),
xservice.PreUpOption(preUp),
xservice.PreDownOption(preDown),
xservice.PostUpOption(postUp),
@@ -374,6 +375,7 @@ func parseForwarder(cfg *config.ForwarderConfig, log logger.Logger) (hop.Hop, er
Bypass: node.Bypass,
Bypasses: node.Bypasses,
Filter: filter,
Matcher: node.Matcher,
HTTP: httpCfg,
TLS: node.TLS,
Metadata: node.Metadata,
+41
View File
@@ -0,0 +1,41 @@
package direct
import (
"io"
"net"
"time"
)
type conn struct{}
func (c *conn) Close() error {
return nil
}
func (c *conn) Read(b []byte) (n int, err error) {
return 0, io.EOF
}
func (c *conn) Write(b []byte) (n int, err error) {
return 0, io.ErrClosedPipe
}
func (c *conn) LocalAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) RemoteAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) SetDeadline(t time.Time) error {
return nil
}
func (c *conn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return nil
}
+7 -2
View File
@@ -1,4 +1,4 @@
package forward
package direct
import (
"context"
@@ -16,6 +16,7 @@ func init() {
}
type directConnector struct {
md metadata
options connector.Options
}
@@ -31,7 +32,7 @@ func NewConnector(opts ...connector.Option) connector.Connector {
}
func (c *directConnector) Init(md md.Metadata) (err error) {
return nil
return c.parseMetadata(md)
}
func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
@@ -40,6 +41,10 @@ func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, addr
opt(&cOpts)
}
if c.md.action == "reject" {
return &conn{}, nil
}
conn, err := cOpts.Dialer.Dial(ctx, network, address)
if err != nil {
return nil, err
+17
View File
@@ -0,0 +1,17 @@
package direct
import (
"strings"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
action string
}
func (c *directConnector) parseMetadata(md mdata.Metadata) (err error) {
c.md.action = strings.ToLower(mdutil.GetString(md, "action"))
return
}
+2 -1
View File
@@ -90,8 +90,9 @@ func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.Dial
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
quic.Version2,
},
MaxIncomingStreams: int64(d.md.maxStreams),
},
+1 -1
View File
@@ -127,7 +127,7 @@ func (d *icmpDialer) initSession(ctx context.Context, addr net.Addr, conn net.Pa
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
quic.Version2,
},
+1 -1
View File
@@ -106,7 +106,7 @@ func (d *quicDialer) initSession(ctx context.Context, addr net.Addr, conn net.Pa
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
quic.Version2,
},
+29 -21
View File
@@ -7,9 +7,9 @@ toolchain go1.22.2
require (
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/gin-contrib/cors v1.6.0
github.com/gin-gonic/gin v1.9.1
github.com/go-gost/core v0.1.7
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/go-gost/core v0.1.11
github.com/go-gost/gosocks4 v0.0.1
github.com/go-gost/gosocks5 v0.4.2
github.com/go-gost/plugin v0.1.1
@@ -19,7 +19,7 @@ require (
github.com/gobwas/glob v0.2.3
github.com/golang/snappy v0.0.4
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.1
github.com/gorilla/websocket v1.5.3
github.com/miekg/dns v1.1.61
github.com/mitchellh/go-homedir v1.1.0
github.com/patrickmn/go-cache v2.1.0+incompatible
@@ -33,21 +33,25 @@ require (
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601
github.com/sirupsen/logrus v1.8.1
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
github.com/spf13/viper v1.18.2
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/vishvananda/netlink v1.1.0
github.com/vishvananda/netns v0.0.4
github.com/vulcand/predicate v1.2.0
github.com/xtaci/kcp-go/v5 v5.6.5
github.com/xtaci/smux v1.5.24
github.com/xtaci/smux v1.5.31
github.com/xtaci/tcpraw v1.2.25
github.com/yl2chen/cidranger v1.0.2
github.com/zalando/go-keyring v0.2.4
golang.org/x/crypto v0.25.0
golang.org/x/net v0.27.0
golang.org/x/sys v0.22.0
golang.org/x/crypto v0.27.0
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8
golang.org/x/net v0.29.0
golang.org/x/sys v0.25.0
golang.org/x/text v0.18.0
golang.org/x/time v0.5.0
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
)
@@ -56,25 +60,29 @@ require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.11.2 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/coreos/go-iptables v0.5.0 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/reedsolomon v1.11.8 // indirect
@@ -85,11 +93,12 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/transport/v2 v2.0.2 // indirect
github.com/pion/udp/v2 v2.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
@@ -109,13 +118,12 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/tools v0.23.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
+60 -49
View File
@@ -10,21 +10,18 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-iptables v0.5.0 h1:mw6SAibtHKZcNzAsOxjoHIG0gy5YFHhypWSSNc6EjbQ=
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
@@ -37,6 +34,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
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=
@@ -47,14 +45,14 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg=
github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gost/core v0.1.7 h1:EP2Ux2SZR1omV6362MfTZG+oZTuaAODGqc0M9c/ZdEI=
github.com/go-gost/core v0.1.7/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-gost/core v0.1.11 h1:7zmRx0stWrClUJM/IT/ltRQJC9wUc8f6TAj1fbBiSPs=
github.com/go-gost/core v0.1.11/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A=
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.2 h1:IianxHTkACPqCwiOAT3MHoMdSUl+SEPSRu1ikawC1Pc=
@@ -73,8 +71,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
@@ -111,10 +109,14 @@ github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JT
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ=
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -154,8 +156,8 @@ github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4=
github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
@@ -200,6 +202,7 @@ github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1H
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 h1:XU9hik0exChEmY92ALW4l9WnDodxLVS9yOSNh2SizaQ=
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
@@ -212,21 +215,24 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
@@ -244,12 +250,14 @@ github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYp
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA=
github.com/xtaci/kcp-go/v5 v5.6.5 h1:oxGZNobj3OddrLzwdJYnR/waNgwrL98u02u0DWNHE3k=
github.com/xtaci/kcp-go/v5 v5.6.5/go.mod h1:Qy3Zf2tWTdFdEs0E8JvhrX+39r5UDZoYac8anvud7/Q=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
github.com/xtaci/smux v1.5.31 h1:3ha7sHtH46h85Iv7MfQogxasuRt1KPRhoFB3S4rmHgU=
github.com/xtaci/smux v1.5.31/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk=
github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
@@ -262,17 +270,18 @@ go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM=
golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
@@ -291,19 +300,20 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -317,22 +327,22 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -356,22 +366,23 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+1 -1
View File
@@ -122,7 +122,7 @@ func (h *fileHandler) handleFunc(w http.ResponseWriter, r *http.Request) {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s %s %d %d", r.Method, r.RequestURI, rw.statusCode, rw.contentLength)
}).Infof("%s %s %s %d %d", r.Method, r.RequestURI, r.Proto, rw.statusCode, rw.contentLength)
}()
if auther := h.options.Auther; auther != nil {
+20 -15
View File
@@ -92,7 +92,10 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
ro.ClientIP = conn.RemoteAddr().String()
if clientAddr := ctxvalue.ClientAddrFromContext(ctx); clientAddr != "" {
ro.ClientIP = string(clientAddr)
} else {
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(conn.RemoteAddr().String()))
}
if h, _, _ := net.SplitHostPort(ro.ClientIP); h != "" {
ro.ClientIP = h
}
@@ -100,11 +103,17 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
"sid": ro.SID,
"client": ro.ClientIP,
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
ro.Network = network
pStats := stats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
@@ -130,12 +139,6 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return rate_limiter.ErrRateLimit
}
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
ro.Network = network
var proto string
if network == "tcp" && h.md.sniffing {
if h.md.sniffingTimeout > 0 {
@@ -157,14 +160,16 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return cc, err
}
sniffer := &forwarder.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
+10 -6
View File
@@ -13,11 +13,13 @@ import (
)
type metadata struct {
readTimeout time.Duration
readTimeout time.Duration
httpKeepalive bool
sniffing bool
sniffingTimeout time.Duration
httpKeepalive bool
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -31,10 +33,12 @@ func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.readTimeout = 15 * time.Second
}
h.md.httpKeepalive = mdutil.GetBool(md, "http.keepalive")
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.httpKeepalive = mdutil.GetBool(md, "http.keepalive")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+31 -13
View File
@@ -23,6 +23,7 @@ import (
"github.com/go-gost/x/internal/util/sniffing"
tls_util "github.com/go-gost/x/internal/util/tls"
rate_limiter "github.com/go-gost/x/limiter/rate"
mdutil "github.com/go-gost/x/metadata/util"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry"
@@ -93,7 +94,10 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
ro.ClientIP = conn.RemoteAddr().String()
if clientAddr := ctxvalue.ClientAddrFromContext(ctx); clientAddr != "" {
ro.ClientIP = string(clientAddr)
} else {
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(conn.RemoteAddr().String()))
}
if h, _, _ := net.SplitHostPort(ro.ClientIP); h != "" {
ro.ClientIP = h
}
@@ -101,11 +105,17 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
"sid": ro.SID,
"client": ro.ClientIP,
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
ro.Network = network
pStats := stats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
@@ -131,11 +141,12 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return rate_limiter.ErrRateLimit
}
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
var host string
if md, ok := conn.(mdata.Metadatable); ok {
if v := mdutil.GetString(md.Metadata(), "host"); v != "" {
host = v
}
}
ro.Network = network
var proto string
if network == "tcp" && h.md.sniffing {
@@ -158,14 +169,16 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return cc, err
}
sniffer := &forwarder.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
@@ -191,6 +204,11 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
}
var target *chain.Node
if host != "" {
target = &chain.Node{
Addr: host,
}
}
if h.hop != nil {
target = h.hop.Select(ctx,
hop.ProtocolSelectOption(proto),
+7 -3
View File
@@ -15,10 +15,12 @@ import (
type metadata struct {
readTimeout time.Duration
proxyProtocol int
httpKeepalive bool
sniffing bool
sniffingTimeout time.Duration
httpKeepalive bool
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -35,6 +37,8 @@ func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
h.md.httpKeepalive = mdutil.GetBool(md, "http.keepalive")
+340 -109
View File
@@ -11,6 +11,7 @@ import (
"fmt"
"hash/crc32"
"io"
"math"
"net"
"net/http"
"net/http/httputil"
@@ -29,17 +30,22 @@ import (
"github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx"
ctx_internal "github.com/go-gost/x/internal/ctx"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
limiter_util "github.com/go-gost/x/internal/util/limiter"
"github.com/go-gost/x/internal/util/sniffing"
stats_util "github.com/go-gost/x/internal/util/stats"
tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
rate_limiter "github.com/go-gost/x/limiter/rate"
traffic_wrapper "github.com/go-gost/x/limiter/traffic/wrapper"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry"
"golang.org/x/net/http/httpguts"
"golang.org/x/time/rate"
)
func init() {
@@ -47,13 +53,14 @@ func init() {
}
type httpHandler struct {
md metadata
options handler.Options
stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter
cancel context.CancelFunc
recorder recorder.RecorderObject
certPool tls_util.CertPool
md metadata
options handler.Options
stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter
cancel context.CancelFunc
recorder recorder.RecorderObject
certPool tls_util.CertPool
transport http.RoundTripper
}
func NewHandler(opts ...handler.Option) handler.Handler {
@@ -81,7 +88,7 @@ func (h *httpHandler) Init(md md.Metadata) error {
}
if limiter := h.options.Limiter; limiter != nil {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, h.md.limiterRefreshInterval, 60*time.Second)
}
for _, ro := range h.options.Recorders {
@@ -95,6 +102,14 @@ func (h *httpHandler) Init(md md.Metadata) error {
h.certPool = tls_util.NewMemoryCertPool()
}
h.transport = &http.Transport{
DialContext: h.dial,
IdleConnTimeout: 30 * time.Second,
ResponseHeaderTimeout: h.md.readTimeout,
DisableKeepAlives: !h.md.keepalive,
DisableCompression: !h.md.compression,
}
return nil
}
@@ -153,7 +168,8 @@ func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
return rate_limiter.ErrRateLimit
}
req, err := http.ReadRequest(bufio.NewReader(conn))
br := bufio.NewReader(conn)
req, err := http.ReadRequest(br)
if err != nil {
log.Error(err)
return err
@@ -164,6 +180,8 @@ func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
ro.ClientIP = clientIP.String()
}
conn = xnet.NewReadWriteConn(br, conn, conn)
return h.handleRequest(ctx, conn, req, ro, log)
}
@@ -205,7 +223,8 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
ro.Host = addr
fields := map[string]any{
"dst": addr,
"dst": addr,
"host": addr,
}
if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")); u != "" {
@@ -249,13 +268,26 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
ro.HTTP.Response.Header = resp.Header
}()
if req.Method == "PRI" ||
(req.Method != http.MethodConnect && req.URL.Scheme != "http") {
resp.StatusCode = http.StatusBadRequest
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
return resp.Write(conn)
}
clientID, ok := h.authenticate(ctx, conn, req, resp, log)
if !ok {
return errors.New("authentication failed")
}
ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID))
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, network, addr) {
if h.options.Bypass != nil &&
h.options.Bypass.Contains(ctx, network, addr) {
resp.StatusCode = http.StatusForbidden
if log.IsLevelEnabled(logger.TraceLevel) {
@@ -271,38 +303,6 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
return h.handleUDP(ctx, conn, ro, log)
}
if req.Method == "PRI" ||
(req.Method != http.MethodConnect && req.URL.Scheme != "http") {
resp.StatusCode = http.StatusBadRequest
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
return resp.Write(conn)
}
switch h.md.hash {
case "host":
ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr})
}
var buf bytes.Buffer
cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr)
ro.Route = buf.String()
if err != nil {
resp.StatusCode = http.StatusServiceUnavailable
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
resp.Write(conn)
return err
}
defer cc.Close()
{
rw := traffic_wrapper.WrapReadWriter(
h.limiter,
@@ -327,12 +327,25 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
}
if req.Method != http.MethodConnect {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro.Time = time.Time{}
return h.handleProxy(ctx, conn, cc, req, ro2, log)
return h.handleProxy(ctx, conn, req, ro, log)
}
ctx = ctx_internal.ContextWithRecorderObject(ctx, ro)
ctx = ctxvalue.ContextWithLogger(ctx, log)
cc, err := h.dial(ctx, "tcp", addr)
if err != nil {
resp.StatusCode = http.StatusServiceUnavailable
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
resp.Write(conn)
return err
}
defer cc.Close()
resp.StatusCode = http.StatusOK
resp.Status = "200 Connection established"
@@ -365,14 +378,16 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
return cc, nil
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
@@ -404,13 +419,21 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
return nil
}
func (h *httpHandler) handleProxy(ctx context.Context, rw io.ReadWriter, cc net.Conn, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
if shouldClose, err := h.proxyRoundTrip(ctx, rw, cc, req, ro, log); err != nil || shouldClose {
func (h *httpHandler) handleProxy(ctx context.Context, conn net.Conn, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
pStats := stats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
ro.Time = time.Time{}
if close, err := h.proxyRoundTrip(ctx, conn, req, ro, &pStats, log); err != nil || close {
return err
}
br := bufio.NewReader(conn)
for {
req, err := http.ReadRequest(bufio.NewReader(rw))
pStats.Reset()
req, err := http.ReadRequest(br)
if err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
return nil
@@ -423,40 +446,49 @@ func (h *httpHandler) handleProxy(ctx context.Context, rw io.ReadWriter, cc net.
log.Trace(string(dump))
}
if shouldClose, err := h.proxyRoundTrip(ctx, rw, cc, req, ro, log); err != nil || shouldClose {
if close, err := h.proxyRoundTrip(ctx, xio.NewReadWriter(br, conn), req, ro, &pStats, log); err != nil || close {
return err
}
}
}
func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, cc net.Conn, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (close bool, err error) {
func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, req *http.Request, ro *xrecorder.HandlerRecorderObject, pStats *stats.Stats, log logger.Logger) (close bool, err error) {
close = true
if req == nil {
return
}
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
start := time.Now()
ro.Time = start
host := req.Host
if _, port, _ := net.SplitHostPort(host); port == "" {
host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
}
ro.Host = host
ro.Time = time.Now()
log = log.WithFields(map[string]any{
"host": host,
})
log.Infof("%s <-> %s", ro.RemoteAddr, req.Host)
defer func() {
ro.Duration = time.Since(start)
if err != nil {
ro.Err = err.Error()
}
ro.InputBytes = pStats.Get(stats.KindInputBytes)
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
ro.Duration = time.Since(ro.Time)
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
log.WithFields(map[string]any{
"duration": time.Since(start),
"duration": time.Since(ro.Time),
"inputBytes": ro.InputBytes,
"outputBytes": ro.OutputBytes,
}).Infof("%s >-< %s", ro.RemoteAddr, req.Host)
}()
if clientIP := xhttp.GetClientIP(req); clientIP != nil {
ro.ClientIP = clientIP.String()
}
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
@@ -468,9 +500,20 @@ func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, cc n
Header: req.Header.Clone(),
},
}
if clientIP := xhttp.GetClientIP(req); clientIP != nil {
ro.ClientIP = clientIP.String()
}
clientAddr := ro.RemoteAddr
if ro.ClientIP != "" {
if _, port, _ := net.SplitHostPort(ro.RemoteAddr); port != "" {
clientAddr = net.JoinHostPort(ro.ClientIP, port)
}
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
http10 := req.ProtoMajor == 1 && req.ProtoMinor == 0
if http10 {
if strings.ToLower(req.Header.Get("Connection")) == "keep-alive" {
req.Header.Del("Connection")
} else {
@@ -478,27 +521,11 @@ func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, cc n
}
}
if !h.md.keepalive {
req.Header.Set("Connection", "close")
}
req.Header.Del("Proxy-Authorization")
req.Header.Del("Proxy-Connection")
req.Header.Del("Gost-Target")
req.Header.Del("X-Gost-Target")
var reqBody *xhttp.Body
if opts := h.recorder.Options; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = sniffing.DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
req.Body = reqBody
}
}
res := &http.Response{
ProtoMajor: req.ProtoMajor,
ProtoMinor: req.ProtoMinor,
@@ -507,25 +534,51 @@ func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, cc n
}
ro.HTTP.StatusCode = res.StatusCode
if err = req.Write(cc); err != nil {
if h.options.Bypass != nil &&
h.options.Bypass.Contains(ctx, "tcp", host) {
res.StatusCode = http.StatusForbidden
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(res, false)
log.Trace(string(dump))
}
log.Debug("bypass: ", host)
res.Write(rw)
err = xbypass.ErrBypass
return
}
var reqBody *xhttp.Body
if opts := h.recorder.Options; opts != nil && opts.HTTPBody {
if req.Body != nil {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr))
ctx = ctx_internal.ContextWithRecorderObject(ctx, ro)
ctx = ctxvalue.ContextWithLogger(ctx, log)
resp, err := h.transport.RoundTrip(req.WithContext(ctx))
if reqBody != nil {
ro.HTTP.Request.Body = reqBody.Content()
ro.HTTP.Request.ContentLength = reqBody.Length()
}
cc.SetReadDeadline(time.Now().Add(h.md.readTimeout))
resp, err := http.ReadResponse(bufio.NewReader(cc), req)
if err != nil {
log.Errorf("read response: %v", err)
res.Write(rw)
return
}
defer resp.Body.Close()
cc.SetReadDeadline(time.Time{})
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.Response.Header = resp.Header.Clone()
@@ -537,35 +590,217 @@ func (h *httpHandler) proxyRoundTrip(ctx context.Context, rw io.ReadWriter, cc n
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if http10 {
if !resp.Close {
resp.Header.Set("Connection", "keep-alive")
}
resp.ProtoMajor = req.ProtoMajor
resp.ProtoMinor = req.ProtoMinor
resp.ProtoMajor = 1
resp.ProtoMinor = 0
}
if resp.StatusCode == http.StatusSwitchingProtocols {
err = h.handleUpgradeResponse(ctx, rw, req, resp, ro, log)
return
}
var respBody *xhttp.Body
if opts := h.recorder.Options; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = sniffing.DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
if err = resp.Write(rw); err != nil {
log.Errorf("write response: %v", err)
return
}
err = resp.Write(rw)
if respBody != nil {
ro.HTTP.Response.Body = respBody.Content()
ro.HTTP.Response.ContentLength = respBody.Length()
}
return resp.Close, nil
if err != nil {
err = fmt.Errorf("write response: %v", err)
return
}
if resp.ContentLength >= 0 {
close = resp.Close
}
return
}
func (h *httpHandler) dial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
switch h.md.hash {
case "host":
ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr})
}
if log := ctxvalue.LoggerFromContext(ctx); log != nil {
log.Debugf("dial: new connection to host %s", addr)
}
var buf bytes.Buffer
conn, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr)
if ro := ctx_internal.RecorderObjectFromContext(ctx); ro != nil {
ro.Route = buf.String()
}
return
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *httpHandler) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
return fmt.Errorf("internal error: 101 switching protocols response with non-writable body")
}
res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.md.sniffingWebsocket {
return h.sniffingWebsocketFrame(ctx, rw, backConn, ro, log)
}
return xnet.Transport(rw, backConn)
}
func (h *httpHandler) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.md.sniffingWebsocketSampleRate
if sampleRate == 0 {
sampleRate = sniffing.DefaultSampleRate
}
if sampleRate < 0 {
sampleRate = math.MaxFloat64
}
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(cc, rw, buf, "client", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(rw, cc, buf, "server", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
<-errc
return nil
}
func (h *httpHandler) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.recorder.Options; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (h *httpHandler) decodeServerName(s string) (string, error) {
@@ -714,11 +949,7 @@ func (h *httpHandler) observeStats(ctx context.Context) {
return
}
d := h.md.observePeriod
if d < time.Millisecond {
d = 5 * time.Second
}
ticker := time.NewTicker(d)
ticker := time.NewTicker(h.md.observePeriod)
defer ticker.Stop()
for {
+33 -12
View File
@@ -20,18 +20,22 @@ const (
)
type metadata struct {
readTimeout time.Duration
keepalive bool
probeResistance *probeResistance
enableUDP bool
header http.Header
hash string
authBasicRealm string
observePeriod time.Duration
proxyAgent string
readTimeout time.Duration
keepalive bool
compression bool
probeResistance *probeResistance
enableUDP bool
header http.Header
hash string
authBasicRealm string
proxyAgent string
observePeriod time.Duration
limiterRefreshInterval time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -54,6 +58,7 @@ func (h *httpHandler) parseMetadata(md mdata.Metadata) error {
}
h.md.keepalive = mdutil.GetBool(md, "http.keepalive", "keepalive")
h.md.compression = mdutil.GetBool(md, "http.compression", "compression")
if pr := mdutil.GetString(md, "probeResist", "probe_resist"); pr != "" {
if ss := strings.SplitN(pr, ":", 2); len(ss) == 2 {
@@ -68,7 +73,21 @@ func (h *httpHandler) parseMetadata(md mdata.Metadata) error {
h.md.hash = mdutil.GetString(md, "hash")
h.md.authBasicRealm = mdutil.GetString(md, "authBasicRealm")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod", "observer.observePeriod")
if h.md.observePeriod == 0 {
h.md.observePeriod = 5 * time.Second
}
if h.md.observePeriod < time.Second {
h.md.observePeriod = time.Second
}
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if h.md.limiterRefreshInterval == 0 {
h.md.limiterRefreshInterval = 30 * time.Second
}
if h.md.limiterRefreshInterval < time.Second {
h.md.limiterRefreshInterval = time.Second
}
h.md.proxyAgent = mdutil.GetString(md, "http.proxyAgent", "proxyAgent")
if h.md.proxyAgent == "" {
@@ -77,6 +96,8 @@ func (h *httpHandler) parseMetadata(md mdata.Metadata) error {
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+4 -7
View File
@@ -77,7 +77,7 @@ func (h *http2Handler) Init(md md.Metadata) error {
}
if limiter := h.options.Limiter; limiter != nil {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, h.md.limiterRefreshInterval, 60*time.Second)
}
for _, ro := range h.options.Recorders {
@@ -178,7 +178,8 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
ro.Host = host
fields := map[string]any{
"dst": host,
"dst": host,
"host": host,
}
if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")); u != "" {
fields["user"] = u
@@ -486,11 +487,7 @@ func (h *http2Handler) observeStats(ctx context.Context) {
return
}
d := h.md.observePeriod
if d < time.Millisecond {
d = 5 * time.Second
}
ticker := time.NewTicker(d)
ticker := time.NewTicker(h.md.observePeriod)
defer ticker.Stop()
for {
+21 -6
View File
@@ -14,11 +14,12 @@ const (
)
type metadata struct {
probeResistance *probeResistance
header http.Header
hash string
authBasicRealm string
observePeriod time.Duration
probeResistance *probeResistance
header http.Header
hash string
authBasicRealm string
observePeriod time.Duration
limiterRefreshInterval time.Duration
}
func (h *http2Handler) parseMetadata(md mdata.Metadata) error {
@@ -42,7 +43,21 @@ func (h *http2Handler) parseMetadata(md mdata.Metadata) error {
h.md.hash = mdutil.GetString(md, "hash")
h.md.authBasicRealm = mdutil.GetString(md, "authBasicRealm")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod", "observer.observePeriod")
if h.md.observePeriod == 0 {
h.md.observePeriod = 5 * time.Second
}
if h.md.observePeriod < time.Second {
h.md.observePeriod = time.Second
}
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if h.md.limiterRefreshInterval == 0 {
h.md.limiterRefreshInterval = 30 * time.Second
}
if h.md.limiterRefreshInterval < time.Second {
h.md.limiterRefreshInterval = time.Second
}
return nil
}
+2 -1
View File
@@ -124,7 +124,8 @@ func (h *http3Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
}
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", target.Addr, "tcp"),
"dst": fmt.Sprintf("%s/%s", target.Addr, "tcp"),
"host": target.Addr,
})
log.Debugf("%s >> %s", req.RemoteAddr, addr)
+13 -9
View File
@@ -128,9 +128,11 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
}
ro.Host = dstAddr.String()
ro.Dst = dstAddr.String()
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()),
"dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()),
"host": dstAddr.String(),
})
if h.md.sniffing {
@@ -183,14 +185,16 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
+7 -3
View File
@@ -16,9 +16,11 @@ type metadata struct {
readTimeout time.Duration
tproxy bool
sniffing bool
sniffingTimeout time.Duration
sniffingFallback bool
sniffing bool
sniffingTimeout time.Duration
sniffingFallback bool
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -36,6 +38,8 @@ func (h *redirectHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingFallback = mdutil.GetBool(md, "sniffing.fallback")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+2 -1
View File
@@ -108,7 +108,8 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
ro.Host = dstAddr.String()
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()),
"dst": fmt.Sprintf("%s/%s", dstAddr, dstAddr.Network()),
"host": dstAddr.String(),
})
log.Debugf("%s >> %s", conn.RemoteAddr(), dstAddr)
+13 -10
View File
@@ -33,8 +33,9 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
}
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", address, network),
"cmd": "connect",
"dst": fmt.Sprintf("%s/%s", address, network),
"cmd": "connect",
"host": address,
})
log.Debugf("%s >> %s/%s", conn.RemoteAddr(), address, network)
@@ -165,14 +166,16 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
return cc, nil
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
+2 -6
View File
@@ -70,7 +70,7 @@ func (h *relayHandler) Init(md md.Metadata) (err error) {
}
if limiter := h.options.Limiter; limiter != nil {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, h.md.limiterRefreshInterval, 60*time.Second)
}
for _, ro := range h.options.Recorders {
@@ -253,11 +253,7 @@ func (h *relayHandler) observeStats(ctx context.Context) {
return
}
d := h.md.observePeriod
if d < time.Millisecond {
d = 5 * time.Second
}
ticker := time.NewTicker(d)
ticker := time.NewTicker(h.md.observePeriod)
defer ticker.Stop()
for {
+30 -11
View File
@@ -9,22 +9,25 @@ import (
"github.com/go-gost/core/bypass"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry"
)
type metadata struct {
readTimeout time.Duration
enableBind bool
udpBufferSize int
noDelay bool
hash string
muxCfg *mux.Config
observePeriod time.Duration
readTimeout time.Duration
enableBind bool
udpBufferSize int
noDelay bool
hash string
muxCfg *mux.Config
observePeriod time.Duration
limiterRefreshInterval time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -59,10 +62,26 @@ func (h *relayHandler) parseMetadata(md mdata.Metadata) (err error) {
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod", "observer.observePeriod")
if h.md.observePeriod == 0 {
h.md.observePeriod = 5 * time.Second
}
if h.md.observePeriod < time.Second {
h.md.observePeriod = time.Second
}
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if h.md.limiterRefreshInterval == 0 {
h.md.limiterRefreshInterval = 30 * time.Second
}
if h.md.limiterRefreshInterval < time.Second {
h.md.limiterRefreshInterval = time.Second
}
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+10 -8
View File
@@ -143,14 +143,16 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
switch proto {
+6
View File
@@ -16,6 +16,9 @@ type metadata struct {
readTimeout time.Duration
hash string
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
alpn string
@@ -29,6 +32,9 @@ func (h *sniHandler) parseMetadata(md mdata.Metadata) (err error) {
}
h.md.hash = mdutil.GetString(md, "hash")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
if certFile != "" && keyFile != "" {
+14 -15
View File
@@ -75,7 +75,7 @@ func (h *socks4Handler) Init(md md.Metadata) (err error) {
}
if limiter := h.options.Limiter; limiter != nil {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, h.md.limiterRefreshInterval, 60*time.Second)
}
for _, ro := range h.options.Recorders {
@@ -196,7 +196,8 @@ func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *g
addr := req.Addr.String()
log = log.WithFields(map[string]any{
"dst": addr,
"dst": addr,
"host": addr,
})
log.Debugf("%s >> %s", conn.RemoteAddr(), addr)
@@ -274,14 +275,16 @@ func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *g
return cc, nil
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
@@ -335,11 +338,7 @@ func (h *socks4Handler) observeStats(ctx context.Context) {
return
}
d := h.md.observePeriod
if d < time.Millisecond {
d = 5 * time.Second
}
ticker := time.NewTicker(d)
ticker := time.NewTicker(h.md.observePeriod)
defer ticker.Stop()
for {
+26 -6
View File
@@ -13,12 +13,15 @@ import (
)
type metadata struct {
readTimeout time.Duration
hash string
observePeriod time.Duration
readTimeout time.Duration
hash string
observePeriod time.Duration
limiterRefreshInterval time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -33,10 +36,27 @@ func (h *socks4Handler) parseMetadata(md mdata.Metadata) (err error) {
}
h.md.hash = mdutil.GetString(md, "hash")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod", "observer.observePeriod")
if h.md.observePeriod == 0 {
h.md.observePeriod = 5 * time.Second
}
if h.md.observePeriod < time.Second {
h.md.observePeriod = time.Second
}
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if h.md.limiterRefreshInterval == 0 {
h.md.limiterRefreshInterval = 30 * time.Second
}
if h.md.limiterRefreshInterval < time.Second {
h.md.limiterRefreshInterval = time.Second
}
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+13 -10
View File
@@ -23,8 +23,9 @@ import (
func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, network, address string, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", address, network),
"cmd": "connect",
"dst": fmt.Sprintf("%s/%s", address, network),
"cmd": "connect",
"host": address,
})
log.Debugf("%s >> %s", conn.RemoteAddr(), address)
@@ -102,14 +103,16 @@ func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, networ
return cc, nil
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
+2 -6
View File
@@ -75,7 +75,7 @@ func (h *socks5Handler) Init(md md.Metadata) (err error) {
}
if limiter := h.options.Limiter; limiter != nil {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, h.md.limiterRefreshInterval, 60*time.Second)
}
for _, ro := range h.options.Recorders {
@@ -216,11 +216,7 @@ func (h *socks5Handler) observeStats(ctx context.Context) {
return
}
d := h.md.observePeriod
if d < time.Millisecond {
d = 5 * time.Second
}
ticker := time.NewTicker(d)
ticker := time.NewTicker(h.md.observePeriod)
defer ticker.Stop()
for {
+32 -13
View File
@@ -9,24 +9,27 @@ import (
"github.com/go-gost/core/bypass"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/registry"
)
type metadata struct {
readTimeout time.Duration
noTLS bool
enableBind bool
enableUDP bool
udpBufferSize int
compatibilityMode bool
hash string
muxCfg *mux.Config
observePeriod time.Duration
readTimeout time.Duration
noTLS bool
enableBind bool
enableUDP bool
udpBufferSize int
compatibilityMode bool
hash string
muxCfg *mux.Config
observePeriod time.Duration
limiterRefreshInterval time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -63,10 +66,26 @@ func (h *socks5Handler) parseMetadata(md mdata.Metadata) (err error) {
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod", "observer.observePeriod")
if h.md.observePeriod == 0 {
h.md.observePeriod = 5 * time.Second
}
if h.md.observePeriod < time.Second {
h.md.observePeriod = time.Second
}
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if h.md.limiterRefreshInterval == 0 {
h.md.limiterRefreshInterval = 30 * time.Second
}
if h.md.limiterRefreshInterval < time.Second {
h.md.limiterRefreshInterval = time.Second
}
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+12 -9
View File
@@ -148,7 +148,8 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H
conn.SetReadDeadline(time.Time{})
log = log.WithFields(map[string]any{
"dst": addr.String(),
"dst": addr.String(),
"host": addr.String(),
})
log.Debugf("%s >> %s", conn.RemoteAddr(), addr)
@@ -191,14 +192,16 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H
return cc, nil
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
conn = xnet.NewReadWriteConn(br, conn, conn)
+6 -2
View File
@@ -17,8 +17,10 @@ type metadata struct {
hash string
readTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -37,6 +39,8 @@ func (h *ssHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+13 -10
View File
@@ -140,8 +140,9 @@ func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_uti
ro.Host = targetAddr
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", targetAddr, "tcp"),
"cmd": "connect",
"dst": fmt.Sprintf("%s/%s", targetAddr, "tcp"),
"cmd": "connect",
"host": targetAddr,
})
log.Debugf("%s >> %s", conn.RemoteAddr(), targetAddr)
@@ -179,14 +180,16 @@ func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_uti
return cc, nil
}
sniffer := &sniffing.Sniffer{
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
Websocket: h.md.sniffingWebsocket,
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
Recorder: h.recorder.Recorder,
RecorderOptions: h.recorder.Options,
Certificate: h.md.certificate,
PrivateKey: h.md.privateKey,
NegotiatedProtocol: h.md.alpn,
CertPool: h.certPool,
MitmBypass: h.md.mitmBypass,
ReadTimeout: h.md.readTimeout,
}
switch proto {
+6 -2
View File
@@ -15,8 +15,10 @@ import (
type metadata struct {
readTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffing bool
sniffingTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
certificate *x509.Certificate
privateKey crypto.PrivateKey
@@ -32,6 +34,8 @@ func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.sniffing = mdutil.GetBool(md, "sniffing")
h.md.sniffingTimeout = mdutil.GetDuration(md, "sniffing.timeout")
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
certFile := mdutil.GetString(md, "mitm.certFile", "mitm.caCertFile")
keyFile := mdutil.GetString(md, "mitm.keyFile", "mitm.caKeyFile")
+8 -4
View File
@@ -37,11 +37,15 @@ func (h *tunnelHandler) handleBind(ctx context.Context, conn net.Conn, network,
v := md5.Sum([]byte(tunnelID.String()))
endpoint := hex.EncodeToString(v[:8])
addr := address
host, port, _ := net.SplitHostPort(addr)
if host == "" {
addr = net.JoinHostPort(endpoint, port)
host, port, _ := net.SplitHostPort(address)
if host == "" || h.md.ingress == nil {
host = endpoint
} else if host != endpoint {
if rule := h.md.ingress.GetRule(ctx, host); rule != nil && rule.Endpoint != tunnelID.String() {
host = endpoint
}
}
addr := net.JoinHostPort(host, port)
af := &relay.AddrFeature{}
err = af.ParseFrom(addr)
+29 -12
View File
@@ -16,6 +16,7 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, c
"dst": fmt.Sprintf("%s/%s", dstAddr, network),
"cmd": "connect",
"tunnel": tunnelID.String(),
"host": dstAddr,
})
resp := relay.Response{
@@ -32,18 +33,34 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, c
host, _, _ := net.SplitHostPort(dstAddr)
// client is a public entrypoint.
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 != "" {
if rule := ingress.GetRule(ctx, host); rule != nil {
tid = parseTunnelID(rule.Endpoint)
}
}
if !h.md.directTunnel {
var tid relay.TunnelID
if ingress := h.md.ingress; ingress != nil && host != "" {
if rule := ingress.GetRule(ctx, host); rule != nil {
tid = parseTunnelID(rule.Endpoint)
}
// visitor is a public entrypoint.
if tunnelID.Equal(h.md.entryPointID) {
if tid.IsZero() {
resp.Status = relay.StatusNetworkUnreachable
resp.WriteTo(conn)
err := fmt.Errorf("no route to host %s", host)
log.Error(err)
return err
}
if tid.IsPrivate() {
resp.Status = relay.StatusHostUnreachable
resp.WriteTo(conn)
err := fmt.Errorf("tunnel %s is private for host %s", tid, host)
log.Error(err)
return err
}
} else {
// direct routing
if h.md.directTunnel {
tid = tunnelID
}
if !tid.Equal(tunnelID) {
resp.Status = relay.StatusHostUnreachable
@@ -62,7 +79,7 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, c
timeout: 15 * time.Second,
log: log,
}
cc, node, cid, err := d.Dial(ctx, network, tunnelID.String())
cc, node, cid, err := d.Dial(ctx, network, tid.String())
if err != nil {
log.Error(err)
resp.Status = relay.StatusServiceUnavailable
@@ -71,7 +88,7 @@ func (h *tunnelHandler) handleConnect(ctx context.Context, req *relay.Request, c
}
defer cc.Close()
log.Debugf("new connection to tunnel: %s, connector: %s", tunnelID, cid)
log.Debugf("connect to node=%s tunnel=%s connector=%s OK", node, tid, cid)
if node == h.id {
if _, err := resp.WriteTo(conn); err != nil {
+2 -2
View File
@@ -68,11 +68,11 @@ func (d *Dialer) Dial(ctx context.Context, network string, tid string) (conn net
}
node = service.Node
cid = service.Name
cid = service.ID
dialer := net.Dialer{
Timeout: d.timeout,
}
conn, err = dialer.DialContext(ctx, network, service.Address)
conn, err = dialer.DialContext(ctx, "tcp", service.Address)
return
}
+241 -94
View File
@@ -2,10 +2,12 @@ package tunnel
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"math"
"net"
"net/http"
"net/http/httputil"
@@ -24,35 +26,26 @@ import (
"github.com/go-gost/relay"
admission "github.com/go-gost/x/admission/wrapper"
ctxvalue "github.com/go-gost/x/ctx"
ctx_internal "github.com/go-gost/x/internal/ctx"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/net/proxyproto"
"github.com/go-gost/x/internal/util/sniffing"
ws_util "github.com/go-gost/x/internal/util/ws"
climiter "github.com/go-gost/x/limiter/conn/wrapper"
metrics "github.com/go-gost/x/metrics/wrapper"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/time/rate"
)
const (
defaultBodySize = 1024 * 1024 // 1MB
httpHeaderSID = "Gost-Sid"
httpHeaderForwardedNode = "Gost-Forwarded-Node"
)
type recorderObjectCtxKey struct{}
var (
ctxKeyRecorderObject = &recorderObjectCtxKey{}
)
func contextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
return context.WithValue(ctx, ctxKeyRecorderObject, ro)
}
func recorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
v, _ := ctx.Value(ctxKeyRecorderObject).(*xrecorder.HandlerRecorderObject)
return v
}
type entrypoint struct {
node string
service string
@@ -62,6 +55,9 @@ type entrypoint struct {
log logger.Logger
recorder recorder.RecorderObject
transport http.RoundTripper
sniffingWebsocket bool
websocketSampleRate float64
}
func (ep *entrypoint) Handle(ctx context.Context, conn net.Conn) (err error) {
@@ -119,6 +115,77 @@ func (ep *entrypoint) Handle(ctx context.Context, conn net.Conn) (err error) {
return ep.handleHTTP(ctx, conn, ro, log)
}
func (ep *entrypoint) dial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
var tunnelID relay.TunnelID
if ep.ingress != nil {
if rule := ep.ingress.GetRule(ctx, addr); rule != nil {
tunnelID = parseTunnelID(rule.Endpoint)
}
}
log := ctxvalue.LoggerFromContext(ctx)
if log == nil {
log = ep.log
}
log.Debugf("dial: new connection to host %s", addr)
if tunnelID.IsZero() {
return nil, fmt.Errorf("%w %s", ErrTunnelRoute, addr)
}
ro := ctx_internal.RecorderObjectFromContext(ctx)
ro.ClientID = tunnelID.String()
if tunnelID.IsPrivate() {
return nil, fmt.Errorf("%w: tunnel %s is private for host %s", ErrPrivateTunnel, tunnelID, addr)
}
log = log.WithFields(map[string]any{
"tunnel": tunnelID.String(),
})
d := &Dialer{
node: ep.node,
pool: ep.pool,
sd: ep.sd,
retry: 3,
timeout: 15 * time.Second,
log: log,
}
conn, node, cid, err := d.Dial(ctx, "tcp", tunnelID.String())
if err != nil {
return
}
log.Debugf("dial: connected to host %s, tunnel: %s, connector: %s", addr, tunnelID, cid)
if node == ep.node {
ro.Redirect = ""
clientAddr := ctxvalue.ClientAddrFromContext(ctx)
var features []relay.Feature
af := &relay.AddrFeature{}
af.ParseFrom(string(clientAddr))
features = append(features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(addr)
features = append(features, af) // dst address
if _, err = (&relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
Features: features,
}).WriteTo(conn); err != nil {
conn.Close()
return nil, err
}
} else {
ro.Redirect = node
}
return conn, nil
}
func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
pStats := stats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
@@ -148,7 +215,7 @@ func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecord
ro.Time = time.Time{}
if err := ep.httpRoundTrip(ctx, conn, req, ro, &pStats, log); err != nil {
if err := ep.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), req, ro, &pStats, log); err != nil {
log.Error(err)
return err
}
@@ -169,7 +236,7 @@ func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecord
log.Trace(string(dump))
}
if err := ep.httpRoundTrip(ctx, conn, req, ro, &pStats, log); err != nil {
if err := ep.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), req, ro, &pStats, log); err != nil {
return err
}
}
@@ -180,7 +247,23 @@ func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriter, req *
*ro2 = *ro
ro = ro2
if sid := req.Header.Get(httpHeaderSID); sid != "" {
ro.SID = sid
} else {
req.Header.Set(httpHeaderSID, ro.SID)
}
req.Header.Set(httpHeaderForwardedNode, ro.Node)
host := req.Host
if _, port, _ := net.SplitHostPort(host); port == "" {
host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
}
ro.Host = host
ro.Time = time.Now()
log = log.WithFields(map[string]any{
"host": host,
})
log.Infof("%s <-> %s", ro.RemoteAddr, req.Host)
defer func() {
if err != nil {
@@ -241,20 +324,29 @@ func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriter, req *
var reqBody *xhttp.Body
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = defaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr))
ctx = contextWithRecorderObject(ctx, ro)
ctx = ctx_internal.ContextWithRecorderObject(ctx, ro)
ctx = ctxvalue.ContextWithLogger(ctx, log)
resp, err := ep.transport.RoundTrip(req.WithContext(ctx))
if reqBody != nil {
ro.HTTP.Request.Body = reqBody.Content()
ro.HTTP.Request.ContentLength = reqBody.Length()
}
if err != nil {
if errors.Is(err, ErrTunnelRoute) || errors.Is(err, ErrPrivateTunnel) {
res.StatusCode = http.StatusBadGateway
@@ -265,11 +357,6 @@ func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriter, req *
}
defer resp.Body.Close()
if reqBody != nil {
ro.HTTP.Request.Body = reqBody.Content()
ro.HTTP.Request.ContentLength = reqBody.Length()
}
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.Response.Header = resp.Header
ro.HTTP.Response.ContentLength = resp.ContentLength
@@ -280,28 +367,33 @@ func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriter, req *
}
if resp.StatusCode == http.StatusSwitchingProtocols {
return ep.handleUpgradeResponse(rw, req, resp)
return ep.handleUpgradeResponse(ctx, rw, req, resp, ro, log)
}
var respBody *xhttp.Body
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = defaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
if err = resp.Write(rw); err != nil {
return fmt.Errorf("write response: %v", err)
}
err = resp.Write(rw)
if respBody != nil {
ro.HTTP.Response.Body = respBody.Content()
ro.HTTP.Response.ContentLength = respBody.Length()
}
if err != nil {
return fmt.Errorf("write response: %v", err)
}
return
}
@@ -312,7 +404,7 @@ func upgradeType(h http.Header) string {
return h.Get("Upgrade")
}
func (ep *entrypoint) handleUpgradeResponse(rw io.ReadWriter, req *http.Request, res *http.Response) error {
func (ep *entrypoint) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
@@ -323,81 +415,136 @@ func (ep *entrypoint) handleUpgradeResponse(rw io.ReadWriter, req *http.Request,
if !ok {
return fmt.Errorf("internal error: 101 switching protocols response with non-writable body")
}
defer backConn.Close()
res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && ep.sniffingWebsocket {
return ep.sniffingWebsocketFrame(ctx, rw, backConn, ro, log)
}
return xnet.Transport(rw, backConn)
}
func (h *entrypoint) dial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
var tunnelID relay.TunnelID
if h.ingress != nil {
if rule := h.ingress.GetRule(ctx, addr); rule != nil {
tunnelID = parseTunnelID(rule.Endpoint)
func (ep *entrypoint) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := ep.websocketSampleRate
if sampleRate == 0 {
sampleRate = sniffing.DefaultSampleRate
}
if sampleRate < 0 {
sampleRate = math.MaxFloat64
}
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := ep.copyWebsocketFrame(cc, rw, buf, "client", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, ep.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}
}()
log := ctxvalue.LoggerFromContext(ctx)
if log == nil {
log = h.log
}
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
if tunnelID.IsZero() {
return nil, fmt.Errorf("%w %s", ErrTunnelRoute, addr)
}
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
if ro := recorderObjectFromContext(ctx); ro != nil {
ro.ClientID = tunnelID.String()
}
buf := &bytes.Buffer{}
for {
start := time.Now()
if tunnelID.IsPrivate() {
return nil, fmt.Errorf("%w: tunnel %s is private for host %s", ErrPrivateTunnel, tunnelID, addr)
}
if err := ep.copyWebsocketFrame(rw, cc, buf, "server", ro); err != nil {
errc <- err
return
}
log = log.WithFields(map[string]any{
"host": addr,
"tunnel": tunnelID.String(),
})
d := &Dialer{
node: h.node,
pool: h.pool,
sd: h.sd,
retry: 3,
timeout: 15 * time.Second,
log: log,
}
conn, node, cid, err := d.Dial(ctx, "tcp", tunnelID.String())
if err != nil {
return
}
log.Debugf("new connection to tunnel: %s, connector: %s", tunnelID, cid)
if node == h.node {
clientAddr := ctxvalue.ClientAddrFromContext(ctx)
var features []relay.Feature
af := &relay.AddrFeature{}
af.ParseFrom(string(clientAddr))
features = append(features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(addr)
features = append(features, af) // dst address
if _, err = (&relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
Features: features,
}).WriteTo(conn); err != nil {
conn.Close()
return nil, err
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, ep.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
<-errc
return nil
}
func (ep *entrypoint) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
return conn, nil
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (ep *entrypoint) handleConnect(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
+6 -7
View File
@@ -79,7 +79,7 @@ func (h *tunnelHandler) Init(md md.Metadata) (err error) {
h.log = h.options.Logger.WithFields(map[string]any{
"node": h.id,
})
h.pool = NewConnectorPool(h.id, h.md.sd)
h.pool = NewConnectorPool(h.id)
h.ep = &entrypoint{
node: h.id,
@@ -90,12 +90,15 @@ func (h *tunnelHandler) Init(md md.Metadata) (err error) {
log: h.log.WithFields(map[string]any{
"kind": "entrypoint",
}),
sniffingWebsocket: h.md.sniffingWebsocket,
websocketSampleRate: h.md.sniffingWebsocketSampleRate,
}
h.ep.transport = &http.Transport{
DialContext: h.ep.dial,
IdleConnTimeout: 30 * time.Second,
ResponseHeaderTimeout: h.md.entryPointReadTimeout,
DisableKeepAlives: !h.md.entryPointKeepalive,
DisableCompression: !h.md.entryPointCompression,
}
for _, ro := range h.options.Recorders {
@@ -117,7 +120,7 @@ func (h *tunnelHandler) Init(md md.Metadata) (err error) {
}
if limiter := h.options.Limiter; limiter != nil {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, h.md.limiterRefreshInterval, 60*time.Second)
}
return nil
@@ -317,11 +320,7 @@ func (h *tunnelHandler) observeStats(ctx context.Context) {
return
}
d := h.md.observePeriod
if d < time.Millisecond {
d = 5 * time.Second
}
ticker := time.NewTicker(d)
ticker := time.NewTicker(h.md.observePeriod)
defer ticker.Stop()
for {
+35 -16
View File
@@ -22,18 +22,22 @@ const (
type metadata struct {
readTimeout time.Duration
entryPoint string
entryPointID relay.TunnelID
entryPointProxyProtocol int
entryPointKeepalive bool
entryPointReadTimeout time.Duration
entryPoint string
entryPointID relay.TunnelID
entryPointProxyProtocol int
entryPointKeepalive bool
entryPointCompression bool
entryPointReadTimeout time.Duration
sniffingWebsocket bool
sniffingWebsocketSampleRate float64
directTunnel bool
tunnelTTL time.Duration
ingress ingress.Ingress
sd sd.SD
muxCfg *mux.Config
observePeriod time.Duration
directTunnel bool
tunnelTTL time.Duration
ingress ingress.Ingress
sd sd.SD
muxCfg *mux.Config
observePeriod time.Duration
limiterRefreshInterval time.Duration
}
func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) {
@@ -43,16 +47,17 @@ func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.entryPointID = parseTunnelID(mdutil.GetString(md, "entrypoint.id"))
h.md.entryPointProxyProtocol = mdutil.GetInt(md, "entrypoint.ProxyProtocol")
h.md.entryPointKeepalive = true
if mdutil.IsExists(md, "entrypoint.keepalive") {
h.md.entryPointKeepalive = mdutil.GetBool(md, "entrypoint.keepalive")
}
h.md.entryPointKeepalive = mdutil.GetBool(md, "entrypoint.keepalive")
h.md.entryPointCompression = mdutil.GetBool(md, "entrypoint.compression")
h.md.entryPointReadTimeout = mdutil.GetDuration(md, "entrypoint.readTimeout")
if h.md.entryPointReadTimeout <= 0 {
h.md.entryPointReadTimeout = 15 * time.Second
}
h.md.sniffingWebsocket = mdutil.GetBool(md, "sniffing.websocket")
h.md.sniffingWebsocketSampleRate = mdutil.GetFloat(md, "sniffing.websocket.sampleRate")
h.md.tunnelTTL = mdutil.GetDuration(md, "tunnel.ttl")
if h.md.tunnelTTL <= 0 {
h.md.tunnelTTL = defaultTTL
@@ -100,7 +105,21 @@ func (h *tunnelHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.muxCfg.MaxStreamBuffer = 1048576
}
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod")
h.md.observePeriod = mdutil.GetDuration(md, "observePeriod", "observer.observePeriod")
if h.md.observePeriod == 0 {
h.md.observePeriod = 5 * time.Second
}
if h.md.observePeriod < time.Second {
h.md.observePeriod = time.Second
}
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if h.md.limiterRefreshInterval == 0 {
h.md.limiterRefreshInterval = 30 * time.Second
}
if h.md.limiterRefreshInterval < time.Second {
h.md.limiterRefreshInterval = time.Second
}
return
}
+46 -34
View File
@@ -38,6 +38,7 @@ type Connector struct {
s *mux.Session
t time.Time
opts *ConnectorOptions
log logger.Logger
}
func NewConnector(id relay.ConnectorID, tid relay.TunnelID, node string, s *mux.Session, opts *ConnectorOptions) *Connector {
@@ -52,23 +53,30 @@ func NewConnector(id relay.ConnectorID, tid relay.TunnelID, node string, s *mux.
s: s,
t: time.Now(),
opts: opts,
log: logger.Default().WithFields(map[string]any{
"node": node,
"tunnel": tid.String(),
"connector": id.String(),
}),
}
go c.accept()
go c.waitClose()
return c
}
func (c *Connector) accept() {
func (c *Connector) waitClose() {
for {
conn, err := c.s.Accept()
if err != nil {
logger.Default().Errorf("connector %s: %v", c.id, err)
c.s.Close()
c.log.Errorf("connector %s: %v", c.id, err)
c.Close()
if c.opts.sd != nil {
c.opts.sd.Deregister(context.Background(), &sd.Service{
ID: c.id.String(),
Name: c.tid.String(),
Node: c.node,
})
c.log.Debugf("deregister connector %s from sd", c.id.String())
}
return
}
@@ -132,7 +140,6 @@ type Tunnel struct {
t time.Time
close chan struct{}
mu sync.RWMutex
sd sd.SD
ttl time.Duration
}
@@ -151,10 +158,6 @@ func NewTunnel(node string, tid relay.TunnelID, ttl time.Duration) *Tunnel {
return t
}
func (t *Tunnel) WithSD(sd sd.SD) {
t.sd = sd
}
func (t *Tunnel) ID() relay.TunnelID {
return t.id
}
@@ -175,6 +178,9 @@ func (t *Tunnel) GetConnector(network string) *Connector {
defer t.mu.RUnlock()
if len(t.connectors) == 1 {
if t.connectors[0].IsClosed() {
return nil
}
return t.connectors[0]
}
@@ -250,23 +256,17 @@ func (t *Tunnel) clean() {
t.mu.Unlock()
break
}
var connectors []*Connector
for _, c := range t.connectors {
if c.IsClosed() {
logger.Default().Debugf("remove tunnel: %s, connector: %s", t.id, c.id)
if t.sd != nil {
t.sd.Deregister(context.Background(), &sd.Service{
ID: c.id.String(),
Name: t.id.String(),
Node: t.node,
})
}
c.log.Debugf("remove connector: %s %s", t.id, c.id)
continue
}
connectors = append(connectors, c)
if t.sd != nil {
t.sd.Renew(context.Background(), &sd.Service{
if c.opts.sd != nil {
c.opts.sd.Renew(context.Background(), &sd.Service{
ID: c.id.String(),
Name: t.id.String(),
Node: t.node,
@@ -277,6 +277,7 @@ func (t *Tunnel) clean() {
t.connectors = connectors
}
t.mu.Unlock()
case <-t.close:
return
}
@@ -285,18 +286,21 @@ func (t *Tunnel) clean() {
type ConnectorPool struct {
node string
sd sd.SD
tunnels map[string]*Tunnel
mu sync.RWMutex
cancel context.CancelFunc
}
func NewConnectorPool(node string, sd sd.SD) *ConnectorPool {
func NewConnectorPool(node string) *ConnectorPool {
ctx, cancel := context.WithCancel(context.Background())
p := &ConnectorPool{
node: node,
sd: sd,
tunnels: make(map[string]*Tunnel),
cancel: cancel,
}
go p.closeIdles()
go p.closeIdles(ctx)
return p
}
@@ -309,8 +313,6 @@ func (p *ConnectorPool) Add(tid relay.TunnelID, c *Connector, ttl time.Duration)
t := p.tunnels[s]
if t == nil {
t = NewTunnel(p.node, tid, ttl)
t.WithSD(p.sd)
p.tunnels[s] = t
}
t.AddConnector(c)
@@ -337,6 +339,10 @@ func (p *ConnectorPool) Close() error {
return nil
}
if p.cancel != nil {
p.cancel()
}
p.mu.Lock()
defer p.mu.Unlock()
@@ -348,19 +354,25 @@ func (p *ConnectorPool) Close() error {
return nil
}
func (p *ConnectorPool) closeIdles() {
ticker := time.NewTicker(1 * time.Hour)
func (p *ConnectorPool) closeIdles(ctx context.Context) {
ticker := time.NewTicker(15 * time.Minute)
defer ticker.Stop()
for range ticker.C {
p.mu.Lock()
for k, v := range p.tunnels {
if v.CloseOnIdle() {
delete(p.tunnels, k)
logger.Default().Debugf("remove idle tunnel: %s", k)
for {
select {
case <-ticker.C:
p.mu.Lock()
for k, v := range p.tunnels {
if v.CloseOnIdle() {
delete(p.tunnels, k)
logger.Default().Debugf("remove idle tunnel: %s", k)
}
}
p.mu.Unlock()
case <-ctx.Done():
return
}
p.mu.Unlock()
}
}
+1
View File
@@ -126,6 +126,7 @@ func (h *unixHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
log = log.WithFields(map[string]any{
"node": target.Name,
"dst": target.Addr,
"host": target.Addr,
})
ro.Host = target.Addr
+77 -107
View File
@@ -14,6 +14,7 @@ import (
"github.com/go-gost/core/chain"
"github.com/go-gost/core/hop"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/routing"
"github.com/go-gost/core/selector"
"github.com/go-gost/x/config"
node_parser "github.com/go-gost/x/config/parsing/node"
@@ -132,18 +133,16 @@ func (p *chainHop) Select(ctx context.Context, opts ...hop.SelectOption) *chain.
opt(&options)
}
log := p.options.logger
// hop level bypass
if p.options.bypass != nil &&
p.options.bypass.Contains(ctx, options.Network, options.Addr, bypass.WithHostOpton(options.Host)) {
return nil
}
filters := p.filterByHost(options.Host, p.Nodes()...)
filters = p.filterByProtocol(options.Protocol, filters...)
filters = p.filterByPath(options.Path, filters...)
var nodes []*chain.Node
for _, node := range filters {
for _, node := range p.Nodes() {
if node == nil {
continue
}
@@ -153,11 +152,42 @@ func (p *chainHop) Select(ctx context.Context, opts ...hop.SelectOption) *chain.
continue
}
if matcher := node.Options().Matcher; matcher != nil {
req := routing.Request{
ClientIP: options.ClientIP,
Host: options.Host,
Protocol: options.Protocol,
Method: options.Method,
Path: options.Path,
Query: options.Query,
Header: options.Header,
}
if !matcher.Match(&req) {
continue
}
log.Debugf("node %s match request %s %s, priority %d", node.Name, req.Protocol, req.Host, node.Options().Priority)
} else {
if !p.isEligible(node, &options) {
continue
}
}
nodes = append(nodes, node)
}
if len(nodes) == 0 {
return nil
}
if len(nodes) == 1 {
return nodes[0]
}
sort.Slice(nodes, func(i, j int) bool {
return nodes[i].Options().Priority > nodes[j].Options().Priority
})
if nodes[0].Options().Priority > 0 {
return nodes[0]
}
if s := p.options.selector; s != nil {
return s.Select(ctx, nodes...)
@@ -165,126 +195,66 @@ func (p *chainHop) Select(ctx context.Context, opts ...hop.SelectOption) *chain.
return nodes[0]
}
func (p *chainHop) filterByHost(host string, nodes ...*chain.Node) (filters []*chain.Node) {
if host == "" || len(nodes) == 0 {
return nodes
func (p *chainHop) isEligible(node *chain.Node, opts *hop.SelectOptions) bool {
if node == nil {
return false
}
if node.Options().Filter == nil {
return true
}
if !p.checkHost(opts.Host, node) || !p.checkProtocol(opts.Protocol, node) || !p.checkPath(opts.Path, node) {
return false
}
return true
}
func (p *chainHop) checkHost(host string, node *chain.Node) bool {
var vhost string
if filter := node.Options().Filter; filter != nil {
vhost = filter.Host
}
if vhost == "" { // backup node
return true
}
if host == "" {
return false
}
if v, _, _ := net.SplitHostPort(host); v != "" {
host = v
}
p.options.logger.Debugf("filter by host: %s", host)
found := false
for _, node := range nodes {
if node == nil {
continue
}
var vhost string
if filter := node.Options().Filter; filter != nil {
vhost = filter.Host
}
if vhost == "" { // backup node
if !found {
filters = append(filters, node)
}
continue
}
if vhost == host ||
vhost[0] == '.' && strings.HasSuffix(host, vhost[1:]) {
if !found { // clear all backup nodes when matched node found
filters = nil
}
filters = append(filters, node)
found = true
continue
}
if vhost == host || vhost[0] == '.' && strings.HasSuffix(host, vhost[1:]) {
return true
}
return
return false
}
func (p *chainHop) filterByProtocol(protocol string, nodes ...*chain.Node) (filters []*chain.Node) {
if protocol == "" || len(nodes) == 0 {
return nodes
func (p *chainHop) checkProtocol(protocol string, node *chain.Node) bool {
var prot string
if filter := node.Options().Filter; filter != nil {
prot = filter.Protocol
}
p.options.logger.Debugf("filter by protocol: %s", protocol)
found := false
for _, node := range nodes {
if node == nil {
continue
}
var prot string
if filter := node.Options().Filter; filter != nil {
prot = filter.Protocol
}
if prot == "" {
if !found {
filters = append(filters, node)
}
continue
}
if prot == protocol {
if !found {
filters = nil
}
filters = append(filters, node)
found = true
continue
}
if prot == "" {
return true
}
return
return prot == protocol
}
func (p *chainHop) filterByPath(path string, nodes ...*chain.Node) (filters []*chain.Node) {
if path == "" || len(nodes) == 0 {
return nodes
func (p *chainHop) checkPath(path string, node *chain.Node) bool {
var pathFilter string
if filter := node.Options().Filter; filter != nil {
pathFilter = filter.Path
}
p.options.logger.Debugf("filter by path: %s", path)
sort.SliceStable(nodes, func(i, j int) bool {
filter1 := nodes[i].Options().Filter
if filter1 == nil {
return false
}
filter2 := nodes[j].Options().Filter
if filter2 == nil {
return true
}
return len(filter1.Path) > len(filter2.Path)
})
found := false
for _, node := range nodes {
var pathFilter string
if filter := node.Options().Filter; filter != nil {
pathFilter = filter.Path
}
if pathFilter == "" {
if !found {
filters = append(filters, node)
}
continue
}
if strings.HasPrefix(path, pathFilter) {
if !found {
filters = nil
}
filters = append(filters, node)
break
}
if pathFilter == "" {
return true
}
return
return strings.HasPrefix(path, pathFilter)
}
func (p *chainHop) periodReload(ctx context.Context) error {
+22
View File
@@ -0,0 +1,22 @@
package ctx
import (
"context"
xrecorder "github.com/go-gost/x/recorder"
)
type recorderObjectCtxKey struct{}
var (
ctxKeyRecorderObject = &recorderObjectCtxKey{}
)
func ContextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
return context.WithValue(ctx, ctxKeyRecorderObject, ro)
}
func RecorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
v, _ := ctx.Value(ctxKeyRecorderObject).(*xrecorder.HandlerRecorderObject)
return v
}
+1 -2
View File
@@ -56,7 +56,6 @@ func NewGRPCConn(addr string, opts *Options) (*grpc.ClientConn, error) {
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
}),
grpc.FailOnNonTempDialError(true),
}
if opts.TLSConfig != nil {
grpcOpts = append(grpcOpts,
@@ -69,7 +68,7 @@ func NewGRPCConn(addr string, opts *Options) (*grpc.ClientConn, error) {
if opts.Token != "" {
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(&rpcCredentials{token: opts.Token}))
}
return grpc.Dial(addr, grpcOpts...)
return grpc.NewClient(addr, grpcOpts...)
}
type rpcCredentials struct {
+226 -44
View File
@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"math"
"net"
"net/http"
"net/http/httputil"
@@ -32,14 +33,12 @@ import (
xhttp "github.com/go-gost/x/internal/net/http"
"github.com/go-gost/x/internal/util/sniffing"
tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
)
const (
// Default max body size to record.
DefaultBodySize = 1024 * 1024 // 1MB
"golang.org/x/time/rate"
)
var (
@@ -102,6 +101,9 @@ func WithLog(log logger.Logger) HandleOption {
}
type Sniffer struct {
Websocket bool
WebsocketSampleRate float64
Recorder recorder.Recorder
RecorderOptions *recorder.Options
@@ -177,7 +179,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
ro.Time = time.Time{}
shouldClose, err := h.httpRoundTrip(ctx, conn, cc, node, req, &pStats, &ho)
shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, node, req, &pStats, &ho)
if err != nil || shouldClose {
return err
}
@@ -198,7 +200,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
log.Trace(string(dump))
}
if shouldClose, err := h.httpRoundTrip(ctx, conn, cc, node, req, &pStats, &ho); err != nil || shouldClose {
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, node, req, &pStats, &ho); err != nil || shouldClose {
return err
}
}
@@ -249,9 +251,13 @@ func (h *Sniffer) dial(ctx context.Context, conn net.Conn, req *http.Request, ho
}
if ho.Hop != nil {
node = ho.Hop.Select(ctx,
hop.HostSelectOption(host),
hop.ClientIPSelectOption(net.ParseIP(ro.ClientIP)),
hop.ProtocolSelectOption(sniffing.ProtoHTTP),
hop.HostSelectOption(host),
hop.MethodSelectOption(req.Method),
hop.PathSelectOption(req.URL.Path),
hop.QuerySelectOption(req.URL.Query()),
hop.HeaderSelectOption(req.Header),
)
}
if node == nil {
@@ -398,11 +404,8 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
}
}
if !ho.HTTPKeepalive {
req.Header.Set("Connection", "close")
}
var bodyRewrites []chain.HTTPBodyRewriteSettings
var responseHeader map[string]string
var respBodyRewrites []chain.HTTPBodyRewriteSettings
if httpSettings := node.Options().HTTP; httpSettings != nil {
if auther := httpSettings.Auther; auther != nil {
username, password, _ := req.BasicAuth()
@@ -422,7 +425,7 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
if httpSettings.Host != "" {
req.Host = httpSettings.Host
}
for k, v := range httpSettings.Header {
for k, v := range httpSettings.RequestHeader {
req.Header.Set(k, v)
}
@@ -435,31 +438,37 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
}
}
bodyRewrites = httpSettings.RewriteBody
responseHeader = httpSettings.ResponseHeader
respBodyRewrites = httpSettings.RewriteResponseBody
}
var reqBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
if err = req.Write(cc); err != nil {
res.Write(rw)
return
}
err = req.Write(cc)
if reqBody != nil {
ro.HTTP.Request.Body = reqBody.Content()
ro.HTTP.Request.ContentLength = reqBody.Length()
}
if err != nil {
res.Write(rw)
return
}
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
resp, err := http.ReadResponse(bufio.NewReader(cc), req)
if err != nil {
@@ -470,6 +479,15 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
defer resp.Body.Close()
xio.SetReadDeadline(cc, time.Time{})
if len(responseHeader) > 0 {
if resp.Header == nil {
resp.Header = http.Header{}
}
for k, v := range responseHeader {
resp.Header.Set(k, v)
}
}
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.Response.Header = resp.Header
ro.HTTP.Response.ContentLength = resp.ContentLength
@@ -479,6 +497,11 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
log.Trace(string(dump))
}
if resp.StatusCode == http.StatusSwitchingProtocols {
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
return
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if !resp.Close {
@@ -488,39 +511,192 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, node
resp.ProtoMinor = req.ProtoMinor
}
if err = h.rewriteBody(resp, bodyRewrites...); err != nil {
if !ho.HTTPKeepalive {
resp.Header.Set("Connection", "close")
}
if err = h.rewriteRespBody(resp, respBodyRewrites...); err != nil {
log.Errorf("rewrite body: %v", err)
return
}
var respBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
if err = resp.Write(rw); err != nil {
log.Errorf("write response: %v", err)
return
}
err = resp.Write(rw)
if respBody != nil {
ro.HTTP.Response.Body = respBody.Content()
ro.HTTP.Response.ContentLength = respBody.Length()
}
if resp.StatusCode == http.StatusSwitchingProtocols {
xnet.Transport(rw, cc)
if err != nil {
log.Errorf("write response: %v", err)
return
}
return resp.Close, nil
if resp.ContentLength >= 0 {
close = resp.Close
}
return
}
func (h *Sniffer) rewriteBody(resp *http.Response, rewrites ...chain.HTTPBodyRewriteSettings) error {
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
}
res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.Websocket {
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
}
return xnet.Transport(rw, cc)
}
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.WebsocketSampleRate
if sampleRate == 0 {
sampleRate = sniffing.DefaultSampleRate
}
if sampleRate < 0 {
sampleRate = math.MaxFloat64
}
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(cc, rw, buf, "client", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(rw, cc, buf, "server", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
<-errc
return nil
}
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (h *Sniffer) rewriteRespBody(resp *http.Response, rewrites ...chain.HTTPBodyRewriteSettings) error {
if resp == nil || len(rewrites) == 0 || resp.ContentLength <= 0 {
return nil
}
@@ -914,11 +1090,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var reqBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
@@ -949,11 +1128,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var respBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = sniffing.DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > sniffing.MaxBodySize {
bodySize = sniffing.MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
+13 -13
View File
@@ -9,22 +9,22 @@ import (
)
type cachedTrafficLimiter struct {
inLimits *Cache
outLimits *Cache
limiter traffic.TrafficLimiter
ttl time.Duration
inLimits *Cache
outLimits *Cache
limiter traffic.TrafficLimiter
refreshInterval time.Duration
}
func NewCachedTrafficLimiter(limiter traffic.TrafficLimiter, ttl time.Duration, cleanupInterval time.Duration) traffic.TrafficLimiter {
func NewCachedTrafficLimiter(limiter traffic.TrafficLimiter, refreshInterval time.Duration, cleanupInterval time.Duration) traffic.TrafficLimiter {
if limiter == nil {
return nil
}
lim := &cachedTrafficLimiter{
inLimits: NewCache(cleanupInterval),
outLimits: NewCache(cleanupInterval),
limiter: limiter,
ttl: ttl,
inLimits: NewCache(cleanupInterval),
outLimits: NewCache(cleanupInterval),
limiter: limiter,
refreshInterval: refreshInterval,
}
return lim
}
@@ -45,11 +45,11 @@ func (p *cachedTrafficLimiter) In(ctx context.Context, key string, opts ...limit
limNew = lim
}
if item == nil || !p.equal(lim, limNew) {
p.inLimits.Set(key, NewItem(limNew, p.ttl))
p.inLimits.Set(key, NewItem(limNew, p.refreshInterval))
return limNew
}
p.inLimits.Set(key, NewItem(lim, p.ttl))
p.inLimits.Set(key, NewItem(lim, p.refreshInterval))
return lim
}
@@ -70,11 +70,11 @@ func (p *cachedTrafficLimiter) Out(ctx context.Context, key string, opts ...limi
limNew = lim
}
if item == nil || !p.equal(lim, limNew) {
p.outLimits.Set(key, NewItem(limNew, p.ttl))
p.outLimits.Set(key, NewItem(limNew, p.refreshInterval))
return limNew
}
p.outLimits.Set(key, NewItem(lim, p.ttl))
p.outLimits.Set(key, NewItem(lim, p.refreshInterval))
return lim
}
+208 -31
View File
@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"math"
"net"
"net/http"
"net/http/httputil"
@@ -28,14 +29,21 @@ import (
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
"golang.org/x/time/rate"
)
const (
// Default max body size to record.
DefaultBodySize = 1024 * 1024 // 1MB
// DefaultBodySize is the default HTTP body or websocket frame size to record.
DefaultBodySize = 64 * 1024 // 64KB
// MaxBodySize is the maximum HTTP body or websocket frame size to record.
MaxBodySize = 1024 * 1024 // 1MB
// DeafultSampleRate is the default websocket sample rate (samples per second).
DefaultSampleRate = 10.0
)
var (
@@ -84,6 +92,9 @@ func WithLog(log logger.Logger) HandleOption {
}
type Sniffer struct {
Websocket bool
WebsocketSampleRate float64
Recorder recorder.Recorder
RecorderOptions *recorder.Options
@@ -176,7 +187,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
ro.Time = time.Time{}
shouldClose, err := h.httpRoundTrip(ctx, conn, cc, req, ro, &pStats, log)
shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log)
if err != nil || shouldClose {
return err
}
@@ -197,7 +208,7 @@ func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleO
log.Trace(string(dump))
}
if shouldClose, err := h.httpRoundTrip(ctx, conn, cc, req, ro, &pStats, log); err != nil || shouldClose {
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log); err != nil || shouldClose {
return err
}
}
@@ -302,28 +313,33 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
var reqBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
if err = req.Write(cc); err != nil {
return
}
err = req.Write(cc)
if reqBody != nil {
ro.HTTP.Request.Body = reqBody.Content()
ro.HTTP.Request.ContentLength = reqBody.Length()
}
if err != nil {
return
}
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
resp, err := http.ReadResponse(bufio.NewReader(cc), req)
if err != nil {
log.Errorf("read response: %v", err)
err = fmt.Errorf("read response: %w", err)
return
}
defer resp.Body.Close()
@@ -338,6 +354,11 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
log.Trace(string(dump))
}
if resp.StatusCode == http.StatusSwitchingProtocols {
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
return
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if !resp.Close {
@@ -349,29 +370,178 @@ func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *
var respBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
if err = resp.Write(rw); err != nil {
log.Errorf("write response: %v", err)
return
}
err = resp.Write(rw)
if respBody != nil {
ro.HTTP.Response.Body = respBody.Content()
ro.HTTP.Response.ContentLength = respBody.Length()
}
if resp.StatusCode == http.StatusSwitchingProtocols {
xnet.Transport(rw, cc)
if err != nil {
err = fmt.Errorf("write response: %w", err)
return
}
return resp.Close, nil
if resp.ContentLength >= 0 {
close = resp.Close
}
return
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
}
res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.Websocket {
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
}
return xnet.Transport(rw, cc)
}
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.WebsocketSampleRate
if sampleRate == 0 {
sampleRate = DefaultSampleRate
}
if sampleRate < 0 {
sampleRate = math.MaxFloat64
}
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(cc, rw, buf, "client", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(rw, cc, buf, "server", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
<-errc
return nil
}
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (h *Sniffer) HandleTLS(ctx context.Context, conn net.Conn, opts ...HandleOption) error {
@@ -644,11 +814,15 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var reqBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
reqBody = xhttp.NewBody(req.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
@@ -679,11 +853,14 @@ func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var respBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
maxSize := opts.MaxBodySize
if maxSize <= 0 {
maxSize = DefaultBodySize
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
respBody = xhttp.NewBody(resp.Body, maxSize)
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
+196
View File
@@ -0,0 +1,196 @@
package ws
import (
"encoding/binary"
"fmt"
"io"
"math"
)
// OpCode represents a WebSocket opcode.
type OpCode int
// https://tools.ietf.org/html/rfc6455#section-11.8.
const (
OpContinuation OpCode = iota
OpText
OpBinary
// 3 - 7 are reserved for further non-control frames.
_
_
_
_
_
OpClose
OpPing
OpPong
// 11-16 are reserved for further control frames.
)
// FrameHeader represents a WebSocket frame header.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
type FrameHeader struct {
Fin bool
Rsv1 bool
Rsv2 bool
Rsv3 bool
OpCode OpCode
PayloadLength int64
Masked bool
MaskKey uint32
}
// ReadFrom reads a header from the reader.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
func (h *FrameHeader) ReadFrom(r io.Reader) (n int64, err error) {
var buf [8]byte
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
nn, err := io.ReadFull(r, buf[:2])
n += int64(n)
if err != nil {
return
}
b := buf[0]
h.Fin = b&(1<<7) != 0
h.Rsv1 = b&(1<<6) != 0
h.Rsv2 = b&(1<<5) != 0
h.Rsv3 = b&(1<<4) != 0
h.OpCode = OpCode(b & 0xf)
b = buf[1]
h.Masked = b&(1<<7) != 0
payloadLength := b &^ (1 << 7)
switch {
case payloadLength < 126:
h.PayloadLength = int64(payloadLength)
case payloadLength == 126:
nn, err = io.ReadFull(r, buf[:2])
h.PayloadLength = int64(binary.BigEndian.Uint16(buf[:]))
case payloadLength == 127:
nn, err = io.ReadFull(r, buf[:])
h.PayloadLength = int64(binary.BigEndian.Uint64(buf[:]))
}
n += int64(nn)
if err != nil {
return
}
if h.PayloadLength < 0 {
err = fmt.Errorf("received negative payload length: %v", h.PayloadLength)
return
}
if h.Masked {
nn, err = io.ReadFull(r, buf[:4])
n += int64(nn)
if err != nil {
return
}
h.MaskKey = binary.LittleEndian.Uint32(buf[:])
}
return
}
func (h FrameHeader) Length() int {
n := 2
switch {
case h.PayloadLength > math.MaxUint16:
n += 8
case h.PayloadLength > 125:
n += 2
}
if h.Masked {
n += 4
}
return n
}
func (h *FrameHeader) WriteTo(w io.Writer) (n int64, err error) {
var buf [14]byte
pos := 0
var b byte
if h.Fin {
b |= 1 << 7
}
if h.Rsv1 {
b |= 1 << 6
}
if h.Rsv2 {
b |= 1 << 5
}
if h.Rsv3 {
b |= 1 << 4
}
b |= byte(h.OpCode)
buf[0] = b
lengthByte := byte(0)
if h.Masked {
lengthByte |= 1 << 7
}
switch {
case h.PayloadLength > math.MaxUint16:
lengthByte |= 127
case h.PayloadLength > 125:
lengthByte |= 126
case h.PayloadLength >= 0:
lengthByte |= byte(h.PayloadLength)
}
buf[1] = lengthByte
pos = 2
switch {
case h.PayloadLength > math.MaxUint16:
binary.BigEndian.PutUint64(buf[2:], uint64(h.PayloadLength))
pos += 8
case h.PayloadLength > 125:
binary.BigEndian.PutUint16(buf[2:], uint16(h.PayloadLength))
pos += 2
}
if h.Masked {
binary.LittleEndian.PutUint32(buf[pos:], h.MaskKey)
pos += 4
}
nn, err := w.Write(buf[:pos])
n = int64(nn)
return
}
type Frame struct {
Header FrameHeader
Data io.Reader
}
func (fr *Frame) ReadFrom(r io.Reader) (n int64, err error) {
if n, err = fr.Header.ReadFrom(r); err != nil {
return
}
fr.Data = io.LimitReader(r, fr.Header.PayloadLength)
return
}
func (fr *Frame) WriteTo(w io.Writer) (n int64, err error) {
n, err = fr.Header.WriteTo(w)
if err != nil {
return
}
nn, err := io.Copy(w, fr.Data)
n += nn
return
}
+5 -5
View File
@@ -83,7 +83,7 @@ func (l *dnsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
l.server = &dnsServer{
@@ -124,7 +124,7 @@ func (l *dnsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
l.server = &dnsServer{
@@ -166,7 +166,7 @@ func (l *dnsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
l.server = &dohServer{
@@ -205,7 +205,7 @@ func (l *dnsListener) Init(md md.Metadata) (err error) {
pc = limiter_wrapper.WrapPacketConn(
pc,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
"",
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
@@ -251,7 +251,7 @@ func (l *dnsListener) Accept() (conn net.Conn, err error) {
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+15 -6
View File
@@ -12,12 +12,13 @@ const (
)
type metadata struct {
mode string
readBufferSize int
readTimeout time.Duration
writeTimeout time.Duration
backlog int
mptcp bool
mode string
readBufferSize int
readTimeout time.Duration
writeTimeout time.Duration
backlog int
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *dnsListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -40,5 +41,13 @@ func (l *dnsListener) parseMetadata(md mdata.Metadata) (err error) {
}
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -87,7 +87,7 @@ func (l *dtlsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -104,7 +104,7 @@ func (l *dtlsListener) Accept() (conn net.Conn, err error) {
conn = xdtls.Conn(c, l.md.bufferSize)
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+12 -3
View File
@@ -12,9 +12,10 @@ const (
)
type metadata struct {
mtu int
bufferSize int
flightInterval time.Duration
mtu int
bufferSize int
flightInterval time.Duration
limiterRefreshInterval time.Duration
}
func (l *dtlsListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -26,5 +27,13 @@ func (l *dtlsListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.flightInterval = mdutil.GetDuration(md, "dtls.flightInterval", "flightInterval")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return nil
}
+2 -2
View File
@@ -60,7 +60,7 @@ func (l *ftcpListener) Init(md md.Metadata) (err error) {
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapPacketConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
"",
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
@@ -88,7 +88,7 @@ func (l *ftcpListener) Accept() (conn net.Conn, err error) {
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+10 -1
View File
@@ -15,7 +15,8 @@ const (
)
type metadata struct {
ttl time.Duration
ttl time.Duration
limiterRefreshInterval time.Duration
readBufferSize int
readQueueSize int
@@ -49,5 +50,13 @@ func (l *ftcpListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.backlog = defaultBacklog
}
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -74,7 +74,7 @@ func (l *grpcListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -124,7 +124,7 @@ func (l *grpcListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+9
View File
@@ -22,6 +22,7 @@ type metadata struct {
keepalivePermitWithoutStream bool
keepaliveMaxConnectionIdle time.Duration
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *grpcListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -53,5 +54,13 @@ func (l *grpcListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.mptcp = mdutil.GetBool(md, "mptcp")
}
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -95,7 +95,7 @@ func (l *h2Listener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -130,7 +130,7 @@ func (l *h2Listener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+14 -3
View File
@@ -1,6 +1,8 @@
package h2
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
@@ -10,9 +12,10 @@ const (
)
type metadata struct {
path string
backlog int
mptcp bool
path string
backlog int
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *h2Listener) parseMetadata(md mdata.Metadata) (err error) {
@@ -29,5 +32,13 @@ func (l *h2Listener) parseMetadata(md mdata.Metadata) (err error) {
l.md.path = mdutil.GetString(md, path)
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -84,7 +84,7 @@ func (l *http2Listener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -111,7 +111,7 @@ func (l *http2Listener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+13 -2
View File
@@ -1,6 +1,8 @@
package http2
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
@@ -10,8 +12,9 @@ const (
)
type metadata struct {
backlog int
mptcp bool
backlog int
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *http2Listener) parseMetadata(md mdata.Metadata) (err error) {
@@ -25,5 +28,13 @@ func (l *http2Listener) parseMetadata(md mdata.Metadata) (err error) {
}
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -62,7 +62,7 @@ func (l *http3Listener) Init(md md.Metadata) (err error) {
KeepAlivePeriod: l.md.keepAlivePeriod,
HandshakeIdleTimeout: l.md.handshakeTimeout,
MaxIdleTimeout: l.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
},
MaxIncomingStreams: int64(l.md.maxStreams),
@@ -93,7 +93,7 @@ func (l *http3Listener) Accept() (conn net.Conn, err error) {
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+10
View File
@@ -26,6 +26,8 @@ type metadata struct {
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
maxStreams int
limiterRefreshInterval time.Duration
}
func (l *http3Listener) parseMetadata(md mdata.Metadata) (err error) {
@@ -67,5 +69,13 @@ func (l *http3Listener) parseMetadata(md mdata.Metadata) (err error) {
l.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
l.md.maxStreams = mdutil.GetInt(md, maxStreams)
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+1 -1
View File
@@ -67,7 +67,7 @@ func (l *http3Listener) Init(md md.Metadata) (err error) {
KeepAlivePeriod: l.md.keepAlivePeriod,
HandshakeIdleTimeout: l.md.handshakeTimeout,
MaxIdleTimeout: l.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
},
MaxIncomingStreams: int64(l.md.maxStreams),
+2 -2
View File
@@ -79,7 +79,7 @@ func (l *wtListener) Init(md md.Metadata) (err error) {
pc = admission.WrapPacketConn(l.options.Admission, pc)
pc = limiter_wrapper.WrapPacketConn(
pc,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
"",
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
@@ -130,7 +130,7 @@ func (l *wtListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+10
View File
@@ -21,6 +21,8 @@ type metadata struct {
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
maxStreams int
limiterRefreshInterval time.Duration
}
func (l *wtListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -54,5 +56,13 @@ func (l *wtListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
l.md.maxStreams = mdutil.GetInt(md, maxStreams)
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+3 -3
View File
@@ -83,7 +83,7 @@ func (l *icmpListener) Init(md md.Metadata) (err error) {
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapPacketConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
"",
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
@@ -94,7 +94,7 @@ func (l *icmpListener) Init(md md.Metadata) (err error) {
KeepAlivePeriod: l.md.keepAlivePeriod,
HandshakeIdleTimeout: l.md.handshakeTimeout,
MaxIdleTimeout: l.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
quic.Version2,
},
@@ -123,7 +123,7 @@ func (l *icmpListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+10 -1
View File
@@ -16,7 +16,8 @@ type metadata struct {
handshakeTimeout time.Duration
maxIdleTimeout time.Duration
backlog int
backlog int
limiterRefreshInterval time.Duration
}
func (l *icmpListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -34,5 +35,13 @@ func (l *icmpListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.handshakeTimeout = mdutil.GetDuration(md, "handshakeTimeout")
l.md.maxIdleTimeout = mdutil.GetDuration(md, "maxIdleTimeout")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -82,7 +82,7 @@ func (l *kcpListener) Init(md md.Metadata) (err error) {
conn = admission.WrapUDPConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapUDPConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
"",
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
@@ -124,7 +124,7 @@ func (l *kcpListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+13 -3
View File
@@ -2,10 +2,11 @@ package kcp
import (
"encoding/json"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
kcp_util "github.com/go-gost/x/internal/util/kcp"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
@@ -13,8 +14,9 @@ const (
)
type metadata struct {
config *kcp_util.Config
backlog int
config *kcp_util.Config
backlog int
limiterRefreshInterval time.Duration
}
func (l *kcpListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -64,5 +66,13 @@ func (l *kcpListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.backlog = defaultBacklog
}
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -74,7 +74,7 @@ func (l *mtcpListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
l.ln = ln
@@ -101,7 +101,7 @@ func (l *mtcpListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+16 -4
View File
@@ -1,9 +1,11 @@
package mtcp
import (
"time"
md "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
@@ -11,9 +13,10 @@ const (
)
type metadata struct {
mptcp bool
muxCfg *mux.Config
backlog int
mptcp bool
muxCfg *mux.Config
backlog int
limiterRefreshInterval time.Duration
}
func (l *mtcpListener) parseMetadata(md md.Metadata) (err error) {
@@ -36,5 +39,14 @@ func (l *mtcpListener) parseMetadata(md md.Metadata) (err error) {
if l.md.backlog <= 0 {
l.md.backlog = defaultBacklog
}
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -73,7 +73,7 @@ func (l *mtlsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
l.Listener = tls.NewListener(ln, l.options.TLSConfig)
@@ -92,7 +92,7 @@ func (l *mtlsListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+15 -4
View File
@@ -1,9 +1,11 @@
package mtls
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
@@ -11,9 +13,10 @@ const (
)
type metadata struct {
muxCfg *mux.Config
backlog int
mptcp bool
muxCfg *mux.Config
backlog int
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *mtlsListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -33,5 +36,13 @@ func (l *mtlsListener) parseMetadata(md mdata.Metadata) (err error) {
}
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -115,7 +115,7 @@ func (l *mwsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -142,7 +142,7 @@ func (l *mwsListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+11 -2
View File
@@ -5,8 +5,8 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
@@ -27,7 +27,8 @@ type metadata struct {
muxCfg *mux.Config
mptcp bool
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *mwsListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -67,5 +68,13 @@ func (l *mwsListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -68,7 +68,7 @@ func (l *obfsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -84,7 +84,7 @@ func (l *obfsListener) Accept() (net.Conn, error) {
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+13 -2
View File
@@ -2,14 +2,16 @@ package http
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
header http.Header
mptcp bool
header http.Header
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *obfsListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -26,5 +28,14 @@ func (l *obfsListener) parseMetadata(md mdata.Metadata) (err error) {
}
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -68,7 +68,7 @@ func (l *obfsListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -84,7 +84,7 @@ func (l *obfsListener) Accept() (net.Conn, error) {
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+13 -1
View File
@@ -1,15 +1,27 @@
package tls
import (
"time"
md "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
mptcp bool
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *obfsListener) parseMetadata(md md.Metadata) (err error) {
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+1 -1
View File
@@ -100,7 +100,7 @@ func (l *phtListener) Accept() (conn net.Conn, err error) {
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+16 -5
View File
@@ -2,6 +2,7 @@ package pht
import (
"strings"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
@@ -15,11 +16,12 @@ const (
)
type metadata struct {
authorizePath string
pushPath string
pullPath string
backlog int
mptcp bool
authorizePath string
pushPath string
pullPath string
backlog int
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *phtListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -50,5 +52,14 @@ func (l *phtListener) parseMetadata(md mdata.Metadata) (err error) {
}
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+3 -3
View File
@@ -78,7 +78,7 @@ func (l *quicListener) Init(md md.Metadata) (err error) {
conn = admission.WrapPacketConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapPacketConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
"",
limiter.ScopeOption(limiter.ScopeService),
limiter.ServiceOption(l.options.Service),
@@ -89,7 +89,7 @@ func (l *quicListener) Init(md md.Metadata) (err error) {
KeepAlivePeriod: l.md.keepAlivePeriod,
HandshakeIdleTimeout: l.md.handshakeTimeout,
MaxIdleTimeout: l.md.maxIdleTimeout,
Versions: []quic.VersionNumber{
Versions: []quic.Version{
quic.Version1,
quic.Version2,
},
@@ -119,7 +119,7 @@ func (l *quicListener) Accept() (conn net.Conn, err error) {
case conn = <-l.cqueue:
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+11 -2
View File
@@ -18,8 +18,9 @@ type metadata struct {
maxIdleTimeout time.Duration
maxStreams int
cipherKey []byte
backlog int
cipherKey []byte
backlog int
limiterRefreshInterval time.Duration
}
func (l *quicListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -53,5 +54,13 @@ func (l *quicListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
l.md.maxStreams = mdutil.GetInt(md, maxStreams)
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -73,7 +73,7 @@ func (l *redirectListener) Init(md md.Metadata) (err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
l.ln = ln
@@ -88,7 +88,7 @@ func (l *redirectListener) Accept() (conn net.Conn, err error) {
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+14 -2
View File
@@ -1,17 +1,29 @@
package tcp
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
tproxy bool
mptcp bool
tproxy bool
mptcp bool
limiterRefreshInterval time.Duration
}
func (l *redirectListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.tproxy = mdutil.GetBool(md, "tproxy")
l.md.mptcp = mdutil.GetBool(md, "mptcp")
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+1 -1
View File
@@ -62,7 +62,7 @@ func (l *redirectListener) Accept() (conn net.Conn, err error) {
conn = admission.WrapConn(l.options.Admission, conn)
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+11 -2
View File
@@ -13,8 +13,9 @@ const (
)
type metadata struct {
ttl time.Duration
readBufferSize int
ttl time.Duration
readBufferSize int
limiterRefreshInterval time.Duration
}
func (l *redirectListener) parseMetadata(md mdata.Metadata) (err error) {
@@ -33,5 +34,13 @@ func (l *redirectListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.readBufferSize = defaultReadBufferSize
}
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+3 -2
View File
@@ -30,6 +30,7 @@ type rtcpListener struct {
ln net.Listener
logger logger.Logger
closed chan struct{}
md metadata
options listener.Options
mu sync.Mutex
}
@@ -87,7 +88,7 @@ func (l *rtcpListener) Accept() (conn net.Conn, err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
l.setListener(ln)
@@ -109,7 +110,7 @@ func (l *rtcpListener) Accept() (conn net.Conn, err error) {
conn = limiter_wrapper.WrapConn(
conn,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
conn.RemoteAddr().String(),
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),
+13 -1
View File
@@ -1,11 +1,23 @@
package rtcp
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct{}
type metadata struct {
limiterRefreshInterval time.Duration
}
func (l *rtcpListener) parseMetadata(md mdata.Metadata) (err error) {
l.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
if l.md.limiterRefreshInterval == 0 {
l.md.limiterRefreshInterval = 30 * time.Second
}
if l.md.limiterRefreshInterval < time.Second {
l.md.limiterRefreshInterval = time.Second
}
return
}
+2 -2
View File
@@ -89,7 +89,7 @@ func (l *rudpListener) Accept() (conn net.Conn, err error) {
ln = limiter_wrapper.WrapListener(
l.options.Service,
ln,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
)
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
@@ -116,7 +116,7 @@ func (l *rudpListener) Accept() (conn net.Conn, err error) {
uc = admission.WrapUDPConn(l.options.Admission, uc)
conn = limiter_wrapper.WrapUDPConn(
uc,
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, 30*time.Second, 60*time.Second),
limiter_util.NewCachedTrafficLimiter(l.options.TrafficLimiter, l.md.limiterRefreshInterval, 60*time.Second),
"",
limiter.ScopeOption(limiter.ScopeConn),
limiter.ServiceOption(l.options.Service),

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