add pkgs from core

This commit is contained in:
ginuerzh 2022-04-04 12:44:35 +08:00
parent 7eb3687e0e
commit a3346ad246
188 changed files with 6084 additions and 283 deletions

86
admission/admission.go Normal file
View File

@ -0,0 +1,86 @@
package admission
import (
"net"
"strconv"
admission_pkg "github.com/go-gost/core/admission"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/util/matcher"
)
type options struct {
logger logger.Logger
}
type Option func(opts *options)
func LoggerOption(logger logger.Logger) Option {
return func(opts *options) {
opts.logger = logger
}
}
type admission struct {
matchers []matcher.Matcher
reversed bool
options options
}
// NewAdmission creates and initializes a new Admission using matchers as its match rules.
// The rules will be reversed if the reversed is true.
func NewAdmission(reversed bool, matchers []matcher.Matcher, opts ...Option) admission_pkg.Admission {
options := options{}
for _, opt := range opts {
opt(&options)
}
return &admission{
matchers: matchers,
reversed: reversed,
options: options,
}
}
// NewAdmissionPatterns creates and initializes a new Admission using matcher patterns as its match rules.
// The rules will be reversed if the reverse is true.
func NewAdmissionPatterns(reversed bool, patterns []string, opts ...Option) admission_pkg.Admission {
var matchers []matcher.Matcher
for _, pattern := range patterns {
if m := matcher.NewMatcher(pattern); m != nil {
matchers = append(matchers, m)
}
}
return NewAdmission(reversed, matchers, opts...)
}
func (p *admission) Admit(addr string) bool {
if addr == "" || p == nil || len(p.matchers) == 0 {
p.options.logger.Debugf("admission: %v is denied", addr)
return false
}
// try to strip the port
if host, port, _ := net.SplitHostPort(addr); host != "" && port != "" {
if p, _ := strconv.Atoi(port); p > 0 { // port is valid
addr = host
}
}
var matched bool
for _, matcher := range p.matchers {
if matcher == nil {
continue
}
if matcher.Match(addr) {
matched = true
break
}
}
b := !p.reversed && matched ||
p.reversed && !matched
if !b {
p.options.logger.Debugf("admission: %v is denied", addr)
}
return b
}

View File

@ -4,9 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
"github.com/go-gost/x/registry"
)
// swagger:parameters createAdmissionRequest

View File

@ -4,9 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
"github.com/go-gost/x/registry"
)
// swagger:parameters createAutherRequest

View File

@ -4,9 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
"github.com/go-gost/x/registry"
)
// swagger:parameters createBypassRequest

View File

@ -4,9 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
"github.com/go-gost/x/registry"
)
// swagger:parameters createChainRequest

View File

@ -4,9 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
"github.com/go-gost/x/registry"
)
// swagger:parameters createHostsRequest

View File

@ -4,9 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
"github.com/go-gost/x/registry"
)
// swagger:parameters createResolverRequest

View File

@ -4,9 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
"github.com/go-gost/x/registry"
)
// swagger:parameters createServiceRequest

27
auth/auth.go Normal file
View File

@ -0,0 +1,27 @@
package auth
import (
"github.com/go-gost/core/auth"
)
// authenticator is an Authenticator that authenticates client by key-value pairs.
type authenticator struct {
kvs map[string]string
}
// NewAuthenticator creates an Authenticator that authenticates client by pre-defined user mapping.
func NewAuthenticator(kvs map[string]string) auth.Authenticator {
return &authenticator{
kvs: kvs,
}
}
// Authenticate checks the validity of the provided user-password pair.
func (au *authenticator) Authenticate(user, password string) bool {
if au == nil || len(au.kvs) == 0 {
return true
}
v, ok := au.kvs[user]
return ok && (v == "" || password == v)
}

85
bypass/bypass.go Normal file
View File

@ -0,0 +1,85 @@
package bypass
import (
"net"
"strconv"
bypass_pkg "github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/util/matcher"
)
type options struct {
logger logger.Logger
}
type Option func(opts *options)
func LoggerOption(logger logger.Logger) Option {
return func(opts *options) {
opts.logger = logger
}
}
type bypass struct {
matchers []matcher.Matcher
reversed bool
options options
}
// NewBypass creates and initializes a new Bypass using matchers as its match rules.
// The rules will be reversed if the reversed is true.
func NewBypass(reversed bool, matchers []matcher.Matcher, opts ...Option) bypass_pkg.Bypass {
options := options{}
for _, opt := range opts {
opt(&options)
}
return &bypass{
matchers: matchers,
reversed: reversed,
options: options,
}
}
// NewBypassPatterns creates and initializes a new Bypass using matcher patterns as its match rules.
// The rules will be reversed if the reverse is true.
func NewBypassPatterns(reversed bool, patterns []string, opts ...Option) bypass_pkg.Bypass {
var matchers []matcher.Matcher
for _, pattern := range patterns {
if m := matcher.NewMatcher(pattern); m != nil {
matchers = append(matchers, m)
}
}
return NewBypass(reversed, matchers, opts...)
}
func (bp *bypass) Contains(addr string) bool {
if addr == "" || bp == nil || len(bp.matchers) == 0 {
return false
}
// try to strip the port
if host, port, _ := net.SplitHostPort(addr); host != "" && port != "" {
if p, _ := strconv.Atoi(port); p > 0 { // port is valid
addr = host
}
}
var matched bool
for _, matcher := range bp.matchers {
if matcher == nil {
continue
}
if matcher.Match(addr) {
matched = true
break
}
}
b := !bp.reversed && matched ||
bp.reversed && !matched
if b {
bp.options.logger.Debugf("bypass: %s", addr)
}
return b
}

View File

