add vhost for port forwarding

This commit is contained in:
ginuerzh 2022-11-11 22:23:36 +08:00
parent 81b6efc9b8
commit 1ff2bab1f0
12 changed files with 195 additions and 23 deletions

View File

@ -2,6 +2,7 @@ package chain
import (
"context"
"net"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
@ -71,19 +72,47 @@ func (p *chainHop) Select(ctx context.Context, opts ...chain.SelectOption) *chai
}
// hop level bypass
if p.options.bypass != nil && p.options.bypass.Contains(options.Addr) {
if p.options.bypass != nil &&
p.options.bypass.Contains(options.Addr) {
return nil
}
filters := p.nodes
if host := options.Host; host != "" {
filters = nil
if v, _, _ := net.SplitHostPort(host); v != "" {
host = v
}
var nodes []*chain.Node
for _, node := range p.nodes {
if node == nil {
continue
}
// node level bypass
if node.Options().Bypass != nil && node.Options().Bypass.Contains(options.Addr) {
if node.Options().Host == "" {
nodes = append(nodes, node)
continue
}
if node.Options().Host == host {
filters = append(filters, node)
p.options.logger.Debugf("find node for host: %s", host)
}
}
if len(filters) == 0 {
filters = nodes
}
}
var nodes []*chain.Node
for _, node := range filters {
if node == nil {
continue
}
// node level bypass
if node.Options().Bypass != nil &&
node.Options().Bypass.Contains(options.Addr) {
continue
}
nodes = append(nodes, node)
}
if len(nodes) == 0 {

View File

@ -260,6 +260,7 @@ type ForwarderConfig struct {
type ForwardNodeConfig struct {
Name string `yaml:",omitempty" json:"name,omitempty"`
Addr string `yaml:",omitempty" json:"addr,omitempty"`
Host string `yaml:",omitempty" json:"host,omitempty"`
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
}
@ -333,6 +334,7 @@ type HopConfig struct {
type NodeConfig struct {
Name string `json:"name"`
Addr string `yaml:",omitempty" json:"addr,omitempty"`
Host string `yaml:",omitempty" json:"host,omitempty"`
Interface string `yaml:",omitempty" json:"interface,omitempty"`
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`

View File

@ -202,6 +202,7 @@ func ParseHop(cfg *config.HopConfig) (chain.Hop, error) {
chain.ResoloverNodeOption(registry.ResolverRegistry().Get(v.Resolver)),
chain.HostMapperNodeOption(registry.HostsRegistry().Get(v.Hosts)),
chain.MetadataNodeOption(nm),
chain.HostNodeOption(v.Host),
)
nodes = append(nodes, node)
}

View File

@ -235,6 +235,7 @@ func parseForwarder(cfg *config.ForwarderConfig) (chain.Hop, error) {
&config.NodeConfig{
Name: node.Name,
Addr: node.Addr,
Host: node.Host,
Bypass: node.Bypass,
Bypasses: node.Bypasses,
},

12
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.7
github.com/go-gost/core v0.0.0-20221020130224-eb9d483127cc
github.com/go-gost/core v0.0.0-20221111142129-c2a1dd2a89cb
github.com/go-gost/gosocks4 v0.0.1
github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09
github.com/go-gost/relay v0.1.1-0.20211123134818-8ef7fd81ffd7
@ -31,10 +31,10 @@ require (
github.com/xtaci/smux v1.5.16
github.com/xtaci/tcpraw v1.2.25
github.com/yl2chen/cidranger v1.0.2
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
golang.org/x/net v0.0.0-20220812174116-3211cb980234
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9
golang.org/x/crypto v0.2.0
golang.org/x/net v0.2.0
golang.org/x/sys v0.2.0
golang.org/x/time v0.2.0
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
google.golang.org/grpc v1.49.0
google.golang.org/protobuf v1.28.0
@ -91,7 +91,7 @@ require (
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/tools v0.1.12 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect

12
go.sum
View File

@ -98,6 +98,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gost/core v0.0.0-20221020130224-eb9d483127cc h1:pS75VLwTkYLIC3n0QbfwE65N/1Zh8BnXfErNq9DGWd4=
github.com/go-gost/core v0.0.0-20221020130224-eb9d483127cc/go.mod h1:bHVbCS9da6XtKNYMkMUVcck5UqDDUkyC37erVfs4GXQ=
github.com/go-gost/core v0.0.0-20221111142129-c2a1dd2a89cb h1:BkuYeTJYfN3nGHtnljjRBuBBXg2hTRBN9EmszZalyzg=
github.com/go-gost/core v0.0.0-20221111142129-c2a1dd2a89cb/go.mod h1:bHVbCS9da6XtKNYMkMUVcck5UqDDUkyC37erVfs4GXQ=
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
github.com/go-gost/gosocks5 v0.3.1-0.20211109033403-d894d75b7f09 h1:A95M6UWcfZgOuJkQ7QLfG0Hs5peWIUSysCDNz4pfe04=
@ -409,6 +411,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -486,6 +490,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -564,9 +570,12 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8=
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -577,11 +586,14 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

View File

@ -0,0 +1,84 @@
package forward
import (
"bufio"
"bytes"
"context"
"crypto/tls"
"encoding/binary"
"io"
"net/http"
"strings"
dissector "github.com/go-gost/tls-dissector"
xio "github.com/go-gost/x/internal/io"
)
func SniffHost(ctx context.Context, rdw io.ReadWriter) (rw io.ReadWriter, host string, err error) {
rw = rdw
// try to sniff TLS traffic
var hdr [dissector.RecordHeaderLen]byte
_, err = io.ReadFull(rw, hdr[:])
rw = xio.NewReadWriter(io.MultiReader(bytes.NewReader(hdr[:]), rw), rw)
if err == nil &&
hdr[0] == dissector.Handshake &&
binary.BigEndian.Uint16(hdr[1:3]) == tls.VersionTLS10 {
return sniffSNI(ctx, rw)
}
// try to sniff HTTP traffic
if isHTTP(string(hdr[:])) {
buf := new(bytes.Buffer)
var r *http.Request
r, err = http.ReadRequest(bufio.NewReader(io.TeeReader(rw, buf)))
rw = xio.NewReadWriter(io.MultiReader(buf, rw), rw)
if err == nil {
host = r.Host
return
}
}
return
}
func sniffSNI(ctx context.Context, rw io.ReadWriter) (io.ReadWriter, string, error) {
buf := new(bytes.Buffer)
host, err := getServerName(ctx, io.TeeReader(rw, buf))
rw = xio.NewReadWriter(io.MultiReader(buf, rw), rw)
return rw, host, err
}
func getServerName(ctx context.Context, r io.Reader) (host string, err error) {
record, err := dissector.ReadRecord(r)
if err != nil {
return
}
clientHello := dissector.ClientHelloMsg{}
if err = clientHello.Decode(record.Opaque); err != nil {
return
}
for _, ext := range clientHello.Extensions {
if ext.Type() == dissector.ExtServerName {
snExtension := ext.(*dissector.ServerNameExtension)
host = snExtension.Name
break
}
}
return
}
func isHTTP(s string) bool {
return strings.HasPrefix(http.MethodGet, s[:3]) ||
strings.HasPrefix(http.MethodPost, s[:4]) ||
strings.HasPrefix(http.MethodPut, s[:3]) ||
strings.HasPrefix(http.MethodDelete, s) ||
strings.HasPrefix(http.MethodOptions, s) ||
strings.HasPrefix(http.MethodPatch, s) ||
strings.HasPrefix(http.MethodHead, s[:4]) ||
strings.HasPrefix(http.MethodConnect, s) ||
strings.HasPrefix(http.MethodTrace, s)
}

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"net"
"time"
@ -11,6 +12,7 @@ import (
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
xchain "github.com/go-gost/x/chain"
"github.com/go-gost/x/handler/forward/internal/forward"
netpkg "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/registry"
)
@ -84,18 +86,29 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return nil
}
target := h.hop.Select(ctx)
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
var rw io.ReadWriter
var host string
if h.md.sniffing {
if network == "tcp" {
rw, host, _ = forward.SniffHost(ctx, conn)
}
}
var target *chain.Node
if h.hop != nil {
target = h.hop.Select(ctx, chain.HostSelectOption(host))
}
if target == nil {
err := errors.New("target not available")
log.Error(err)
return err
}
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", target.Addr, network),
})
@ -119,7 +132,7 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
t := time.Now()
log.Debugf("%s <-> %s", conn.RemoteAddr(), target.Addr)
netpkg.Transport(conn, cc)
netpkg.Transport(rw, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Debugf("%s >-< %s", conn.RemoteAddr(), target.Addr)

View File

@ -9,13 +9,16 @@ import (
type metadata struct {
readTimeout time.Duration
sniffing bool
}
func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
const (
readTimeout = "readTimeout"
sniffing = "sniffing"
)
h.md.readTimeout = mdutil.GetDuration(md, readTimeout)
h.md.sniffing = mdutil.GetBool(md, sniffing)
return
}

View File

@ -4,12 +4,14 @@ import (
"context"
"errors"
"fmt"
"io"
"net"
"time"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/x/handler/forward/internal/forward"
netpkg "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/registry"
)
@ -75,9 +77,21 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return nil
}
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
var rw io.ReadWriter
var host string
if h.md.sniffing {
if network == "tcp" {
rw, host, _ = forward.SniffHost(ctx, conn)
}
}
var target *chain.Node
if h.hop != nil {
target = h.hop.Select(ctx)
target = h.hop.Select(ctx, chain.HostSelectOption(host))
}
if target == nil {
err := errors.New("target not available")
@ -85,11 +99,6 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return err
}
network := "tcp"
if _, ok := conn.(net.PacketConn); ok {
network = "udp"
}
log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", target.Addr, network),
})
@ -113,7 +122,7 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
t := time.Now()
log.Debugf("%s <-> %s", conn.RemoteAddr(), target.Addr)
netpkg.Transport(conn, cc)
netpkg.Transport(rw, cc)
log.WithFields(map[string]any{
"duration": time.Since(t),
}).Debugf("%s >-< %s", conn.RemoteAddr(), target.Addr)

View File

@ -9,13 +9,16 @@ import (
type metadata struct {
readTimeout time.Duration
sniffing bool
}
func (h *forwardHandler) parseMetadata(md mdata.Metadata) (err error) {
const (
readTimeout = "readTimeout"
sniffing = "sniffing"
)
h.md.readTimeout = mdutil.GetDuration(md, readTimeout)
h.md.sniffing = mdutil.GetBool(md, sniffing)
return
}

15
internal/io/io.go Normal file
View File

@ -0,0 +1,15 @@
package io
import "io"
type readWriter struct {
io.Reader
io.Writer
}
func NewReadWriter(r io.Reader, w io.Writer) io.ReadWriter {
return &readWriter{
Reader: r,
Writer: w,
}
}