mirror of
https://github.com/go-gost/x.git
synced 2026-10-19 08:15:33 +00:00
fix(tunnel, forward): route tunneled hostname through hop filter.host
The tunnel bind conns (bindConn/bindUDPConn) now expose their metadata (the tunneled hostname) via the xctx.Context interface, so the service accept loop forwards it into the handler context. handleRawForwarding selects a hop node by that hostname when a hop is configured, so a node with a matching filter.host is picked; otherwise it falls back to dialing the hostname directly. Fixes reverse-proxy-tunnel TCP routing (gost#898) where the docs example failed with "node not available" because the mux path never set the host metadata.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/relay"
|
||||
ictx "github.com/go-gost/x/internal/ctx"
|
||||
xrelay "github.com/go-gost/x/internal/util/relay"
|
||||
)
|
||||
|
||||
@@ -94,6 +96,14 @@ func (c *bindConn) Metadata() mdata.Metadata {
|
||||
return c.md
|
||||
}
|
||||
|
||||
// Context implements xctx.Context, exposing the connection metadata (e.g. the
|
||||
// tunneled hostname) to the handler's context. The service accept loop derives
|
||||
// the handler context from a conn that implements this interface, which lets
|
||||
// reverse-forward handlers (rtcp) route on the hostname carried by the tunnel.
|
||||
func (c *bindConn) Context() context.Context {
|
||||
return ictx.ContextWithMetadata(context.Background(), c.md)
|
||||
}
|
||||
|
||||
type bindUDPConn struct {
|
||||
net.Conn
|
||||
localAddr net.Addr
|
||||
@@ -162,6 +172,12 @@ func (c *bindUDPConn) Metadata() mdata.Metadata {
|
||||
return c.md
|
||||
}
|
||||
|
||||
// Context implements xctx.Context, exposing the connection metadata (e.g. the
|
||||
// tunneled hostname) to the handler's context. See bindConn.Context.
|
||||
func (c *bindUDPConn) Context() context.Context {
|
||||
return ictx.ContextWithMetadata(context.Background(), c.md)
|
||||
}
|
||||
|
||||
type bindAddr struct {
|
||||
network string
|
||||
addr string
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
ictx "github.com/go-gost/x/internal/ctx"
|
||||
xmd "github.com/go-gost/x/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
// TestBindConnContext verifies that a bindConn exposes its connection metadata
|
||||
// (the tunneled hostname) via the xctx.Context interface, so the service accept
|
||||
// loop forwards it into the handler context for filter.host routing (gost#898).
|
||||
func TestBindConnContext(t *testing.T) {
|
||||
md := mdata.Metadata(xmd.NewMetadata(map[string]any{"host": "example.local:80"}))
|
||||
cn := &bindConn{
|
||||
Conn: &net.TCPConn{},
|
||||
localAddr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0},
|
||||
remoteAddr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 80},
|
||||
md: md,
|
||||
}
|
||||
|
||||
got := mdutil.GetString(ictx.MetadataFromContext(cn.Context()), "host")
|
||||
if got != "example.local:80" {
|
||||
t.Errorf("host from Context() = %q, want %q", got, "example.local:80")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBindUDPConnContext is the same check for the UDP reverse-tunnel conn.
|
||||
func TestBindUDPConnContext(t *testing.T) {
|
||||
md := mdata.Metadata(xmd.NewMetadata(map[string]any{"host": "example.local:80"}))
|
||||
cn := &bindUDPConn{
|
||||
Conn: &net.TCPConn{},
|
||||
localAddr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0},
|
||||
remoteAddr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 80},
|
||||
md: md,
|
||||
}
|
||||
|
||||
got := mdutil.GetString(ictx.MetadataFromContext(cn.Context()), "host")
|
||||
if got != "example.local:80" {
|
||||
t.Errorf("host from Context() = %q, want %q", got, "example.local:80")
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,15 @@ func (h *forwardHandler) handleRawForwarding(ctx context.Context, conn net.Conn,
|
||||
// metadata when set, then falling back to hop selection.
|
||||
func (h *forwardHandler) selectTarget(ctx context.Context, proto string) *chain.Node {
|
||||
if host := mdutil.GetString(ictx.MetadataFromContext(ctx), "host"); host != "" {
|
||||
// Route the tunneled hostname through the hop so a node with a matching
|
||||
// filter.host is selected (the node's addr is the real target). If the
|
||||
// hop has no matching node (e.g. no filter configured), fall back to
|
||||
// dialing the hostname directly — the pre-regression behavior.
|
||||
if curHop := h.getHop(); curHop != nil {
|
||||
if node := curHop.Select(ctx, hop.ProtocolSelectOption(proto), hop.HostSelectOption(host)); node != nil {
|
||||
return node
|
||||
}
|
||||
}
|
||||
return &chain.Node{Addr: host}
|
||||
}
|
||||
if curHop := h.getHop(); curHop != nil {
|
||||
|
||||
@@ -201,7 +201,52 @@ func TestHandleRawForwarding_MarkerReset(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRawForwarding_ContextHost(t *testing.T) {
|
||||
// TestHandleRawForwarding_ContextHostHop: a tunneled hostname with a hop whose
|
||||
// node matches the host filter routes to that node's addr (the documented
|
||||
// filter.host client routing). The node addr is the real target.
|
||||
func TestHandleRawForwarding_ContextHostHop(t *testing.T) {
|
||||
server, client := net.Pipe()
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
|
||||
var gotHost string
|
||||
h := newInitdHandler(withRouter(&mockRouter{
|
||||
opts: &chain.RouterOptions{},
|
||||
dialFn: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return server, nil
|
||||
},
|
||||
}))
|
||||
h.Forward(&mockHop{
|
||||
selectFn: func(ctx context.Context, opts ...hop.SelectOption) *chain.Node {
|
||||
var so hop.SelectOptions
|
||||
for _, o := range opts {
|
||||
o(&so)
|
||||
}
|
||||
gotHost = so.Host
|
||||
return &chain.Node{Name: "example", Addr: "10.0.0.2:80"}
|
||||
},
|
||||
})
|
||||
ctx := ictx.ContextWithMetadata(context.Background(), xmd.NewMetadata(map[string]any{
|
||||
"host": "example.local:80",
|
||||
}))
|
||||
conn := newStringConn(nil)
|
||||
ro := &xrecorder.HandlerRecorderObject{}
|
||||
|
||||
err := h.handleRawForwarding(ctx, conn, ro, nopLog(), "tcp", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if gotHost != "example.local:80" {
|
||||
t.Errorf("expected host passed to hop selection, got '%s'", gotHost)
|
||||
}
|
||||
if ro.Host != "10.0.0.2:80" {
|
||||
t.Errorf("expected node addr to be dialed, got '%s'", ro.Host)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleRawForwarding_ContextHostNoHop: when there is no hop, the tunneled
|
||||
// hostname is dialed directly (fallback preserving the pre-regression behavior).
|
||||
func TestHandleRawForwarding_ContextHostNoHop(t *testing.T) {
|
||||
server, client := net.Pipe()
|
||||
defer server.Close()
|
||||
defer client.Close()
|
||||
@@ -212,11 +257,6 @@ func TestHandleRawForwarding_ContextHost(t *testing.T) {
|
||||
return server, nil
|
||||
},
|
||||
}))
|
||||
h.Forward(&mockHop{
|
||||
selectFn: func(ctx context.Context, opts ...hop.SelectOption) *chain.Node {
|
||||
return &chain.Node{Addr: "10.0.0.2:80"}
|
||||
},
|
||||
})
|
||||
ctx := ictx.ContextWithMetadata(context.Background(), xmd.NewMetadata(map[string]any{
|
||||
"host": "10.0.0.99:8080",
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user