@ -2,13 +2,13 @@ package parsing
import (
"github.com/go-gost/core/chain"
tls_util "github.com/go-gost/core/common/util/tls"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/config"
tls_util "github.com/go-gost/x/internal/util/tls"
"github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
)
func ParseChain(cfg *config.ChainConfig) (chain.Chainer, error) {
@ -58,7 +58,7 @@ func ParseChain(cfg *config.ChainConfig) (chain.Chainer, error) {
if v.Connector.Metadata == nil {
v.Connector.Metadata = make(map[string]any)
}
if err := cr.Init(metadata.MapMetadata(v.Connector.Metadata)); err != nil {
if err := cr.Init(metadata.NewMetadata(v.Connector.Metadata)); err != nil {
connectorLogger.Error("init: ", err)
return nil, err
}
@ -88,7 +88,7 @@ func ParseChain(cfg *config.ChainConfig) (chain.Chainer, error) {
if v.Dialer.Metadata == nil {
v.Dialer.Metadata = make(map[string]any)
}
if err := d.Init(metadata.MapMetadata(v.Dialer.Metadata)); err != nil {
if err := d.Init(metadata.NewMetadata(v.Dialer.Metadata)); err != nil {
dialerLogger.Error("init: ", err)
return nil, err
}

View File

@ -8,12 +8,16 @@ import (
"github.com/go-gost/core/auth"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
hostspkg "github.com/go-gost/core/hosts"
"github.com/go-gost/core/hosts"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/registry"
"github.com/go-gost/core/resolver"
resolver_impl "github.com/go-gost/core/resolver/impl"
admission_impl "github.com/go-gost/x/admission"
auth_impl "github.com/go-gost/x/auth"
bypass_impl "github.com/go-gost/x/bypass"
"github.com/go-gost/x/config"
hosts_impl "github.com/go-gost/x/hosts"
"github.com/go-gost/x/registry"
resolver_impl "github.com/go-gost/x/resolver"
)
func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
@ -33,14 +37,14 @@ func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
if len(m) == 0 {
return nil
}
return auth.NewAuthenticator(m)
return auth_impl.NewAuthenticator(m)
}
func ParseAutherFromAuth(au *config.AuthConfig) auth.Authenticator {
if au == nil || au.Username == "" {
return nil
}
return auth.NewAuthenticator(map[string]string{
return auth_impl.NewAuthenticator(map[string]string{
au.Username: au.Password,
})
}
@ -84,10 +88,10 @@ func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
if cfg == nil {
return nil
}
return admission.NewAdmissionPatterns(
return admission_impl.NewAdmissionPatterns(
cfg.Reverse,
cfg.Matchers,
admission.LoggerOption(logger.Default().WithFields(map[string]any{
admission_impl.LoggerOption(logger.Default().WithFields(map[string]any{
"kind": "admission",
"admission": cfg.Name,
})),
@ -98,10 +102,10 @@ func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
if cfg == nil {
return nil
}
return bypass.NewBypassPatterns(
return bypass_impl.NewBypassPatterns(
cfg.Reverse,
cfg.Matchers,
bypass.LoggerOption(logger.Default().WithFields(map[string]any{
bypass_impl.LoggerOption(logger.Default().WithFields(map[string]any{
"kind": "bypass",
"bypass": cfg.Name,
})),
@ -136,11 +140,11 @@ func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
)
}
func ParseHosts(cfg *config.HostsConfig) hostspkg.HostMapper {
func ParseHosts(cfg *config.HostsConfig) hosts.HostMapper {
if cfg == nil || len(cfg.Mappings) == 0 {
return nil
}
hosts := hostspkg.NewHosts()
hosts := hosts_impl.NewHosts()
hosts.Logger = logger.Default().WithFields(map[string]any{
"kind": "hosts",
"hosts": cfg.Name,

View File

@ -4,14 +4,14 @@ import (
"strings"
"github.com/go-gost/core/chain"
tls_util "github.com/go-gost/core/common/util/tls"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/listener"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/core/service"
"github.com/go-gost/x/config"
tls_util "github.com/go-gost/x/internal/util/tls"
"github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
)
func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
@ -46,6 +46,9 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
listenerLogger.Error(err)
return nil, err
}
if tlsConfig == nil {
tlsConfig = defaultTLSConfig.Clone()
}
auther := ParseAutherFromAuth(cfg.Listener.Auth)
if cfg.Listener.Auther != "" {
@ -66,7 +69,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
if cfg.Listener.Metadata == nil {
cfg.Listener.Metadata = make(map[string]any)
}
if err := ln.Init(metadata.MapMetadata(cfg.Listener.Metadata)); err != nil {
if err := ln.Init(metadata.NewMetadata(cfg.Listener.Metadata)); err != nil {
listenerLogger.Error("init: ", err)
return nil, err
}
@ -85,6 +88,9 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
handlerLogger.Error(err)
return nil, err
}
if tlsConfig == nil {
tlsConfig = defaultTLSConfig.Clone()
}
auther = ParseAutherFromAuth(cfg.Handler.Auth)
if cfg.Handler.Auther != "" {
@ -124,7 +130,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
if cfg.Handler.Metadata == nil {
cfg.Handler.Metadata = make(map[string]any)
}
if err := h.Init(metadata.MapMetadata(cfg.Handler.Metadata)); err != nil {
if err := h.Init(metadata.NewMetadata(cfg.Handler.Metadata)); err != nil {
handlerLogger.Error("init: ", err)
return nil, err
}

113
config/parsing/tls.go Normal file
View File

@ -0,0 +1,113 @@
package parsing
import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
)
var (
defaultTLSConfig *tls.Config
)
func BuildDefaultTLSConfig(cfg *config.TLSConfig) {
log := logger.Default()
if cfg == nil {
cfg = &config.TLSConfig{
CertFile: "cert.pem",
KeyFile: "key.pem",
}
}
tlsConfig, err := loadConfig(cfg.CertFile, cfg.KeyFile)
if err != nil {
// generate random self-signed certificate.
cert, err := genCertificate()
if err != nil {
log.Fatal(err)
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}
log.Warn("load TLS certificate files failed, use random generated certificate")
} else {
log.Info("load TLS certificate files OK")
}
defaultTLSConfig = tlsConfig
}
func loadConfig(certFile, keyFile string) (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
cfg := &tls.Config{
Certificates: []tls.Certificate{cert},
}
return cfg, nil
}
func genCertificate() (cert tls.Certificate, err error) {
rawCert, rawKey, err := generateKeyPair()
if err != nil {
return
}
return tls.X509KeyPair(rawCert, rawKey)
}
func generateKeyPair() (rawCert, rawKey []byte, err error) {
// Create private key and self-signed certificate
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
}
validFor := time.Hour * 24 * 365 * 10 // ten years
notBefore := time.Now()
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"gost"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
template.DNSNames = append(template.DNSNames, "gost.run")
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return
}
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return
}
rawKey = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
return
}

View File

@ -0,0 +1,45 @@
package forward
import (
"context"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("forward", NewConnector)
}
type forwardConnector struct {
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &forwardConnector{
options: options,
}
}
func (c *forwardConnector) Init(md md.Metadata) (err error) {
return nil
}
func (c *forwardConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
})
log.Infof("connect %s/%s", address, network)
return conn, nil
}

129
connector/http/connector.go Normal file
View File

@ -0,0 +1,129 @@
package http
import (
"bufio"
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"time"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("http", NewConnector)
}
type httpConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &httpConnector{
options: options,
}
}
func (c *httpConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *httpConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"local": conn.LocalAddr().String(),
"remote": conn.RemoteAddr().String(),
"network": network,
"address": address,
})
log.Infof("connect %s/%s", address, network)
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Host: address},
Host: address,
ProtoMajor: 1,
ProtoMinor: 1,
Header: c.md.header,
}
if req.Header == nil {
req.Header = http.Header{}
}
req.Header.Set("Proxy-Connection", "keep-alive")
if user := c.options.Auth; user != nil {
u := user.Username()
p, _ := user.Password()
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(u+":"+p)))
}
switch network {
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
case "udp", "udp4", "udp6":
req.Header.Set("X-Gost-Protocol", "udp")
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpRequest(req, false)
log.Debug(string(dump))
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
req = req.WithContext(ctx)
if err := req.Write(conn); err != nil {
return nil, err
}
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil {
return nil, err
}
// NOTE: the server may return `Transfer-Encoding: chunked` header,
// then the Content-Length of response will be unknown (-1),
// in this case, close body will be blocked, so we leave it untouched.
// defer resp.Body.Close()
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s", resp.Status)
}
if network == "udp" {
addr, _ := net.ResolveUDPAddr(network, address)
return socks.UDPTunClientConn(conn, addr), nil
}
return conn, nil
}

View File

@ -0,0 +1,33 @@
package http
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
connectTimeout time.Duration
header http.Header
}
func (c *httpConnector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "timeout"
header = "header"
)
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
if mm := mdx.GetStringMapString(md, header); len(mm) > 0 {
hd := http.Header{}
for k, v := range mm {
hd.Add(k, v)
}
c.md.header = hd
}
return
}

View File

@ -15,7 +15,7 @@ import (
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -18,8 +19,8 @@ func (c *http2Connector) parseMetadata(md mdata.Metadata) (err error) {
header = "header"
)
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
if mm := mdata.GetStringMapString(md, header); len(mm) > 0 {
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
if mm := mdx.GetStringMapString(md, header); len(mm) > 0 {
hd := http.Header{}
for k, v := range mm {
hd.Add(k, v)

View File

@ -8,9 +8,9 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/relay"
relay_util "github.com/go-gost/x/internal/util/relay"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -4,6 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -17,8 +18,8 @@ func (c *relayConnector) parseMetadata(md mdata.Metadata) (err error) {
noDelay = "nodelay"
)
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
c.md.noDelay = mdata.GetBool(md, noDelay)
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
c.md.noDelay = mdx.GetBool(md, noDelay)
return
}

View File

@ -6,7 +6,7 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -4,6 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -17,8 +18,8 @@ func (c *sniConnector) parseMetadata(md mdata.Metadata) (err error) {
connectTimeout = "timeout"
)
c.md.host = mdata.GetString(md, host)
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
c.md.host = mdx.GetString(md, host)
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
return
}

View File

@ -0,0 +1,123 @@
package v4
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks4"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("socks4", NewConnector)
registry.ConnectorRegistry().Register("socks4a", NewConnector)
}
type socks4Connector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks4Connector{
options: options,
}
}
func (c *socks4Connector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *socks4Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
})
log.Infof("connect %s/%s", address, network)
switch network {
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
var addr *gosocks4.Addr
if c.md.disable4a {
taddr, err := net.ResolveTCPAddr("tcp4", address)
if err != nil {
log.Error("resolve: ", err)
return nil, err
}
if len(taddr.IP) == 0 {
taddr.IP = net.IPv4zero
}
addr = &gosocks4.Addr{
Type: gosocks4.AddrIPv4,
Host: taddr.IP.String(),
Port: uint16(taddr.Port),
}
} else {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
p, _ := strconv.Atoi(port)
addr = &gosocks4.Addr{
Type: gosocks4.AddrDomain,
Host: host,
Port: uint16(p),
}
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
var userid []byte
if c.options.Auth != nil {
userid = []byte(c.options.Auth.Username())
}
req := gosocks4.NewRequest(gosocks4.CmdConnect, addr, userid)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
log.Debug(req)
reply, err := gosocks4.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Debug(reply)
if reply.Code != gosocks4.Granted {
err = errors.New("host unreachable")
log.Error(err)
return nil, err
}
return conn, nil
}

View File

@ -0,0 +1,25 @@
package v4
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
connectTimeout time.Duration
disable4a bool
}
func (c *socks4Connector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "timeout"
disable4a = "disable4a"
)
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
c.md.disable4a = mdx.GetBool(md, disable4a)
return
}

133
connector/socks/v5/bind.go Normal file
View File

@ -0,0 +1,133 @@
package v5
import (
"context"
"fmt"
"net"
"github.com/go-gost/core/common/net/udp"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/mux"
"github.com/go-gost/x/internal/util/socks"
)
// Bind implements connector.Binder.
func (c *socks5Connector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
})
log.Infof("bind on %s/%s", address, network)
options := connector.BindOptions{}
for _, opt := range opts {
opt(&options)
}
switch network {
case "tcp", "tcp4", "tcp6":
if options.Mux {
return c.muxBindTCP(ctx, conn, network, address, log)
}
return c.bindTCP(ctx, conn, network, address, log)
case "udp", "udp4", "udp6":
return c.bindUDP(ctx, conn, network, address, &options, log)
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
}
func (c *socks5Connector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, gosocks5.CmdBind, network, address, log)
if err != nil {
return nil, err
}
return &tcpListener{
addr: laddr,
conn: conn,
logger: log,
}, nil
}
func (c *socks5Connector) muxBindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, socks.CmdMuxBind, network, address, log)
if err != nil {
return nil, err
}
session, err := mux.ServerSession(conn)
if err != nil {
return nil, err
}
return &tcpMuxListener{
addr: laddr,
session: session,
logger: log,
}, nil
}
func (c *socks5Connector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, socks.CmdUDPTun, network, address, log)
if err != nil {
return nil, err
}
ln := udp.NewListener(socks.UDPTunClientPacketConn(conn),
&udp.ListenConfig{
Addr: laddr,
Backlog: opts.Backlog,
ReadQueueSize: opts.UDPDataQueueSize,
ReadBufferSize: opts.UDPDataBufferSize,
TTL: opts.UDPConnTTL,
KeepAlive: true,
Logger: log,
})
return ln, nil
}
func (l *socks5Connector) bind(conn net.Conn, cmd uint8, network, address string, log logger.Logger) (net.Addr, error) {
addr := gosocks5.Addr{}
addr.ParseFrom(address)
req := gosocks5.NewRequest(cmd, &addr)
if err := req.Write(conn); err != nil {
return nil, err
}
log.Debug(req)
// first reply, bind status
reply, err := gosocks5.ReadReply(conn)
if err != nil {
return nil, err
}
log.Debug(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
}
var baddr net.Addr
switch network {
case "tcp", "tcp4", "tcp6":
baddr, err = net.ResolveTCPAddr(network, reply.Addr.String())
case "udp", "udp4", "udp6":
baddr, err = net.ResolveUDPAddr(network, reply.Addr.String())
default:
err = fmt.Errorf("unknown network %s", network)
}
if err != nil {
return nil, err
}
log.Debugf("bind on %s/%s OK", baddr, baddr.Network())
return baddr, nil
}

