package relay import ( "crypto/sha256" "fmt" "net" "strings" "sync" "testing" "time" "github.com/pion/logging" "github.com/pion/turn/v4" ) // testMaster is the fixture region master every server test derives from. func testMaster() []byte { m := sha256.Sum256([]byte("carlos-relay-test-master")) return m[:] } // newTestServer starts a relay on loopback with one enabled app, // abc/meet, incarnation 0123456789abcdef, in region "test". The returned // setNow moves the server's clock; the returned Masters and Authority are // the live ones, so a test can rewrite them in place. // relayIP is the address the fixture server advertises. Loopback is what // the brief's tests use; a test that carries real traffic between two // loopback sockets must advertise something else, because the EIP port // gate (spec §3) drops anything aimed at the advertised address on a port // that is not a live allocation — and on loopback every peer is on it. // tweak, when given, is applied to the config before the server starts. func newTestServer(t *testing.T, relayIP string, meter *fakeMeter, tweak func(*ServerConfig)) (s *Server, setNow func(time.Time), now func() time.Time, masters *Masters, auth *Authority) { t.Helper() master := testMaster() masters, err := ParseMasters(strings.NewReader("k1 " + b64(master) + " -\n")) if err != nil { t.Fatal(err) } auth = &Authority{ Apps: []AuthorityApp{{Account: "abc", App: "meet", Incarnation: "0123456789abcdef", Regions: []string{"test"}}}, WrittenAt: time.Now(), } var mu sync.Mutex at := time.Now() clock := func() time.Time { mu.Lock(); defer mu.Unlock(); return at } setNow = func(t time.Time) { mu.Lock(); at = t; mu.Unlock() } // A client's retransmit can reach the server's read loop after the test // body returns, and t.Logf from a finished test is a data race, so the // log goes quiet once the server is shut down. var logMu sync.Mutex quiet := false logf := func(format string, args ...any) { logMu.Lock() defer logMu.Unlock() if !quiet { t.Logf(format, args...) } } cfg := ServerConfig{ Region: "test", RelayIP: net.ParseIP(relayIP), UDP: "127.0.0.1:0", MinPort: 40000, MaxPort: 40100, Masters: func() *Masters { return masters }, Authority: func() *Authority { return auth }, Lease: func(time.Time) bool { return true }, Limits: DefaultLimits(), Now: clock, Log: logf, } if meter != nil { cfg.Meter = meter } if tweak != nil { tweak(&cfg) } s, err = NewServer(cfg) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() logMu.Lock() quiet = true logMu.Unlock() }) return s, setNow, clock, masters, auth } // creds mints a username and credential for account/app/incarnation under // kid k1, expiring an hour after now. func creds(now time.Time, account, app, incarnation string) (string, string) { username := fmt.Sprintf("%d:%s", now.Add(time.Hour).Unix(), Suffix(account, app, incarnation, "k1")) key := DeriveKey(testMaster(), account, app, incarnation) return username, Credential(key, username) } func turnClient(t *testing.T, addr net.Addr, username, password string) *turn.Client { t.Helper() conn, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { t.Fatal(err) } c, err := turn.NewClient(&turn.ClientConfig{ STUNServerAddr: addr.String(), TURNServerAddr: addr.String(), Conn: conn, Username: username, Password: password, Realm: "carlos", LoggerFactory: logging.NewDefaultLoggerFactory(), }) if err != nil { conn.Close() t.Fatal(err) } if err := c.Listen(); err != nil { c.Close() conn.Close() t.Fatal(err) } t.Cleanup(func() { c.Close(); conn.Close() }) return c } func waitFor(t *testing.T, cond func() bool) { t.Helper() deadline := time.Now().Add(2 * time.Second) for time.Now().Before(deadline) { if cond() { return } time.Sleep(10 * time.Millisecond) } t.Fatal("condition not met within two seconds") } func TestServerEndToEnd(t *testing.T) { s, setNow, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) relayConn, err := client.Allocate() if err != nil { t.Fatalf("allocate: %v", err) } defer relayConn.Close() if s.Live() != 1 { t.Fatalf("live = %d", s.Live()) } if got := s.Listeners(); len(got) != 1 || !strings.HasPrefix(got[0], "udp:") { t.Fatalf("listeners = %v", got) } // A relay candidate on the advertised address, in our port range. ra, ok := relayConn.LocalAddr().(*net.UDPAddr) if !ok || !ra.IP.Equal(net.ParseIP("127.0.0.1")) || ra.Port < 40000 || ra.Port > 40100 { t.Fatalf("relay address = %v, want 127.0.0.1 in 40000-40100", relayConn.LocalAddr()) } // Wrong app's credential is refused: abc/docs is not in the record. otherUser, otherPass := creds(now(), "abc", "docs", "0123456789abcdef") if _, err := turnClient(t, s.UDPAddr(), otherUser, otherPass).Allocate(); err == nil { t.Fatal("unknown app allocated") } // It is counted against the shared unknown bucket, not against an // identity the caller made up. if s.refusalCount(Identity{}, "unknown app") != 1 { t.Fatalf("refusals = %v", s.refusals) } // Expiry ends the allocation with no client action. setNow(now().Add(2 * time.Hour)) s.tick() waitFor(t, func() bool { return s.Live() == 0 }) } // TestServerRelaysBothLegs carries real traffic client to peer and back, // and checks both meter legs. It advertises an EIP that is not the peers' // address, as a real box does. func TestServerRelaysBothLegs(t *testing.T) { meter := &fakeMeter{} s, _, now, _, _ := newTestServer(t, "203.0.113.5", meter, allowLoopbackPeers) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) relayConn, err := client.Allocate() if err != nil { t.Fatalf("allocate: %v", err) } defer relayConn.Close() peer, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer peer.Close() if err := client.CreatePermission(peer.LocalAddr()); err != nil { t.Fatalf("create permission: %v", err) } if _, err := relayConn.WriteTo([]byte("ping"), peer.LocalAddr()); err != nil { t.Fatalf("write to peer: %v", err) } buf := make([]byte, 64) peer.SetReadDeadline(time.Now().Add(2 * time.Second)) n, from, err := peer.ReadFrom(buf) if err != nil { t.Fatalf("peer read: %v", err) } if string(buf[:n]) != "ping" { t.Fatalf("peer got %q", buf[:n]) } if _, err := peer.WriteTo([]byte("pong"), from); err != nil { t.Fatalf("peer write: %v", err) } relayConn.SetReadDeadline(time.Now().Add(2 * time.Second)) n, _, err = relayConn.ReadFrom(buf) if err != nil { t.Fatalf("client read: %v", err) } if string(buf[:n]) != "pong" { t.Fatalf("client got %q", buf[:n]) } id := Identity{"abc", "meet"} // Socket delivery can finish before the forwarding goroutine records bytes. waitFor(t, func() bool { in, out, _ := meter.totals(id) return in >= 4 && out >= 4 }) in, out, _ := meter.totals(id) if in < 4 { t.Fatalf("in = %d, want at least the 4 bytes forwarded to the peer", in) } if out < 4 { t.Fatalf("out = %d, want at least the 4 bytes forwarded to the client", out) } } // allowLoopbackPeers is the ExtraAllow seam: a relay on a real box never // permits a loopback peer, and a test that carries traffic between two // sockets on this machine has nowhere else to put them. func allowLoopbackPeers(cfg *ServerConfig) { cfg.ExtraAllow = mustCIDRs("127.0.0.0/8") } // fakeMeter is the Task 7 meter's shape, which ServerConfig.Meter names as // an interface so this file does not wait on it. type fakeMeter struct { mu sync.Mutex rows map[Identity][3]int64 } func (m *fakeMeter) Add(id Identity, in, out, rejected int64) { m.mu.Lock() defer m.mu.Unlock() if m.rows == nil { m.rows = map[Identity][3]int64{} } r := m.rows[id] m.rows[id] = [3]int64{r[0] + in, r[1] + out, r[2] + rejected} } func (m *fakeMeter) totals(id Identity) (in, out, rejected int64) { m.mu.Lock() defer m.mu.Unlock() r := m.rows[id] return r[0], r[1], r[2] } func TestServerRefusesPrivatePeer(t *testing.T) { s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) if _, err := client.Allocate(); err != nil { t.Fatalf("allocate: %v", err) } if err := client.CreatePermission(&net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 1}); err == nil { t.Fatal("permission to a private peer was granted") } } func TestServerEndWhere(t *testing.T) { s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) if _, err := client.Allocate(); err != nil { t.Fatalf("allocate: %v", err) } if s.Live() != 1 { t.Fatalf("live = %d", s.Live()) } s.EndWhere(func(b *Binding) bool { return b.ID.App == "meet" }, "disable") waitFor(t, func() bool { return s.Live() == 0 }) } func TestServerShedOldest(t *testing.T) { s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) username, password := creds(now(), "abc", "meet", "0123456789abcdef") first := turnClient(t, s.UDPAddr(), username, password) if _, err := first.Allocate(); err != nil { t.Fatalf("allocate: %v", err) } second := turnClient(t, s.UDPAddr(), username, password) if _, err := second.Allocate(); err != nil { t.Fatalf("allocate: %v", err) } if s.Live() != 2 { t.Fatalf("live = %d", s.Live()) } s.ShedOldest(1) waitFor(t, func() bool { return s.Live() == 1 }) } // TestServerBudgetEndsAllocations is the spec's rule that a budget which // only refuses new allocations bounds nothing: seeded past its hourly // budget, the app's live allocation goes too. func TestServerBudgetEndsAllocations(t *testing.T) { s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) if _, err := client.Allocate(); err != nil { t.Fatalf("allocate: %v", err) } s.Seed(Identity{"abc", "meet"}, now(), DefaultLimits().AppBytesPerHour+1) s.tick() waitFor(t, func() bool { return s.Live() == 0 }) } func TestServerAuthRefusals(t *testing.T) { s, _, now, masters, auth := newTestServer(t, "127.0.0.1", nil, nil) src := &net.UDPAddr{IP: net.ParseIP("192.0.2.7"), Port: 5000} ok := func(username string) bool { key, ok := s.auth(username, "carlos", src) return ok && len(key) > 0 } good, _ := creds(now(), "abc", "meet", "0123456789abcdef") if !ok(good) { t.Fatal("a good username was refused") } cases := []struct{ name, username, reason string }{ {"bad shape", "not-a-username", "bad shape"}, {"expired", fmt.Sprintf("%d:%s", now().Add(-time.Minute).Unix(), Suffix("abc", "meet", "0123456789abcdef", "k1")), "expired"}, {"too far ahead", fmt.Sprintf("%d:%s", now().Add(5*time.Hour).Unix(), Suffix("abc", "meet", "0123456789abcdef", "k1")), "expired"}, {"unknown app", fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "docs", "0123456789abcdef", "k1")), "unknown app"}, {"wrong incarnation", fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "meet", "fedcba9876543210", "k1")), "wrong incarnation"}, {"unknown kid", fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "meet", "0123456789abcdef", "k9")), "unknown kid"}, } for _, c := range cases { if ok(c.username) { t.Fatalf("%s: accepted", c.name) } if s.refusalCount(s.attrib(refusalIdentity(c.username)), c.reason) == 0 { t.Fatalf("%s: reason %q not counted (%v)", c.name, c.reason, s.refusals) } } // Wrong region. auth.Apps[0].Regions = []string{"elsewhere"} if ok(good) { t.Fatal("wrong region accepted") } if s.refusalCount(Identity{"abc", "meet"}, "wrong region") == 0 { t.Fatalf("wrong region not counted (%v)", s.refusals) } auth.Apps[0].Regions = []string{"test"} // A retired master ends the app's use of that kid. masters.byKid["k1"] = Master{Kid: "k1", Key: testMaster(), RetireAt: now().Add(-time.Second)} if ok(good) { t.Fatal("retired kid accepted") } masters.byKid["k1"] = Master{Kid: "k1", Key: testMaster()} // Owner binding: a live allocation's typed source refuses another app's // valid credential. s.mu.Lock() s.bound[typedSrc(src)] = &Binding{ID: Identity{"abc", "meet"}, Incarnation: "0123456789abcdef"} s.mu.Unlock() auth.Apps = append(auth.Apps, AuthorityApp{Account: "abc", App: "docs", Incarnation: "0123456789abcdef", Regions: []string{"test"}}) docs := fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "docs", "0123456789abcdef", "k1")) if ok(docs) { t.Fatal("a second app allocated from a bound source") } if s.refusalCount(Identity{"abc", "docs"}, "bound elsewhere") == 0 { t.Fatalf("bound elsewhere not counted (%v)", s.refusals) } // The bound app itself is still served, and a TCP source with the same // address string is a separate binding. if !ok(good) { t.Fatal("the bound app was refused on its own source") } tcpSrc := &net.TCPAddr{IP: src.IP, Port: src.Port} if key, okAuth := s.auth(docs, "carlos", tcpSrc); !okAuth || len(key) == 0 { t.Fatal("a tcp source shares the udp source's binding") } } // TestServerRefusalStateIsBounded is the memory-exhaustion case: pion // hands the AuthHandler the raw username before it checks // MESSAGE-INTEGRITY, and the nonce is not source-bound, so an unauthenticated // sender chooses the account and app strings. None of that may become a // map entry of its own. func TestServerRefusalStateIsBounded(t *testing.T) { s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) src := &net.UDPAddr{IP: net.ParseIP("192.0.2.10"), Port: 6000} exp := now().Add(time.Hour).Unix() const flood = 10000 for i := 0; i < flood; i++ { username := fmt.Sprintf("%d:%s", exp, Suffix(fmt.Sprintf("a%d", i), "meet", "0123456789abcdef", "k1")) if _, ok := s.auth(username, "carlos", src); ok { t.Fatalf("made-up account %d authenticated", i) } } if n := s.refusalIdentities(); n > 2 { t.Fatalf("refusal identities = %d, want at most 2", n) } if got := s.refusalCount(Identity{}, "unknown app"); got != flood { t.Fatalf("unknown-app refusals = %d, want %d", got, flood) } s.mu.Lock() auths, logs := len(s.lastAuth), len(s.logged) s.mu.Unlock() if auths != 0 { t.Fatalf("lastAuth = %d, want 0: nothing authenticated", auths) } if logs > 2 { t.Fatalf("log records = %d, want the refusal log rate-limited", logs) } } // TestServerAuthMemoryIsCapped covers the other half: usernames that do // verify are remembered for the allocation event, but only so many. func TestServerAuthMemoryIsCapped(t *testing.T) { s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) username, _ := creds(now(), "abc", "meet", "0123456789abcdef") for i := 0; i < s.maxLastAuth+600; i++ { src := &net.UDPAddr{IP: net.ParseIP("192.0.2.11"), Port: 1024 + i} if _, ok := s.auth(username, "carlos", src); !ok { t.Fatalf("source %d refused", i) } } s.mu.Lock() n := len(s.lastAuth) s.mu.Unlock() if n > s.maxLastAuth { t.Fatalf("lastAuth = %d, want at most %d", n, s.maxLastAuth) } } // TestServerCountsRejectedPeerBytes is spec §3's `rejected`: peer bytes // that reached a relay socket and were dropped for want of a permission. func TestServerCountsRejectedPeerBytes(t *testing.T) { meter := &fakeMeter{} s, _, now, _, _ := newTestServer(t, "203.0.113.5", meter, nil) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) relayed, err := client.Allocate() if err != nil { t.Fatalf("allocate: %v", err) } defer relayed.Close() port := relayed.LocalAddr().(*net.UDPAddr).Port peer, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer peer.Close() // No permission was ever created for this peer, so pion drops both. to := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: port} for i := 0; i < 2; i++ { if _, err := peer.WriteTo(make([]byte, 100), to); err != nil { t.Fatalf("peer write: %v", err) } } id := Identity{"abc", "meet"} waitFor(t, func() bool { _, _, rejected := meter.totals(id); return rejected >= 100 }) s.EndAll("test") waitFor(t, func() bool { return s.Live() == 0 }) in, out, rejected := meter.totals(id) if rejected != 200 { t.Fatalf("rejected = %d, want 200", rejected) } if out != 0 { t.Fatalf("out = %d, want 0: nothing was forwarded to the client", out) } if in != 0 { t.Fatalf("in = %d, want 0", in) } } // TestServerControlIsNotBucketed: at a saturated per-allocation bucket a // client must still be able to allocate and refresh. Only relayed payload // is bucketed and metered. func TestServerControlIsNotBucketed(t *testing.T) { meter := &fakeMeter{} s, _, now, _, _ := newTestServer(t, "203.0.113.5", meter, func(cfg *ServerConfig) { cfg.Limits.AllocBitsPerSec = 8 // one byte a second: every control response would be dropped cfg.Limits.BoxBitsPerSec = 8 }) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) relayed, err := client.Allocate() if err != nil { t.Fatalf("allocate at a saturated bucket: %v", err) } defer relayed.Close() // A second round trip on the same listener, after the allocation // exists: its response must get through too. if _, err := client.SendBindingRequest(); err != nil { t.Fatalf("binding request at a saturated bucket: %v", err) } if in, out, rejected := meter.totals(Identity{"abc", "meet"}); in|out|rejected != 0 { t.Fatalf("signalling was metered: in=%d out=%d rejected=%d", in, out, rejected) } } // TestServerClosesOrphanedAllocation: if the pending socket is gone by the // time pion reports the allocation, pion owns an allocation no deadline of // ours can end. It must be ended on the spot. func TestServerClosesOrphanedAllocation(t *testing.T) { s, setNow, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) src := &net.UDPAddr{IP: net.ParseIP("192.0.2.12"), Port: 7000} // The socket is still ours, but its pending record has gone. _, addr, err := s.AllocatePacketConn("udp4", 0) if err != nil { t.Fatal(err) } port := addr.(*net.UDPAddr).Port username, _ := creds(now(), "abc", "meet", "0123456789abcdef") if _, ok := s.auth(username, "carlos", src); !ok { t.Fatal("fixture credential refused") } s.mu.Lock() delete(s.pending, port) s.mu.Unlock() s.created(src, nil, "udp", username, "carlos", addr, 0) if s.Live() != 0 { t.Fatalf("live = %d, want the orphan ended", s.Live()) } if s.ports.has(port) { t.Fatal("the orphaned socket was left open") } // And the real race: the sweep reclaimed the socket before pion // reported the allocation. _, addr2, err := s.AllocatePacketConn("udp4", 0) if err != nil { t.Fatal(err) } port2 := addr2.(*net.UDPAddr).Port setNow(now().Add(pendingLife + time.Second)) s.tick() if s.ports.has(port2) { t.Fatal("the sweep left the socket open") } username2, _ := creds(now(), "abc", "meet", "0123456789abcdef") if _, ok := s.auth(username2, "carlos", src); !ok { t.Fatal("fixture credential refused after the clock moved") } s.created(src, nil, "udp", username2, "carlos", addr2, 0) if s.Live() != 0 { t.Fatalf("live = %d, want no allocation for a reclaimed socket", s.Live()) } } // refusalIdentity is the identity a refusal is attributed to: the parsed // username's, or the zero identity when the username would not parse. func refusalIdentity(username string) Identity { u, err := ParseUsername(username) if err != nil { return Identity{} } return Identity{u.Account, u.App} } func TestServerPermit(t *testing.T) { _, deny, err := net.ParseCIDR("172.31.0.0/16") if err != nil { t.Fatal(err) } s, err := NewServer(ServerConfig{ Region: "test", RelayIP: net.ParseIP("203.0.113.5"), UDP: "127.0.0.1:0", MinPort: 40200, MaxPort: 40300, DenyCIDRs: []*net.IPNet{deny}, Masters: func() *Masters { return nil }, Authority: func() *Authority { return nil }, Lease: func(time.Time) bool { return true }, Limits: DefaultLimits(), Log: t.Logf, }) if err != nil { t.Fatal(err) } defer s.Close() client := &net.UDPAddr{IP: net.ParseIP("198.51.100.1"), Port: 1} refused := []string{ "127.0.0.1", "::1", "169.254.169.254", "fe80::1", "10.0.0.1", "172.16.5.5", "192.168.1.1", "fd00::1", "100.64.0.1", "172.31.9.9", "0.0.0.0", "224.0.0.1", // Discussion #220: "this network" beyond the unspecified address, // IETF protocol assignments, benchmarking. "0.1.2.3", "0.255.255.255", "192.0.0.8", "198.18.0.1", "198.19.255.254", // NAT64 and 6to4 judged by the IPv4 address they carry. "64:ff9b::a00:1", "64:ff9b::7f00:1", "64:ff9b::a9fe:a9fe", "64:ff9b::ac1f:909", "2002:a00:1::1", "2002:a9fe:a9fe::", "2002:c0a8:101::1", "2002:0:0::1", // IPv4-mapped forms are matched by the IPv4 ranges already. "::ffff:10.0.0.1", "::ffff:198.18.0.1", } for _, ip := range refused { if s.permit(client, net.ParseIP(ip)) { t.Fatalf("peer %s was permitted", ip) } } for _, ip := range []string{ "203.0.113.5", "198.51.100.9", "2001:db8::1", // Neighbours of the new ranges stay reachable, and a public address // behind NAT64 or 6to4 is an ordinary peer. "1.0.0.1", "192.0.1.1", "198.17.255.255", "198.20.0.1", "64:ff9b::808:808", "2002:808:808::1", } { if !s.permit(client, net.ParseIP(ip)) { t.Fatalf("peer %s was refused", ip) } } } // TestServerCloseKeepsReplacementBinding is the race the spec calls out: // pion removes an allocation from its manager before closing its socket, so // a replacement allocation on the same typed source can exist while the old // wrapper is still closing. The old wrapper's close must not erase it. func TestServerCloseKeepsReplacementBinding(t *testing.T) { s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) if _, err := client.Allocate(); err != nil { t.Fatalf("allocate: %v", err) } key := "" var old *relayConn s.mu.Lock() for k, e := range s.allocs { key, old = k, e.conn } s.mu.Unlock() // A replacement allocation lands for the same typed source while the // old socket is still closing. replacement := &Binding{ID: Identity{"abc", "meet"}, Src: key} replConn := newRelayConn(&fakePacketConn{}, replacement, nil, s.ports, nil, nil, nil, nil, func(*relayConn) {}) replEntry := &allocEntry{conn: replConn, id: replacement.ID, key: key, seq: 99} s.mu.Lock() s.bound[key] = replacement s.allocs[key] = replEntry s.owners[replConn] = replEntry s.mu.Unlock() old.Close() s.mu.Lock() gotBound, gotAlloc := s.bound[key], s.allocs[key] _, oldOwned := s.owners[old] s.mu.Unlock() if gotBound != replacement { t.Fatalf("close erased the replacement binding: %v", gotBound) } if gotAlloc != replEntry { t.Fatalf("close erased the replacement allocation: %v", gotAlloc) } if oldOwned { t.Fatal("the closed socket is still owned, so its admission never came back") } } // TestServerRefusesPermissionIntoConfiguredDenyCIDR covers the operator's // half of the peer policy, which the built-in private ranges do not reach: // CARLOS_RELAY_DENY_CIDRS carries the box's own VPC CIDR, and that CIDR is // ordinary public-looking space as far as the built-in list is concerned. // It is the one thing standing between a TURN allocation and the deployment's // private network, and until now only the built-in ranges had a test // (discussion #120). func TestServerRefusesPermissionIntoConfiguredDenyCIDR(t *testing.T) { if raceDetector { t.Skip("pion/turn races on Permission.lifetimeTimer between AddPermission and Close; see race_on_test.go") } // 198.51.100.0/24 stands in for the VPC: globally routable as far as // the built-in private list is concerned, denied only because the // operator said so. _, vpc, err := net.ParseCIDR("198.51.100.0/24") if err != nil { t.Fatal(err) } s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, func(cfg *ServerConfig) { cfg.DenyCIDRs = append(cfg.DenyCIDRs, vpc) }) username, password := creds(now(), "abc", "meet", "0123456789abcdef") client := turnClient(t, s.UDPAddr(), username, password) if _, err := client.Allocate(); err != nil { t.Fatalf("allocate: %v", err) } if err := client.CreatePermission(&net.UDPAddr{IP: net.ParseIP("198.51.100.9"), Port: 1}); err == nil { t.Error("permission into the configured deny CIDR was granted — a TURN client could reach the VPC") } // The control: an address outside both the built-in list and the // configured range is still permitted, so the test is not passing // because permissions are broken outright. if err := client.CreatePermission(&net.UDPAddr{IP: net.ParseIP("203.0.113.9"), Port: 1}); err != nil { t.Errorf("permission to an ordinary public peer was refused: %v", err) } }