This commit is contained in:
qaq
2025-06-17 12:17:33 +08:00
parent 4a6b2c8e0f
commit 9fa968d07c
648 changed files with 64968 additions and 1621 deletions
+41
View File
@@ -0,0 +1,41 @@
package direct
import (
"errors"
"net"
"time"
)
type conn struct{}
func (c *conn) Close() error {
return nil
}
func (c *conn) Read(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "read", Net: "nop", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}
func (c *conn) Write(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "write", Net: "nop", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}
func (c *conn) LocalAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) RemoteAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+39
View File
@@ -0,0 +1,39 @@
package direct
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("direct", NewDialer)
registry.DialerRegistry().Register("virtual", NewDialer)
}
type directDialer struct {
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &directDialer{
logger: options.Logger,
}
}
func (d *directDialer) Init(md md.Metadata) (err error) {
return
}
func (d *directDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
return &conn{}, nil
}
+74
View File
@@ -0,0 +1,74 @@
package dtls
import (
"context"
"crypto/tls"
"net"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
xdtls "github.com/go-gost/x/internal/util/dtls"
"github.com/go-gost/x/registry"
"github.com/pion/dtls/v2"
)
func init() {
registry.DialerRegistry().Register("dtls", NewDialer)
}
type dtlsDialer 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 &dtlsDialer{
logger: options.Logger,
options: options,
}
}
func (d *dtlsDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *dtlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err := options.Dialer.Dial(ctx, "udp", addr)
if err != nil {
return nil, err
}
tlsCfg := d.options.TLSConfig
if tlsCfg == nil {
tlsCfg = &tls.Config{
InsecureSkipVerify: true,
}
}
config := dtls.Config{
Certificates: tlsCfg.Certificates,
InsecureSkipVerify: tlsCfg.InsecureSkipVerify,
ExtendedMasterSecret: dtls.RequireExtendedMasterSecret,
ServerName: tlsCfg.ServerName,
RootCAs: tlsCfg.RootCAs,
FlightInterval: d.md.flightInterval,
MTU: d.md.mtu,
}
c, err := dtls.ClientWithContext(ctx, conn, &config)
if err != nil {
return nil, err
}
return xdtls.Conn(c, d.md.bufferSize), nil
}
+28
View File
@@ -0,0 +1,28 @@
package dtls
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
defaultBufferSize = 1200
)
type metadata struct {
mtu int
bufferSize int
flightInterval time.Duration
}
func (d *dtlsDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.mtu = mdutil.GetInt(md, "dtls.mtu", "mtu")
d.md.bufferSize = mdutil.GetInt(md, "dtls.bufferSize", "bufferSize")
if d.md.bufferSize <= 0 {
d.md.bufferSize = defaultBufferSize
}
d.md.flightInterval = mdutil.GetDuration(md, "dtls.flightInterval", "flightInterval")
return
}
+44
View File
@@ -0,0 +1,44 @@
package ftcp
import (
"net"
"time"
)
type fakeTCPConn struct {
raddr net.Addr
pc net.PacketConn
}
func (c *fakeTCPConn) Read(b []byte) (n int, err error) {
n, _, err = c.pc.ReadFrom(b)
return
}
func (c *fakeTCPConn) Write(b []byte) (n int, err error) {
return c.pc.WriteTo(b, c.raddr)
}
func (c *fakeTCPConn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *fakeTCPConn) LocalAddr() net.Addr {
return c.pc.LocalAddr()
}
func (c *fakeTCPConn) SetDeadline(t time.Time) error {
return c.pc.SetDeadline(t)
}
func (c *fakeTCPConn) SetReadDeadline(t time.Time) error {
return c.pc.SetReadDeadline(t)
}
func (c *fakeTCPConn) SetWriteDeadline(t time.Time) error {
return c.pc.SetWriteDeadline(t)
}
func (c *fakeTCPConn) Close() error {
return c.pc.Close()
}
+51
View File
@@ -0,0 +1,51 @@
package ftcp
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"
"github.com/xtaci/tcpraw"
)
func init() {
registry.DialerRegistry().Register("ftcp", NewDialer)
}
type ftcpDialer struct {
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &ftcpDialer{
logger: options.Logger,
}
}
func (d *ftcpDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *ftcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
raddr, er := net.ResolveTCPAddr("tcp", addr)
if er != nil {
return nil, er
}
c, err := tcpraw.Dial("tcp", addr)
if err != nil {
return
}
return &fakeTCPConn{
raddr: raddr,
pc: c,
}, nil
}
+11
View File
@@ -0,0 +1,11 @@
package ftcp
import (
md "github.com/go-gost/core/metadata"
)
type metadata struct{}
func (d *ftcpDialer) parseMetadata(md md.Metadata) (err error) {
return
}
+81
View File
@@ -0,0 +1,81 @@
package grpc
import (
"context"
"errors"
"net"
"time"
pb "github.com/go-gost/x/internal/util/grpc/proto"
)
type conn struct {
c pb.GostTunel_TunnelClient
rb []byte
localAddr net.Addr
remoteAddr net.Addr
cancelFunc context.CancelFunc
}
func (c *conn) Read(b []byte) (n int, err error) {
select {
case <-c.c.Context().Done():
err = c.c.Context().Err()
return
default:
}
if len(c.rb) == 0 {
chunk, err := c.c.Recv()
if err != nil {
return 0, err
}
c.rb = chunk.Data
}
n = copy(b, c.rb)
c.rb = c.rb[n:]
return
}
func (c *conn) Write(b []byte) (n int, err error) {
select {
case <-c.c.Context().Done():
err = c.c.Context().Err()
return
default:
}
if err = c.c.Send(&pb.Chunk{
Data: b,
}); err != nil {
return
}
n = len(b)
return
}
func (c *conn) Close() error {
defer c.cancelFunc()
return c.c.CloseSend()
}
func (c *conn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *conn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "grpc", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "grpc", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "grpc", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+118
View File
@@ -0,0 +1,118 @@
package grpc
import (
"context"
"net"
"sync"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
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"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
)
func init() {
registry.DialerRegistry().Register("grpc", NewDialer)
}
type grpcDialer struct {
clients map[string]pb.GostTunelClientX
clientMutex sync.Mutex
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &grpcDialer{
clients: make(map[string]pb.GostTunelClientX),
options: options,
}
}
func (d *grpcDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
// Multiplex implements dialer.Multiplexer interface.
func (d *grpcDialer) Multiplex() bool {
return true
}
func (d *grpcDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
d.clientMutex.Lock()
defer d.clientMutex.Unlock()
client, ok := d.clients[addr]
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
host := d.md.host
if host == "" {
host = options.Host
}
if h, _, _ := net.SplitHostPort(host); h != "" {
host = h
}
// d.options.Logger.Infof("grpc dialer, addr %s, host %s/%s", addr, d.md.host, options.Host)
grpcOpts := []grpc.DialOption{
// grpc.WithBlock(),
grpc.WithContextDialer(func(c context.Context, s string) (net.Conn, error) {
return options.Dialer.Dial(c, "tcp", s)
}),
grpc.WithAuthority(host),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
MinConnectTimeout: d.md.minConnectTimeout,
}),
}
if !d.md.insecure {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(credentials.NewTLS(d.options.TLSConfig)))
} else {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
if d.md.keepalive {
grpcOpts = append(grpcOpts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: d.md.keepaliveTime,
Timeout: d.md.keepaliveTimeout,
PermitWithoutStream: d.md.keepalivePermitWithoutStream,
}))
}
cc, err := grpc.NewClient(addr, grpcOpts...)
if err != nil {
d.options.Logger.Error(err)
return nil, err
}
client = pb.NewGostTunelClientX(cc)
d.clients[addr] = client
}
ctx2, cancel := context.WithCancel(context.Background())
cli, err := client.TunnelX(ctx2, d.md.path)
if err != nil {
cancel()
return nil, err
}
return &conn{
c: cli,
localAddr: &net.TCPAddr{},
remoteAddr: &net.TCPAddr{},
cancelFunc: cancel,
}, nil
}
+43
View File
@@ -0,0 +1,43 @@
package grpc
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
insecure bool
host string
path string
keepalive bool
keepaliveTime time.Duration
keepaliveTimeout time.Duration
keepalivePermitWithoutStream bool
minConnectTimeout time.Duration
}
func (d *grpcDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.insecure = mdutil.GetBool(md, "grpc.insecure", "grpcInsecure", "insecure")
d.md.host = mdutil.GetString(md, "grpc.authority", "grpc.host", "host")
d.md.path = mdutil.GetString(md, "grpc.path", "path")
d.md.keepalive = mdutil.GetBool(md, "grpc.keepalive", "keepalive", "keepAlive")
if d.md.keepalive {
d.md.keepaliveTime = mdutil.GetDuration(md, "grpc.keepalive.time", "keepalive.time")
if d.md.keepaliveTime <= 0 {
d.md.keepaliveTime = 30 * time.Second
}
d.md.keepaliveTimeout = mdutil.GetDuration(md, "grpc.keepalive.timeout", "keepalive.timeout")
if d.md.keepaliveTimeout <= 0 {
d.md.keepaliveTimeout = 30 * time.Second
}
d.md.keepalivePermitWithoutStream = mdutil.GetBool(md, "grpc.keepalive.permitWithoutStream", "keepalive.permitWithoutStream")
}
d.md.minConnectTimeout = mdutil.GetDuration(md, "grpc.minConnectTimeout", "minConnectTimeout")
if d.md.minConnectTimeout <= 0 {
d.md.minConnectTimeout = 30 * time.Second
}
return
}
+57
View File
@@ -0,0 +1,57 @@
package http2
import (
"errors"
"net"
"time"
mdata "github.com/go-gost/core/metadata"
)
// a dummy HTTP2 client conn used by HTTP2 client connector
type conn struct {
localAddr net.Addr
remoteAddr net.Addr
onClose func()
md mdata.Metadata
}
func (c *conn) Close() error {
if c.onClose != nil {
c.onClose()
}
return nil
}
func (c *conn) Read(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "read", Net: "nop", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}
func (c *conn) Write(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "write", Net: "nop", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}
func (c *conn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *conn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
// Metadata implements metadata.Metadatable interface.
func (c *conn) Metadata() mdata.Metadata {
return c.md
}
+118
View File
@@ -0,0 +1,118 @@
package http2
import (
"context"
"net"
"net/http"
"sync"
"time"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
net_dialer "github.com/go-gost/x/internal/net/dialer"
mdx "github.com/go-gost/x/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("http2", NewDialer)
}
type http2Dialer struct {
clients map[string]*http.Client
clientMutex sync.Mutex
logger logger.Logger
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &http2Dialer{
clients: make(map[string]*http.Client),
logger: options.Logger,
options: options,
}
}
func (d *http2Dialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *http2Dialer) Multiplex() bool {
return true
}
func (d *http2Dialer) Dial(ctx context.Context, address string, opts ...dialer.DialOption) (net.Conn, error) {
raddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
d.logger.Error(err)
return nil, err
}
d.clientMutex.Lock()
defer d.clientMutex.Unlock()
client, ok := d.clients[address]
if !ok {
options := dialer.DialOptions{}
for _, opt := range opts {
opt(&options)
}
{
// Check whether the connection is established properly
netd := options.Dialer
if netd == nil {
netd = net_dialer.DefaultNetDialer
}
conn, err := netd.Dial(ctx, "tcp", address)
if err != nil {
return nil, err
}
conn.Close()
}
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: d.options.TLSConfig,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
netd := options.Dialer
if netd == nil {
netd = net_dialer.DefaultNetDialer
}
return netd.Dial(ctx, network, addr)
},
ForceAttemptHTTP2: true,
MaxIdleConns: 16,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 30 * time.Second,
ExpectContinueTimeout: 15 * time.Second,
},
}
d.clients[address] = client
}
var c net.Conn = &conn{
localAddr: &net.TCPAddr{},
remoteAddr: raddr,
onClose: func() {
d.clientMutex.Lock()
defer d.clientMutex.Unlock()
delete(d.clients, address)
},
md: mdx.NewMetadata(map[string]any{"client": client}),
}
return c, nil
}
+54
View File
@@ -0,0 +1,54 @@
package h2
import (
"errors"
"io"
"net"
"time"
)
// HTTP2 connection, wrapped up just like a net.Conn.
type http2Conn struct {
r io.Reader
w io.Writer
remoteAddr net.Addr
localAddr net.Addr
}
func (c *http2Conn) Read(b []byte) (n int, err error) {
return c.r.Read(b)
}
func (c *http2Conn) Write(b []byte) (n int, err error) {
return c.w.Write(b)
}
func (c *http2Conn) Close() (err error) {
if r, ok := c.r.(io.Closer); ok {
err = r.Close()
}
if w, ok := c.w.(io.Closer); ok {
err = w.Close()
}
return
}
func (c *http2Conn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *http2Conn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *http2Conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "h2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *http2Conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "h2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *http2Conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "h2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+170
View File
@@ -0,0 +1,170 @@
package h2
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"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"
"golang.org/x/net/http2"
)
func init() {
registry.DialerRegistry().Register("h2", NewTLSDialer)
registry.DialerRegistry().Register("h2c", NewDialer)
}
type h2Dialer struct {
clients map[string]*http.Client
clientMutex sync.Mutex
h2c bool
logger logger.Logger
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &h2Dialer{
h2c: true,
clients: make(map[string]*http.Client),
logger: options.Logger,
options: options,
}
}
func NewTLSDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &h2Dialer{
clients: make(map[string]*http.Client),
logger: options.Logger,
options: options,
}
}
func (d *h2Dialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *h2Dialer) Multiplex() bool {
return true
}
func (d *h2Dialer) Dial(ctx context.Context, address string, opts ...dialer.DialOption) (net.Conn, error) {
raddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
d.logger.Error(err)
return nil, err
}
d.clientMutex.Lock()
client, ok := d.clients[address]
if !ok {
options := &dialer.DialOptions{}
for _, opt := range opts {
opt(options)
}
client = &http.Client{}
if d.h2c {
client.Transport = &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
return options.Dialer.Dial(ctx, network, addr)
},
}
} else {
client.Transport = &http.Transport{
TLSClientConfig: d.options.TLSConfig,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return options.Dialer.Dial(ctx, network, addr)
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}
d.clients[address] = client
}
d.clientMutex.Unlock()
host := d.md.host
if host == "" {
host = address
}
scheme := "https"
if d.h2c {
scheme = "http"
}
pr, pw := io.Pipe()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Scheme: scheme, Host: host},
Header: d.md.header,
Body: pr,
Host: host,
}
if req.Header == nil {
req.Header = make(http.Header)
}
if d.md.path != "" {
req.Method = http.MethodGet
req.URL.Path = d.md.path
}
if d.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false)
d.logger.Trace(string(dump))
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
if d.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
d.logger.Trace(string(dump))
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, errors.New(resp.Status)
}
conn := &http2Conn{
r: resp.Body,
w: pw,
remoteAddr: raddr,
localAddr: &net.TCPAddr{IP: net.IPv4zero, Port: 0},
}
return conn, nil
}
+33
View File
@@ -0,0 +1,33 @@
package h2
import (
"net/http"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
host string
path string
header http.Header
}
func (d *h2Dialer) parseMetadata(md mdata.Metadata) (err error) {
const (
host = "host"
path = "path"
header = "header"
)
d.md.host = mdutil.GetString(md, host)
d.md.path = mdutil.GetString(md, path)
if m := mdutil.GetStringMapString(md, header); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
return
}
+11
View File
@@ -0,0 +1,11 @@
package http2
import (
mdata "github.com/go-gost/core/metadata"
)
type metadata struct{}
func (d *http2Dialer) parseMetadata(md mdata.Metadata) (err error) {
return
}
+116
View File
@@ -0,0 +1,116 @@
package http3
import (
"context"
"crypto/tls"
"net"
"net/http"
"sync"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
pht_util "github.com/go-gost/x/internal/util/pht"
"github.com/go-gost/x/registry"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)
func init() {
registry.DialerRegistry().Register("http3", NewDialer)
registry.DialerRegistry().Register("h3", NewDialer)
}
type http3Dialer struct {
clients map[string]*pht_util.Client
clientMutex sync.Mutex
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &http3Dialer{
clients: make(map[string]*pht_util.Client),
options: options,
}
}
func (d *http3Dialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
func (d *http3Dialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
d.clientMutex.Lock()
defer d.clientMutex.Unlock()
client, ok := d.clients[addr]
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
host := d.md.host
if host == "" {
host = options.Host
}
if h, _, _ := net.SplitHostPort(host); h != "" {
host = h
}
client = &pht_util.Client{
Host: host,
Client: &http.Client{
// Timeout: 60 * time.Second,
Transport: &http3.Transport{
TLSClientConfig: d.options.TLSConfig,
Dial: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
// d.options.Logger.Infof("dial: %s/%s, %s", addr, network, host)
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
udpConn, err := options.Dialer.Dial(ctx, "udp", "")
if err != nil {
return nil, err
}
return quic.DialEarly(context.Background(), udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
},
QUICConfig: &quic.Config{
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
Versions: []quic.Version{
quic.Version1,
},
MaxIncomingStreams: int64(d.md.maxStreams),
},
},
},
AuthorizePath: d.md.authorizePath,
PushPath: d.md.pushPath,
PullPath: d.md.pullPath,
TLSEnabled: true,
Logger: d.options.Logger,
}
d.clients[addr] = client
}
return client.Dial(ctx, addr)
}
// Multiplex implements dialer.Multiplexer interface.
func (d *http3Dialer) Multiplex() bool {
return true
}
+64
View File
@@ -0,0 +1,64 @@
package http3
import (
"strings"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
defaultAuthorizePath = "/authorize"
defaultPushPath = "/push"
defaultPullPath = "/pull"
)
type metadata struct {
authorizePath string
pushPath string
pullPath string
host string
// QUIC config options
keepAlivePeriod time.Duration
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
maxStreams int
}
func (d *http3Dialer) parseMetadata(md mdata.Metadata) (err error) {
const (
keepAlive = "keepalive"
keepAlivePeriod = "ttl"
handshakeTimeout = "handshakeTimeout"
maxIdleTimeout = "maxIdleTimeout"
maxStreams = "maxStreams"
)
d.md.authorizePath = mdutil.GetString(md, "pht.authorizePath", "authorizePath")
if !strings.HasPrefix(d.md.authorizePath, "/") {
d.md.authorizePath = defaultAuthorizePath
}
d.md.pushPath = mdutil.GetString(md, "pht.pushPath", "pushPath")
if !strings.HasPrefix(d.md.pushPath, "/") {
d.md.pushPath = defaultPushPath
}
d.md.pullPath = mdutil.GetString(md, "pht.pullPath", "pullPath")
if !strings.HasPrefix(d.md.pullPath, "/") {
d.md.pullPath = defaultPullPath
}
d.md.host = mdutil.GetString(md, "host")
if md == nil || !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
if d.md.keepAlivePeriod <= 0 {
d.md.keepAlivePeriod = 10 * time.Second
}
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
d.md.maxStreams = mdutil.GetInt(md, maxStreams)
return
}
+58
View File
@@ -0,0 +1,58 @@
package wt
import (
"context"
"net"
"net/http"
"net/http/httputil"
"net/url"
"github.com/go-gost/core/logger"
wt_util "github.com/go-gost/x/internal/util/wt"
wt "github.com/quic-go/webtransport-go"
)
type Client struct {
host string
path string
header http.Header
dialer *wt.Dialer
session *wt.Session
log logger.Logger
}
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
ok := false
if c.session != nil {
select {
case <-c.session.Context().Done():
default:
ok = true
}
}
if !ok {
url := url.URL{
Scheme: "https",
Host: c.host,
Path: c.path,
}
resp, session, err := c.dialer.Dial(ctx, url.String(), c.header)
if err != nil {
return nil, err
}
if c.log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
c.log.Trace(string(dump))
}
c.session = session
}
stream, err := c.session.OpenStream()
if err != nil {
return nil, err
}
return wt_util.Conn(c.session, stream), nil
}
+109
View File
@@ -0,0 +1,109 @@
package wt
import (
"context"
"crypto/tls"
"net"
"sync"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
"github.com/quic-go/quic-go"
wt "github.com/quic-go/webtransport-go"
)
func init() {
registry.DialerRegistry().Register("wt", NewDialer)
}
type wtDialer struct {
clients map[string]*Client
clientMutex sync.Mutex
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &wtDialer{
clients: make(map[string]*Client),
options: options,
}
}
func (d *wtDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return
}
// Multiplex implements dialer.Multiplexer interface.
func (d *wtDialer) Multiplex() bool {
return true
}
func (d *wtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
d.clientMutex.Lock()
defer d.clientMutex.Unlock()
client := d.clients[addr]
if client == nil {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
host := d.md.host
if host == "" {
host = options.Host
}
if h, _, _ := net.SplitHostPort(host); h != "" {
host = h
}
client = &Client{
log: d.options.Logger,
host: host,
path: d.md.path,
header: d.md.header,
dialer: &wt.Dialer{
TLSClientConfig: d.options.TLSConfig,
DialAddr: func(ctx context.Context, adr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
// d.options.Logger.Infof("dial: %s, %s, %s", addr, adr, host)
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
udpConn, err := options.Dialer.Dial(ctx, "udp", "")
if err != nil {
return nil, err
}
return quic.DialEarly(ctx, udpConn.(net.PacketConn), udpAddr, tlsCfg, cfg)
},
QUICConfig: &quic.Config{
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
/*
Versions: []quic.VersionNumber{
quic.Version1,
},
*/
MaxIncomingStreams: int64(d.md.maxStreams),
EnableDatagrams: true,
},
},
}
d.clients[addr] = client
}
return client.Dial(ctx, addr)
}
+62
View File
@@ -0,0 +1,62 @@
package wt
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
defaultPath = "/wt"
defaultKeepalivePeriod = 15 * time.Second
)
type metadata struct {
host string
path string
header http.Header
// QUIC config options
keepAlivePeriod time.Duration
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
maxStreams int
}
func (d *wtDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
keepAlive = "keepalive"
keepAlivePeriod = "ttl"
handshakeTimeout = "handshakeTimeout"
maxIdleTimeout = "maxIdleTimeout"
maxStreams = "maxStreams"
)
d.md.host = mdutil.GetString(md, "wt.host", "host")
d.md.path = mdutil.GetString(md, "wt.path", "path")
if d.md.path == "" {
d.md.path = defaultPath
}
if md == nil || !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
if d.md.keepAlivePeriod <= 0 {
d.md.keepAlivePeriod = 10 * time.Second
}
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
d.md.maxStreams = mdutil.GetInt(md, maxStreams)
if m := mdutil.GetStringMapString(md, "wt.header", "header"); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
return
}
+42
View File
@@ -0,0 +1,42 @@
package quic
import (
"context"
"net"
"github.com/quic-go/quic-go"
)
type quicSession struct {
session quic.EarlyConnection
}
func (session *quicSession) GetConn() (*quicConn, error) {
stream, err := session.session.OpenStreamSync(context.Background())
if err != nil {
return nil, err
}
return &quicConn{
Stream: stream,
laddr: session.session.LocalAddr(),
raddr: session.session.RemoteAddr(),
}, nil
}
func (session *quicSession) Close() error {
return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
}
type quicConn struct {
quic.Stream
laddr net.Addr
raddr net.Addr
}
func (c *quicConn) LocalAddr() net.Addr {
return c.laddr
}
func (c *quicConn) RemoteAddr() net.Addr {
return c.raddr
}
+149
View File
@@ -0,0 +1,149 @@
package quic
import (
"context"
"math"
"math/rand"
"net"
"sync"
"time"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
icmp_pkg "github.com/go-gost/x/internal/util/icmp"
"github.com/go-gost/x/registry"
"github.com/quic-go/quic-go"
"golang.org/x/net/icmp"
)
func init() {
registry.DialerRegistry().Register("icmp", NewDialer)
registry.DialerRegistry().Register("icmp6", NewDialer6)
}
type icmpDialer struct {
ip6 bool
sessions map[string]*quicSession
sessionMutex sync.Mutex
logger logger.Logger
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &icmpDialer{
sessions: make(map[string]*quicSession),
logger: options.Logger,
options: options,
}
}
func NewDialer6(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &icmpDialer{
ip6: true,
sessions: make(map[string]*quicSession),
logger: options.Logger,
options: options,
}
}
func (d *icmpDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
if _, _, err := net.SplitHostPort(addr); err != nil {
addr = net.JoinHostPort(addr, "0")
}
raddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if !ok {
options := &dialer.DialOptions{}
for _, opt := range opts {
opt(options)
}
var pc net.PacketConn
if d.ip6 {
pc, err = icmp.ListenPacket("ip6:ipv6-icmp", "")
} else {
pc, err = icmp.ListenPacket("ip4:icmp", "")
}
if err != nil {
return
}
id := raddr.Port
if id == 0 {
id = rand.New(rand.NewSource(time.Now().UnixNano())).Intn(math.MaxUint16) + 1
raddr.Port = id
}
pc = icmp_pkg.ClientConn(d.ip6, pc, id)
session, err = d.initSession(ctx, raddr, pc)
if err != nil {
d.logger.Error(err)
pc.Close()
return nil, err
}
d.sessions[addr] = session
}
conn, err = session.GetConn()
if err != nil {
session.Close()
delete(d.sessions, addr)
return nil, err
}
return
}
func (d *icmpDialer) initSession(ctx context.Context, addr net.Addr, conn net.PacketConn) (*quicSession, error) {
quicConfig := &quic.Config{
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
Versions: []quic.Version{
quic.Version1,
quic.Version2,
},
}
tlsCfg := d.options.TLSConfig
tlsCfg.NextProtos = []string{"h3", "quic/v1"}
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
if err != nil {
return nil, err
}
return &quicSession{session: session}, nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *icmpDialer) Multiplex() bool {
return true
}
+27
View File
@@ -0,0 +1,27 @@
package quic
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
keepAlivePeriod time.Duration
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
}
func (d *icmpDialer) parseMetadata(md mdata.Metadata) (err error) {
if mdutil.GetBool(md, "keepalive") {
d.md.keepAlivePeriod = mdutil.GetDuration(md, "ttl")
if d.md.keepAlivePeriod <= 0 {
d.md.keepAlivePeriod = 10 * time.Second
}
}
d.md.handshakeTimeout = mdutil.GetDuration(md, "handshakeTimeout")
d.md.maxIdleTimeout = mdutil.GetDuration(md, "maxIdleTimeout")
return
}
+55
View File
@@ -0,0 +1,55 @@
package kcp
import (
"net"
"github.com/xtaci/smux"
)
type muxSession struct {
session *smux.Session
}
func (session *muxSession) GetConn() (net.Conn, error) {
return session.session.OpenStream()
}
func (session *muxSession) Accept() (net.Conn, error) {
return session.session.AcceptStream()
}
func (session *muxSession) Close() error {
if session.session == nil {
return nil
}
return session.session.Close()
}
func (session *muxSession) IsClosed() bool {
if session.session == nil {
return true
}
return session.session.IsClosed()
}
func (session *muxSession) NumStreams() int {
return session.session.NumStreams()
}
type fakeTCPConn struct {
raddr net.Addr
net.PacketConn
}
func (c *fakeTCPConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *fakeTCPConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.raddr)
}
func (c *fakeTCPConn) RemoteAddr() net.Addr {
return c.raddr
}
+169
View File
@@ -0,0 +1,169 @@
package kcp
import (
"context"
"errors"
"net"
"sync"
"time"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
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"
)
func init() {
registry.DialerRegistry().Register("kcp", NewDialer)
}
type kcpDialer struct {
sessions map[string]*muxSession
sessionMutex sync.Mutex
logger logger.Logger
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &kcpDialer{
sessions: make(map[string]*muxSession),
logger: options.Logger,
options: options,
}
}
func (d *kcpDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
d.md.config.Init()
return nil
}
func (d *kcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
raddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if session != nil && session.IsClosed() {
delete(d.sessions, addr) // session is dead
ok = false
}
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
var pc net.PacketConn
if d.md.config.TCP {
pc, err = tcpraw.Dial("tcp", addr)
if err != nil {
return nil, err
}
pc = &fakeTCPConn{
raddr: raddr,
PacketConn: pc,
}
} else {
c, err := options.Dialer.Dial(ctx, "udp", "")
if err != nil {
return nil, err
}
var ok bool
pc, ok = c.(net.PacketConn)
if !ok {
c.Close()
return nil, errors.New("quic: wrong connection type")
}
}
session, err = d.initSession(ctx, raddr, pc)
if err != nil {
d.logger.Error(err)
pc.Close()
return nil, err
}
d.sessions[addr] = session
}
conn, err = session.GetConn()
if err != nil {
session.Close()
delete(d.sessions, addr)
return nil, err
}
return
}
func (d *kcpDialer) initSession(ctx context.Context, addr net.Addr, conn net.PacketConn) (*muxSession, error) {
config := d.md.config
kcpconn, err := kcp.NewConn(addr.String(),
kcp_util.BlockCrypt(config.Key, config.Crypt, kcp_util.DefaultSalt),
config.DataShard, config.ParityShard, conn)
if err != nil {
return nil, err
}
kcpconn.SetStreamMode(true)
kcpconn.SetWriteDelay(false)
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
kcpconn.SetMtu(config.MTU)
kcpconn.SetACKNoDelay(config.AckNodelay)
if config.DSCP > 0 {
if er := kcpconn.SetDSCP(config.DSCP); er != nil {
d.logger.Warn("SetDSCP: ", er)
}
}
if er := kcpconn.SetReadBuffer(config.SockBuf); er != nil {
d.logger.Warn("SetReadBuffer: ", er)
}
if er := kcpconn.SetWriteBuffer(config.SockBuf); er != nil {
d.logger.Warn("SetWriteBuffer: ", er)
}
// stream multiplex
smuxConfig := smux.DefaultConfig()
smuxConfig.Version = config.SmuxVer
smuxConfig.MaxReceiveBuffer = config.SmuxBuf
smuxConfig.MaxStreamBuffer = config.StreamBuf
if config.KeepAlive > 0 {
smuxConfig.KeepAliveInterval = time.Duration(config.KeepAlive) * time.Second
}
var cc net.Conn = kcpconn
if !config.NoComp {
cc = kcp_util.CompStreamConn(kcpconn)
}
session, err := smux.Client(cc, smuxConfig)
if err != nil {
return nil, err
}
return &muxSession{session: session}, nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *kcpDialer) Multiplex() bool {
return true
}
+61
View File
@@ -0,0 +1,61 @@
package kcp
import (
"encoding/json"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
kcp_util "github.com/go-gost/x/internal/util/kcp"
)
type metadata struct {
handshakeTimeout time.Duration
config *kcp_util.Config
}
func (d *kcpDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
config = "config"
configFile = "c"
handshakeTimeout = "handshakeTimeout"
)
if file := mdutil.GetString(md, "kcp.configFile", "configFile", "c"); file != "" {
d.md.config, err = kcp_util.ParseFromFile(file)
if err != nil {
return
}
}
if m := mdutil.GetStringMap(md, "kcp.config", "config"); len(m) > 0 {
b, err := json.Marshal(m)
if err != nil {
return err
}
cfg := &kcp_util.Config{}
if err := json.Unmarshal(b, cfg); err != nil {
return err
}
d.md.config = cfg
}
if d.md.config == nil {
d.md.config = kcp_util.DefaultConfig
}
d.md.config.TCP = mdutil.GetBool(md, "kcp.tcp", "tcp")
d.md.config.Key = mdutil.GetString(md, "kcp.key")
d.md.config.Crypt = mdutil.GetString(md, "kcp.crypt")
d.md.config.Mode = mdutil.GetString(md, "kcp.mode")
d.md.config.KeepAlive = mdutil.GetInt(md, "kcp.keepalive")
d.md.config.Interval = mdutil.GetInt(md, "kcp.interval")
d.md.config.MTU = mdutil.GetInt(md, "kcp.mtu")
d.md.config.RcvWnd = mdutil.GetInt(md, "kcp.rcvwnd")
d.md.config.SndWnd = mdutil.GetInt(md, "kcp.sndwnd")
d.md.config.SmuxVer = mdutil.GetInt(md, "kcp.smuxver")
d.md.config.SmuxBuf = mdutil.GetInt(md, "kcp.smuxbuf")
d.md.config.StreamBuf = mdutil.GetInt(md, "kcp.streambuf")
d.md.config.NoComp = mdutil.GetBool(md, "kcp.nocomp")
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
return
}
+38
View File
@@ -0,0 +1,38 @@
package mtcp
import (
"net"
"github.com/go-gost/x/internal/util/mux"
)
type muxSession struct {
conn net.Conn
session *mux.Session
}
func (session *muxSession) GetConn() (net.Conn, error) {
return session.session.GetConn()
}
func (session *muxSession) Accept() (net.Conn, error) {
return session.session.Accept()
}
func (session *muxSession) Close() error {
if session.session == nil {
return nil
}
return session.session.Close()
}
func (session *muxSession) IsClosed() bool {
if session.session == nil {
return true
}
return session.session.IsClosed()
}
func (session *muxSession) NumStreams() int {
return session.session.NumStreams()
}
+131
View File
@@ -0,0 +1,131 @@
package mtcp
import (
"context"
"errors"
"net"
"sync"
"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/internal/util/mux"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("mtcp", NewDialer)
}
type mtcpDialer struct {
sessions map[string]*muxSession
sessionMutex sync.Mutex
logger logger.Logger
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &mtcpDialer{
sessions: make(map[string]*muxSession),
logger: options.Logger,
options: options,
}
}
func (d *mtcpDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *mtcpDialer) Multiplex() bool {
return true
}
func (d *mtcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if session != nil && session.IsClosed() {
delete(d.sessions, addr) // session is dead
ok = false
}
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
return
}
session = &muxSession{conn: conn}
d.sessions[addr] = session
}
return session.conn, err
}
// Handshake implements dialer.Handshaker
func (d *mtcpDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
opts := &dialer.HandshakeOptions{}
for _, option := range options {
option(opts)
}
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
if d.md.handshakeTimeout > 0 {
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
defer conn.SetDeadline(time.Time{})
}
session, ok := d.sessions[opts.Addr]
if session != nil && session.conn != conn {
conn.Close()
return nil, errors.New("mtls: unrecognized connection")
}
if !ok || session.session == nil {
s, err := d.initSession(ctx, conn)
if err != nil {
d.logger.Error(err)
conn.Close()
delete(d.sessions, opts.Addr)
return nil, err
}
session = s
d.sessions[opts.Addr] = session
}
cc, err := session.GetConn()
if err != nil {
session.Close()
delete(d.sessions, opts.Addr)
return nil, err
}
return cc, nil
}
func (d *mtcpDialer) initSession(ctx context.Context, conn net.Conn) (*muxSession, error) {
// stream multiplex
session, err := mux.ClientSession(conn, d.md.muxCfg)
if err != nil {
return nil, err
}
return &muxSession{conn: conn, session: session}, nil
}
+33
View File
@@ -0,0 +1,33 @@
package mtcp
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
)
type metadata struct {
handshakeTimeout time.Duration
muxCfg *mux.Config
}
func (d *mtcpDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.handshakeTimeout = mdutil.GetDuration(md, "handshakeTimeout")
d.md.muxCfg = &mux.Config{
Version: mdutil.GetInt(md, "mux.version"),
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
KeepAliveDisabled: mdutil.GetBool(md, "mux.keepaliveDisabled"),
KeepAliveTimeout: mdutil.GetDuration(md, "mux.keepaliveTimeout"),
MaxFrameSize: mdutil.GetInt(md, "mux.maxFrameSize"),
MaxReceiveBuffer: mdutil.GetInt(md, "mux.maxReceiveBuffer"),
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
if d.md.muxCfg.Version == 0 {
d.md.muxCfg.Version = 2
}
return
}
+38
View File
@@ -0,0 +1,38 @@
package mtls
import (
"net"
"github.com/go-gost/x/internal/util/mux"
)
type muxSession struct {
conn net.Conn
session *mux.Session
}
func (session *muxSession) GetConn() (net.Conn, error) {
return session.session.GetConn()
}
func (session *muxSession) Accept() (net.Conn, error) {
return session.session.Accept()
}
func (session *muxSession) Close() error {
if session.session == nil {
return nil
}
return session.session.Close()
}
func (session *muxSession) IsClosed() bool {
if session.session == nil {
return true
}
return session.session.IsClosed()
}
func (session *muxSession) NumStreams() int {
return session.session.NumStreams()
}
+138
View File
@@ -0,0 +1,138 @@
package mtls
import (
"context"
"crypto/tls"
"errors"
"net"
"sync"
"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/internal/util/mux"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("mtls", NewDialer)
}
type mtlsDialer struct {
sessions map[string]*muxSession
sessionMutex sync.Mutex
logger logger.Logger
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &mtlsDialer{
sessions: make(map[string]*muxSession),
logger: options.Logger,
options: options,
}
}
func (d *mtlsDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *mtlsDialer) Multiplex() bool {
return true
}
func (d *mtlsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if session != nil && session.IsClosed() {
delete(d.sessions, addr) // session is dead
ok = false
}
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
return
}
session = &muxSession{conn: conn}
d.sessions[addr] = session
}
return session.conn, err
}
// Handshake implements dialer.Handshaker
func (d *mtlsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
opts := &dialer.HandshakeOptions{}
for _, option := range options {
option(opts)
}
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
if d.md.handshakeTimeout > 0 {
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
defer conn.SetDeadline(time.Time{})
}
session, ok := d.sessions[opts.Addr]
if session != nil && session.conn != conn {
conn.Close()
return nil, errors.New("mtls: unrecognized connection")
}
if !ok || session.session == nil {
s, err := d.initSession(ctx, conn)
if err != nil {
d.logger.Error(err)
conn.Close()
delete(d.sessions, opts.Addr)
return nil, err
}
session = s
d.sessions[opts.Addr] = session
}
cc, err := session.GetConn()
if err != nil {
session.Close()
delete(d.sessions, opts.Addr)
return nil, err
}
return cc, nil
}
func (d *mtlsDialer) initSession(ctx context.Context, conn net.Conn) (*muxSession, error) {
tlsConn := tls.Client(conn, d.options.TLSConfig)
if err := tlsConn.HandshakeContext(ctx); err != nil {
return nil, err
}
conn = tlsConn
// stream multiplex
session, err := mux.ClientSession(conn, d.md.muxCfg)
if err != nil {
return nil, err
}
return &muxSession{conn: conn, session: session}, nil
}
+29
View File
@@ -0,0 +1,29 @@
package mtls
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
)
type metadata struct {
handshakeTimeout time.Duration
muxCfg *mux.Config
}
func (d *mtlsDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.handshakeTimeout = mdutil.GetDuration(md, "handshakeTimeout")
d.md.muxCfg = &mux.Config{
Version: mdutil.GetInt(md, "mux.version"),
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
KeepAliveDisabled: mdutil.GetBool(md, "mux.keepaliveDisabled"),
KeepAliveTimeout: mdutil.GetDuration(md, "mux.keepaliveTimeout"),
MaxFrameSize: mdutil.GetInt(md, "mux.maxFrameSize"),
MaxReceiveBuffer: mdutil.GetInt(md, "mux.maxReceiveBuffer"),
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
return
}
+38
View File
@@ -0,0 +1,38 @@
package mws
import (
"net"
"github.com/go-gost/x/internal/util/mux"
)
type muxSession struct {
conn net.Conn
session *mux.Session
}
func (session *muxSession) GetConn() (net.Conn, error) {
return session.session.GetConn()
}
func (session *muxSession) Accept() (net.Conn, error) {
return session.session.Accept()
}
func (session *muxSession) Close() error {
if session.session == nil {
return nil
}
return session.session.Close()
}
func (session *muxSession) IsClosed() bool {
if session.session == nil {
return true
}
return session.session.IsClosed()
}
func (session *muxSession) NumStreams() int {
return session.session.NumStreams()
}
+210
View File
@@ -0,0 +1,210 @@
package mws
import (
"context"
"errors"
"net"
"net/url"
"sync"
"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/internal/util/mux"
ws_util "github.com/go-gost/x/internal/util/ws"
"github.com/go-gost/x/registry"
"github.com/gorilla/websocket"
)
func init() {
registry.DialerRegistry().Register("mws", NewDialer)
registry.DialerRegistry().Register("mwss", NewTLSDialer)
}
type mwsDialer struct {
sessions map[string]*muxSession
sessionMutex sync.Mutex
tlsEnabled bool
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &mwsDialer{
sessions: make(map[string]*muxSession),
options: options,
}
}
func NewTLSDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &mwsDialer{
tlsEnabled: true,
sessions: make(map[string]*muxSession),
options: options,
}
}
func (d *mwsDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *mwsDialer) Multiplex() bool {
return true
}
func (d *mwsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if session != nil && session.IsClosed() {
delete(d.sessions, addr) // session is dead
ok = false
}
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
return
}
session = &muxSession{conn: conn}
d.sessions[addr] = session
}
return session.conn, err
}
// Handshake implements dialer.Handshaker
func (d *mwsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
opts := &dialer.HandshakeOptions{}
for _, option := range options {
option(opts)
}
log := d.options.Logger.WithFields(map[string]any{
"local": conn.LocalAddr().String(),
"remote": conn.RemoteAddr().String(),
})
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[opts.Addr]
if session != nil && session.conn != conn {
err := errors.New("mws: unrecognized connection")
log.Error(err)
conn.Close()
return nil, err
}
if !ok || session.session == nil {
host := d.md.host
if host == "" {
host = opts.Addr
}
s, err := d.initSession(ctx, host, conn, log)
if err != nil {
log.Error(err)
conn.Close()
delete(d.sessions, opts.Addr)
return nil, err
}
session = s
d.sessions[opts.Addr] = session
}
cc, err := session.GetConn()
if err != nil {
log.Error(err)
session.Close()
delete(d.sessions, opts.Addr)
return nil, err
}
return cc, nil
}
func (d *mwsDialer) initSession(ctx context.Context, host string, conn net.Conn, log logger.Logger) (*muxSession, error) {
dialer := websocket.Dialer{
HandshakeTimeout: d.md.handshakeTimeout,
ReadBufferSize: d.md.readBufferSize,
WriteBufferSize: d.md.writeBufferSize,
EnableCompression: d.md.enableCompression,
NetDial: func(net, addr string) (net.Conn, error) {
return conn, nil
},
}
url := url.URL{Scheme: "ws", Host: host, Path: d.md.path}
if d.tlsEnabled {
url.Scheme = "wss"
dialer.TLSClientConfig = d.options.TLSConfig
}
if d.md.handshakeTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(d.md.handshakeTimeout))
}
c, resp, err := dialer.DialContext(ctx, url.String(), d.md.header)
if err != nil {
return nil, err
}
resp.Body.Close()
if d.md.handshakeTimeout > 0 {
conn.SetReadDeadline(time.Time{})
}
cc := ws_util.Conn(c)
if d.md.keepaliveInterval > 0 {
log.Debugf("keepalive is enabled, ttl: %v", d.md.keepaliveInterval)
c.SetReadDeadline(time.Now().Add(d.md.keepaliveInterval * 2))
c.SetPongHandler(func(string) error {
c.SetReadDeadline(time.Now().Add(d.md.keepaliveInterval * 2))
return nil
})
go d.keepAlive(cc)
}
// stream multiplex
session, err := mux.ClientSession(cc, d.md.muxCfg)
if err != nil {
log.Error(err)
return nil, err
}
return &muxSession{conn: cc, session: session}, nil
}
func (d *mwsDialer) keepAlive(conn ws_util.WebsocketConn) {
ticker := time.NewTicker(d.md.keepaliveInterval)
defer ticker.Stop()
for range ticker.C {
d.options.Logger.Debug("send ping")
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
conn.SetWriteDeadline(time.Time{})
}
}
+71
View File
@@ -0,0 +1,71 @@
package mws
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/go-gost/x/internal/util/mux"
)
const (
defaultPath = "/ws"
defaultKeepalivePeriod = 15 * time.Second
)
type metadata struct {
host string
path string
handshakeTimeout time.Duration
readHeaderTimeout time.Duration
readBufferSize int
writeBufferSize int
enableCompression bool
header http.Header
keepaliveInterval time.Duration
muxCfg *mux.Config
}
func (d *mwsDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.host = mdutil.GetString(md, "ws.host", "host")
d.md.path = mdutil.GetString(md, "ws.path", "path")
if d.md.path == "" {
d.md.path = defaultPath
}
d.md.muxCfg = &mux.Config{
Version: mdutil.GetInt(md, "mux.version"),
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
KeepAliveDisabled: mdutil.GetBool(md, "mux.keepaliveDisabled"),
KeepAliveTimeout: mdutil.GetDuration(md, "mux.keepaliveTimeout"),
MaxFrameSize: mdutil.GetInt(md, "mux.maxFrameSize"),
MaxReceiveBuffer: mdutil.GetInt(md, "mux.maxReceiveBuffer"),
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
}
d.md.handshakeTimeout = mdutil.GetDuration(md, "ws.handshakeTimeout", "handshakeTimeout")
d.md.readHeaderTimeout = mdutil.GetDuration(md, "ws.readHeaderTimeout", "readHeaderTimeout")
d.md.readBufferSize = mdutil.GetInt(md, "ws.readBufferSize", "readBufferSize")
d.md.writeBufferSize = mdutil.GetInt(md, "ws.writeBufferSize", "writeBufferSize")
d.md.enableCompression = mdutil.GetBool(md, "ws.enableCompression", "enableCompression")
if m := mdutil.GetStringMapString(md, "ws.header", "header"); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
if mdutil.GetBool(md, "ws.keepalive", "keepalive") {
d.md.keepaliveInterval = mdutil.GetDuration(md, "ttl", "keepalive.interval")
if d.md.keepaliveInterval <= 0 {
d.md.keepaliveInterval = defaultKeepalivePeriod
}
}
return
}
+145
View File
@@ -0,0 +1,145 @@
package http
import (
"bufio"
"bytes"
"crypto/rand"
"encoding/base64"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"github.com/go-gost/core/logger"
)
type obfsHTTPConn struct {
net.Conn
host string
path string
rbuf bytes.Buffer
wbuf bytes.Buffer
headerDrained bool
handshaked bool
handshakeMutex sync.Mutex
header http.Header
logger logger.Logger
}
func (c *obfsHTTPConn) Handshake() (err error) {
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
if c.handshaked {
return nil
}
err = c.handshake()
if err != nil {
return
}
c.handshaked = true
return nil
}
func (c *obfsHTTPConn) handshake() (err error) {
r := &http.Request{
Method: http.MethodGet,
ProtoMajor: 1,
ProtoMinor: 1,
URL: &url.URL{Scheme: "http", Host: c.host},
Header: c.header,
}
if r.Header == nil {
r.Header = http.Header{}
}
r.Header.Set("Connection", "Upgrade")
r.Header.Set("Upgrade", "websocket")
key, _ := c.generateChallengeKey()
r.Header.Set("Sec-WebSocket-Key", key)
// cache the request header
if err = r.Write(&c.wbuf); err != nil {
return
}
if c.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
c.logger.Trace(string(dump))
}
return nil
}
func (c *obfsHTTPConn) Read(b []byte) (n int, err error) {
if err = c.Handshake(); err != nil {
return
}
if err = c.drainHeader(); err != nil {
return
}
if c.rbuf.Len() > 0 {
return c.rbuf.Read(b)
}
return c.Conn.Read(b)
}
func (c *obfsHTTPConn) drainHeader() (err error) {
if c.headerDrained {
return
}
c.headerDrained = true
br := bufio.NewReader(c.Conn)
// drain and discard the response header
var line string
var buf bytes.Buffer
for {
line, err = br.ReadString('\n')
if err != nil {
return
}
buf.WriteString(line)
if line == "\r\n" {
break
}
}
if c.logger.IsLevelEnabled(logger.TraceLevel) {
c.logger.Trace(buf.String())
}
// cache the extra data for next read.
var b []byte
b, err = br.Peek(br.Buffered())
if len(b) > 0 {
_, err = c.rbuf.Write(b)
}
return
}
func (c *obfsHTTPConn) Write(b []byte) (n int, err error) {
if err = c.Handshake(); err != nil {
return
}
if c.wbuf.Len() > 0 {
c.wbuf.Write(b) // append the data to the cached header
_, err = c.wbuf.WriteTo(c.Conn)
n = len(b) // exclude the header length
return
}
return c.Conn.Write(b)
}
func (c *obfsHTTPConn) generateChallengeKey() (string, error) {
p := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, p); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(p), nil
}
+90
View File
@@ -0,0 +1,90 @@
package http
import (
"context"
"crypto/tls"
"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("ohttp", NewDialer)
registry.DialerRegistry().Register("ohttps", NewDialer)
}
type obfsHTTPDialer struct {
tlsEnabled bool
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &obfsHTTPDialer{
logger: options.Logger,
}
}
func NewTLSDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &obfsHTTPDialer{
tlsEnabled: true,
logger: options.Logger,
}
}
func (d *obfsHTTPDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *obfsHTTPDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
options := &dialer.DialOptions{}
for _, opt := range opts {
opt(options)
}
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
d.logger.Error(err)
}
return conn, err
}
// Handshake implements dialer.Handshaker
func (d *obfsHTTPDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
opts := &dialer.HandshakeOptions{}
for _, option := range options {
option(opts)
}
host := d.md.host
if host == "" {
host = opts.Addr
}
if d.tlsEnabled {
conn = tls.Client(conn, &tls.Config{
ServerName: host,
})
}
return &obfsHTTPConn{
Conn: conn,
host: host,
path: d.md.path,
header: d.md.header,
logger: d.logger,
}, nil
}
+35
View File
@@ -0,0 +1,35 @@
package http
import (
"net/http"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
defaultPath = "/"
)
type metadata struct {
host string
path string
header http.Header
}
func (d *obfsHTTPDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.host = mdutil.GetString(md, "obfs.host", "host")
d.md.path = mdutil.GetString(md, "obfs.path", "path")
if d.md.path == "" {
d.md.path = defaultPath
}
if m := mdutil.GetStringMapString(md, "obfs.header", "header"); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
return
}
+266
View File
@@ -0,0 +1,266 @@
package tls
import (
"bytes"
"crypto/rand"
"crypto/tls"
"errors"
"net"
"sync"
"time"
dissector "github.com/go-gost/tls-dissector"
)
const (
maxTLSDataLen = 16384
)
var (
cipherSuites = []uint16{
0xc02c, 0xc030, 0x009f, 0xcca9, 0xcca8, 0xccaa, 0xc02b, 0xc02f,
0x009e, 0xc024, 0xc028, 0x006b, 0xc023, 0xc027, 0x0067, 0xc00a,
0xc014, 0x0039, 0xc009, 0xc013, 0x0033, 0x009d, 0x009c, 0x003d,
0x003c, 0x0035, 0x002f, 0x00ff,
}
compressionMethods = []uint8{0x00}
algorithms = []uint16{
0x0601, 0x0602, 0x0603, 0x0501, 0x0502, 0x0503, 0x0401, 0x0402,
0x0403, 0x0301, 0x0302, 0x0303, 0x0201, 0x0202, 0x0203,
}
tlsRecordTypes = []uint8{0x16, 0x14, 0x16, 0x17}
tlsVersionMinors = []uint8{0x01, 0x03, 0x03, 0x03}
ErrBadType = errors.New("bad type")
ErrBadMajorVersion = errors.New("bad major version")
ErrBadMinorVersion = errors.New("bad minor version")
ErrMaxDataLen = errors.New("bad tls data len")
)
const (
tlsRecordStateType = iota
tlsRecordStateVersion0
tlsRecordStateVersion1
tlsRecordStateLength0
tlsRecordStateLength1
tlsRecordStateData
)
type obfsTLSParser struct {
step uint8
state uint8
length uint16
}
func (r *obfsTLSParser) Parse(b []byte) (int, error) {
i := 0
last := 0
length := len(b)
for i < length {
ch := b[i]
switch r.state {
case tlsRecordStateType:
if tlsRecordTypes[r.step] != ch {
return 0, ErrBadType
}
r.state = tlsRecordStateVersion0
i++
case tlsRecordStateVersion0:
if ch != 0x03 {
return 0, ErrBadMajorVersion
}
r.state = tlsRecordStateVersion1
i++
case tlsRecordStateVersion1:
if ch != tlsVersionMinors[r.step] {
return 0, ErrBadMinorVersion
}
r.state = tlsRecordStateLength0
i++
case tlsRecordStateLength0:
r.length = uint16(ch) << 8
r.state = tlsRecordStateLength1
i++
case tlsRecordStateLength1:
r.length |= uint16(ch)
if r.step == 0 {
r.length = 91
} else if r.step == 1 {
r.length = 1
} else if r.length > maxTLSDataLen {
return 0, ErrMaxDataLen
}
if r.length > 0 {
r.state = tlsRecordStateData
} else {
r.state = tlsRecordStateType
r.step++
}
i++
case tlsRecordStateData:
left := uint16(length - i)
if left > r.length {
left = r.length
}
if r.step >= 2 {
skip := i - last
copy(b[last:], b[i:length])
length -= int(skip)
last += int(left)
i = last
} else {
i += int(left)
}
r.length -= left
if r.length == 0 {
if r.step < 3 {
r.step++
}
r.state = tlsRecordStateType
}
}
}
if last == 0 {
return 0, nil
} else if last < length {
length -= last
}
return length, nil
}
type obfsTLSConn struct {
net.Conn
wbuf bytes.Buffer
host string
handshaked chan struct{}
parser obfsTLSParser
handshakeMutex sync.Mutex
}
func (c *obfsTLSConn) Handshaked() bool {
select {
case <-c.handshaked:
return true
default:
return false
}
}
func (c *obfsTLSConn) Handshake(payload []byte) (err error) {
c.handshakeMutex.Lock()
defer c.handshakeMutex.Unlock()
if c.Handshaked() {
return
}
if err = c.handshake(payload); err != nil {
return
}
close(c.handshaked)
return nil
}
func (c *obfsTLSConn) handshake(payload []byte) error {
clientMsg := &dissector.ClientHelloMsg{
Version: tls.VersionTLS12,
SessionID: make([]byte, 32),
CipherSuites: cipherSuites,
CompressionMethods: compressionMethods,
Extensions: []dissector.Extension{
&dissector.SessionTicketExtension{
Data: payload,
},
&dissector.ServerNameExtension{
Name: c.host,
},
&dissector.ECPointFormatsExtension{
Formats: []uint8{0x01, 0x00, 0x02},
},
&dissector.SupportedGroupsExtension{
Groups: []uint16{0x001d, 0x0017, 0x0019, 0x0018},
},
&dissector.SignatureAlgorithmsExtension{
Algorithms: algorithms,
},
&dissector.EncryptThenMacExtension{},
&dissector.ExtendedMasterSecretExtension{},
},
}
clientMsg.Random.Time = uint32(time.Now().Unix())
rand.Read(clientMsg.Random.Opaque[:])
rand.Read(clientMsg.SessionID)
b, err := clientMsg.Encode()
if err != nil {
return err
}
record := &dissector.Record{
Type: dissector.Handshake,
Version: tls.VersionTLS10,
Opaque: b,
}
if _, err := record.WriteTo(c.Conn); err != nil {
return err
}
return err
}
func (c *obfsTLSConn) Read(b []byte) (n int, err error) {
<-c.handshaked
n, err = c.Conn.Read(b)
if err != nil {
return
}
if n > 0 {
n, err = c.parser.Parse(b[:n])
}
return
}
func (c *obfsTLSConn) Write(b []byte) (n int, err error) {
n = len(b)
if !c.Handshaked() {
if err = c.Handshake(b); err != nil {
return
}
return
}
for len(b) > 0 {
data := b
if len(b) > maxTLSDataLen {
data = b[:maxTLSDataLen]
b = b[maxTLSDataLen:]
} else {
b = b[:0]
}
record := &dissector.Record{
Type: dissector.AppData,
Version: tls.VersionTLS12,
Opaque: data,
}
if c.wbuf.Len() > 0 {
record.Type = dissector.Handshake
record.WriteTo(&c.wbuf)
_, err = c.wbuf.WriteTo(c.Conn)
return
}
if _, err = record.WriteTo(c.Conn); err != nil {
return
}
}
return
}
+67
View File
@@ -0,0 +1,67 @@
package tls
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("otls", NewDialer)
}
type obfsTLSDialer struct {
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &obfsTLSDialer{
logger: options.Logger,
}
}
func (d *obfsTLSDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *obfsTLSDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
options := &dialer.DialOptions{}
for _, opt := range opts {
opt(options)
}
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
d.logger.Error(err)
}
return conn, err
}
// Handshake implements dialer.Handshaker
func (d *obfsTLSDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
opts := &dialer.HandshakeOptions{}
for _, option := range options {
option(opts)
}
host := d.md.host
if host == "" {
host = opts.Addr
}
return &obfsTLSConn{
Conn: conn,
host: host,
handshaked: make(chan struct{}),
}, nil
}
+19
View File
@@ -0,0 +1,19 @@
package tls
import (
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
host string
}
func (d *obfsTLSDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
host = "host"
)
d.md.host = mdutil.GetString(md, host)
return
}
+118
View File
@@ -0,0 +1,118 @@
package pht
import (
"context"
"net"
"net/http"
"sync"
"time"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
pht_util "github.com/go-gost/x/internal/util/pht"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("pht", NewDialer)
registry.DialerRegistry().Register("phts", NewTLSDialer)
}
type phtDialer struct {
clients map[string]*pht_util.Client
clientMutex sync.Mutex
tlsEnabled bool
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &phtDialer{
clients: make(map[string]*pht_util.Client),
options: options,
}
}
func NewTLSDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &phtDialer{
tlsEnabled: true,
clients: make(map[string]*pht_util.Client),
options: options,
}
}
func (d *phtDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
// NOTE: PHT is not a real multiplexed tunnel.
func (d *phtDialer) Multiplex() bool {
return true
}
func (d *phtDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
d.clientMutex.Lock()
defer d.clientMutex.Unlock()
client, ok := d.clients[addr]
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
host := d.md.host
if host == "" {
host = options.Host
}
if h, _, _ := net.SplitHostPort(host); h != "" {
host = h
}
tr := &http.Transport{
// Proxy: http.ProxyFromEnvironment,
DialContext: func(ctx context.Context, network, adr string) (net.Conn, error) {
return options.Dialer.Dial(ctx, network, addr)
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if d.tlsEnabled {
tr.TLSClientConfig = d.options.TLSConfig
}
client = &pht_util.Client{
Host: host,
Client: &http.Client{
// Timeout: 60 * time.Second,
Transport: tr,
},
AuthorizePath: d.md.authorizePath,
PushPath: d.md.pushPath,
PullPath: d.md.pullPath,
TLSEnabled: d.tlsEnabled,
Logger: d.options.Logger,
}
d.clients[addr] = client
}
return client.Dial(ctx, addr)
}
+47
View File
@@ -0,0 +1,47 @@
package pht
import (
"strings"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
dialTimeout = "dialTimeout"
defaultAuthorizePath = "/authorize"
defaultPushPath = "/push"
defaultPullPath = "/pull"
)
type metadata struct {
authorizePath string
pushPath string
pullPath string
host string
}
func (d *phtDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
authorizePath = "authorizePath"
pushPath = "pushPath"
pullPath = "pullPath"
host = "host"
)
d.md.authorizePath = mdutil.GetString(md, authorizePath)
if !strings.HasPrefix(d.md.authorizePath, "/") {
d.md.authorizePath = defaultAuthorizePath
}
d.md.pushPath = mdutil.GetString(md, pushPath)
if !strings.HasPrefix(d.md.pushPath, "/") {
d.md.pushPath = defaultPushPath
}
d.md.pullPath = mdutil.GetString(md, pullPath)
if !strings.HasPrefix(d.md.pullPath, "/") {
d.md.pullPath = defaultPullPath
}
d.md.host = mdutil.GetString(md, host)
return
}
+42
View File
@@ -0,0 +1,42 @@
package quic
import (
"context"
"net"
"github.com/quic-go/quic-go"
)
type quicSession struct {
session quic.EarlyConnection
}
func (session *quicSession) GetConn() (*quicConn, error) {
stream, err := session.session.OpenStreamSync(context.Background())
if err != nil {
return nil, err
}
return &quicConn{
Stream: stream,
laddr: session.session.LocalAddr(),
raddr: session.session.RemoteAddr(),
}, nil
}
func (session *quicSession) Close() error {
return session.session.CloseWithError(quic.ApplicationErrorCode(0), "closed")
}
type quicConn struct {
quic.Stream
laddr net.Addr
raddr net.Addr
}
func (c *quicConn) LocalAddr() net.Addr {
return c.laddr
}
func (c *quicConn) RemoteAddr() net.Addr {
return c.raddr
}
+130
View File
@@ -0,0 +1,130 @@
package quic
import (
"context"
"errors"
"net"
"sync"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
quic_util "github.com/go-gost/x/internal/util/quic"
"github.com/go-gost/x/registry"
"github.com/quic-go/quic-go"
)
func init() {
registry.DialerRegistry().Register("quic", NewDialer)
}
type quicDialer struct {
sessions map[string]*quicSession
sessionMutex sync.Mutex
logger logger.Logger
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &quicDialer{
sessions: make(map[string]*quicSession),
logger: options.Logger,
options: options,
}
}
func (d *quicDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
func (d *quicDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
if _, _, err := net.SplitHostPort(addr); err != nil {
addr = net.JoinHostPort(addr, "0")
}
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if !ok {
options := &dialer.DialOptions{}
for _, opt := range opts {
opt(options)
}
c, err := options.Dialer.Dial(ctx, "udp", "")
if err != nil {
return nil, err
}
pc, ok := c.(net.PacketConn)
if !ok {
c.Close()
return nil, errors.New("quic: wrong connection type")
}
if d.md.cipherKey != nil {
pc = quic_util.CipherPacketConn(pc, d.md.cipherKey)
}
session, err = d.initSession(ctx, udpAddr, pc)
if err != nil {
d.logger.Error(err)
pc.Close()
return nil, err
}
d.sessions[addr] = session
}
conn, err = session.GetConn()
if err != nil {
session.Close()
delete(d.sessions, addr)
return nil, err
}
return
}
func (d *quicDialer) initSession(ctx context.Context, addr net.Addr, conn net.PacketConn) (*quicSession, error) {
quicConfig := &quic.Config{
KeepAlivePeriod: d.md.keepAlivePeriod,
HandshakeIdleTimeout: d.md.handshakeTimeout,
MaxIdleTimeout: d.md.maxIdleTimeout,
Versions: []quic.Version{
quic.Version1,
quic.Version2,
},
MaxIncomingStreams: int64(d.md.maxStreams),
EnableDatagrams: d.md.enableDatagram,
}
tlsCfg := d.options.TLSConfig
tlsCfg.NextProtos = []string{"h3", "quic/v1"}
session, err := quic.DialEarly(ctx, conn, addr, tlsCfg, quicConfig)
if err != nil {
return nil, err
}
return &quicSession{session: session}, nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *quicDialer) Multiplex() bool {
return true
}
+47
View File
@@ -0,0 +1,47 @@
package quic
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
keepAlivePeriod time.Duration
maxIdleTimeout time.Duration
handshakeTimeout time.Duration
maxStreams int
enableDatagram bool
cipherKey []byte
}
func (d *quicDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
keepAlive = "keepAlive"
keepAlivePeriod = "ttl"
handshakeTimeout = "handshakeTimeout"
maxIdleTimeout = "maxIdleTimeout"
maxStreams = "maxStreams"
cipherKey = "cipherKey"
)
if key := mdutil.GetString(md, cipherKey); key != "" {
d.md.cipherKey = []byte(key)
}
if md == nil || !md.IsExists(keepAlive) || mdutil.GetBool(md, keepAlive) {
d.md.keepAlivePeriod = mdutil.GetDuration(md, keepAlivePeriod)
if d.md.keepAlivePeriod <= 0 {
d.md.keepAlivePeriod = 10 * time.Second
}
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
d.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
d.md.maxStreams = mdutil.GetInt(md, maxStreams)
d.md.enableDatagram = mdutil.GetBool(md, "quic.enableDatagram", "enableDatagram")
return
}
+51
View File
@@ -0,0 +1,51 @@
package serial
import (
"context"
"net"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
serial "github.com/go-gost/x/internal/util/serial"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("serial", NewDialer)
}
type serialDialer struct {
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &serialDialer{
logger: options.Logger,
}
}
func (d *serialDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *serialDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
cfg := serial.ParseConfigFromAddr(addr)
port, err := serial.OpenPort(cfg)
if err != nil {
return nil, err
}
return serial.NewConn(port, &serial.Addr{Port: cfg.Name}, nil), nil
}
+12
View File
@@ -0,0 +1,12 @@
package serial
import (
md "github.com/go-gost/core/metadata"
)
type metadata struct {
}
func (d *serialDialer) parseMetadata(md md.Metadata) (err error) {
return
}
+121
View File
@@ -0,0 +1,121 @@
package ssh
import (
"context"
"net"
"sync"
"time"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
ssh_util "github.com/go-gost/x/internal/util/ssh"
"github.com/go-gost/x/registry"
"golang.org/x/crypto/ssh"
)
func init() {
registry.DialerRegistry().Register("ssh", NewDialer)
}
type sshDialer struct {
sessions map[string]*ssh_util.Session
sessionMutex sync.Mutex
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &sshDialer{
sessions: make(map[string]*ssh_util.Session),
options: options,
}
}
func (d *sshDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *sshDialer) Multiplex() bool {
return true
}
func (d *sshDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if session != nil && session.IsClosed() {
delete(d.sessions, addr) // session is dead
ok = false
}
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
return
}
if d.md.handshakeTimeout > 0 {
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
defer conn.SetDeadline(time.Time{})
}
session, err = d.initSession(ctx, addr, conn)
if err != nil {
conn.Close()
return nil, err
}
if d.md.keepalive {
go session.Keepalive(d.md.keepaliveInterval, d.md.keepaliveTimeout, d.md.keepaliveRetries)
}
go session.Wait()
go session.WaitClose()
d.sessions[addr] = session
}
channel, reqs, err := session.OpenChannel(ssh_util.GostSSHTunnelRequest)
if err != nil {
return nil, err
}
go ssh.DiscardRequests(reqs)
return ssh_util.NewConn(session, channel), nil
}
func (d *sshDialer) initSession(ctx context.Context, addr string, conn net.Conn) (*ssh_util.Session, error) {
config := ssh.ClientConfig{
Timeout: d.md.handshakeTimeout,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
if d.options.Auth != nil {
config.User = d.options.Auth.Username()
if password, _ := d.options.Auth.Password(); password != "" {
config.Auth = []ssh.AuthMethod{
ssh.Password(password),
}
}
}
if d.md.signer != nil {
config.Auth = append(config.Auth, ssh.PublicKeys(d.md.signer))
}
sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &config)
if err != nil {
return nil, err
}
return ssh_util.NewSession(conn, ssh.NewClient(sshConn, chans, reqs), d.options.Logger), nil
}
+70
View File
@@ -0,0 +1,70 @@
package ssh
import (
"fmt"
"os"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/mitchellh/go-homedir"
"github.com/zalando/go-keyring"
"golang.org/x/crypto/ssh"
)
type metadata struct {
handshakeTimeout time.Duration
signer ssh.Signer
keepalive bool
keepaliveInterval time.Duration
keepaliveTimeout time.Duration
keepaliveRetries int
}
func (d *sshDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
handshakeTimeout = "handshakeTimeout"
privateKeyFile = "privateKeyFile"
passphrase = "passphrase"
passphraseFromKeyring = "passphraseFromKeyring"
)
if key := mdutil.GetString(md, privateKeyFile); key != "" {
key, err = homedir.Expand(key)
if err != nil {
return err
}
data, err := os.ReadFile(key)
if err != nil {
return err
}
var pp string
if mdutil.GetBool(md, passphraseFromKeyring) {
pp, err = keyring.Get(fmt.Sprintf("SSH %s", key), d.options.Auth.Username())
if err != nil {
return fmt.Errorf("unable to get secret(%s) from keyring: %w", key, err)
}
} else {
pp = mdutil.GetString(md, passphrase)
}
if pp == "" {
d.md.signer, err = ssh.ParsePrivateKey(data)
} else {
d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp))
}
if err != nil {
return err
}
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
if d.md.keepalive = mdutil.GetBool(md, "keepalive"); d.md.keepalive {
d.md.keepaliveInterval = mdutil.GetDuration(md, "ttl", "keepalive.interval")
d.md.keepaliveTimeout = mdutil.GetDuration(md, "keepalive.timeout")
d.md.keepaliveRetries = mdutil.GetInt(md, "keepalive.retries")
}
return
}
+124
View File
@@ -0,0 +1,124 @@
package sshd
import (
"context"
"net"
"sync"
"time"
"os"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
ssh_util "github.com/go-gost/x/internal/util/ssh"
"github.com/go-gost/x/registry"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func init() {
registry.DialerRegistry().Register("sshd", NewDialer)
}
type sshdDialer struct {
sessions map[string]*ssh_util.Session
sessionMutex sync.Mutex
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &sshdDialer{
sessions: make(map[string]*ssh_util.Session),
options: options,
}
}
func (d *sshdDialer) Init(md md.Metadata) (err error) {
if err = d.parseMetadata(md); err != nil {
return
}
return nil
}
// Multiplex implements dialer.Multiplexer interface.
func (d *sshdDialer) Multiplex() bool {
return true
}
func (d *sshdDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (conn net.Conn, err error) {
d.sessionMutex.Lock()
defer d.sessionMutex.Unlock()
session, ok := d.sessions[addr]
if session != nil && session.IsClosed() {
delete(d.sessions, addr) // session is dead
ok = false
}
if !ok {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err = options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
return
}
if d.md.handshakeTimeout > 0 {
conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout))
defer conn.SetDeadline(time.Time{})
}
session, err = d.initSession(ctx, addr, conn)
if err != nil {
conn.Close()
return nil, err
}
if d.md.keepalive {
go session.Keepalive(d.md.keepaliveInterval, d.md.keepaliveTimeout, d.md.keepaliveRetries)
}
go session.Wait()
go session.WaitClose()
d.sessions[addr] = session
}
return ssh_util.NewClientConn(session), nil
}
func (d *sshdDialer) initSession(ctx context.Context, addr string, conn net.Conn) (*ssh_util.Session, error) {
config := ssh.ClientConfig{
Timeout: d.md.handshakeTimeout,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
if d.options.Auth != nil {
config.User = d.options.Auth.Username()
if password, _ := d.options.Auth.Password(); password != "" {
config.Auth = []ssh.AuthMethod{
ssh.Password(password),
}
}
}
if d.md.signer != nil {
config.Auth = append(config.Auth, ssh.PublicKeys(d.md.signer))
}
socket := os.Getenv("SSH_AUTH_SOCK")
if agentConn, err := net.Dial("unix", socket); err == nil {
agentClient := agent.NewClient(agentConn)
config.Auth = append(config.Auth, ssh.PublicKeysCallback(agentClient.Signers))
}
sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &config)
if err != nil {
return nil, err
}
return ssh_util.NewSession(conn, ssh.NewClient(sshConn, chans, reqs), d.options.Logger), nil
}
+68
View File
@@ -0,0 +1,68 @@
package sshd
import (
"fmt"
"os"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/mitchellh/go-homedir"
"github.com/zalando/go-keyring"
"golang.org/x/crypto/ssh"
)
type metadata struct {
handshakeTimeout time.Duration
signer ssh.Signer
keepalive bool
keepaliveInterval time.Duration
keepaliveTimeout time.Duration
keepaliveRetries int
}
func (d *sshdDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
handshakeTimeout = "handshakeTimeout"
privateKeyFile = "privateKeyFile"
passphrase = "passphrase"
)
if key := mdutil.GetString(md, privateKeyFile); key != "" {
key, err = homedir.Expand(key)
if err != nil {
return err
}
data, err := os.ReadFile(key)
if err != nil {
return err
}
var pp string
if mdutil.GetBool(md, "passphraseFromKeyring") {
pp, err = keyring.Get(fmt.Sprintf("SSH %s", key), key)
if err != nil {
return fmt.Errorf("unable to get secret(%s) from keyring: %w", key, err)
}
} else {
pp = mdutil.GetString(md, passphrase)
}
if pp == "" {
d.md.signer, err = ssh.ParsePrivateKey(data)
} else {
d.md.signer, err = ssh.ParsePrivateKeyWithPassphrase(data, []byte(pp))
}
if err != nil {
return err
}
}
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
if d.md.keepalive = mdutil.GetBool(md, "keepalive"); d.md.keepalive {
d.md.keepaliveInterval = mdutil.GetDuration(md, "ttl", "keepalive.interval")
d.md.keepaliveTimeout = mdutil.GetDuration(md, "keepalive.timeout")
d.md.keepaliveRetries = mdutil.GetInt(md, "keepalive.retries")
}
return
}
+48
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.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
d.logger.Error(err)
}
return conn, err
}
+23
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
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.Dialer.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
View File
@@ -0,0 +1,22 @@
package tls
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
handshakeTimeout time.Duration
}
func (d *tlsDialer) parseMetadata(md mdata.Metadata) (err error) {
const (
handshakeTimeout = "handshakeTimeout"
)
d.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
return
}
+17
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
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.Dialer.Dial(ctx, "udp", addr)
if err != nil {
return nil, err
}
return &conn{
UDPConn: c.(*net.UDPConn),
}, nil
}
+23
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
}
+48
View File
@@ -0,0 +1,48 @@
package unix
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("unix", NewDialer)
}
type unixDialer struct {
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &unixDialer{
logger: options.Logger,
}
}
func (d *unixDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *unixDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err := (&net.Dialer{}).DialContext(ctx, "unix", addr)
if err != nil {
d.logger.Error(err)
}
return conn, err
}
+12
View File
@@ -0,0 +1,12 @@
package unix
import (
md "github.com/go-gost/core/metadata"
)
type metadata struct {
}
func (d *unixDialer) parseMetadata(md md.Metadata) (err error) {
return
}
+48
View File
@@ -0,0 +1,48 @@
package wg
import (
"context"
"net"
"github.com/go-gost/core/dialer"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/registry"
)
func init() {
registry.DialerRegistry().Register("wg", NewDialer)
}
type wgDialer struct {
md metadata
logger logger.Logger
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := &dialer.Options{}
for _, opt := range opts {
opt(options)
}
return &wgDialer{
logger: options.Logger,
}
}
func (d *wgDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *wgDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
d.logger.Error(err)
}
return conn, err
}
+23
View File
@@ -0,0 +1,23 @@
package wg
import (
"time"
md "github.com/go-gost/core/metadata"
)
const (
dialTimeout = "dialTimeout"
)
const (
defaultDialTimeout = 5 * time.Second
)
type metadata struct {
dialTimeout time.Duration
}
func (d *wgDialer) parseMetadata(md md.Metadata) (err error) {
return
}
+135
View File
@@ -0,0 +1,135 @@
package ws
import (
"context"
"net"
"net/url"
"time"
"github.com/go-gost/core/dialer"
md "github.com/go-gost/core/metadata"
ws_util "github.com/go-gost/x/internal/util/ws"
"github.com/go-gost/x/registry"
"github.com/gorilla/websocket"
)
func init() {
registry.DialerRegistry().Register("ws", NewDialer)
registry.DialerRegistry().Register("wss", NewTLSDialer)
}
type wsDialer struct {
tlsEnabled bool
md metadata
options dialer.Options
}
func NewDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &wsDialer{
options: options,
}
}
func NewTLSDialer(opts ...dialer.Option) dialer.Dialer {
options := dialer.Options{}
for _, opt := range opts {
opt(&options)
}
return &wsDialer{
tlsEnabled: true,
options: options,
}
}
func (d *wsDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md)
}
func (d *wsDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions
for _, opt := range opts {
opt(&options)
}
conn, err := options.Dialer.Dial(ctx, "tcp", addr)
if err != nil {
d.options.Logger.Error(err)
}
return conn, err
}
// Handshake implements dialer.Handshaker
func (d *wsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dialer.HandshakeOption) (net.Conn, error) {
opts := &dialer.HandshakeOptions{}
for _, option := range options {
option(opts)
}
if d.md.handshakeTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(d.md.handshakeTimeout))
defer conn.SetReadDeadline(time.Time{})
}
host := d.md.host
if host == "" {
host = opts.Addr
}
dialer := websocket.Dialer{
HandshakeTimeout: d.md.handshakeTimeout,
ReadBufferSize: d.md.readBufferSize,
WriteBufferSize: d.md.writeBufferSize,
EnableCompression: d.md.enableCompression,
NetDial: func(net, addr string) (net.Conn, error) {
return conn, nil
},
}
url := url.URL{Scheme: "ws", Host: host, Path: d.md.path}
if d.tlsEnabled {
url.Scheme = "wss"
dialer.TLSClientConfig = d.options.TLSConfig
}
c, resp, err := dialer.DialContext(ctx, url.String(), d.md.header)
if err != nil {
return nil, err
}
resp.Body.Close()
cc := ws_util.Conn(c)
if d.md.keepaliveInterval > 0 {
d.options.Logger.Debugf("keepalive is enabled, ttl: %v", d.md.keepaliveInterval)
c.SetReadDeadline(time.Now().Add(d.md.keepaliveInterval * 2))
c.SetPongHandler(func(string) error {
c.SetReadDeadline(time.Now().Add(d.md.keepaliveInterval * 2))
d.options.Logger.Debugf("pong: set read deadline: %v", d.md.keepaliveInterval*2)
return nil
})
go d.keepalive(cc)
}
return cc, nil
}
func (d *wsDialer) keepalive(conn ws_util.WebsocketConn) {
ticker := time.NewTicker(d.md.keepaliveInterval)
defer ticker.Stop()
for range ticker.C {
d.options.Logger.Debug("send ping")
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
// d.options.Logger.Error(err)
return
}
conn.SetWriteDeadline(time.Time{})
}
}
+60
View File
@@ -0,0 +1,60 @@
package ws
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
defaultPath = "/ws"
defaultKeepalivePeriod = 15 * time.Second
)
type metadata struct {
host string
path string
handshakeTimeout time.Duration
readHeaderTimeout time.Duration
readBufferSize int
writeBufferSize int
enableCompression bool
header http.Header
keepaliveInterval time.Duration
}
func (d *wsDialer) parseMetadata(md mdata.Metadata) (err error) {
d.md.host = mdutil.GetString(md, "ws.host", "host")
d.md.path = mdutil.GetString(md, "ws.path", "path")
if d.md.path == "" {
d.md.path = defaultPath
}
d.md.handshakeTimeout = mdutil.GetDuration(md, "ws.handshakeTimeout", "handshakeTimeout")
d.md.readHeaderTimeout = mdutil.GetDuration(md, "ws.readHeaderTimeout", "readHeaderTimeout")
d.md.readBufferSize = mdutil.GetInt(md, "ws.readBufferSize", "readBufferSize")
d.md.writeBufferSize = mdutil.GetInt(md, "ws.writeBufferSize", "writeBufferSize")
d.md.enableCompression = mdutil.GetBool(md, "ws.enableCompression", "enableCompression")
if m := mdutil.GetStringMapString(md, "ws.header", "header"); len(m) > 0 {
h := http.Header{}
for k, v := range m {
h.Add(k, v)
}
d.md.header = h
}
if mdutil.GetBool(md, "ws.keepalive", "keepalive") {
d.md.keepaliveInterval = mdutil.GetDuration(md, "ttl", "keepalive.interval")
if d.md.keepaliveInterval <= 0 {
d.md.keepaliveInterval = defaultKeepalivePeriod
}
}
return
}