View File

@ -0,0 +1,17 @@
package v5
import "net"
type bindConn struct {
net.Conn
localAddr net.Addr
remoteAddr net.Addr
}
func (c *bindConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *bindConn) RemoteAddr() net.Addr {
return c.remoteAddr
}

View File

@ -0,0 +1,173 @@
package v5
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("socks5", NewConnector)
registry.ConnectorRegistry().Register("socks", NewConnector)
}
type socks5Connector struct {
selector gosocks5.Selector
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks5Connector{
options: options,
}
}
func (c *socks5Connector) Init(md md.Metadata) (err error) {
if err = c.parseMetadata(md); err != nil {
return
}
selector := &clientSelector{
methods: []uint8{
gosocks5.MethodNoAuth,
gosocks5.MethodUserPass,
},
User: c.options.Auth,
TLSConfig: c.options.TLSConfig,
logger: c.options.Logger,
}
if !c.md.noTLS {
selector.methods = append(selector.methods, socks.MethodTLS)
if selector.TLSConfig == nil {
selector.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
}
c.selector = selector
return
}
// Handshake implements connector.Handshaker.
func (c *socks5Connector) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
cc := gosocks5.ClientConn(conn, c.selector)
if err := cc.Handleshake(); err != nil {
log.Error(err)
return nil, err
}
return cc, nil
}
func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
})
log.Infof("connect %s/%s", address, network)
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
switch network {
case "udp", "udp4", "udp6":
return c.connectUDP(ctx, conn, network, address, log)
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
addr := gosocks5.Addr{}
if err := addr.ParseFrom(address); err != nil {
log.Error(err)
return nil, err
}
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
log.Debug(req)
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Debug(reply)
if reply.Rep != gosocks5.Succeeded {
err = errors.New("host unreachable")
log.Error(err)
return nil, err
}
return conn, nil
}
func (c *socks5Connector) connectUDP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Conn, error) {
addr, err := net.ResolveUDPAddr(network, address)
if err != nil {
log.Error(err)
return nil, err
}
req := gosocks5.NewRequest(socks.CmdUDPTun, nil)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
log.Debug(req)
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Debug(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, errors.New("get socks5 UDP tunnel failure")
}
return socks.UDPTunClientConn(conn, addr), nil
}

View File

@ -0,0 +1,102 @@
package v5
import (
"fmt"
"net"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/mux"
)
type tcpListener struct {
addr net.Addr
conn net.Conn
logger logger.Logger
}
func (p *tcpListener) Accept() (net.Conn, error) {
// second reply, peer connected
rep, err := gosocks5.ReadReply(p.conn)
if err != nil {
return nil, err
}
p.logger.Debug(rep)
if rep.Rep != gosocks5.Succeeded {
return nil, fmt.Errorf("peer connect failed")
}
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
if err != nil {
return nil, err
}
return &bindConn{
Conn: p.conn,
localAddr: p.addr,
remoteAddr: raddr,
}, nil
}
func (p *tcpListener) Addr() net.Addr {
return p.addr
}
func (p *tcpListener) Close() error {
return p.conn.Close()
}
type tcpMuxListener struct {
addr net.Addr
session *mux.Session
logger logger.Logger
}
func (p *tcpMuxListener) Accept() (net.Conn, error) {
cc, err := p.session.Accept()
if err != nil {
return nil, err
}
conn, err := p.getPeerConn(cc)
if err != nil {
cc.Close()
return nil, err
}
return conn, nil
}
func (p *tcpMuxListener) getPeerConn(conn net.Conn) (net.Conn, error) {
// second reply, peer connected
rep, err := gosocks5.ReadReply(conn)
if err != nil {
return nil, err
}
p.logger.Debug(rep)
if rep.Rep != gosocks5.Succeeded {
err = fmt.Errorf("peer connect failed")
return nil, err
}
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
if err != nil {
return nil, err
}
return &bindConn{
Conn: conn,
localAddr: p.addr,
remoteAddr: raddr,
}, nil
}
func (p *tcpMuxListener) Addr() net.Addr {
return p.addr
}
func (p *tcpMuxListener) Close() error {
return p.session.Close()
}

View File

@ -0,0 +1,25 @@
package v5
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
connectTimeout time.Duration
noTLS bool
}
func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "timeout"
noTLS = "notls"
)
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
c.md.noTLS = mdx.GetBool(md, noTLS)
return
}

View File

@ -0,0 +1,73 @@
package v5
import (
"crypto/tls"
"net"
"net/url"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/socks"
)
type clientSelector struct {
methods []uint8
User *url.Userinfo
TLSConfig *tls.Config
logger logger.Logger
}
func (s *clientSelector) Methods() []uint8 {
s.logger.Debug("methods: ", s.methods)
return s.methods
}
func (s *clientSelector) AddMethod(methods ...uint8) {
s.methods = append(s.methods, methods...)
}
func (s *clientSelector) Select(methods ...uint8) (method uint8) {
return
}
func (s *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) {
s.logger.Debug("method selected: ", method)
switch method {
case socks.MethodTLS:
conn = tls.Client(conn, s.TLSConfig)
case gosocks5.MethodUserPass, socks.MethodTLSAuth:
if method == socks.MethodTLSAuth {
conn = tls.Client(conn, s.TLSConfig)
}
var username, password string
if s.User != nil {
username = s.User.Username()
password, _ = s.User.Password()
}
req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password)
if err := req.Write(conn); err != nil {
s.logger.Error(err)
return nil, err
}
s.logger.Debug(req)
resp, err := gosocks5.ReadUserPassResponse(conn)
if err != nil {
s.logger.Error(err)
return nil, err
}
s.logger.Debug(resp)
if resp.Status != gosocks5.Succeeded {
return nil, gosocks5.ErrAuthFailure
}
case gosocks5.MethodNoAcceptable:
return nil, gosocks5.ErrBadMethod
}
return conn, nil
}

View File

@ -9,9 +9,9 @@ import (
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/ss"
"github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core"
)

View File

@ -4,6 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -19,9 +20,9 @@ func (c *ssConnector) parseMetadata(md mdata.Metadata) (err error) {
noDelay = "nodelay"
)
c.md.key = mdata.GetString(md, key)
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
c.md.noDelay = mdata.GetBool(md, noDelay)
c.md.key = mdx.GetString(md, key)
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
c.md.noDelay = mdx.GetBool(md, noDelay)
return
}

View File

@ -8,9 +8,9 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/internal/util/relay"
"github.com/go-gost/x/internal/util/ss"
"github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core"
)

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -20,10 +21,10 @@ func (c *ssuConnector) parseMetadata(md mdata.Metadata) (err error) {
bufferSize = "bufferSize" // udp buffer size
)
c.md.key = mdata.GetString(md, key)
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
c.md.key = mdx.GetString(md, key)
c.md.connectTimeout = mdx.GetDuration(md, connectTimeout)
if bs := mdata.GetInt(md, bufferSize); bs > 0 {
if bs := mdx.GetInt(md, bufferSize); bs > 0 {
c.md.bufferSize = int(math.Min(math.Max(float64(bs), 512), 64*1024))
} else {
c.md.bufferSize = 1500

View File

@ -7,8 +7,8 @@ import (
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
ssh_util "github.com/go-gost/x/internal/util/ssh"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -7,7 +7,7 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
"github.com/xtaci/tcpraw"
)

View File

@ -8,8 +8,8 @@ import (
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
pb "github.com/go-gost/x/internal/util/grpc/proto"
"github.com/go-gost/x/registry"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials"

View File

@ -2,6 +2,7 @@ package grpc
import (
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -15,8 +16,8 @@ func (d *grpcDialer) parseMetadata(md mdata.Metadata) (err error) {
host = "host"
)
d.md.insecure = mdata.GetBool(md, insecure)
d.md.host = mdata.GetString(md, host)
d.md.insecure = mdx.GetBool(md, insecure)
d.md.host = mdx.GetString(md, host)
return
}

View File

@ -11,7 +11,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
mdx "github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
)
func init() {
@ -98,7 +99,7 @@ func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.D
defer d.clientMutex.Unlock()
delete(d.clients, address)
},
md: md.MapMetadata{"client": client},
md: mdx.NewMetadata(map[string]any{"client": client}),
}
return c, nil

View File

@ -15,7 +15,7 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
"golang.org/x/net/http2"
)

View File

@ -2,6 +2,7 @@ package h2
import (
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -15,8 +16,8 @@ func (d *h2Dialer) parseMetadata(md mdata.Metadata) (err error) {
path = "path"
)
d.md.host = mdata.GetString(md, host)
d.md.path = mdata.GetString(md, path)
d.md.host = mdx.GetString(md, host)
d.md.path = mdx.GetString(md, path)
return
}

View File

@ -9,8 +9,8 @@ import (
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
pht_util "github.com/go-gost/x/internal/util/pht"
"github.com/go-gost/x/registry"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
)

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
const (
@ -34,19 +35,19 @@ func (d *http3Dialer) parseMetadata(md mdata.Metadata) (err error) {
host = "host"
)
d.md.authorizePath = mdata.GetString(md, authorizePath)
d.md.authorizePath = mdx.GetString(md, authorizePath)
if !strings.HasPrefix(d.md.authorizePath, "/") {
d.md.authorizePath = defaultAuthorizePath
}
d.md.pushPath = mdata.GetString(md, pushPath)
d.md.pushPath = mdx.GetString(md, pushPath)
if !strings.HasPrefix(d.md.pushPath, "/") {
d.md.pushPath = defaultPushPath
}
d.md.pullPath = mdata.GetString(md, pullPath)
d.md.pullPath = mdx.GetString(md, pullPath)
if !strings.HasPrefix(d.md.pullPath, "/") {
d.md.pullPath = defaultPullPath
}
d.md.host = mdata.GetString(md, host)
d.md.host = mdx.GetString(md, host)
return
}

