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 (
"io"
"net"
"time"
)
type conn struct{}
func (c *conn) Close() error {
return nil
}
func (c *conn) Read(b []byte) (n int, err error) {
return 0, io.EOF
}
func (c *conn) Write(b []byte) (n int, err error) {
return 0, io.ErrClosedPipe
}
func (c *conn) LocalAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) RemoteAddr() net.Addr {
return &net.TCPAddr{}
}
func (c *conn) SetDeadline(t time.Time) error {
return nil
}
func (c *conn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return nil
}
+71
View File
@@ -0,0 +1,71 @@
package direct
import (
"context"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("direct", NewConnector)
registry.ConnectorRegistry().Register("virtual", NewConnector)
}
type directConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &directConnector{
options: options,
}
}
func (c *directConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
var cOpts connector.ConnectOptions
for _, opt := range opts {
opt(&cOpts)
}
if c.md.action == "reject" {
return &conn{}, nil
}
conn, err := cOpts.Dialer.Dial(ctx, network, address)
if err != nil {
return nil, err
}
var localAddr, remoteAddr string
if addr := conn.LocalAddr(); addr != nil {
localAddr = addr.String()
}
if addr := conn.RemoteAddr(); addr != nil {
remoteAddr = addr.String()
}
log := c.options.Logger.WithFields(map[string]any{
"remote": remoteAddr,
"local": localAddr,
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
return conn, nil
}
+17
View File
@@ -0,0 +1,17 @@
package direct
import (
"strings"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
action string
}
func (c *directConnector) parseMetadata(md mdata.Metadata) (err error) {
c.md.action = strings.ToLower(mdutil.GetString(md, "action"))
return
}
+47
View File
@@ -0,0 +1,47 @@
package forward
import (
"context"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("forward", NewConnector)
}
type forwardConnector struct {
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &forwardConnector{
options: options,
}
}
func (c *forwardConnector) Init(md md.Metadata) (err error) {
return nil
}
func (c *forwardConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
return conn, nil
}
+131
View File
@@ -0,0 +1,131 @@
package http
import (
"bufio"
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"time"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("http", NewConnector)
}
type httpConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &httpConnector{
options: options,
}
}
func (c *httpConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *httpConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"local": conn.LocalAddr().String(),
"remote": conn.RemoteAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Host: address},
Host: address,
ProtoMajor: 1,
ProtoMinor: 1,
Header: c.md.header,
}
if req.Header == nil {
req.Header = http.Header{}
}
req.Header.Set("Proxy-Connection", "keep-alive")
if user := c.options.Auth; user != nil {
u := user.Username()
p, _ := user.Password()
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(u+":"+p)))
}
switch network {
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
case "udp", "udp4", "udp6":
req.Header.Set("X-Gost-Protocol", "udp")
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false)
log.Trace(string(dump))
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
req = req.WithContext(ctx)
if err := req.Write(conn); err != nil {
return nil, err
}
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
if err != nil {
return nil, err
}
// NOTE: the server may return `Transfer-Encoding: chunked` header,
// then the Content-Length of response will be unknown (-1),
// in this case, close body will be blocked, so we leave it untouched.
// defer resp.Body.Close()
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s", resp.Status)
}
if network == "udp" {
addr, _ := net.ResolveUDPAddr(network, address)
return socks.UDPTunClientConn(conn, addr), nil
}
return conn, nil
}
+33
View File
@@ -0,0 +1,33 @@
package http
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
connectTimeout time.Duration
header http.Header
}
func (c *httpConnector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "timeout"
header = "header"
)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
if mm := mdutil.GetStringMapString(md, header); len(mm) > 0 {
hd := http.Header{}
for k, v := range mm {
hd.Add(k, v)
}
c.md.header = hd
}
return
}
+54
View File
@@ -0,0 +1,54 @@
package http2
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: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *http2Conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *http2Conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
+123
View File
@@ -0,0 +1,123 @@
package http2
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"net/url"
"time"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("http2", NewConnector)
}
type http2Connector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &http2Connector{
options: options,
}
}
func (c *http2Connector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *http2Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"local": conn.LocalAddr().String(),
"remote": conn.RemoteAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
v, _ := conn.(md.Metadatable)
if v == nil {
err := errors.New("http2: wrong connection type")
log.Error(err)
return nil, err
}
pr, pw := io.Pipe()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Scheme: "https", Host: conn.RemoteAddr().String()},
Host: address,
ProtoMajor: 2,
ProtoMinor: 0,
Header: c.md.header,
Body: pr,
// ContentLength: -1,
}
if req.Header == nil {
req.Header = make(http.Header)
}
if user := c.options.Auth; user != nil {
u := user.Username()
p, _ := user.Password()
req.Header.Set("Proxy-Authorization",
"Basic "+base64.StdEncoding.EncodeToString([]byte(u+":"+p)))
}
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false)
log.Trace(string(dump))
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
client := v.Metadata().Get("client").(*http.Client)
resp, err := client.Do(req)
if err != nil {
log.Error(err)
conn.Close()
return nil, err
}
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
err = fmt.Errorf("%s", resp.Status)
log.Error(err)
return nil, err
}
hc := &http2Conn{
r: resp.Body,
w: pw,
localAddr: conn.RemoteAddr(),
}
hc.remoteAddr, _ = net.ResolveTCPAddr(network, address)
return hc, nil
}
+31
View File
@@ -0,0 +1,31 @@
package http2
import (
"net/http"
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
connectTimeout time.Duration
header http.Header
}
func (c *http2Connector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "timeout"
header = "header"
)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
if mm := mdutil.GetStringMapString(md, header); len(mm) > 0 {
hd := http.Header{}
for k, v := range mm {
hd.Add(k, v)
}
c.md.header = hd
}
return
}
+145
View File
@@ -0,0 +1,145 @@
package relay
import (
"context"
"fmt"
"net"
"strconv"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
"github.com/go-gost/relay"
"github.com/go-gost/x/internal/net/udp"
"github.com/go-gost/x/internal/util/mux"
relay_util "github.com/go-gost/x/internal/util/relay"
)
// Bind implements connector.Binder.
func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
log := c.options.Logger.WithFields(map[string]any{
"network": network,
"address": address,
})
log.Debugf("bind on %s/%s", address, network)
options := connector.BindOptions{}
for _, opt := range opts {
opt(&options)
}
switch network {
case "tcp", "tcp4", "tcp6":
return c.bindTCP(ctx, conn, network, address, log)
case "udp", "udp4", "udp6":
return c.bindUDP(ctx, conn, network, address, &options, log)
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
}
func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, relay.CmdBind, network, address)
if err != nil {
return nil, err
}
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
session, err := mux.ServerSession(conn, c.md.muxCfg)
if err != nil {
return nil, err
}
return &bindListener{
addr: laddr,
session: session,
logger: log,
}, nil
}
func (c *relayConnector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, relay.FUDP|relay.CmdBind, network, address)
if err != nil {
return nil, err
}
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
ln := udp.NewListener(
relay_util.UDPTunClientPacketConn(conn),
&udp.ListenConfig{
Addr: laddr,
Backlog: opts.Backlog,
ReadQueueSize: opts.UDPDataQueueSize,
ReadBufferSize: opts.UDPDataBufferSize,
TTL: opts.UDPConnTTL,
Keepalive: true,
Logger: log,
})
return ln, nil
}
func (c *relayConnector) bind(conn net.Conn, cmd relay.CmdType, network, address string) (net.Addr, error) {
req := relay.Request{
Version: relay.Version1,
Cmd: cmd,
}
if c.options.Auth != nil {
pwd, _ := c.options.Auth.Password()
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: c.options.Auth.Username(),
Password: pwd,
})
}
nid := relay.NetworkTCP
if network == "udp" || network == "udp4" || network == "udp6" {
nid = relay.NetworkUDP
}
req.Features = append(req.Features, &relay.NetworkFeature{
Network: nid,
})
fa := &relay.AddrFeature{}
fa.ParseFrom(address)
req.Features = append(req.Features, fa)
if _, err := req.WriteTo(conn); err != nil {
return nil, err
}
// first reply, bind status
resp := relay.Response{}
if _, err := resp.ReadFrom(conn); err != nil {
return nil, err
}
if resp.Status != relay.StatusOK {
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
}
var addr string
for _, f := range resp.Features {
if f.Type() == relay.FeatureAddr {
if fa, ok := f.(*relay.AddrFeature); ok {
addr = net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
}
}
}
var baddr net.Addr
var err error
switch network {
case "tcp", "tcp4", "tcp6":
baddr, err = net.ResolveTCPAddr(network, addr)
case "udp", "udp4", "udp6":
baddr, err = net.ResolveUDPAddr(network, addr)
default:
err = fmt.Errorf("unknown network %s", network)
}
if err != nil {
return nil, err
}
return baddr, nil
}
+225
View File
@@ -0,0 +1,225 @@
package relay
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"sync"
"github.com/go-gost/core/common/bufpool"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
xrelay "github.com/go-gost/x/internal/util/relay"
)
type tcpConn struct {
net.Conn
wbuf *bytes.Buffer
once sync.Once
mu sync.Mutex
}
func (c *tcpConn) Read(b []byte) (n int, err error) {
c.once.Do(func() {
if c.wbuf != nil {
err = readResponse(c.Conn)
}
})
if err != nil {
return
}
return c.Conn.Read(b)
}
func (c *tcpConn) Write(b []byte) (n int, err error) {
n = len(b) // force byte length consistent
c.mu.Lock()
defer c.mu.Unlock()
if c.wbuf != nil && c.wbuf.Len() > 0 {
c.wbuf.Write(b) // append the data to the cached header
_, err = c.Conn.Write(c.wbuf.Bytes())
c.wbuf.Reset()
return
}
_, err = c.Conn.Write(b)
return
}
type udpConn struct {
net.Conn
wbuf *bytes.Buffer
once sync.Once
mu sync.Mutex
}
func (c *udpConn) Read(b []byte) (n int, err error) {
c.once.Do(func() {
if c.wbuf != nil {
err = readResponse(c.Conn)
}
})
if err != nil {
return
}
var bb [2]byte
_, err = io.ReadFull(c.Conn, bb[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bb[:]))
if len(b) >= dlen {
return io.ReadFull(c.Conn, b[:dlen])
}
buf := bufpool.Get(dlen)
defer bufpool.Put(buf)
_, err = io.ReadFull(c.Conn, buf)
n = copy(b, buf)
return
}
func (c *udpConn) Write(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
n = len(b)
c.mu.Lock()
defer c.mu.Unlock()
if c.wbuf != nil && c.wbuf.Len() > 0 {
var bb [2]byte
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
c.wbuf.Write(bb[:])
c.wbuf.Write(b) // append the data to the cached header
_, err = c.wbuf.WriteTo(c.Conn)
return
}
var bb [2]byte
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
_, err = c.Conn.Write(bb[:])
if err != nil {
return
}
return c.Conn.Write(b)
}
func readResponse(r io.Reader) (err error) {
resp := relay.Response{}
_, err = resp.ReadFrom(r)
if err != nil {
return
}
if resp.Version != relay.Version1 {
err = relay.ErrBadVersion
return
}
if resp.Status != relay.StatusOK {
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
return
}
return nil
}
type bindConn struct {
net.Conn
localAddr net.Addr
remoteAddr net.Addr
md mdata.Metadata
}
func (c *bindConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *bindConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
// Metadata implements metadata.Metadatable interface.
func (c *bindConn) Metadata() mdata.Metadata {
return c.md
}
type bindUDPConn struct {
net.Conn
localAddr net.Addr
remoteAddr net.Addr
md mdata.Metadata
}
func (c *bindUDPConn) Read(b []byte) (n int, err error) {
// 2-byte data length header
var bh [2]byte
_, err = io.ReadFull(c.Conn, bh[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bh[:]))
if len(b) >= dlen {
n, err = io.ReadFull(c.Conn, b[:dlen])
return
}
buf := bufpool.Get(dlen)
defer bufpool.Put(buf)
_, err = io.ReadFull(c.Conn, buf)
n = copy(b, buf)
return
}
func (c *bindUDPConn) Write(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
buf := bufpool.Get(len(b) + 2)
defer bufpool.Put(buf)
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
n = copy(buf[2:], b)
return c.Conn.Write(buf)
}
func (c *bindUDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
addr = c.remoteAddr
n, err = c.Read(b)
return
}
func (c *bindUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return c.Write(b)
}
func (c *bindUDPConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *bindUDPConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
// Metadata implements metadata.Metadatable interface.
func (c *bindUDPConn) Metadata() mdata.Metadata {
return c.md
}
+146
View File
@@ -0,0 +1,146 @@
package relay
import (
"bytes"
"context"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/ctx"
relay_util "github.com/go-gost/x/internal/util/relay"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("relay", NewConnector)
}
type relayConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &relayConnector{
options: options,
}
}
func (c *relayConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *relayConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
req := relay.Request{
Version: relay.Version1,
Cmd: relay.CmdConnect,
}
switch network {
case "udp", "udp4", "udp6":
req.Cmd |= relay.FUDP
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUDP,
})
// UDP association
if address == "" {
baddr, err := c.bind(conn, relay.FUDP|relay.CmdBind, network, address)
if err != nil {
return nil, err
}
log.Debugf("associate on %s OK", baddr)
return relay_util.UDPTunClientConn(conn, nil), nil
}
case "unix":
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUnix,
})
case "serial":
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkSerial,
})
}
if c.options.Auth != nil {
pwd, _ := c.options.Auth.Password()
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: c.options.Auth.Username(),
Password: pwd,
})
}
if address != "" {
af := &relay.AddrFeature{}
if err := af.ParseFrom(address); err != nil {
return nil, err
}
req.Features = append(req.Features, af)
}
if c.md.noDelay {
if _, err := req.WriteTo(conn); err != nil {
return nil, err
}
// drain the response
if err := readResponse(conn); err != nil {
return nil, err
}
}
switch network {
case "tcp", "tcp4", "tcp6", "unix", "serial":
if !c.md.noDelay {
cc := &tcpConn{
Conn: conn,
wbuf: &bytes.Buffer{},
}
if _, err := req.WriteTo(cc.wbuf); err != nil {
return nil, err
}
conn = cc
}
case "udp", "udp4", "udp6":
cc := &udpConn{
Conn: conn,
}
if !c.md.noDelay {
cc.wbuf = &bytes.Buffer{}
if _, err := req.WriteTo(cc.wbuf); err != nil {
return nil, err
}
}
conn = cc
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
return conn, nil
}
+99
View File
@@ -0,0 +1,99 @@
package relay
import (
"fmt"
"net"
"strconv"
"github.com/go-gost/core/logger"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
"github.com/go-gost/x/internal/util/mux"
mdx "github.com/go-gost/x/metadata"
)
type bindListener struct {
network string
addr net.Addr
session *mux.Session
logger logger.Logger
}
func (p *bindListener) Accept() (net.Conn, error) {
cc, err := p.session.Accept()
if err != nil {
return nil, err
}
conn, err := p.getPeerConn(cc)
if err != nil {
cc.Close()
p.logger.Errorf("get peer failed: %s", err)
return nil, err
}
return conn, nil
}
func (p *bindListener) getPeerConn(conn net.Conn) (net.Conn, error) {
// second reply, peer connected
resp := relay.Response{}
if _, err := resp.ReadFrom(conn); err != nil {
return nil, err
}
if resp.Status != relay.StatusOK {
err := fmt.Errorf("peer connect failed")
return nil, err
}
var address, host string
// the first addr is the client address, the optional second addr is the target host address.
for _, f := range resp.Features {
if f.Type() == relay.FeatureAddr {
if fa, ok := f.(*relay.AddrFeature); ok {
v := net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
if address != "" {
host = v
} else {
address = v
}
}
}
}
raddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return nil, err
}
var md mdata.Metadata
if host != "" {
md = mdx.NewMetadata(map[string]any{"host": host})
}
if p.network == "udp" {
return &bindUDPConn{
Conn: conn,
localAddr: p.addr,
remoteAddr: raddr,
md: md,
}, nil
}
cn := &bindConn{
Conn: conn,
localAddr: p.addr,
remoteAddr: raddr,
md: md,
}
return cn, nil
}
func (p *bindListener) Addr() net.Addr {
return p.addr
}
func (p *bindListener) Close() error {
return p.session.Close()
}
+37
View File
@@ -0,0 +1,37 @@
package relay
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 {
connectTimeout time.Duration
noDelay bool
muxCfg *mux.Config
}
func (c *relayConnector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "connectTimeout"
noDelay = "nodelay"
)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
c.md.noDelay = mdutil.GetBool(md, noDelay)
c.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
}
+73
View File
@@ -0,0 +1,73 @@
package tunnel
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/relay"
xrelay "github.com/go-gost/x/internal/util/relay"
)
type packetConn struct {
net.Conn
}
func (c *packetConn) Read(b []byte) (n int, err error) {
var bb [2]byte
_, err = io.ReadFull(c.Conn, bb[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bb[:]))
if len(b) >= dlen {
return io.ReadFull(c.Conn, b[:dlen])
}
buf := bufpool.Get(dlen)
defer bufpool.Put(buf)
n, err = io.ReadFull(c.Conn, buf)
copy(b, buf[:n])
return
}
func (c *packetConn) Write(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
buf := bufpool.Get(len(b) + 2)
defer bufpool.Put(buf)
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
n = copy(buf[2:], b)
return c.Conn.Write(buf)
}
func readResponse(r io.Reader) (err error) {
resp := relay.Response{}
_, err = resp.ReadFrom(r)
if err != nil {
return
}
if resp.Version != relay.Version1 {
err = relay.ErrBadVersion
return
}
if resp.Status != relay.StatusOK {
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
return
}
return nil
}
+120
View File
@@ -0,0 +1,120 @@
package tunnel
import (
"context"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("router", NewConnector)
}
type routerConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &routerConnector{
options: options,
}
}
func (c *routerConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *routerConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
req := relay.Request{
Version: relay.Version1,
Cmd: relay.CmdAssociate,
}
if c.options.Auth != nil {
pwd, _ := c.options.Auth.Password()
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: c.options.Auth.Username(),
Password: pwd,
})
}
switch network {
case "udp":
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUDP,
})
case "ip":
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkIP,
})
}
srcAddr := conn.LocalAddr().String()
if v := ctxvalue.ClientAddrFromContext(ctx); v != "" {
srcAddr = string(v)
}
af := &relay.AddrFeature{}
af.ParseFrom(srcAddr)
req.Features = append(req.Features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(address)
req.Features = append(req.Features, af) // dst address
if !c.md.routerID.IsZero() {
req.Features = append(req.Features, &relay.TunnelFeature{
ID: c.md.routerID,
})
}
if _, err := req.WriteTo(conn); err != nil {
return nil, err
}
// drain the response
if err := readResponse(conn); err != nil {
return nil, err
}
switch network {
case "udp", "ip":
conn = &packetConn{
Conn: conn,
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
log.Debugf("connect to router %s@%s OK", address, c.md.routerID)
return conn, nil
}
+34
View File
@@ -0,0 +1,34 @@
package tunnel
import (
"errors"
"time"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/google/uuid"
)
var (
ErrInvalidRouterID = errors.New("router: invalid router ID")
)
type metadata struct {
connectTimeout time.Duration
routerID relay.TunnelID
}
func (c *routerConnector) parseMetadata(md mdata.Metadata) (err error) {
c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout")
if s := mdutil.GetString(md, "router.id"); s != "" {
uuid, err := uuid.Parse(s)
if err != nil {
return err
}
c.md.routerID = relay.NewTunnelID(uuid[:])
}
return
}
+47
View File
@@ -0,0 +1,47 @@
package serial
import (
"context"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("serial", NewConnector)
}
type serialConnector struct {
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &serialConnector{
options: options,
}
}
func (c *serialConnector) Init(md md.Metadata) (err error) {
return nil
}
func (c *serialConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
return conn, nil
}
+128
View File
@@ -0,0 +1,128 @@
package sni
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/binary"
"hash/crc32"
"io"
"net"
"strings"
dissector "github.com/go-gost/tls-dissector"
)
type sniClientConn struct {
host string
obfuscated bool
net.Conn
}
func (c *sniClientConn) Write(p []byte) (int, error) {
b, err := c.obfuscate(p)
if err != nil {
return 0, err
}
if _, err = c.Conn.Write(b); err != nil {
return 0, err
}
return len(p), nil
}
func (c *sniClientConn) obfuscate(p []byte) ([]byte, error) {
if c.host == "" {
return p, nil
}
if c.obfuscated {
return p, nil
}
if p[0] == dissector.Handshake {
b, err := readClientHelloRecord(bytes.NewReader(p), c.host)
if err != nil {
return nil, err
}
c.obfuscated = true
return b, nil
}
buf := &bytes.Buffer{}
br := bufio.NewReader(bytes.NewReader(p))
for {
s, err := br.ReadString('\n')
if err != nil {
if err != io.EOF {
return nil, err
}
if s != "" {
buf.Write([]byte(s))
}
break
}
// end of HTTP header
if s == "\r\n" {
buf.Write([]byte(s))
// drain the remain bytes.
io.Copy(buf, br)
break
}
if strings.HasPrefix(s, "Host") {
s = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(s, "Host:"), "\r\n"))
host := encodeServerName(s)
buf.WriteString("Host: " + c.host + "\r\n")
buf.WriteString("Gost-Target: " + host + "\r\n")
// drain the remain bytes.
io.Copy(buf, br)
break
}
buf.Write([]byte(s))
}
c.obfuscated = true
return buf.Bytes(), nil
}
func readClientHelloRecord(r io.Reader, host string) ([]byte, error) {
record, err := dissector.ReadRecord(r)
if err != nil {
return nil, err
}
clientHello := dissector.ClientHelloMsg{}
if err := clientHello.Decode(record.Opaque); err != nil {
return nil, err
}
for _, ext := range clientHello.Extensions {
if ext.Type() == dissector.ExtServerName {
snExtension := ext.(*dissector.ServerNameExtension)
if host != "" {
e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))
clientHello.Extensions = append(clientHello.Extensions, e)
snExtension.Name = host
}
break
}
}
record.Opaque, err = clientHello.Encode()
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
if _, err := record.WriteTo(buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func encodeServerName(name string) string {
buf := &bytes.Buffer{}
binary.Write(buf, binary.BigEndian, crc32.ChecksumIEEE([]byte(name)))
buf.WriteString(base64.RawURLEncoding.EncodeToString([]byte(name)))
return base64.RawURLEncoding.EncodeToString(buf.Bytes())
}
+48
View File
@@ -0,0 +1,48 @@
package sni
import (
"context"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("sni", NewConnector)
}
type sniConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &sniConnector{
options: options,
}
}
func (c *sniConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *sniConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
return &sniClientConn{Conn: conn, host: c.md.host}, nil
}
+25
View File
@@ -0,0 +1,25 @@
package sni
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
host string
connectTimeout time.Duration
}
func (c *sniConnector) parseMetadata(md mdata.Metadata) (err error) {
const (
host = "host"
connectTimeout = "timeout"
)
c.md.host = mdutil.GetString(md, host)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
return
}
+125
View File
@@ -0,0 +1,125 @@
package v4
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks4"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("socks4", NewConnector)
registry.ConnectorRegistry().Register("socks4a", NewConnector)
}
type socks4Connector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks4Connector{
options: options,
}
}
func (c *socks4Connector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *socks4Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
switch network {
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
var addr *gosocks4.Addr
if c.md.disable4a {
taddr, err := net.ResolveTCPAddr("tcp4", address)
if err != nil {
log.Error("resolve: ", err)
return nil, err
}
if len(taddr.IP) == 0 {
taddr.IP = net.IPv4zero
}
addr = &gosocks4.Addr{
Type: gosocks4.AddrIPv4,
Host: taddr.IP.String(),
Port: uint16(taddr.Port),
}
} else {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
p, _ := strconv.Atoi(port)
addr = &gosocks4.Addr{
Type: gosocks4.AddrDomain,
Host: host,
Port: uint16(p),
}
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
var userid []byte
if c.options.Auth != nil {
userid = []byte(c.options.Auth.Username())
}
req := gosocks4.NewRequest(gosocks4.CmdConnect, addr, userid)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks4.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Code != gosocks4.Granted {
err = errors.New("host unreachable")
log.Error(err)
return nil, err
}
return conn, nil
}
+25
View File
@@ -0,0 +1,25 @@
package v4
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
connectTimeout time.Duration
disable4a bool
}
func (c *socks4Connector) parseMetadata(md mdata.Metadata) (err error) {
const (
connectTimeout = "timeout"
disable4a = "disable4a"
)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
c.md.disable4a = mdutil.GetBool(md, disable4a)
return
}
+132
View File
@@ -0,0 +1,132 @@
package v5
import (
"context"
"fmt"
"net"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/net/udp"
"github.com/go-gost/x/internal/util/mux"
"github.com/go-gost/x/internal/util/socks"
)
// Bind implements connector.Binder.
func (c *socks5Connector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
})
log.Debugf("bind on %s/%s", address, network)
options := connector.BindOptions{}
for _, opt := range opts {
opt(&options)
}
switch network {
case "tcp", "tcp4", "tcp6":
if options.Mux {
return c.muxBindTCP(ctx, conn, network, address, log)
}
return c.bindTCP(ctx, conn, network, address, log)
case "udp", "udp4", "udp6":
return c.bindUDP(ctx, conn, network, address, &options, log)
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
}
func (c *socks5Connector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, gosocks5.CmdBind, network, address, log)
if err != nil {
return nil, err
}
return &tcpListener{
addr: laddr,
conn: conn,
logger: log,
}, nil
}
func (c *socks5Connector) muxBindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, socks.CmdMuxBind, network, address, log)
if err != nil {
return nil, err
}
session, err := mux.ServerSession(conn, c.md.muxCfg)
if err != nil {
return nil, err
}
return &tcpMuxListener{
addr: laddr,
session: session,
logger: log,
}, nil
}
func (c *socks5Connector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
laddr, err := c.bind(conn, socks.CmdUDPTun, network, address, log)
if err != nil {
return nil, err
}
ln := udp.NewListener(socks.UDPTunClientPacketConn(conn),
&udp.ListenConfig{
Addr: laddr,
Backlog: opts.Backlog,
ReadQueueSize: opts.UDPDataQueueSize,
ReadBufferSize: opts.UDPDataBufferSize,
TTL: opts.UDPConnTTL,
Keepalive: true,
Logger: log,
})
return ln, nil
}
func (l *socks5Connector) bind(conn net.Conn, cmd uint8, network, address string, log logger.Logger) (net.Addr, error) {
addr := gosocks5.Addr{}
addr.ParseFrom(address)
req := gosocks5.NewRequest(cmd, &addr)
log.Trace(req)
if err := req.Write(conn); err != nil {
return nil, err
}
// first reply, bind status
reply, err := gosocks5.ReadReply(conn)
if err != nil {
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
}
var baddr net.Addr
switch network {
case "tcp", "tcp4", "tcp6":
baddr, err = net.ResolveTCPAddr(network, reply.Addr.String())
case "udp", "udp4", "udp6":
baddr, err = net.ResolveUDPAddr(network, reply.Addr.String())
default:
err = fmt.Errorf("unknown network %s", network)
}
if err != nil {
return nil, err
}
log.Debugf("bind on %s/%s OK", baddr, baddr.Network())
return baddr, nil
}
+129
View File
@@ -0,0 +1,129 @@
package v5
import (
"bytes"
"net"
"time"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
)
type bindConn struct {
net.Conn
localAddr net.Addr
remoteAddr net.Addr
}
func (c *bindConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *bindConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
type udpRelayConn struct {
udpConn *net.UDPConn
tcpConn net.Conn
taddr net.Addr
bufferSize int
logger logger.Logger
}
func (c *udpRelayConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
buf := bufpool.Get(c.bufferSize)
defer bufpool.Put(buf)
nn, err := c.udpConn.Read(buf)
if err != nil {
return
}
socksAddr := gosocks5.Addr{}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
}
_, err = dgram.ReadFrom(bytes.NewReader(buf[:nn]))
if err != nil {
return
}
n = copy(b, dgram.Data)
addr, err = net.ResolveUDPAddr("udp", header.Addr.String())
return
}
func (c *udpRelayConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *udpRelayConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
socksAddr := gosocks5.Addr{}
if err = socksAddr.ParseFrom(addr.String()); err != nil {
return
}
if socksAddr.Host == "" {
socksAddr.Type = gosocks5.AddrIPv4
socksAddr.Host = "127.0.0.1"
}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
buf := bufpool.Get(c.bufferSize)
defer bufpool.Put(buf)
nn, err := dgram.WriteTo(bytes.NewBuffer(buf[:0]))
if err != nil {
return
}
if nn > int64(len(buf)) {
nn = int64(len(buf))
}
_, err = c.udpConn.Write(buf[:nn])
n = len(b)
return
}
func (c *udpRelayConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
func (c *udpRelayConn) RemoteAddr() net.Addr {
return c.taddr
}
func (c *udpRelayConn) LocalAddr() net.Addr {
return c.udpConn.LocalAddr()
}
func (c *udpRelayConn) Close() error {
c.udpConn.Close()
return c.tcpConn.Close()
}
func (c *udpRelayConn) SetDeadline(t time.Time) error {
return c.udpConn.SetDeadline(t)
}
func (c *udpRelayConn) SetReadDeadline(t time.Time) error {
return c.udpConn.SetReadDeadline(t)
}
func (c *udpRelayConn) SetWriteDeadline(t time.Time) error {
return c.udpConn.SetWriteDeadline(t)
}
+234
View File
@@ -0,0 +1,234 @@
package v5
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
"github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/socks"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("socks5", NewConnector)
registry.ConnectorRegistry().Register("socks", NewConnector)
}
type socks5Connector struct {
selector gosocks5.Selector
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &socks5Connector{
options: options,
}
}
func (c *socks5Connector) Init(md md.Metadata) (err error) {
if err = c.parseMetadata(md); err != nil {
return
}
selector := &clientSelector{
methods: []uint8{
gosocks5.MethodNoAuth,
},
User: c.options.Auth,
TLSConfig: c.options.TLSConfig,
logger: c.options.Logger,
}
if selector.User != nil {
selector.methods = append(selector.methods, gosocks5.MethodUserPass)
}
if !c.md.noTLS {
selector.methods = append(selector.methods, socks.MethodTLS)
if selector.TLSConfig == nil {
selector.TLSConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
if selector.User != nil {
selector.methods = append(selector.methods, socks.MethodTLSAuth)
}
}
c.selector = selector
return
}
// Handshake implements connector.Handshaker.
func (c *socks5Connector) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
cc := gosocks5.ClientConn(conn, c.selector)
if err := cc.Handleshake(); err != nil {
log.Error(err)
return nil, err
}
return cc, nil
}
func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
var cOpts connector.ConnectOptions
for _, opt := range opts {
opt(&cOpts)
}
switch network {
case "udp", "udp4", "udp6":
return c.connectUDP(ctx, conn, network, address, log, &cOpts)
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
addr := gosocks5.Addr{}
if err := addr.ParseFrom(address); err != nil {
log.Error(err)
return nil, err
}
if addr.Host == "" {
addr.Type = gosocks5.AddrIPv4
addr.Host = "127.0.0.1"
}
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
err = errors.New("host unreachable")
log.Error(err)
return nil, err
}
return conn, nil
}
func (c *socks5Connector) connectUDP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger, opts *connector.ConnectOptions) (net.Conn, error) {
addr, err := net.ResolveUDPAddr(network, address)
if err != nil {
log.Error(err)
return nil, err
}
if c.md.relay == "udp" {
return c.relayUDP(ctx, conn, addr, log, opts)
}
req := gosocks5.NewRequest(socks.CmdUDPTun, nil)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, errors.New("get socks5 UDP tunnel failure")
}
return socks.UDPTunClientConn(conn, addr), nil
}
func (c *socks5Connector) relayUDP(ctx context.Context, conn net.Conn, addr net.Addr, log logger.Logger, opts *connector.ConnectOptions) (net.Conn, error) {
req := gosocks5.NewRequest(gosocks5.CmdUdp, nil)
log.Trace(req)
if err := req.Write(conn); err != nil {
log.Error(err)
return nil, err
}
reply, err := gosocks5.ReadReply(conn)
if err != nil {
log.Error(err)
return nil, err
}
log.Trace(reply)
if reply.Rep != gosocks5.Succeeded {
return nil, errors.New("get socks5 UDP tunnel failure")
}
log.Debugf("bind on: %v", reply.Addr)
cc, err := opts.Dialer.Dial(ctx, "udp", reply.Addr.String())
if err != nil {
c.options.Logger.Error(err)
return nil, err
}
log.Debugf("%s <- %s -> %s", cc.LocalAddr(), cc.RemoteAddr(), addr)
if c.md.udpTimeout > 0 {
cc.SetReadDeadline(time.Now().Add(c.md.udpTimeout))
}
return &udpRelayConn{
udpConn: cc.(*net.UDPConn),
tcpConn: conn,
taddr: addr,
bufferSize: c.md.udpBufferSize,
logger: log,
}, nil
}
+102
View File
@@ -0,0 +1,102 @@
package v5
import (
"fmt"
"net"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/mux"
)
type tcpListener struct {
addr net.Addr
conn net.Conn
logger logger.Logger
}
func (p *tcpListener) Accept() (net.Conn, error) {
// second reply, peer connected
rep, err := gosocks5.ReadReply(p.conn)
if err != nil {
return nil, err
}
p.logger.Trace(rep)
if rep.Rep != gosocks5.Succeeded {
return nil, fmt.Errorf("peer connect failed")
}
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
if err != nil {
return nil, err
}
return &bindConn{
Conn: p.conn,
localAddr: p.addr,
remoteAddr: raddr,
}, nil
}
func (p *tcpListener) Addr() net.Addr {
return p.addr
}
func (p *tcpListener) Close() error {
return p.conn.Close()
}
type tcpMuxListener struct {
addr net.Addr
session *mux.Session
logger logger.Logger
}
func (p *tcpMuxListener) Accept() (net.Conn, error) {
cc, err := p.session.Accept()
if err != nil {
return nil, err
}
conn, err := p.getPeerConn(cc)
if err != nil {
cc.Close()
return nil, err
}
return conn, nil
}
func (p *tcpMuxListener) getPeerConn(conn net.Conn) (net.Conn, error) {
// second reply, peer connected
rep, err := gosocks5.ReadReply(conn)
if err != nil {
return nil, err
}
p.logger.Trace(rep)
if rep.Rep != gosocks5.Succeeded {
err = fmt.Errorf("peer connect failed")
return nil, err
}
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
if err != nil {
return nil, err
}
return &bindConn{
Conn: conn,
localAddr: p.addr,
remoteAddr: raddr,
}, nil
}
func (p *tcpMuxListener) Addr() net.Addr {
return p.addr
}
func (p *tcpMuxListener) Close() error {
return p.session.Close()
}
+44
View File
@@ -0,0 +1,44 @@
package v5
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"
)
const (
defaultUDPBufferSize = 4096
)
type metadata struct {
connectTimeout time.Duration
noTLS bool
relay string
udpBufferSize int
udpTimeout time.Duration
muxCfg *mux.Config
}
func (c *socks5Connector) parseMetadata(md mdata.Metadata) (err error) {
c.md.connectTimeout = mdutil.GetDuration(md, "timeout")
c.md.noTLS = mdutil.GetBool(md, "notls")
c.md.relay = mdutil.GetString(md, "relay")
c.md.udpBufferSize = mdutil.GetInt(md, "udp.bufferSize", "udpBufferSize")
if c.md.udpBufferSize <= 0 {
c.md.udpBufferSize = defaultUDPBufferSize
}
c.md.udpTimeout = mdutil.GetDuration(md, "udp.timeout")
c.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
}
+77
View File
@@ -0,0 +1,77 @@
package v5
import (
"crypto/tls"
"net"
"net/url"
"github.com/go-gost/core/logger"
"github.com/go-gost/gosocks5"
"github.com/go-gost/x/internal/util/socks"
)
type clientSelector struct {
methods []uint8
User *url.Userinfo
TLSConfig *tls.Config
logger logger.Logger
}
func (s *clientSelector) Methods() []uint8 {
s.logger.Debug("methods: ", s.methods)
return s.methods
}
func (s *clientSelector) AddMethod(methods ...uint8) {
s.methods = append(s.methods, methods...)
}
func (s *clientSelector) Select(methods ...uint8) (method uint8) {
return
}
func (s *clientSelector) OnSelected(method uint8, conn net.Conn) (string, net.Conn, error) {
s.logger.Debug("method selected: ", method)
switch method {
case gosocks5.MethodNoAuth:
case socks.MethodTLS:
conn = tls.Client(conn, s.TLSConfig)
case gosocks5.MethodUserPass, socks.MethodTLSAuth:
if method == socks.MethodTLSAuth {
conn = tls.Client(conn, s.TLSConfig)
}
var username, password string
if s.User != nil {
username = s.User.Username()
password, _ = s.User.Password()
}
req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password)
s.logger.Trace(req)
if err := req.Write(conn); err != nil {
s.logger.Error(err)
return "", nil, err
}
resp, err := gosocks5.ReadUserPassResponse(conn)
if err != nil {
s.logger.Error(err)
return "", nil, err
}
s.logger.Trace(resp)
if resp.Status != gosocks5.Succeeded {
return "", nil, gosocks5.ErrAuthFailure
}
case gosocks5.MethodNoAcceptable:
return "", nil, gosocks5.ErrBadMethod
default:
return "", nil, gosocks5.ErrBadFormat
}
return "", conn, nil
}
+113
View File
@@ -0,0 +1,113 @@
package ss
import (
"context"
"fmt"
"net"
"time"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/ss"
"github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core"
)
func init() {
registry.ConnectorRegistry().Register("ss", NewConnector)
}
type ssConnector struct {
cipher core.Cipher
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &ssConnector{
options: options,
}
}
func (c *ssConnector) Init(md md.Metadata) (err error) {
if err = c.parseMetadata(md); err != nil {
return
}
if c.options.Auth != nil {
method := c.options.Auth.Username()
password, _ := c.options.Auth.Password()
c.cipher, err = ss.ShadowCipher(method, password, c.md.key)
}
return
}
func (c *ssConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
switch network {
case "tcp", "tcp4", "tcp6":
if _, ok := conn.(net.PacketConn); ok {
err := fmt.Errorf("tcp over udp is unsupported")
log.Error(err)
return nil, err
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
addr := gosocks5.Addr{}
if err := addr.ParseFrom(address); err != nil {
log.Error(err)
return nil, err
}
rawaddr := bufpool.Get(512)
defer bufpool.Put(rawaddr)
n, err := addr.Encode(rawaddr)
if err != nil {
log.Error("encoding addr: ", err)
return nil, err
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
if c.cipher != nil {
conn = c.cipher.StreamConn(conn)
}
var sc net.Conn
if c.md.noDelay {
sc = ss.ShadowConn(conn, nil)
// write the addr at once.
if _, err := sc.Write(rawaddr[:n]); err != nil {
return nil, err
}
} else {
// cache the header
sc = ss.ShadowConn(conn, rawaddr[:n])
}
return sc, nil
}
+28
View File
@@ -0,0 +1,28 @@
package ss
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
key string
connectTimeout time.Duration
noDelay bool
}
func (c *ssConnector) parseMetadata(md mdata.Metadata) (err error) {
const (
key = "key"
connectTimeout = "timeout"
noDelay = "nodelay"
)
c.md.key = mdutil.GetString(md, key)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
c.md.noDelay = mdutil.GetBool(md, noDelay)
return
}
+97
View File
@@ -0,0 +1,97 @@
package ss
import (
"context"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/relay"
"github.com/go-gost/x/internal/util/ss"
"github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core"
)
func init() {
registry.ConnectorRegistry().Register("ssu", NewConnector)
}
type ssuConnector struct {
cipher core.Cipher
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &ssuConnector{
options: options,
}
}
func (c *ssuConnector) Init(md md.Metadata) (err error) {
if err = c.parseMetadata(md); err != nil {
return
}
if c.options.Auth != nil {
method := c.options.Auth.Username()
password, _ := c.options.Auth.Password()
c.cipher, err = ss.ShadowCipher(method, password, c.md.key)
}
return
}
func (c *ssuConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
switch network {
case "udp", "udp4", "udp6":
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
taddr, _ := net.ResolveUDPAddr(network, address)
if taddr == nil {
taddr = &net.UDPAddr{}
}
pc, ok := conn.(net.PacketConn)
if ok {
if c.cipher != nil {
pc = c.cipher.PacketConn(pc)
}
// standard UDP relay
return ss.UDPClientConn(pc, conn.RemoteAddr(), taddr), nil
}
if c.cipher != nil {
conn = ss.ShadowConn(c.cipher.StreamConn(conn), nil)
}
// UDP over TCP
return relay.UDPTunClientConn(conn, taddr), nil
}
+25
View File
@@ -0,0 +1,25 @@
package ss
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
type metadata struct {
key string
connectTimeout time.Duration
}
func (c *ssuConnector) parseMetadata(md mdata.Metadata) (err error) {
const (
key = "key"
connectTimeout = "timeout"
)
c.md.key = mdutil.GetString(md, key)
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
return
}
+82
View File
@@ -0,0 +1,82 @@
package sshd
import (
"context"
"errors"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
ssh_util "github.com/go-gost/x/internal/util/ssh"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("sshd", NewConnector)
}
type sshdConnector struct {
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &sshdConnector{
options: options,
}
}
func (c *sshdConnector) Init(md md.Metadata) (err error) {
return nil
}
func (c *sshdConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
cc, ok := conn.(*ssh_util.ClientConn)
if !ok {
return nil, errors.New("ssh: invalid connection")
}
conn, err := cc.Client().Dial(network, address)
if err != nil {
log.Error(err)
return nil, err
}
return conn, nil
}
// Bind implements connector.Binder.
func (c *sshdConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
})
log.Debugf("bind on %s/%s", address, network)
cc, ok := conn.(*ssh_util.ClientConn)
if !ok {
return nil, errors.New("ssh: invalid connection")
}
if host, port, _ := net.SplitHostPort(address); host == "" {
address = net.JoinHostPort("0.0.0.0", port)
}
return cc.Client().Listen(network, address)
}
+47
View File
@@ -0,0 +1,47 @@
package tcp
import (
"context"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("tcp", NewConnector)
}
type tcpConnector struct {
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &tcpConnector{
options: options,
}
}
func (c *tcpConnector) Init(md md.Metadata) (err error) {
return nil
}
func (c *tcpConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
return conn, nil
}
+110
View File
@@ -0,0 +1,110 @@
package tunnel
import (
"context"
"fmt"
"net"
"strconv"
"github.com/go-gost/core/connector"
"github.com/go-gost/relay"
"github.com/go-gost/x/internal/util/mux"
)
// Bind implements connector.Binder.
func (c *tunnelConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
addr, cid, err := c.initTunnel(conn, network, address)
if err != nil {
return nil, err
}
endpoint := addr.String()
if v, _, _ := net.SplitHostPort(addr.String()); v != "" {
endpoint = v
}
log := c.options.Logger.WithFields(map[string]any{
"endpoint": endpoint,
"tunnel": c.md.tunnelID.String(),
})
log.Infof("create tunnel on %s/%s OK, tunnel=%s, connector=%s, weight=%d",
addr, network, c.md.tunnelID.String(), cid, cid.Weight())
session, err := mux.ServerSession(conn, c.md.muxCfg)
if err != nil {
return nil, err
}
return &bindListener{
network: network,
addr: addr,
session: session,
logger: log,
}, nil
}
func (c *tunnelConnector) initTunnel(conn net.Conn, network, address string) (addr net.Addr, cid relay.ConnectorID, err error) {
req := relay.Request{
Version: relay.Version1,
Cmd: relay.CmdBind,
}
if network == "udp" {
req.Cmd |= relay.FUDP
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUDP,
})
}
if c.options.Auth != nil {
pwd, _ := c.options.Auth.Password()
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: c.options.Auth.Username(),
Password: pwd,
})
}
af := &relay.AddrFeature{}
af.ParseFrom(conn.LocalAddr().String()) // src address
req.Features = append(req.Features, af)
af = &relay.AddrFeature{}
af.ParseFrom(address)
req.Features = append(req.Features, af) // dst address
req.Features = append(req.Features, &relay.TunnelFeature{
ID: c.md.tunnelID,
})
if _, err = req.WriteTo(conn); err != nil {
return
}
// first reply, bind status
resp := relay.Response{}
if _, err = resp.ReadFrom(conn); err != nil {
return
}
if resp.Status != relay.StatusOK {
err = fmt.Errorf("%d: create tunnel %s failed", resp.Status, c.md.tunnelID.String())
return
}
for _, f := range resp.Features {
switch f.Type() {
case relay.FeatureAddr:
if feature, _ := f.(*relay.AddrFeature); feature != nil {
addr = &bindAddr{
network: network,
addr: net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port))),
}
}
case relay.FeatureTunnel:
if feature, _ := f.(*relay.TunnelFeature); feature != nil {
cid = feature.ID
}
}
}
return
}
+176
View File
@@ -0,0 +1,176 @@
package tunnel
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"github.com/go-gost/core/common/bufpool"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
xrelay "github.com/go-gost/x/internal/util/relay"
)
type udpConn struct {
net.Conn
}
func (c *udpConn) Read(b []byte) (n int, err error) {
var bb [2]byte
_, err = io.ReadFull(c.Conn, bb[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bb[:]))
if len(b) >= dlen {
return io.ReadFull(c.Conn, b[:dlen])
}
buf := bufpool.Get(dlen)
defer bufpool.Put(buf)
_, err = io.ReadFull(c.Conn, buf)
n = copy(b, buf)
return
}
func (c *udpConn) Write(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
n = len(b)
var bb [2]byte
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
_, err = c.Conn.Write(bb[:])
if err != nil {
return
}
return c.Conn.Write(b)
}
func readResponse(r io.Reader) (err error) {
resp := relay.Response{}
_, err = resp.ReadFrom(r)
if err != nil {
return
}
if resp.Version != relay.Version1 {
err = relay.ErrBadVersion
return
}
if resp.Status != relay.StatusOK {
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
return
}
return nil
}
type bindConn struct {
net.Conn
localAddr net.Addr
remoteAddr net.Addr
md mdata.Metadata
}
func (c *bindConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *bindConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
// Metadata implements metadata.Metadatable interface.
func (c *bindConn) Metadata() mdata.Metadata {
return c.md
}
type bindUDPConn struct {
net.Conn
localAddr net.Addr
remoteAddr net.Addr
md mdata.Metadata
}
func (c *bindUDPConn) Read(b []byte) (n int, err error) {
// 2-byte data length header
var bh [2]byte
_, err = io.ReadFull(c.Conn, bh[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bh[:]))
if len(b) >= dlen {
n, err = io.ReadFull(c.Conn, b[:dlen])
return
}
buf := bufpool.Get(dlen)
defer bufpool.Put(buf)
_, err = io.ReadFull(c.Conn, buf)
n = copy(b, buf)
return
}
func (c *bindUDPConn) Write(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
buf := bufpool.Get(len(b) + 2)
defer bufpool.Put(buf)
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
n = copy(buf[2:], b)
return c.Conn.Write(buf)
}
func (c *bindUDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
addr = c.remoteAddr
n, err = c.Read(b)
return
}
func (c *bindUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
return c.Write(b)
}
func (c *bindUDPConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *bindUDPConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
// Metadata implements metadata.Metadatable interface.
func (c *bindUDPConn) Metadata() mdata.Metadata {
return c.md
}
type bindAddr struct {
network string
addr string
}
func (p *bindAddr) Network() string {
return p.network
}
func (p *bindAddr) String() string {
return p.addr
}
+114
View File
@@ -0,0 +1,114 @@
package tunnel
import (
"context"
"fmt"
"net"
"time"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("tunnel", NewConnector)
}
type tunnelConnector struct {
md metadata
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &tunnelConnector{
options: options,
}
}
func (c *tunnelConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md)
}
func (c *tunnelConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
if c.md.connectTimeout > 0 {
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
defer conn.SetDeadline(time.Time{})
}
req := relay.Request{
Version: relay.Version1,
Cmd: relay.CmdConnect,
}
switch network {
case "udp", "udp4", "udp6":
req.Cmd |= relay.FUDP
req.Features = append(req.Features, &relay.NetworkFeature{
Network: relay.NetworkUDP,
})
}
if c.options.Auth != nil {
pwd, _ := c.options.Auth.Password()
req.Features = append(req.Features, &relay.UserAuthFeature{
Username: c.options.Auth.Username(),
Password: pwd,
})
}
srcAddr := conn.LocalAddr().String()
if v := ctxvalue.ClientAddrFromContext(ctx); v != "" {
srcAddr = string(v)
}
af := &relay.AddrFeature{}
af.ParseFrom(srcAddr)
req.Features = append(req.Features, af) // src address
af = &relay.AddrFeature{}
af.ParseFrom(address)
req.Features = append(req.Features, af) // dst address
req.Features = append(req.Features, &relay.TunnelFeature{
ID: c.md.tunnelID,
})
if _, err := req.WriteTo(conn); err != nil {
return nil, err
}
// drain the response
if err := readResponse(conn); err != nil {
return nil, err
}
switch network {
case "tcp", "tcp4", "tcp6":
case "udp", "udp4", "udp6":
conn = &udpConn{
Conn: conn,
}
default:
err := fmt.Errorf("network %s is unsupported", network)
log.Error(err)
return nil, err
}
return conn, nil
}
+99
View File
@@ -0,0 +1,99 @@
package tunnel
import (
"fmt"
"net"
"strconv"
"github.com/go-gost/core/logger"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
"github.com/go-gost/x/internal/util/mux"
mdx "github.com/go-gost/x/metadata"
)
type bindListener struct {
network string
addr net.Addr
session *mux.Session
logger logger.Logger
}
func (p *bindListener) Accept() (net.Conn, error) {
cc, err := p.session.Accept()
if err != nil {
return nil, err
}
conn, err := p.getPeerConn(cc)
if err != nil {
cc.Close()
p.logger.Errorf("get peer failed: %s", err)
return nil, err
}
return conn, nil
}
func (p *bindListener) getPeerConn(conn net.Conn) (net.Conn, error) {
// second reply, peer connected
resp := relay.Response{}
if _, err := resp.ReadFrom(conn); err != nil {
return nil, err
}
if resp.Status != relay.StatusOK {
err := fmt.Errorf("peer connect failed")
return nil, err
}
var address, host string
// the first addr is the client address, the optional second addr is the target host address.
for _, f := range resp.Features {
if f.Type() == relay.FeatureAddr {
if fa, ok := f.(*relay.AddrFeature); ok {
v := net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
if address != "" {
host = v
} else {
address = v
}
}
}
}
raddr, err := net.ResolveTCPAddr("tcp", address)
if err != nil {
return nil, err
}
var md mdata.Metadata
if host != "" {
md = mdx.NewMetadata(map[string]any{"host": host})
}
if p.network == "udp" {
return &bindUDPConn{
Conn: conn,
localAddr: p.addr,
remoteAddr: raddr,
md: md,
}, nil
}
cn := &bindConn{
Conn: conn,
localAddr: p.addr,
remoteAddr: raddr,
md: md,
}
return cn, nil
}
func (p *bindListener) Addr() net.Addr {
return p.addr
}
func (p *bindListener) Close() error {
return p.session.Close()
}
+64
View File
@@ -0,0 +1,64 @@
package tunnel
import (
"errors"
"time"
mdata "github.com/go-gost/core/metadata"
"github.com/go-gost/relay"
"github.com/go-gost/x/internal/util/mux"
mdutil "github.com/go-gost/x/metadata/util"
"github.com/google/uuid"
)
var (
ErrInvalidTunnelID = errors.New("tunnel: invalid tunnel ID")
)
type metadata struct {
connectTimeout time.Duration
tunnelID relay.TunnelID
muxCfg *mux.Config
}
func (c *tunnelConnector) parseMetadata(md mdata.Metadata) (err error) {
c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout")
if s := mdutil.GetString(md, "tunnelID", "tunnel.id"); s != "" {
uuid, err := uuid.Parse(s)
if err != nil {
return err
}
c.md.tunnelID = relay.NewTunnelID(uuid[:])
}
if c.md.tunnelID.IsZero() {
uuid, err := uuid.NewUUID()
if err != nil {
return err
}
c.md.tunnelID = relay.NewTunnelID(uuid[:])
}
if weight := mdutil.GetInt(md, "tunnel.weight"); weight > 0 {
c.md.tunnelID = c.md.tunnelID.SetWeight(uint8(weight))
}
c.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 c.md.muxCfg.Version == 0 {
c.md.muxCfg.Version = 2
}
if c.md.muxCfg.MaxStreamBuffer == 0 {
c.md.muxCfg.MaxStreamBuffer = 1048576
}
return
}
+47
View File
@@ -0,0 +1,47 @@
package unix
import (
"context"
"net"
"github.com/go-gost/core/connector"
md "github.com/go-gost/core/metadata"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/registry"
)
func init() {
registry.ConnectorRegistry().Register("unix", NewConnector)
}
type unixConnector struct {
options connector.Options
}
func NewConnector(opts ...connector.Option) connector.Connector {
options := connector.Options{}
for _, opt := range opts {
opt(&options)
}
return &unixConnector{
options: options,
}
}
func (c *unixConnector) Init(md md.Metadata) (err error) {
return nil
}
func (c *unixConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
log := c.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"network": network,
"address": address,
"sid": string(ctxvalue.SidFromContext(ctx)),
})
log.Debugf("connect %s/%s", address, network)
return conn, nil
}