mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-27 20:56:44 +00:00
147 lines
4.3 KiB
Go
147 lines
4.3 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/netbirdio/netbird/client/iface"
|
|
"github.com/netbirdio/netbird/relay/server"
|
|
)
|
|
|
|
// TestManager_ForeignRelayFallbackIP exercises the foreign-relay path
|
|
// end-to-end through Manager.OpenConn. Alice and Bob register on different
|
|
// relay servers; Alice dials Bob's foreign relay using an unresolvable
|
|
// FQDN. Without a fallback IP the dial fails; with Bob's advertised IP it
|
|
// recovers and a payload round-trips between the peers.
|
|
func TestManager_ForeignRelayFallbackIP(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
|
|
// Alice's home relay
|
|
homeCfg := server.ListenerConfig{Address: "127.0.0.1:52401"}
|
|
homeSrv, err := server.NewServer(newManagerTestServerConfig(homeCfg.Address))
|
|
if err != nil {
|
|
t.Fatalf("create home server: %s", err)
|
|
}
|
|
homeErr := make(chan error, 1)
|
|
go func() {
|
|
if err := homeSrv.Listen(homeCfg); err != nil {
|
|
homeErr <- err
|
|
}
|
|
}()
|
|
t.Cleanup(func() { _ = homeSrv.Shutdown(context.Background()) })
|
|
if err := waitForServerToStart(homeErr); err != nil {
|
|
t.Fatalf("home server: %s", err)
|
|
}
|
|
|
|
// Bob's foreign relay
|
|
foreignCfg := server.ListenerConfig{Address: "127.0.0.1:52402"}
|
|
foreignSrv, err := server.NewServer(newManagerTestServerConfig(foreignCfg.Address))
|
|
if err != nil {
|
|
t.Fatalf("create foreign server: %s", err)
|
|
}
|
|
foreignErr := make(chan error, 1)
|
|
go func() {
|
|
if err := foreignSrv.Listen(foreignCfg); err != nil {
|
|
foreignErr <- err
|
|
}
|
|
}()
|
|
t.Cleanup(func() { _ = foreignSrv.Shutdown(context.Background()) })
|
|
if err := waitForServerToStart(foreignErr); err != nil {
|
|
t.Fatalf("foreign server: %s", err)
|
|
}
|
|
|
|
mCtx, mCancel := context.WithCancel(ctx)
|
|
t.Cleanup(mCancel)
|
|
|
|
mgrAlice := NewManager(mCtx, toURL(homeCfg), "alice", iface.DefaultMTU)
|
|
if err := mgrAlice.Serve(); err != nil {
|
|
t.Fatalf("alice manager serve: %s", err)
|
|
}
|
|
|
|
mgrBob := NewManager(mCtx, toURL(foreignCfg), "bob", iface.DefaultMTU)
|
|
if err := mgrBob.Serve(); err != nil {
|
|
t.Fatalf("bob manager serve: %s", err)
|
|
}
|
|
|
|
// Bob's real relay URL (what mgrBob.RelayInstanceAddress returns).
|
|
bobRealAddr, err := mgrBob.RelayInstanceAddress()
|
|
if err != nil {
|
|
t.Fatalf("bob relay address: %s", err)
|
|
}
|
|
// What Bob's RelayInstanceIP() reports — this is the field that
|
|
// would ride along in signal as relayServerIP.
|
|
bobAdvertisedIP := mgrBob.RelayInstanceIP()
|
|
if !bobAdvertisedIP.IsValid() {
|
|
t.Fatalf("expected valid RelayInstanceIP for bob, got zero")
|
|
}
|
|
|
|
// .invalid is reserved (RFC 2606), so DNS resolution always fails.
|
|
const brokenFQDN = "rel://relay-bob-instance.invalid:52402"
|
|
if brokenFQDN == bobRealAddr {
|
|
t.Fatalf("broken FQDN must differ from bob's real address (%s)", bobRealAddr)
|
|
}
|
|
|
|
t.Run("no fallback IP, dial fails", func(t *testing.T) {
|
|
dialCtx, dialCancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer dialCancel()
|
|
_, err := mgrAlice.OpenConn(dialCtx, brokenFQDN, "bob", netip.Addr{})
|
|
if err == nil {
|
|
t.Fatalf("expected OpenConn to fail without fallback IP, got success")
|
|
}
|
|
})
|
|
|
|
t.Run("fallback IP recovers", func(t *testing.T) {
|
|
// Bob waits for Alice's incoming peer connection on his side.
|
|
bobSideCh := make(chan error, 1)
|
|
go func() {
|
|
conn, err := mgrBob.OpenConn(ctx, bobRealAddr, "alice", netip.Addr{})
|
|
if err != nil {
|
|
bobSideCh <- err
|
|
return
|
|
}
|
|
buf := make([]byte, 1024)
|
|
n, err := conn.Read(buf)
|
|
if err != nil {
|
|
bobSideCh <- err
|
|
return
|
|
}
|
|
if _, err := conn.Write(buf[:n]); err != nil {
|
|
bobSideCh <- err
|
|
return
|
|
}
|
|
bobSideCh <- nil
|
|
}()
|
|
|
|
aliceConn, err := mgrAlice.OpenConn(ctx, brokenFQDN, "bob", bobAdvertisedIP)
|
|
if err != nil {
|
|
t.Fatalf("alice OpenConn with fallback IP: %s", err)
|
|
}
|
|
t.Cleanup(func() { _ = aliceConn.Close() })
|
|
|
|
payload := []byte("alice-to-bob")
|
|
if _, err := aliceConn.Write(payload); err != nil {
|
|
t.Fatalf("alice write: %s", err)
|
|
}
|
|
|
|
buf := make([]byte, len(payload))
|
|
if _, err := aliceConn.Read(buf); err != nil {
|
|
t.Fatalf("alice read echo: %s", err)
|
|
}
|
|
if string(buf) != string(payload) {
|
|
t.Fatalf("echo mismatch: got %q want %q", buf, payload)
|
|
}
|
|
|
|
select {
|
|
case err := <-bobSideCh:
|
|
if err != nil {
|
|
t.Fatalf("bob side: %s", err)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatalf("timed out waiting for bob side")
|
|
}
|
|
})
|
|
}
|