View File

@ -11,8 +11,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
icmp_pkg "github.com/go-gost/x/internal/util/icmp"
"github.com/go-gost/x/registry"
"github.com/lucas-clemente/quic-go"
"golang.org/x/net/icmp"
)

View File

@ -4,6 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -19,11 +20,11 @@ func (d *icmpDialer) parseMetadata(md mdata.Metadata) (err error) {
maxIdleTimeout = "maxIdleTimeout"
)
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
d.md.keepAlive = mdata.GetBool(md, keepAlive)
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdata.GetDuration(md, maxIdleTimeout)
d.md.keepAlive = mdx.GetBool(md, keepAlive)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdx.GetDuration(md, maxIdleTimeout)
return
}

View File

@ -10,8 +10,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
kcp_util "github.com/go-gost/x/internal/util/kcp"
"github.com/go-gost/x/registry"
"github.com/xtaci/kcp-go/v5"
"github.com/xtaci/smux"
"github.com/xtaci/tcpraw"

View File

@ -6,6 +6,7 @@ import (
mdata "github.com/go-gost/core/metadata"
kcp_util "github.com/go-gost/x/internal/util/kcp"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -19,7 +20,7 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
handshakeTimeout = "handshakeTimeout"
)
if m := mdata.GetStringMap(md, config); len(m) > 0 {
if m := mdx.GetStringMap(md, config); len(m) > 0 {
b, err := json.Marshal(m)
if err != nil {
return err
@ -34,6 +35,6 @@ func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.config = kcp_util.DefaultConfig
}
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
return
}

View File

@ -11,7 +11,7 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
"github.com/xtaci/smux"
)

View File

@ -4,6 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -29,14 +30,14 @@ func (d *mtlsDialer) parseMetadata(md mdata.Metadata) (err error) {
muxMaxStreamBuffer = "muxMaxStreamBuffer"
)
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
d.md.muxKeepAliveDisabled = mdata.GetBool(md, muxKeepAliveDisabled)
d.md.muxKeepAliveInterval = mdata.GetDuration(md, muxKeepAliveInterval)
d.md.muxKeepAliveTimeout = mdata.GetDuration(md, muxKeepAliveTimeout)
d.md.muxMaxFrameSize = mdata.GetInt(md, muxMaxFrameSize)
d.md.muxMaxReceiveBuffer = mdata.GetInt(md, muxMaxReceiveBuffer)
d.md.muxMaxStreamBuffer = mdata.GetInt(md, muxMaxStreamBuffer)
d.md.muxKeepAliveDisabled = mdx.GetBool(md, muxKeepAliveDisabled)
d.md.muxKeepAliveInterval = mdx.GetDuration(md, muxKeepAliveInterval)
d.md.muxKeepAliveTimeout = mdx.GetDuration(md, muxKeepAliveTimeout)
d.md.muxMaxFrameSize = mdx.GetInt(md, muxMaxFrameSize)
d.md.muxMaxReceiveBuffer = mdx.GetInt(md, muxMaxReceiveBuffer)
d.md.muxMaxStreamBuffer = mdx.GetInt(md, muxMaxStreamBuffer)
return
}

View File

@ -10,8 +10,8 @@ import (
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
ws_util "github.com/go-gost/x/internal/util/ws"
"github.com/go-gost/x/registry"
"github.com/gorilla/websocket"
"github.com/xtaci/smux"
)

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
const (
@ -54,34 +55,34 @@ func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
muxMaxStreamBuffer = "muxMaxStreamBuffer"
)
d.md.host = mdata.GetString(md, host)
d.md.host = mdx.GetString(md, host)
d.md.path = mdata.GetString(md, path)
d.md.path = mdx.GetString(md, path)
if d.md.path == "" {
d.md.path = defaultPath
}
d.md.muxKeepAliveDisabled = mdata.GetBool(md, muxKeepAliveDisabled)
d.md.muxKeepAliveInterval = mdata.GetDuration(md, muxKeepAliveInterval)
d.md.muxKeepAliveTimeout = mdata.GetDuration(md, muxKeepAliveTimeout)
d.md.muxMaxFrameSize = mdata.GetInt(md, muxMaxFrameSize)
d.md.muxMaxReceiveBuffer = mdata.GetInt(md, muxMaxReceiveBuffer)
d.md.muxMaxStreamBuffer = mdata.GetInt(md, muxMaxStreamBuffer)
d.md.muxKeepAliveDisabled = mdx.GetBool(md, muxKeepAliveDisabled)
d.md.muxKeepAliveInterval = mdx.GetDuration(md, muxKeepAliveInterval)
d.md.muxKeepAliveTimeout = mdx.GetDuration(md, muxKeepAliveTimeout)
d.md.muxMaxFrameSize = mdx.GetInt(md, muxMaxFrameSize)
d.md.muxMaxReceiveBuffer = mdx.GetInt(md, muxMaxReceiveBuffer)
d.md.muxMaxStreamBuffer = mdx.GetInt(md, muxMaxStreamBuffer)
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.readHeaderTimeout = mdata.GetDuration(md, readHeaderTimeout)
d.md.readBufferSize = mdata.GetInt(md, readBufferSize)
d.md.writeBufferSize = mdata.GetInt(md, writeBufferSize)
d.md.enableCompression = mdata.GetBool(md, enableCompression)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
d.md.readHeaderTimeout = mdx.GetDuration(md, readHeaderTimeout)
d.md.readBufferSize = mdx.GetInt(md, readBufferSize)
d.md.writeBufferSize = mdx.GetInt(md, writeBufferSize)
d.md.enableCompression = mdx.GetBool(md, enableCompression)
if m := mdata.GetStringMapString(md, header); len(m) > 0 {
if m := mdx.GetStringMapString(md, header); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
d.md.keepAlive = mdata.GetDuration(md, keepAlive)
d.md.keepAlive = mdx.GetDuration(md, keepAlive)
return
}

View File

@ -7,7 +7,7 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -4,6 +4,7 @@ import (
"net/http"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -17,13 +18,13 @@ func (d *obfsHTTPDialer) parseMetadata(md mdata.Metadata) (err error) {
host = "host"
)
if m := mdata.GetStringMapString(md, header); len(m) > 0 {
if m := mdx.GetStringMapString(md, header); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
d.md.host = mdata.GetString(md, host)
d.md.host = mdx.GetString(md, host)
return
}

View File

@ -7,7 +7,7 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -2,6 +2,7 @@ package tls
import (
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -13,6 +14,6 @@ func (d *obfsTLSDialer) parseMetadata(md mdata.Metadata) (err error) {
host = "host"
)
d.md.host = mdata.GetString(md, host)
d.md.host = mdx.GetString(md, host)
return
}

View File

@ -10,8 +10,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
pht_util "github.com/go-gost/x/internal/util/pht"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
const (
@ -34,19 +35,19 @@ func (d *phtDialer) parseMetadata(md mdata.Metadata) (err error) {
host = "host"
)
d.md.authorizePath = mdata.GetString(md, authorizePath)
d.md.authorizePath = mdx.GetString(md, authorizePath)
if !strings.HasPrefix(d.md.authorizePath, "/") {
d.md.authorizePath = defaultAuthorizePath
}
d.md.pushPath = mdata.GetString(md, pushPath)
d.md.pushPath = mdx.GetString(md, pushPath)
if !strings.HasPrefix(d.md.pushPath, "/") {
d.md.pushPath = defaultPushPath
}
d.md.pullPath = mdata.GetString(md, pullPath)
d.md.pullPath = mdx.GetString(md, pullPath)
if !strings.HasPrefix(d.md.pullPath, "/") {
d.md.pullPath = defaultPullPath
}
d.md.host = mdata.GetString(md, host)
d.md.host = mdx.GetString(md, host)
return
}

View File

@ -9,8 +9,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
quic_util "github.com/go-gost/x/internal/util/quic"
"github.com/go-gost/x/registry"
"github.com/lucas-clemente/quic-go"
)

View File

@ -4,6 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -25,16 +26,16 @@ func (d *quicDialer) parseMetadata(md mdata.Metadata) (err error) {
host = "host"
)
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
if key := mdata.GetString(md, cipherKey); key != "" {
if key := mdx.GetString(md, cipherKey); key != "" {
d.md.cipherKey = []byte(key)
}
d.md.keepAlive = mdata.GetBool(md, keepAlive)
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdata.GetDuration(md, maxIdleTimeout)
d.md.keepAlive = mdx.GetBool(md, keepAlive)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdx.GetDuration(md, maxIdleTimeout)
d.md.host = mdata.GetString(md, host)
d.md.host = mdx.GetString(md, host)
return
}

View File

@ -10,8 +10,8 @@ import (
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
ssh_util "github.com/go-gost/x/internal/util/ssh"
"github.com/go-gost/x/registry"
"golang.org/x/crypto/ssh"
)

View File

