initial commit
This commit is contained in:
54
connector/http2/conn.go
Normal file
54
connector/http2/conn.go
Normal 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")}
|
||||
}
|
121
connector/http2/connector.go
Normal file
121
connector/http2/connector.go
Normal file
@ -0,0 +1,121 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/connector"
|
||||
"github.com/go-gost/gost/v3/pkg/logger"
|
||||
md "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
"github.com/go-gost/gost/v3/pkg/registry"
|
||||
http2_util "github.com/go-gost/x/internal/util/http2"
|
||||
)
|
||||
|
||||
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,
|
||||
})
|
||||
log.Infof("connect %s/%s", address, network)
|
||||
|
||||
cc, ok := conn.(*http2_util.ClientConn)
|
||||
if !ok {
|
||||
err := errors.New("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: make(http.Header),
|
||||
Body: pr,
|
||||
// ContentLength: -1,
|
||||
}
|
||||
if c.md.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.md.UserAgent)
|
||||
}
|
||||
|
||||
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.DebugLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Debug(string(dump))
|
||||
}
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
resp, err := cc.Client().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
cc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.DebugLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Debug(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
connector/http2/metadata.go
Normal file
31
connector/http2/metadata.go
Normal file
@ -0,0 +1,31 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultUserAgent = "Chrome/78.0.3904.106"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
func (c *http2Connector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
connectTimeout = "timeout"
|
||||
userAgent = "userAgent"
|
||||
)
|
||||
|
||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
||||
c.md.UserAgent = mdata.GetString(md, userAgent)
|
||||
if c.md.UserAgent == "" {
|
||||
c.md.UserAgent = defaultUserAgent
|
||||
}
|
||||
|
||||
return
|
||||
}
|
133
connector/relay/bind.go
Normal file
133
connector/relay/bind.go
Normal file
@ -0,0 +1,133 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/common/util/udp"
|
||||
"github.com/go-gost/gost/v3/pkg/connector"
|
||||
"github.com/go-gost/gost/v3/pkg/logger"
|
||||
"github.com/go-gost/relay"
|
||||
"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.Infof("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.BIND, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
|
||||
|
||||
session, err := mux.ServerSession(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tcpListener{
|
||||
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.BIND, 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),
|
||||
laddr,
|
||||
opts.Backlog,
|
||||
opts.UDPDataQueueSize, opts.UDPDataBufferSize,
|
||||
opts.UDPConnTTL,
|
||||
log)
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (c *relayConnector) bind(conn net.Conn, cmd uint8, network, address string) (net.Addr, error) {
|
||||
req := relay.Request{
|
||||
Version: relay.Version1,
|
||||
Flags: 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,
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
132
connector/relay/conn.go
Normal file
132
connector/relay/conn.go
Normal file
@ -0,0 +1,132 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/go-gost/relay"
|
||||
)
|
||||
|
||||
type tcpConn struct {
|
||||
net.Conn
|
||||
wbuf bytes.Buffer
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (c *tcpConn) Read(b []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
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
|
||||
if 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
|
||||
}
|
||||
|
||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
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 := make([]byte, dlen)
|
||||
_, 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)
|
||||
if 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("status %d", resp.Status)
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
127
connector/relay/connector.go
Normal file
127
connector/relay/connector.go
Normal file
@ -0,0 +1,127 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/connector"
|
||||
md "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
"github.com/go-gost/gost/v3/pkg/registry"
|
||||
"github.com/go-gost/relay"
|
||||
relay_util "github.com/go-gost/x/internal/util/relay"
|
||||
)
|
||||
|
||||
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,
|
||||
})
|
||||
log.Infof("connect %s/%s", address, network)
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
req := relay.Request{
|
||||
Version: relay.Version1,
|
||||
Flags: relay.CONNECT,
|
||||
}
|
||||
if network == "udp" || network == "udp4" || network == "udp6" {
|
||||
req.Flags |= relay.FUDP
|
||||
|
||||
// UDP association
|
||||
if address == "" {
|
||||
baddr, err := c.bind(conn, relay.FUDP|relay.BIND, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("associate on %s OK", baddr)
|
||||
|
||||
return relay_util.UDPTunClientConn(conn, nil), nil
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// forward mode if port is 0.
|
||||
if af.Port > 0 {
|
||||
req.Features = append(req.Features, af)
|
||||
}
|
||||
}
|
||||
|
||||
if c.md.noDelay {
|
||||
if _, err := req.WriteTo(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
cc := &tcpConn{
|
||||
Conn: conn,
|
||||
}
|
||||
if !c.md.noDelay {
|
||||
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 {
|
||||
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
|
||||
}
|
73
connector/relay/listener.go
Normal file
73
connector/relay/listener.go
Normal file
@ -0,0 +1,73 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/logger"
|
||||
"github.com/go-gost/relay"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
)
|
||||
|
||||
type tcpListener struct {
|
||||
addr net.Addr
|
||||
session *mux.Session
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (p *tcpListener) 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 *tcpListener) 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 string
|
||||
for _, f := range resp.Features {
|
||||
if f.Type() == relay.FeatureAddr {
|
||||
if fa, ok := f.(*relay.AddrFeature); ok {
|
||||
address = net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveTCPAddr("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bindConn{
|
||||
Conn: conn,
|
||||
localAddr: p.addr,
|
||||
remoteAddr: raddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *tcpListener) Addr() net.Addr {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *tcpListener) Close() error {
|
||||
return p.session.Close()
|
||||
}
|
24
connector/relay/metadata.go
Normal file
24
connector/relay/metadata.go
Normal file
@ -0,0 +1,24 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
noDelay bool
|
||||
}
|
||||
|
||||
func (c *relayConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
connectTimeout = "connectTimeout"
|
||||
noDelay = "nodelay"
|
||||
)
|
||||
|
||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
||||
c.md.noDelay = mdata.GetBool(md, noDelay)
|
||||
|
||||
return
|
||||
}
|
128
connector/sni/conn.go
Normal file
128
connector/sni/conn.go
Normal 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())
|
||||
}
|
46
connector/sni/connector.go
Normal file
46
connector/sni/connector.go
Normal file
@ -0,0 +1,46 @@
|
||||
package sni
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/connector"
|
||||
md "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
"github.com/go-gost/gost/v3/pkg/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,
|
||||
})
|
||||
log.Infof("connect %s/%s", address, network)
|
||||
|
||||
return &sniClientConn{Conn: conn, host: c.md.host}, nil
|
||||
}
|
24
connector/sni/metadata.go
Normal file
24
connector/sni/metadata.go
Normal file
@ -0,0 +1,24 @@
|
||||
package sni
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
)
|
||||
|
||||
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 = mdata.GetString(md, host)
|
||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
||||
|
||||
return
|
||||
}
|
111
connector/ss/connector.go
Normal file
111
connector/ss/connector.go
Normal file
@ -0,0 +1,111 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/gosocks5"
|
||||
"github.com/go-gost/gost/v3/pkg/common/bufpool"
|
||||
"github.com/go-gost/gost/v3/pkg/connector"
|
||||
md "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
"github.com/go-gost/gost/v3/pkg/registry"
|
||||
"github.com/go-gost/x/internal/util/ss"
|
||||
"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,
|
||||
})
|
||||
log.Infof("connect %s/%s", address, network)
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if _, ok := conn.(net.PacketConn); ok {
|
||||
err := fmt.Errorf("tcp over udp is unsupported")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
27
connector/ss/metadata.go
Normal file
27
connector/ss/metadata.go
Normal file
@ -0,0 +1,27 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
)
|
||||
|
||||
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 = mdata.GetString(md, key)
|
||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
||||
c.md.noDelay = mdata.GetBool(md, noDelay)
|
||||
|
||||
return
|
||||
}
|
95
connector/ss/udp/connector.go
Normal file
95
connector/ss/udp/connector.go
Normal file
@ -0,0 +1,95 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/connector"
|
||||
md "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
"github.com/go-gost/gost/v3/pkg/registry"
|
||||
"github.com/go-gost/x/internal/util/relay"
|
||||
"github.com/go-gost/x/internal/util/ss"
|
||||
"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,
|
||||
})
|
||||
log.Infof("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, c.md.bufferSize), nil
|
||||
}
|
||||
|
||||
if c.cipher != nil {
|
||||
conn = ss.ShadowConn(c.cipher.StreamConn(conn), nil)
|
||||
}
|
||||
|
||||
// UDP over TCP
|
||||
return relay.UDPTunClientConn(conn, taddr), nil
|
||||
}
|
33
connector/ss/udp/metadata.go
Normal file
33
connector/ss/udp/metadata.go
Normal file
@ -0,0 +1,33 @@
|
||||
package ss
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
key string
|
||||
connectTimeout time.Duration
|
||||
bufferSize int
|
||||
}
|
||||
|
||||
func (c *ssuConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
key = "key"
|
||||
connectTimeout = "timeout"
|
||||
bufferSize = "bufferSize" // udp buffer size
|
||||
)
|
||||
|
||||
c.md.key = mdata.GetString(md, key)
|
||||
c.md.connectTimeout = mdata.GetDuration(md, connectTimeout)
|
||||
|
||||
if bs := mdata.GetInt(md, bufferSize); bs > 0 {
|
||||
c.md.bufferSize = int(math.Min(math.Max(float64(bs), 512), 64*1024))
|
||||
} else {
|
||||
c.md.bufferSize = 1024
|
||||
}
|
||||
|
||||
return
|
||||
}
|
80
connector/sshd/connector.go
Normal file
80
connector/sshd/connector.go
Normal file
@ -0,0 +1,80 @@
|
||||
package sshd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/gost/v3/pkg/connector"
|
||||
md "github.com/go-gost/gost/v3/pkg/metadata"
|
||||
"github.com/go-gost/gost/v3/pkg/registry"
|
||||
ssh_util "github.com/go-gost/x/internal/util/ssh"
|
||||
)
|
||||
|
||||
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,
|
||||
})
|
||||
log.Infof("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.Infof("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)
|
||||
}
|
Reference in New Issue
Block a user