mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-28 07:19:08 +01:00
[client] Send the relay hostname as Host when dialing a relay by IP (#8359)
This commit is contained in:
@@ -8,8 +8,9 @@ import (
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
func createDialOptions(serverName string, underlyingOut *net.Conn) *websocket.DialOptions {
|
||||
func createDialOptions(serverName, host string, underlyingOut *net.Conn) *websocket.DialOptions {
|
||||
return &websocket.DialOptions{
|
||||
HTTPClient: httpClientNbDialer(serverName, underlyingOut),
|
||||
Host: host,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
func createDialOptions(_ string, _ *net.Conn) *websocket.DialOptions {
|
||||
func createDialOptions(_, _ string, _ *net.Conn) *websocket.DialOptions {
|
||||
// WASM version doesn't support HTTPClient or custom TLS config.
|
||||
return &websocket.DialOptions{}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,13 @@ func (d Dialer) Dial(ctx context.Context, address, serverName string) (net.Conn,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host, err := requestHost(wsURL, serverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var underlying net.Conn
|
||||
opts := createDialOptions(serverName, &underlying)
|
||||
opts := createDialOptions(serverName, host, &underlying)
|
||||
|
||||
wsConn, resp, err := websocket.Dial(ctx, wsURL, opts)
|
||||
if err != nil {
|
||||
@@ -78,6 +83,22 @@ func prepareURL(address string) (string, error) {
|
||||
return parsed.String(), nil
|
||||
}
|
||||
|
||||
// requestHost keeps the server name in the Host header when dialing by IP, so
|
||||
// proxies that route by host still match.
|
||||
func requestHost(wsURL, serverName string) (string, error) {
|
||||
if serverName == "" {
|
||||
return "", nil
|
||||
}
|
||||
parsed, err := url.Parse(wsURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse relay URL %q: %w", wsURL, err)
|
||||
}
|
||||
if port := parsed.Port(); port != "" {
|
||||
return net.JoinHostPort(serverName, port), nil
|
||||
}
|
||||
return serverName, nil
|
||||
}
|
||||
|
||||
// httpClientNbDialer builds the http client used by the websocket library.
|
||||
// underlyingOut, when non-nil, is populated with the raw conn from the
|
||||
// transport's DialContext so the caller can read its RemoteAddr.
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPrepareURL(t *testing.T) {
|
||||
@@ -74,3 +79,95 @@ func TestPrepareURL(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestHost(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wsURL string
|
||||
serverName string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no server name keeps the URL host",
|
||||
wsURL: "wss://1.2.3.4:443/relay",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "IP with port",
|
||||
wsURL: "wss://1.2.3.4:443/relay",
|
||||
serverName: "relay.example.com",
|
||||
want: "relay.example.com:443",
|
||||
},
|
||||
{
|
||||
name: "IP with non-standard port",
|
||||
wsURL: "ws://1.2.3.4:45678/relay",
|
||||
serverName: "relay.example.com",
|
||||
want: "relay.example.com:45678",
|
||||
},
|
||||
{
|
||||
name: "IP without port",
|
||||
wsURL: "wss://1.2.3.4/relay",
|
||||
serverName: "relay.example.com",
|
||||
want: "relay.example.com",
|
||||
},
|
||||
{
|
||||
name: "IPv6 with port",
|
||||
wsURL: "wss://[2001:db8::1]:443/relay",
|
||||
serverName: "relay.example.com",
|
||||
want: "relay.example.com:443",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := requestHost(tt.wsURL, tt.serverName)
|
||||
if err != nil {
|
||||
t.Fatalf("requestHost(%q, %q) err = %v", tt.wsURL, tt.serverName, err)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("requestHost(%q, %q) = %q, want %q", tt.wsURL, tt.serverName, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialByIPSendsServerNameAsHost(t *testing.T) {
|
||||
hosts := make(chan string, 1)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
hosts <- r.Host
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
_, port, err := net.SplitHostPort(srv.Listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
serverName string
|
||||
want string
|
||||
}{
|
||||
{name: "dial by IP with server name", serverName: "relay.example.com", want: "relay.example.com:" + port},
|
||||
{name: "dial without server name", want: "127.0.0.1:" + port},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := (Dialer{}).Dial(ctx, "rel://127.0.0.1:"+port, tt.serverName); err == nil {
|
||||
t.Fatal("expected the handshake to fail against a plain HTTP server")
|
||||
}
|
||||
|
||||
select {
|
||||
case got := <-hosts:
|
||||
if got != tt.want {
|
||||
t.Errorf("server saw Host %q, want %q", got, tt.want)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("server never received the handshake request")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user