@ -7,6 +7,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
"golang.org/x/crypto/ssh"
)
@ -24,7 +25,7 @@ func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) {
passphrase = "passphrase"
)
if v := mdata.GetString(md, user); v != "" {
if v := mdx.GetString(md, user); v != "" {
ss := strings.SplitN(v, ":", 2)
if len(ss) == 1 {
d.md.user = url.User(ss[0])
@ -33,13 +34,13 @@ func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) {
}
}
if key := mdata.GetString(md, privateKeyFile); key != "" {
if key := mdx.GetString(md, privateKeyFile); key != "" {
data, err := ioutil.ReadFile(key)
if err != nil {
return err
}
pp := mdata.GetString(md, passphrase)
pp := mdx.GetString(md, passphrase)
if pp == "" {
d.md.signer, err = ssh.ParsePrivateKey(data)
} else {
@ -50,7 +51,7 @@ func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) {
}
}
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
return
}

View File

@ -9,8 +9,8 @@ import (
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
ssh_util "github.com/go-gost/x/internal/util/ssh"
"github.com/go-gost/x/registry"
"golang.org/x/crypto/ssh"
)

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
"golang.org/x/crypto/ssh"
)
@ -20,13 +21,13 @@ func (d *sshdDialer) parseMetadata(md mdata.Metadata) (err error) {
passphrase = "passphrase"
)
if key := mdata.GetString(md, privateKeyFile); key != "" {
if key := mdx.GetString(md, privateKeyFile); key != "" {
data, err := ioutil.ReadFile(key)
if err != nil {
return err
}
pp := mdata.GetString(md, passphrase)
pp := mdx.GetString(md, passphrase)
if pp == "" {
d.md.signer, err = ssh.ParsePrivateKey(data)
} else {
@ -37,7 +38,7 @@ func (d *sshdDialer) parseMetadata(md mdata.Metadata) (err error) {
}
}
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
return
}

48
dialer/tcp/dialer.go Normal file
View File

@ -0,0 +1,48 @@
package tcp
import (
"context"
"net"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("tcp", NewDialer)
}
type tcpDialer struct {
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &tcpDialer{
logger: options.Logger,
}
}
func (d *tcpDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *tcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
if err != nil {
d.logger.Error(err)
}
return conn, err
}

23
dialer/tcp/metadata.go Normal file
View File

@ -0,0 +1,23 @@
package tcp
import (
"time"
md "github.com/go-gost/core/metadata"
)
const (
dialTimeout = "dialTimeout"
)
const (
defaultDialTimeout = 5 * time.Second
)
type metadata struct {
dialTimeout time.Duration
}
func (d *tcpDialer) parseMetadata(md md.Metadata) (err error) {
return
}

68
dialer/tls/dialer.go Normal file
View File

@ -0,0 +1,68 @@
package tls
import (
"context"
"crypto/tls"
"net"
"time"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("tls", NewDialer)
}
type tlsDialer struct {
md metadata
logger logger.Logger
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &tlsDialer{
logger: options.Logger,
options: options,
}
}
func (d *tlsDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *tlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err := options.NetDialer.Dial(ctx, "tcp", addr)
if err != nil {
d.logger.Error(err)
}
return conn, err
}
// Handshake implements dialer.Handshaker
func (d *tlsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
if d.md.handshakeTimeout > 0 {
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
defer conn.SetDeadline(time.Time{})
}
tlsConn := tls.Client(conn, d.options.TLSConfig)
if err := tlsConn.HandshakeContext(ctx); err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
}

22
dialer/tls/metadata.go Normal file
View File

@ -0,0 +1,22 @@
package tls
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
handshakeTimeout time.Duration
}
func (d *tlsDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
handshakeTimeout = "handshakeTimeout"
)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
return
}

17
dialer/udp/conn.go Normal file
View File

@ -0,0 +1,17 @@
package udp
import "net"
type conn struct {
*net.UDPConn
}
func (c *conn) WriteTo(b []byte, addr net.Addr) (int, error) {
return c.UDPConn.Write(b)
}
func (c *conn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
n, err = c.UDPConn.Read(b)
addr = c.RemoteAddr()
return
}

50
dialer/udp/dialer.go Normal file
View File

@ -0,0 +1,50 @@
package udp
import (
"context"
"net"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("udp", NewDialer)
}
type udpDialer struct {
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &udpDialer{
logger: options.Logger,
}
}
func (d *udpDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *udpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
c, err := options.NetDialer.Dial(ctx, "udp", addr)
if err != nil {
return nil, err
}
return &conn{
UDPConn: c.(*net.UDPConn),
}, nil
}

23
dialer/udp/metadata.go Normal file
View File

@ -0,0 +1,23 @@
package udp
import (
"time"
md "github.com/go-gost/core/metadata"
)
const (
dialTimeout = "dialTimeout"
)
const (
defaultDialTimeout = 5 * time.Second
)
type metadata struct {
dialTimeout time.Duration
}
func (d *udpDialer) parseMetadata(md md.Metadata) (err error) {
return
}

View File

@ -8,8 +8,8 @@ import (
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
ws_util "github.com/go-gost/x/internal/util/ws"
"github.com/go-gost/x/registry"
"github.com/gorilla/websocket"
)

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
const (
@ -40,27 +41,27 @@ func (d *wsDialer) parseMetadata(md mdata.Metadata) (err error) {
keepAlive = "keepAlive"
)
d.md.host = mdata.GetString(md, host)
d.md.host = mdx.GetString(md, host)
d.md.path = mdata.GetString(md, path)
d.md.path = mdx.GetString(md, path)
if d.md.path == "" {
d.md.path = defaultPath
}
d.md.handshakeTimeout = mdata.GetDuration(md, handshakeTimeout)
d.md.readHeaderTimeout = mdata.GetDuration(md, readHeaderTimeout)
d.md.readBufferSize = mdata.GetInt(md, readBufferSize)
d.md.writeBufferSize = mdata.GetInt(md, writeBufferSize)
d.md.enableCompression = mdata.GetBool(md, enableCompression)
d.md.handshakeTimeout = mdx.GetDuration(md, handshakeTimeout)
d.md.readHeaderTimeout = mdx.GetDuration(md, readHeaderTimeout)
d.md.readBufferSize = mdx.GetInt(md, readBufferSize)
d.md.writeBufferSize = mdx.GetInt(md, writeBufferSize)
d.md.enableCompression = mdx.GetBool(md, enableCompression)
if m := mdata.GetStringMapString(md, header); len(m) > 0 {
if m := mdx.GetStringMapString(md, header); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
d.md.keepAlive = mdata.GetDuration(md, keepAlive)
d.md.keepAlive = mdx.GetDuration(md, keepAlive)
return
}

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/docker/libcontainer v2.2.1+incompatible
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.7
github.com/go-gost/core v0.0.0-20220403142327-6340d5198f83
github.com/go-gost/core v0.0.0-20220404033031-04f6ed470873
github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7
github.com/go-gost/tls-dissector v0.0.2-0.20211125135007-2b5d5bd9c07e

2
go.sum
View File

@ -119,6 +119,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gost/core v0.0.0-20220403142327-6340d5198f83 h1:Tt11K5yA/qnxSY8SDH774PSE/VP4Jqes+ab79g/N4Uw=
github.com/go-gost/core v0.0.0-20220403142327-6340d5198f83/go.mod h1:oga1T7DJPJM+DpiQaZvTES9P9jvybRSgR/V5j+sEDpg=
github.com/go-gost/core v0.0.0-20220404033031-04f6ed470873 h1:u+g28xvN00bW5ivbhb2GGo0R+JIBy5arxy5R+rKesqk=
github.com/go-gost/core v0.0.0-20220404033031-04f6ed470873/go.mod h1:/LzdiQ+0+3FMhyqw0phjFjXFdOa1fcQR5/bL/7ripCs=
github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 h1:A95M6UWcfZgOuJkQ7QLfG0Hs5peWIUSysCDNz4pfe04=
github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7 h1:itaaJhQJ19kUXEB4Igb0EbY8m+1Py2AaNNSBds/9gk4=

115
handler/auto/handler.go Normal file
View File

@ -0,0 +1,115 @@
package auto
import (
"bufio"
"context"
"net"
"time"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks4"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/registry"
)
func init() {
registry.HandlerRegistry().Register("auto", NewHandler)
}
type autoHandler struct {
httpHandler handler.Handler
socks4Handler handler.Handler
socks5Handler handler.Handler
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
h := &autoHandler{
options: options,
}
if f := registry.HandlerRegistry().Get("http"); f != nil {
v := append(opts,
handler.LoggerOption(options.Logger.WithFields(map[string]any{"type": "http"})))
h.httpHandler = f(v...)
}
if f := registry.HandlerRegistry().Get("socks4"); f != nil {
v := append(opts,
handler.LoggerOption(options.Logger.WithFields(map[string]any{"type": "socks4"})))
h.socks4Handler = f(v...)
}
if f := registry.HandlerRegistry().Get("socks5"); f != nil {
v := append(opts,
handler.LoggerOption(options.Logger.WithFields(map[string]any{"type": "socks5"})))
h.socks5Handler = f(v...)
}
return h
}
func (h *autoHandler) Init(md md.Metadata) error {
if h.httpHandler != nil {
if err := h.httpHandler.Init(md); err != nil {
return err
}
}
if h.socks4Handler != nil {
if err := h.socks4Handler.Init(md); err != nil {
return err
}
}
if h.socks5Handler != nil {
if err := h.socks5Handler.Init(md); err != nil {
return err
}
}
return nil
}
func (h *autoHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
start := time.Now()
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
br := bufio.NewReader(conn)
b, err := br.Peek(1)
if err != nil {
log.Error(err)
conn.Close()
return err
}
conn = netpkg.NewBufferReaderConn(conn, br)
switch b[0] {
case gosocks4.Ver4: // socks4
if h.socks4Handler != nil {
return h.socks4Handler.Handle(ctx, conn)
}
case gosocks5.Ver5: // socks5
if h.socks5Handler != nil {
return h.socks5Handler.Handle(ctx, conn)
}
default: // http
if h.httpHandler != nil {
return h.httpHandler.Handle(ctx, conn)
}
}
return nil
}

View File

@ -12,13 +12,13 @@ import (
"github.com/go-gost/core/chain"
"github.com/go-gost/core/common/bufpool"
resolver_util "github.com/go-gost/core/common/util/resolver"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/hosts"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/core/resolver/exchanger"
resolver_util "github.com/go-gost/x/internal/util/resolver"
"github.com/go-gost/x/registry"
"github.com/go-gost/x/resolver/exchanger"
"github.com/miekg/dns"
)

View File

@ -5,9 +5,11 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
const (
defaultTimeout = 5 * time.Second
defaultBufferSize = 1024
)
@ -29,17 +31,17 @@ func (h *dnsHandler) parseMetadata(md mdata.Metadata) (err error) {
dns = "dns"
)
h.md.readTimeout = mdata.GetDuration(md, readTimeout)
h.md.ttl = mdata.GetDuration(md, ttl)
h.md.timeout = mdata.GetDuration(md, timeout)
h.md.readTimeout = mdx.GetDuration(md, readTimeout)
h.md.ttl = mdx.GetDuration(md, ttl)
h.md.timeout = mdx.GetDuration(md, timeout)
if h.md.timeout <= 0 {
h.md.timeout = 5 * time.Second
h.md.timeout = defaultTimeout
}
sip := mdata.GetString(md, clientIP)
sip := mdx.GetString(md, clientIP)
if sip != "" {
h.md.clientIP = net.ParseIP(sip)
}
h.md.dns = mdata.GetStrings(md, dns)
h.md.dns = mdx.GetStrings(md, dns)
return
}

View File

@ -0,0 +1,117 @@
package local
import (
"context"
"errors"
"fmt"
"net"
"time"
"github.com/go-gost/core/chain"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.HandlerRegistry().Register("tcp", NewHandler)
registry.HandlerRegistry().Register("udp", NewHandler)
registry.HandlerRegistry().Register("forward", NewHandler)
}
type forwardHandler struct {
group *chain.NodeGroup
router *chain.Router
md metadata
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &forwardHandler{
options: options,
}
}
func (h *forwardHandler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(md); err != nil {
return
}
if h.group == nil {
// dummy node used by relay connector.
h.group = chain.NewNodeGroup(&chain.Node{Name: "dummy", Addr: ":0"})
}
h.router = h.options.Router
if h.router == nil {
h.router = (&chain.Router{}).WithLogger(h.options.Logger)
}
return
}
// Forward implements handler.Forwarder.
func (h *forwardHandler) Forward(group *chain.NodeGroup) {
h.group = group
}
func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
defer conn.Close()
start := time.Now()
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
target := h.group.Next()
if target == nil {
err := errors.New("target not available")
log.Error(err)
return err
}
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", target.Addr, network),
})
log.Infof("%s >> %s", conn.RemoteAddr(), target.Addr)
cc, err := h.router.Dial(ctx, network, target.Addr)
if err != nil {
log.Error(err)
// TODO: the router itself may be failed due to the failed node in the router,
// the dead marker may be a wrong operation.
target.Marker.Mark()
return err
}
defer cc.Close()
target.Marker.Reset()
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), target.Addr)
netpkg.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", conn.RemoteAddr(), target.Addr)
return nil
}

View File

@ -0,0 +1,21 @@
package local
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
readTimeout time.Duration
}
func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
const (
readTimeout = "readTimeout"
)
h.md.readTimeout = mdx.GetDuration(md, readTimeout)
return
}

View File

@ -0,0 +1,111 @@
package remote
import (
"context"
"errors"
"fmt"
"net"
"time"
"github.com/go-gost/core/chain"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.HandlerRegistry().Register("rtcp", NewHandler)
registry.HandlerRegistry().Register("rudp", NewHandler)
}
type forwardHandler struct {
group *chain.NodeGroup
router *chain.Router
md metadata
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &forwardHandler{
options: options,
}
}
func (h *forwardHandler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(md); err != nil {
return
}
h.router = h.options.Router
if h.router == nil {
h.router = (&chain.Router{}).WithLogger(h.options.Logger)
}
return
}
// Forward implements handler.Forwarder.
func (h *forwardHandler) Forward(group *chain.NodeGroup) {
h.group = group
}
func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
defer conn.Close()
start := time.Now()
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
target := h.group.Next()
if target == nil {
err := errors.New("target not available")
log.Error(err)
return err
}
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", target.Addr, network),
})
log.Infof("%s >> %s", conn.RemoteAddr(), target.Addr)
cc, err := h.router.Dial(ctx, network, target.Addr)
if err != nil {
log.Error(err)
// TODO: the router itself may be failed due to the failed node in the router,
// the dead marker may be a wrong operation.
target.Marker.Mark()
return err
}
defer cc.Close()
target.Marker.Reset()
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), target.Addr)
netpkg.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", conn.RemoteAddr(), target.Addr)
return nil
}

View File

@ -0,0 +1,21 @@
package remote
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
readTimeout time.Duration
}
func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
const (
readTimeout = "readTimeout"
)
h.md.readTimeout = mdx.GetDuration(md, readTimeout)
return
}

337
handler/http/handler.go Normal file
View File

@ -0,0 +1,337 @@
package http
import (
"bufio"
"context"
"encoding/base64"
"encoding/binary"
"errors"
"hash/crc32"
"net"
"net/http"
"net/http/httputil"
"os"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
"github.com/go-gost/core/chain"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.HandlerRegistry().Register("http", NewHandler)
}
type httpHandler struct {
router *chain.Router
md metadata
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &httpHandler{
options: options,
}
}
func (h *httpHandler) Init(md md.Metadata) error {
if err := h.parseMetadata(md); err != nil {
return err
}
h.router = h.options.Router
if h.router == nil {
h.router = (&chain.Router{}).WithLogger(h.options.Logger)
}
return nil
}
func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
defer conn.Close()
start := time.Now()
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
req, err := http.ReadRequest(bufio.NewReader(conn))
if err != nil {
log.Error(err)
return err
}
defer req.Body.Close()
return h.handleRequest(ctx, conn, req, log)
}
func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *http.Request, log logger.Logger) error {
if h.md.sni && !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) {
req.URL.Scheme = "http"
}
network := req.Header.Get("X-Gost-Protocol")
if network != "udp" {
network = "tcp"
}
// Try to get the actual host.
// Compatible with GOST 2.x.
if v := req.Header.Get("Gost-Target"); v != "" {
if h, err := h.decodeServerName(v); err == nil {
req.Host = h
}
}
req.Header.Del("Gost-Target")
if v := req.Header.Get("X-Gost-Target"); v != "" {
if h, err := h.decodeServerName(v); err == nil {
req.Host = h
}
}
req.Header.Del("X-Gost-Target")
addr := req.Host
if _, port, _ := net.SplitHostPort(addr); port == "" {
addr = net.JoinHostPort(addr, "80")
}
fields := map[string]any{
"dst": addr,
}
if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization"), log); u != "" {
fields["user"] = u
}
log = log.WithFields(fields)
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpRequest(req, false)
log.Debug(string(dump))
}
log.Infof("%s >> %s", conn.RemoteAddr(), addr)
resp := &http.Response{
ProtoMajor: 1,
ProtoMinor: 1,
Header: h.md.header,
}
if resp.Header == nil {
resp.Header = http.Header{}
}
if h.options.Bypass != nil && h.options.Bypass.Contains(addr) {
resp.StatusCode = http.StatusForbidden
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
log.Info("bypass: ", addr)
return resp.Write(conn)
}
if !h.authenticate(conn, req, resp, log) {
return nil
}
if network == "udp" {
return h.handleUDP(ctx, conn, log)
}
if req.Method == "PRI" ||
(req.Method != http.MethodConnect && req.URL.Scheme != "http") {
resp.StatusCode = http.StatusBadRequest
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
return resp.Write(conn)
}
req.Header.Del("Proxy-Authorization")
cc, err := h.router.Dial(ctx, network, addr)
if err != nil {
resp.StatusCode = http.StatusServiceUnavailable
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
resp.Write(conn)
return err
}
defer cc.Close()
if req.Method == http.MethodConnect {
resp.StatusCode = http.StatusOK
resp.Status = "200 Connection established"
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
if err = resp.Write(conn); err != nil {
log.Error(err)
return err
}
} else {
req.Header.Del("Proxy-Connection")
if err = req.Write(cc); err != nil {
log.Error(err)
return err
}
}
start := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), addr)
netpkg.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >-< %s", conn.RemoteAddr(), addr)
return nil
}
func (h *httpHandler) decodeServerName(s string) (string, error) {
b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil {
return "", err
}
if len(b) < 4 {
return "", errors.New("invalid name")
}
v, err := base64.RawURLEncoding.DecodeString(string(b[4:]))
if err != nil {
return "", err
}
if crc32.ChecksumIEEE(v) != binary.BigEndian.Uint32(b[:4]) {
return "", errors.New("invalid name")
}
return string(v), nil
}
func (h *httpHandler) basicProxyAuth(proxyAuth string, log logger.Logger) (username, password string, ok bool) {
if proxyAuth == "" {
return
}
if !strings.HasPrefix(proxyAuth, "Basic ") {
return
}
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(proxyAuth, "Basic "))
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.Response, log logger.Logger) (ok bool) {
u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization"), log)
if h.options.Auther == nil || h.options.Auther.Authenticate(u, p) {
return true
}
pr := h.md.probeResistance
// probing resistance is enabled, and knocking host is mismatch.
if pr != nil && (pr.Knock == "" || !strings.EqualFold(req.URL.Hostname(), pr.Knock)) {
resp.StatusCode = http.StatusServiceUnavailable // default status code
switch pr.Type {
case "code":
resp.StatusCode, _ = strconv.Atoi(pr.Value)
case "web":
url := pr.Value
if !strings.HasPrefix(url, "http") {
url = "http://" + url
}
r, err := http.Get(url)
if err != nil {
log.Error(err)
break
}
resp = r
defer resp.Body.Close()
case "host":
cc, err := net.Dial("tcp", pr.Value)
if err != nil {
log.Error(err)
break
}
defer cc.Close()
req.Write(cc)
netpkg.Transport(conn, cc)
return
case "file":
f, _ := os.Open(pr.Value)
if f != nil {
defer f.Close()
resp.StatusCode = http.StatusOK
if finfo, _ := f.Stat(); finfo != nil {
resp.ContentLength = finfo.Size()
}
resp.Header.Set("Content-Type", "text/html")
resp.Body = f
}
}
}
if resp.Header == nil {
resp.Header = http.Header{}
}
if resp.StatusCode == 0 {
resp.StatusCode = http.StatusProxyAuthRequired
resp.Header.Add("Proxy-Authenticate", "Basic realm=\"gost\"")
if strings.ToLower(req.Header.Get("Proxy-Connection")) == "keep-alive" {
// XXX libcurl will keep sending auth request in same conn
// which we don't supported yet.
resp.Header.Add("Connection", "close")
resp.Header.Add("Proxy-Connection", "close")
}
log.Info("proxy authentication required")
} else {
resp.Header.Set("Server", "nginx/1.20.1")
resp.Header.Set("Date", time.Now().Format(http.TimeFormat))
if resp.StatusCode == http.StatusOK {
resp.Header.Set("Connection", "keep-alive")
}
}
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
resp.Write(conn)
return
}

54
handler/http/metadata.go Normal file
View File

@ -0,0 +1,54 @@
package http
import (
"net/http"
"strings"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
probeResistance *probeResistance
sni bool
enableUDP bool
header http.Header
}
func (h *httpHandler) parseMetadata(md mdata.Metadata) error {
const (
header = "header"
probeResistKey = "probeResistance"
knock = "knock"
sni = "sni"
enableUDP = "udp"
)
if m := mdx.GetStringMapString(md, header); len(m) > 0 {
hd := http.Header{}
for k, v := range m {
hd.Add(k, v)
}
h.md.header = hd
}
if v := mdx.GetString(md, probeResistKey); v != "" {
if ss := strings.SplitN(v, ":", 2); len(ss) == 2 {
h.md.probeResistance = &probeResistance{
Type: ss[0],
Value: ss[1],
Knock: mdx.GetString(md, knock),
}
}
}
h.md.sni = mdx.GetBool(md, sni)
h.md.enableUDP = mdx.GetBool(md, enableUDP)
return nil
}
type probeResistance struct {
Type string
Value string
Knock string
}

80
handler/http/udp.go Normal file
View File

@ -0,0 +1,80 @@
package http
import (
"context"
"errors"
"net"
"net/http"
"net/http/httputil"
"time"
"github.com/go-gost/core/common/net/relay"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/internal/util/socks"
)
func (h *httpHandler) handleUDP(ctx context.Context, conn net.Conn, log logger.Logger) error {
log = log.WithFields(map[string]any{
"cmd": "udp",
})
resp := &http.Response{
ProtoMajor: 1,
ProtoMinor: 1,
Header: h.md.header,
}
if resp.Header == nil {
resp.Header = http.Header{}
}
if !h.md.enableUDP {
resp.StatusCode = http.StatusForbidden
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
log.Error("http: UDP relay is disabled")
return resp.Write(conn)
}
resp.StatusCode = http.StatusOK
if log.IsLevelEnabled(logger.DebugLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Debug(string(dump))
}
if err := resp.Write(conn); err != nil {
log.Error(err)
return err
}
// obtain a udp connection
c, err := h.router.Dial(ctx, "udp", "") // UDP association
if err != nil {
log.Error(err)
return err
}
defer c.Close()
pc, ok := c.(net.PacketConn)
if !ok {
err = errors.New("wrong connection type")
log.Error(err)
return err
}
relay := relay.NewUDPRelay(socks.UDPTunServerConn(conn), pc).
WithBypass(h.options.Bypass).
WithLogger(log)
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), pc.LocalAddr())
relay.Run()
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", conn.RemoteAddr(), pc.LocalAddr())
return nil
}

View File

@ -23,7 +23,7 @@ import (
"github.com/go-gost/core/handler"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -5,6 +5,7 @@ import (
"strings"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -19,7 +20,7 @@ func (h *http2Handler) parseMetadata(md mdata.Metadata) error {
knock = "knock"
)
if m := mdata.GetStringMapString(md, header); len(m) > 0 {
if m := mdx.GetStringMapString(md, header); len(m) > 0 {
hd := http.Header{}
for k, v := range m {
hd.Add(k, v)
@ -27,12 +28,12 @@ func (h *http2Handler) parseMetadata(md mdata.Metadata) error {
h.md.header = hd
}
if v := mdata.GetString(md, probeResistKey); v != "" {
if v := mdx.GetString(md, probeResistKey); v != "" {
if ss := strings.SplitN(v, ":", 2); len(ss) == 2 {
h.md.probeResistance = &probeResistance{
Type: ss[0],
Value: ss[1],
Knock: mdata.GetString(md, knock),
Knock: mdx.GetString(md, knock),
}
}
}

View File

@ -18,8 +18,8 @@ import (
"github.com/go-gost/core/handler"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
dissector "github.com/go-gost/tls-dissector"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -2,6 +2,7 @@ package redirect
import (
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -14,7 +15,7 @@ func (h *redirectHandler) parseMetadata(md mdata.Metadata) (err error) {
sniffing = "sniffing"
tproxy = "tproxy"
)
h.md.sniffing = mdata.GetBool(md, sniffing)
h.md.tproxy = mdata.GetBool(md, tproxy)
h.md.sniffing = mdx.GetBool(md, sniffing)
h.md.tproxy = mdx.GetBool(md, tproxy)
return
}

View File

@ -10,7 +10,7 @@ import (
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -10,8 +10,8 @@ import (
"github.com/go-gost/core/chain"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
"github.com/go-gost/relay"
"github.com/go-gost/x/registry"
)
var (

View File

@ -5,6 +5,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -22,11 +23,11 @@ func (h *relayHandler) parseMetadata(md mdata.Metadata) (err error) {
noDelay = "nodelay"
)
h.md.readTimeout = mdata.GetDuration(md, readTimeout)
h.md.enableBind = mdata.GetBool(md, enableBind)
h.md.noDelay = mdata.GetBool(md, noDelay)
h.md.readTimeout = mdx.GetDuration(md, readTimeout)
h.md.enableBind = mdx.GetBool(md, enableBind)
h.md.noDelay = mdx.GetBool(md, noDelay)
if bs := mdata.GetInt(md, udpBufferSize); bs > 0 {
if bs := mdx.GetInt(md, udpBufferSize); bs > 0 {
h.md.udpBufferSize = int(math.Min(math.Max(float64(bs), 512), 64*1024))
} else {
h.md.udpBufferSize = 1500

View File

@ -16,8 +16,8 @@ import (
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/registry"
dissector "github.com/go-gost/tls-dissector"
"github.com/go-gost/x/registry"
)
func init() {

View File

@ -4,6 +4,7 @@ import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
@ -15,6 +16,6 @@ func (h *sniHandler) parseMetadata(md mdata.Metadata) (err error) {
readTimeout = "readTimeout"
)
h.md.readTimeout = mdata.GetDuration(md, readTimeout)
h.md.readTimeout = mdx.GetDuration(md, readTimeout)
return
}

152
handler/socks/v4/handler.go Normal file
View File

@ -0,0 +1,152 @@
package v4
import (
"context"
"errors"
"net"
"time"
"github.com/go-gost/core/chain"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks4"
"github.com/go-gost/x/registry"
)
var (
ErrUnknownCmd = errors.New("socks4: unknown command")
ErrUnimplemented = errors.New("socks4: unimplemented")
)
func init() {
registry.HandlerRegistry().Register("socks4", NewHandler)
registry.HandlerRegistry().Register("socks4a", NewHandler)
}
type socks4Handler struct {
router *chain.Router
md metadata
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks4Handler{
options: options,
}
}
func (h *socks4Handler) Init(md md.Metadata) (err error) {
if err := h.parseMetadata(md); err != nil {
return err
}
h.router = h.options.Router
if h.router == nil {
h.router = (&chain.Router{}).WithLogger(h.options.Logger)
}
return nil
}
func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
defer conn.Close()
start := time.Now()
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
if h.md.readTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
}
req, err := gosocks4.ReadRequest(conn)
if err != nil {
log.Error(err)
return err
}
log.Debug(req)
conn.SetReadDeadline(time.Time{})
if h.options.Auther != nil &&
!h.options.Auther.Authenticate(string(req.Userid), "") {
resp := gosocks4.NewReply(gosocks4.RejectedUserid, nil)
log.Debug(resp)
return resp.Write(conn)
}
switch req.Cmd {
case gosocks4.CmdConnect:
return h.handleConnect(ctx, conn, req, log)
case gosocks4.CmdBind:
return h.handleBind(ctx, conn, req)
default:
err = ErrUnknownCmd
log.Error(err)
return err
}
}
func (h *socks4Handler) handleConnect(ctx context.Context, conn net.Conn, req *gosocks4.Request, log logger.Logger) error {
addr := req.Addr.String()
log = log.WithFields(map[string]any{
"dst": addr,
})
log.Infof("%s >> %s", conn.RemoteAddr(), addr)
if h.options.Bypass != nil && h.options.Bypass.Contains(addr) {
resp := gosocks4.NewReply(gosocks4.Rejected, nil)
log.Debug(resp)
log.Info("bypass: ", addr)
return resp.Write(conn)
}
cc, err := h.router.Dial(ctx, "tcp", addr)
if err != nil {
resp := gosocks4.NewReply(gosocks4.Failed, nil)
resp.Write(conn)
log.Debug(resp)
return err
}
defer cc.Close()
resp := gosocks4.NewReply(gosocks4.Granted, nil)
if err := resp.Write(conn); err != nil {
log.Error(err)
return err
}
log.Debug(resp)
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), addr)
netpkg.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", conn.RemoteAddr(), addr)
return nil
}
func (h *socks4Handler) handleBind(ctx context.Context, conn net.Conn, req *gosocks4.Request) error {
// TODO: bind
return ErrUnimplemented
}

View File

@ -0,0 +1,21 @@
package v4
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdx "github.com/go-gost/x/metadata"
)
type metadata struct {
readTimeout time.Duration
}
func (h *socks4Handler) parseMetadata(md mdata.Metadata) (err error) {
const (
readTimeout = "readTimeout"
)
h.md.readTimeout = mdx.GetDuration(md, readTimeout)
return
}

149
handler/socks/v5/bind.go Normal file
View File

@ -0,0 +1,149 @@
package v5
import (
"context"
"fmt"
"net"
"time"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
)
func (h *socks5Handler) handleBind(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error {
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", address, network),
"cmd": "bind",
})
log.Infof("%s >> %s", conn.RemoteAddr(), address)
if !h.md.enableBind {
reply := gosocks5.NewReply(gosocks5.NotAllowed, nil)
log.Debug(reply)
log.Error("socks5: BIND is disabled")
return reply.Write(conn)
}
// BIND does not support chain.
return h.bindLocal(ctx, conn, network, address, log)
}
func (h *socks5Handler) bindLocal(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error {
ln, err := net.Listen(network, address) // strict mode: if the port already in use, it will return error
if err != nil {
log.Error(err)
reply := gosocks5.NewReply(gosocks5.Failure, nil)
if err := reply.Write(conn); err != nil {
log.Error(err)
}
log.Debug(reply)
return err
}
socksAddr := gosocks5.Addr{}
if err := socksAddr.ParseFrom(ln.Addr().String()); err != nil {
log.Warn(err)
}
// Issue: may not reachable when host has multi-interface
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
socksAddr.Type = 0
reply := gosocks5.NewReply(gosocks5.Succeeded, &socksAddr)
if err := reply.Write(conn); err != nil {
log.Error(err)
ln.Close()
return err
}
log.Debug(reply)
log = log.WithFields(map[string]any{
"bind": fmt.Sprintf("%s/%s", ln.Addr(), ln.Addr().Network()),
})
log.Debugf("bind on %s OK", ln.Addr())
h.serveBind(ctx, conn, ln, log)
return nil
}
func (h *socks5Handler) serveBind(ctx context.Context, conn net.Conn, ln net.Listener, log logger.Logger) {
var rc net.Conn
accept := func() <-chan error {
errc := make(chan error, 1)
go func() {
defer close(errc)
defer ln.Close()
c, err := ln.Accept()
if err != nil {
errc <- err
}
rc = c
}()
return errc
}
pc1, pc2 := net.Pipe()
pipe := func() <-chan error {
errc := make(chan error, 1)
go func() {
defer close(errc)
defer pc1.Close()
errc <- netpkg.Transport(conn, pc1)
}()
return errc
}
defer pc2.Close()
select {
case err := <-accept():
if err != nil {
log.Error(err)
reply := gosocks5.NewReply(gosocks5.Failure, nil)
if err := reply.Write(pc2); err != nil {
log.Error(err)
}
log.Debug(reply)
return
}
defer rc.Close()
log.Debugf("peer %s accepted", rc.RemoteAddr())
log = log.WithFields(map[string]any{
"local": rc.LocalAddr().String(),
"remote": rc.RemoteAddr().String(),
})
raddr := gosocks5.Addr{}
raddr.ParseFrom(rc.RemoteAddr().String())
reply := gosocks5.NewReply(gosocks5.Succeeded, &raddr)
if err := reply.Write(pc2); err != nil {
log.Error(err)
}
log.Debug(reply)
start := time.Now()
log.Infof("%s <-> %s", rc.LocalAddr(), rc.RemoteAddr())
netpkg.Transport(pc2, rc)
log.WithFields(map[string]any{"duration": time.Since(start)}).
Infof("%s >-< %s", rc.LocalAddr(), rc.RemoteAddr())
case err := <-pipe():
if err != nil {
log.Error(err)
}
ln.Close()
return
}
}

View File

@ -0,0 +1,53 @@
package v5
import (
"context"
"fmt"
"net"
"time"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
)
func (h *socks5Handler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error {
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", address, network),
"cmd": "connect",
})
log.Infof("%s >> %s", conn.RemoteAddr(), address)
if h.options.Bypass != nil && h.options.Bypass.Contains(address) {
resp := gosocks5.NewReply(gosocks5.NotAllowed, nil)
log.Debug(resp)
log.Info("bypass: ", address)
return resp.Write(conn)
}
cc, err := h.router.Dial(ctx, network, address)
if err != nil {
resp := gosocks5.NewReply(gosocks5.NetUnreachable, nil)
log.Debug(resp)
resp.Write(conn)
return err
}
defer cc.Close()
resp := gosocks5.NewReply(gosocks5.Succeeded, nil)
if err := resp.Write(conn); err != nil {
log.Error(err)
return err
}
log.Debug(resp)
t := time.Now()
log.Infof("%s <-> %s", conn.RemoteAddr(), address)
netpkg.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Infof("%s >-< %s", conn.RemoteAddr(), address)
return nil
}

115
handler/socks/v5/handler.go Normal file
View File

@ -0,0 +1,115 @@
package v5
import (
"context"
"errors"
"net"
"time"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
var (
ErrUnknownCmd = errors.New("socks5: unknown command")
)
func init() {
registry.HandlerRegistry().Register("socks5", NewHandler)
registry.HandlerRegistry().Register("socks", NewHandler)
}
type socks5Handler struct {
selector gosocks5.Selector
router *chain.Router
md metadata
options handler.Options
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks5Handler{
options: options,
}
}
func (h *socks5Handler) Init(md md.Metadata) (err error) {
if err = h.parseMetadata(md); err != nil {
return
}
h.router = h.options.Router
if h.router == nil {
h.router = (&chain.Router{}).WithLogger(h.options.Logger)
}
h.selector = &serverSelector{
Authenticator: h.options.Auther,
TLSConfig: h.options.TLSConfig,
logger: h.options.Logger,
noTLS: h.md.noTLS,
}
return
}
func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
defer conn.Close()
start := time.Now()
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
if h.md.readTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
}
conn = gosocks5.ServerConn(conn, h.selector)
req, err := gosocks5.ReadRequest(conn)
if err != nil {
log.Error(err)
return err
}
log.Debug(req)
conn.SetReadDeadline(time.Time{})
address := req.Addr.String()
switch req.Cmd {
case gosocks5.CmdConnect:
return h.handleConnect(ctx, conn, "tcp", address, log)
case gosocks5.CmdBind:
return h.handleBind(ctx, conn, "tcp", address, log)
case socks.CmdMuxBind:
return h.handleMuxBind(ctx, conn, "tcp", address, log)
case gosocks5.CmdUdp:
return h.handleUDP(ctx, conn, log)
case socks.CmdUDPTun:
return h.handleUDPTun(ctx, conn, "udp", address, log)
default:
err = ErrUnknownCmd
log.Error(err)
resp := gosocks5.NewReply(gosocks5.CmdUnsupported, nil)
resp.Write(conn)
log.Debug(resp)
return err
}
}

133
handler/socks/v5/mbind.go Normal file
View File

@ -0,0 +1,133 @@
package v5
import (
"context"
"fmt"
"net"
"time"
netpkg "github.com/go-gost/core/common/net"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/mux"
)
func (h *socks5Handler) handleMuxBind(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error {
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", address, network),
"cmd": "mbind",
})
log.Infof("%s >> %s", conn.RemoteAddr(), address)
if !h.md.enableBind {
reply := gosocks5.NewReply(gosocks5.NotAllowed, nil)
log.Debug(reply)
log.Error("socks5: BIND is disabled")
return reply.Write(conn)
}
return h.muxBindLocal(ctx, conn, network, address, log)
}
func (h *socks5Handler) muxBindLocal(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) error {
ln, err := net.Listen(network, address) // strict mode: if the port already in use, it will return error
if err != nil {
log.Error(err)
reply := gosocks5.NewReply(gosocks5.Failure, nil)
if err := reply.Write(conn); err != nil {
log.Error(err)
}
log.Debug(reply)
return err
}
socksAddr := gosocks5.Addr{}
err = socksAddr.ParseFrom(ln.Addr().String())
if err != nil {
log.Warn(err)
}
// Issue: may not reachable when host has multi-interface
socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String())
socksAddr.Type = 0
reply := gosocks5.NewReply(gosocks5.Succeeded, &socksAddr)
if err := reply.Write(conn); err != nil {
log.Error(err)
ln.Close()
return err
}
log.Debug(reply)
log = log.WithFields(map[string]any{
"bind": fmt.Sprintf("%s/%s", ln.Addr(), ln.Addr().Network()),
})
log.Debugf("bind on %s OK", ln.Addr())
return h.serveMuxBind(ctx, conn, ln, log)
}
func (h *socks5Handler) serveMuxBind(ctx context.Context, conn net.Conn, ln net.Listener, log logger.Logger) error {
// Upgrade connection to multiplex stream.
session, err := mux.ClientSession(conn)
if err != nil {
log.Error(err)
return err
}
defer session.Close()
go func() {
defer ln.Close()
for {
conn, err := session.Accept()
if err != nil {
log.Error(err)
return
}
conn.Close() // we do not handle incoming connections.
}
}()
for {
rc, err := ln.Accept()
if err != nil {
log.Error(err)
return err
}
log.Debugf("peer %s accepted", rc.RemoteAddr())
go func(c net.Conn) {
defer c.Close()
log = log.WithFields(map[string]any{
"local": rc.LocalAddr().String(),
"remote": rc.RemoteAddr().String(),
})
sc, err := session.GetConn()
if err != nil {
log.Error(err)
return
}
defer sc.Close()
// incompatible with GOST v2.x
if !h.md.compatibilityMode {
addr := gosocks5.Addr{}
addr.ParseFrom(c.RemoteAddr().String())
reply := gosocks5.NewReply(gosocks5.Succeeded, &addr)
if err := reply.Write(sc); err != nil {
log.Error(err)
return
}
log.Debug(reply)
}
t := time.Now()
log.Infof("%s <-> %s", c.LocalAddr(), c.RemoteAddr())
netpkg.Transport(sc, c)
log.WithFields(map[string]any{"duration": time.Since(t)}).
Infof("%s >-< %s", c.LocalAddr(), c.RemoteAddr())
}(rc)
}
}

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