| 1 | package relay |
| 2 | |
| 3 | import ( |
| 4 | "crypto/sha256" |
| 5 | "fmt" |
| 6 | "net" |
| 7 | "strings" |
| 8 | "sync" |
| 9 | "testing" |
| 10 | "time" |
| 11 | |
| 12 | "github.com/pion/logging" |
| 13 | "github.com/pion/turn/v4" |
| 14 | ) |
| 15 | |
| 16 | // testMaster is the fixture region master every server test derives from. |
| 17 | func testMaster() []byte { |
| 18 | m := sha256.Sum256([]byte("carlos-relay-test-master")) |
| 19 | return m[:] |
| 20 | } |
| 21 | |
| 22 | // newTestServer starts a relay on loopback with one enabled app, |
| 23 | // abc/meet, incarnation 0123456789abcdef, in region "test". The returned |
| 24 | // setNow moves the server's clock; the returned Masters and Authority are |
| 25 | // the live ones, so a test can rewrite them in place. |
| 26 | // relayIP is the address the fixture server advertises. Loopback is what |
| 27 | // the brief's tests use; a test that carries real traffic between two |
| 28 | // loopback sockets must advertise something else, because the EIP port |
| 29 | // gate (spec §3) drops anything aimed at the advertised address on a port |
| 30 | // that is not a live allocation — and on loopback every peer is on it. |
| 31 | // tweak, when given, is applied to the config before the server starts. |
| 32 | 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) { |
| 33 | t.Helper() |
| 34 | master := testMaster() |
| 35 | masters, err := ParseMasters(strings.NewReader("k1 " + b64(master) + " -\n")) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | auth = &Authority{ |
| 40 | Apps: []AuthorityApp{{Account: "abc", App: "meet", Incarnation: "0123456789abcdef", Regions: []string{"test"}}}, |
| 41 | WrittenAt: time.Now(), |
| 42 | } |
| 43 | var mu sync.Mutex |
| 44 | at := time.Now() |
| 45 | clock := func() time.Time { mu.Lock(); defer mu.Unlock(); return at } |
| 46 | setNow = func(t time.Time) { mu.Lock(); at = t; mu.Unlock() } |
| 47 | |
| 48 | // A client's retransmit can reach the server's read loop after the test |
| 49 | // body returns, and t.Logf from a finished test is a data race, so the |
| 50 | // log goes quiet once the server is shut down. |
| 51 | var logMu sync.Mutex |
| 52 | quiet := false |
| 53 | logf := func(format string, args ...any) { |
| 54 | logMu.Lock() |
| 55 | defer logMu.Unlock() |
| 56 | if !quiet { |
| 57 | t.Logf(format, args...) |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | cfg := ServerConfig{ |
| 62 | Region: "test", RelayIP: net.ParseIP(relayIP), UDP: "127.0.0.1:0", |
| 63 | MinPort: 40000, MaxPort: 40100, |
| 64 | Masters: func() *Masters { return masters }, Authority: func() *Authority { return auth }, |
| 65 | Lease: func(time.Time) bool { return true }, Limits: DefaultLimits(), Now: clock, Log: logf, |
| 66 | } |
| 67 | if meter != nil { |
| 68 | cfg.Meter = meter |
| 69 | } |
| 70 | if tweak != nil { |
| 71 | tweak(&cfg) |
| 72 | } |
| 73 | s, err = NewServer(cfg) |
| 74 | if err != nil { |
| 75 | t.Fatal(err) |
| 76 | } |
| 77 | t.Cleanup(func() { |
| 78 | s.Close() |
| 79 | logMu.Lock() |
| 80 | quiet = true |
| 81 | logMu.Unlock() |
| 82 | }) |
| 83 | return s, setNow, clock, masters, auth |
| 84 | } |
| 85 | |
| 86 | // creds mints a username and credential for account/app/incarnation under |
| 87 | // kid k1, expiring an hour after now. |
| 88 | func creds(now time.Time, account, app, incarnation string) (string, string) { |
| 89 | username := fmt.Sprintf("%d:%s", now.Add(time.Hour).Unix(), Suffix(account, app, incarnation, "k1")) |
| 90 | key := DeriveKey(testMaster(), account, app, incarnation) |
| 91 | return username, Credential(key, username) |
| 92 | } |
| 93 | |
| 94 | func turnClient(t *testing.T, addr net.Addr, username, password string) *turn.Client { |
| 95 | t.Helper() |
| 96 | conn, err := net.ListenPacket("udp4", "127.0.0.1:0") |
| 97 | if err != nil { |
| 98 | t.Fatal(err) |
| 99 | } |
| 100 | c, err := turn.NewClient(&turn.ClientConfig{ |
| 101 | STUNServerAddr: addr.String(), |
| 102 | TURNServerAddr: addr.String(), |
| 103 | Conn: conn, |
| 104 | Username: username, |
| 105 | Password: password, |
| 106 | Realm: "carlos", |
| 107 | LoggerFactory: logging.NewDefaultLoggerFactory(), |
| 108 | }) |
| 109 | if err != nil { |
| 110 | conn.Close() |
| 111 | t.Fatal(err) |
| 112 | } |
| 113 | if err := c.Listen(); err != nil { |
| 114 | c.Close() |
| 115 | conn.Close() |
| 116 | t.Fatal(err) |
| 117 | } |
| 118 | t.Cleanup(func() { c.Close(); conn.Close() }) |
| 119 | return c |
| 120 | } |
| 121 | |
| 122 | func waitFor(t *testing.T, cond func() bool) { |
| 123 | t.Helper() |
| 124 | deadline := time.Now().Add(2 * time.Second) |
| 125 | for time.Now().Before(deadline) { |
| 126 | if cond() { |
| 127 | return |
| 128 | } |
| 129 | time.Sleep(10 * time.Millisecond) |
| 130 | } |
| 131 | t.Fatal("condition not met within two seconds") |
| 132 | } |
| 133 | |
| 134 | func TestServerEndToEnd(t *testing.T) { |
| 135 | s, setNow, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 136 | |
| 137 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 138 | client := turnClient(t, s.UDPAddr(), username, password) |
| 139 | relayConn, err := client.Allocate() |
| 140 | if err != nil { |
| 141 | t.Fatalf("allocate: %v", err) |
| 142 | } |
| 143 | defer relayConn.Close() |
| 144 | if s.Live() != 1 { |
| 145 | t.Fatalf("live = %d", s.Live()) |
| 146 | } |
| 147 | if got := s.Listeners(); len(got) != 1 || !strings.HasPrefix(got[0], "udp:") { |
| 148 | t.Fatalf("listeners = %v", got) |
| 149 | } |
| 150 | |
| 151 | // A relay candidate on the advertised address, in our port range. |
| 152 | ra, ok := relayConn.LocalAddr().(*net.UDPAddr) |
| 153 | if !ok || !ra.IP.Equal(net.ParseIP("127.0.0.1")) || ra.Port < 40000 || ra.Port > 40100 { |
| 154 | t.Fatalf("relay address = %v, want 127.0.0.1 in 40000-40100", relayConn.LocalAddr()) |
| 155 | } |
| 156 | |
| 157 | // Wrong app's credential is refused: abc/docs is not in the record. |
| 158 | otherUser, otherPass := creds(now(), "abc", "docs", "0123456789abcdef") |
| 159 | if _, err := turnClient(t, s.UDPAddr(), otherUser, otherPass).Allocate(); err == nil { |
| 160 | t.Fatal("unknown app allocated") |
| 161 | } |
| 162 | // It is counted against the shared unknown bucket, not against an |
| 163 | // identity the caller made up. |
| 164 | if s.refusalCount(Identity{}, "unknown app") != 1 { |
| 165 | t.Fatalf("refusals = %v", s.refusals) |
| 166 | } |
| 167 | |
| 168 | // Expiry ends the allocation with no client action. |
| 169 | setNow(now().Add(2 * time.Hour)) |
| 170 | s.tick() |
| 171 | waitFor(t, func() bool { return s.Live() == 0 }) |
| 172 | } |
| 173 | |
| 174 | // TestServerRelaysBothLegs carries real traffic client to peer and back, |
| 175 | // and checks both meter legs. It advertises an EIP that is not the peers' |
| 176 | // address, as a real box does. |
| 177 | func TestServerRelaysBothLegs(t *testing.T) { |
| 178 | meter := &fakeMeter{} |
| 179 | s, _, now, _, _ := newTestServer(t, "203.0.113.5", meter, allowLoopbackPeers) |
| 180 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 181 | client := turnClient(t, s.UDPAddr(), username, password) |
| 182 | relayConn, err := client.Allocate() |
| 183 | if err != nil { |
| 184 | t.Fatalf("allocate: %v", err) |
| 185 | } |
| 186 | defer relayConn.Close() |
| 187 | |
| 188 | peer, err := net.ListenPacket("udp4", "127.0.0.1:0") |
| 189 | if err != nil { |
| 190 | t.Fatal(err) |
| 191 | } |
| 192 | defer peer.Close() |
| 193 | if err := client.CreatePermission(peer.LocalAddr()); err != nil { |
| 194 | t.Fatalf("create permission: %v", err) |
| 195 | } |
| 196 | if _, err := relayConn.WriteTo([]byte("ping"), peer.LocalAddr()); err != nil { |
| 197 | t.Fatalf("write to peer: %v", err) |
| 198 | } |
| 199 | buf := make([]byte, 64) |
| 200 | peer.SetReadDeadline(time.Now().Add(2 * time.Second)) |
| 201 | n, from, err := peer.ReadFrom(buf) |
| 202 | if err != nil { |
| 203 | t.Fatalf("peer read: %v", err) |
| 204 | } |
| 205 | if string(buf[:n]) != "ping" { |
| 206 | t.Fatalf("peer got %q", buf[:n]) |
| 207 | } |
| 208 | if _, err := peer.WriteTo([]byte("pong"), from); err != nil { |
| 209 | t.Fatalf("peer write: %v", err) |
| 210 | } |
| 211 | relayConn.SetReadDeadline(time.Now().Add(2 * time.Second)) |
| 212 | n, _, err = relayConn.ReadFrom(buf) |
| 213 | if err != nil { |
| 214 | t.Fatalf("client read: %v", err) |
| 215 | } |
| 216 | if string(buf[:n]) != "pong" { |
| 217 | t.Fatalf("client got %q", buf[:n]) |
| 218 | } |
| 219 | |
| 220 | id := Identity{"abc", "meet"} |
| 221 | // Socket delivery can finish before the forwarding goroutine records bytes. |
| 222 | waitFor(t, func() bool { |
| 223 | in, out, _ := meter.totals(id) |
| 224 | return in >= 4 && out >= 4 |
| 225 | }) |
| 226 | in, out, _ := meter.totals(id) |
| 227 | if in < 4 { |
| 228 | t.Fatalf("in = %d, want at least the 4 bytes forwarded to the peer", in) |
| 229 | } |
| 230 | if out < 4 { |
| 231 | t.Fatalf("out = %d, want at least the 4 bytes forwarded to the client", out) |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // allowLoopbackPeers is the ExtraAllow seam: a relay on a real box never |
| 236 | // permits a loopback peer, and a test that carries traffic between two |
| 237 | // sockets on this machine has nowhere else to put them. |
| 238 | func allowLoopbackPeers(cfg *ServerConfig) { |
| 239 | cfg.ExtraAllow = mustCIDRs("127.0.0.0/8") |
| 240 | } |
| 241 | |
| 242 | // fakeMeter is the Task 7 meter's shape, which ServerConfig.Meter names as |
| 243 | // an interface so this file does not wait on it. |
| 244 | type fakeMeter struct { |
| 245 | mu sync.Mutex |
| 246 | rows map[Identity][3]int64 |
| 247 | } |
| 248 | |
| 249 | func (m *fakeMeter) Add(id Identity, in, out, rejected int64) { |
| 250 | m.mu.Lock() |
| 251 | defer m.mu.Unlock() |
| 252 | if m.rows == nil { |
| 253 | m.rows = map[Identity][3]int64{} |
| 254 | } |
| 255 | r := m.rows[id] |
| 256 | m.rows[id] = [3]int64{r[0] + in, r[1] + out, r[2] + rejected} |
| 257 | } |
| 258 | |
| 259 | func (m *fakeMeter) totals(id Identity) (in, out, rejected int64) { |
| 260 | m.mu.Lock() |
| 261 | defer m.mu.Unlock() |
| 262 | r := m.rows[id] |
| 263 | return r[0], r[1], r[2] |
| 264 | } |
| 265 | |
| 266 | func TestServerRefusesPrivatePeer(t *testing.T) { |
| 267 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 268 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 269 | client := turnClient(t, s.UDPAddr(), username, password) |
| 270 | if _, err := client.Allocate(); err != nil { |
| 271 | t.Fatalf("allocate: %v", err) |
| 272 | } |
| 273 | if err := client.CreatePermission(&net.UDPAddr{IP: net.ParseIP("10.0.0.1"), Port: 1}); err == nil { |
| 274 | t.Fatal("permission to a private peer was granted") |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | func TestServerEndWhere(t *testing.T) { |
| 279 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 280 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 281 | client := turnClient(t, s.UDPAddr(), username, password) |
| 282 | if _, err := client.Allocate(); err != nil { |
| 283 | t.Fatalf("allocate: %v", err) |
| 284 | } |
| 285 | if s.Live() != 1 { |
| 286 | t.Fatalf("live = %d", s.Live()) |
| 287 | } |
| 288 | s.EndWhere(func(b *Binding) bool { return b.ID.App == "meet" }, "disable") |
| 289 | waitFor(t, func() bool { return s.Live() == 0 }) |
| 290 | } |
| 291 | |
| 292 | func TestServerShedOldest(t *testing.T) { |
| 293 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 294 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 295 | first := turnClient(t, s.UDPAddr(), username, password) |
| 296 | if _, err := first.Allocate(); err != nil { |
| 297 | t.Fatalf("allocate: %v", err) |
| 298 | } |
| 299 | second := turnClient(t, s.UDPAddr(), username, password) |
| 300 | if _, err := second.Allocate(); err != nil { |
| 301 | t.Fatalf("allocate: %v", err) |
| 302 | } |
| 303 | if s.Live() != 2 { |
| 304 | t.Fatalf("live = %d", s.Live()) |
| 305 | } |
| 306 | s.ShedOldest(1) |
| 307 | waitFor(t, func() bool { return s.Live() == 1 }) |
| 308 | } |
| 309 | |
| 310 | // TestServerBudgetEndsAllocations is the spec's rule that a budget which |
| 311 | // only refuses new allocations bounds nothing: seeded past its hourly |
| 312 | // budget, the app's live allocation goes too. |
| 313 | func TestServerBudgetEndsAllocations(t *testing.T) { |
| 314 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 315 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 316 | client := turnClient(t, s.UDPAddr(), username, password) |
| 317 | if _, err := client.Allocate(); err != nil { |
| 318 | t.Fatalf("allocate: %v", err) |
| 319 | } |
| 320 | s.Seed(Identity{"abc", "meet"}, now(), DefaultLimits().AppBytesPerHour+1) |
| 321 | s.tick() |
| 322 | waitFor(t, func() bool { return s.Live() == 0 }) |
| 323 | } |
| 324 | |
| 325 | func TestServerAuthRefusals(t *testing.T) { |
| 326 | s, _, now, masters, auth := newTestServer(t, "127.0.0.1", nil, nil) |
| 327 | src := &net.UDPAddr{IP: net.ParseIP("192.0.2.7"), Port: 5000} |
| 328 | |
| 329 | ok := func(username string) bool { |
| 330 | key, ok := s.auth(username, "carlos", src) |
| 331 | return ok && len(key) > 0 |
| 332 | } |
| 333 | |
| 334 | good, _ := creds(now(), "abc", "meet", "0123456789abcdef") |
| 335 | if !ok(good) { |
| 336 | t.Fatal("a good username was refused") |
| 337 | } |
| 338 | |
| 339 | cases := []struct{ name, username, reason string }{ |
| 340 | {"bad shape", "not-a-username", "bad shape"}, |
| 341 | {"expired", fmt.Sprintf("%d:%s", now().Add(-time.Minute).Unix(), Suffix("abc", "meet", "0123456789abcdef", "k1")), "expired"}, |
| 342 | {"too far ahead", fmt.Sprintf("%d:%s", now().Add(5*time.Hour).Unix(), Suffix("abc", "meet", "0123456789abcdef", "k1")), "expired"}, |
| 343 | {"unknown app", fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "docs", "0123456789abcdef", "k1")), "unknown app"}, |
| 344 | {"wrong incarnation", fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "meet", "fedcba9876543210", "k1")), "wrong incarnation"}, |
| 345 | {"unknown kid", fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "meet", "0123456789abcdef", "k9")), "unknown kid"}, |
| 346 | } |
| 347 | for _, c := range cases { |
| 348 | if ok(c.username) { |
| 349 | t.Fatalf("%s: accepted", c.name) |
| 350 | } |
| 351 | if s.refusalCount(s.attrib(refusalIdentity(c.username)), c.reason) == 0 { |
| 352 | t.Fatalf("%s: reason %q not counted (%v)", c.name, c.reason, s.refusals) |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | // Wrong region. |
| 357 | auth.Apps[0].Regions = []string{"elsewhere"} |
| 358 | if ok(good) { |
| 359 | t.Fatal("wrong region accepted") |
| 360 | } |
| 361 | if s.refusalCount(Identity{"abc", "meet"}, "wrong region") == 0 { |
| 362 | t.Fatalf("wrong region not counted (%v)", s.refusals) |
| 363 | } |
| 364 | auth.Apps[0].Regions = []string{"test"} |
| 365 | |
| 366 | // A retired master ends the app's use of that kid. |
| 367 | masters.byKid["k1"] = Master{Kid: "k1", Key: testMaster(), RetireAt: now().Add(-time.Second)} |
| 368 | if ok(good) { |
| 369 | t.Fatal("retired kid accepted") |
| 370 | } |
| 371 | masters.byKid["k1"] = Master{Kid: "k1", Key: testMaster()} |
| 372 | |
| 373 | // Owner binding: a live allocation's typed source refuses another app's |
| 374 | // valid credential. |
| 375 | s.mu.Lock() |
| 376 | s.bound[typedSrc(src)] = &Binding{ID: Identity{"abc", "meet"}, Incarnation: "0123456789abcdef"} |
| 377 | s.mu.Unlock() |
| 378 | auth.Apps = append(auth.Apps, AuthorityApp{Account: "abc", App: "docs", Incarnation: "0123456789abcdef", Regions: []string{"test"}}) |
| 379 | docs := fmt.Sprintf("%d:%s", now().Add(time.Hour).Unix(), Suffix("abc", "docs", "0123456789abcdef", "k1")) |
| 380 | if ok(docs) { |
| 381 | t.Fatal("a second app allocated from a bound source") |
| 382 | } |
| 383 | if s.refusalCount(Identity{"abc", "docs"}, "bound elsewhere") == 0 { |
| 384 | t.Fatalf("bound elsewhere not counted (%v)", s.refusals) |
| 385 | } |
| 386 | // The bound app itself is still served, and a TCP source with the same |
| 387 | // address string is a separate binding. |
| 388 | if !ok(good) { |
| 389 | t.Fatal("the bound app was refused on its own source") |
| 390 | } |
| 391 | tcpSrc := &net.TCPAddr{IP: src.IP, Port: src.Port} |
| 392 | if key, okAuth := s.auth(docs, "carlos", tcpSrc); !okAuth || len(key) == 0 { |
| 393 | t.Fatal("a tcp source shares the udp source's binding") |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | // TestServerRefusalStateIsBounded is the memory-exhaustion case: pion |
| 398 | // hands the AuthHandler the raw username before it checks |
| 399 | // MESSAGE-INTEGRITY, and the nonce is not source-bound, so an unauthenticated |
| 400 | // sender chooses the account and app strings. None of that may become a |
| 401 | // map entry of its own. |
| 402 | func TestServerRefusalStateIsBounded(t *testing.T) { |
| 403 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 404 | src := &net.UDPAddr{IP: net.ParseIP("192.0.2.10"), Port: 6000} |
| 405 | exp := now().Add(time.Hour).Unix() |
| 406 | const flood = 10000 |
| 407 | for i := 0; i < flood; i++ { |
| 408 | username := fmt.Sprintf("%d:%s", exp, Suffix(fmt.Sprintf("a%d", i), "meet", "0123456789abcdef", "k1")) |
| 409 | if _, ok := s.auth(username, "carlos", src); ok { |
| 410 | t.Fatalf("made-up account %d authenticated", i) |
| 411 | } |
| 412 | } |
| 413 | if n := s.refusalIdentities(); n > 2 { |
| 414 | t.Fatalf("refusal identities = %d, want at most 2", n) |
| 415 | } |
| 416 | if got := s.refusalCount(Identity{}, "unknown app"); got != flood { |
| 417 | t.Fatalf("unknown-app refusals = %d, want %d", got, flood) |
| 418 | } |
| 419 | s.mu.Lock() |
| 420 | auths, logs := len(s.lastAuth), len(s.logged) |
| 421 | s.mu.Unlock() |
| 422 | if auths != 0 { |
| 423 | t.Fatalf("lastAuth = %d, want 0: nothing authenticated", auths) |
| 424 | } |
| 425 | if logs > 2 { |
| 426 | t.Fatalf("log records = %d, want the refusal log rate-limited", logs) |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | // TestServerAuthMemoryIsCapped covers the other half: usernames that do |
| 431 | // verify are remembered for the allocation event, but only so many. |
| 432 | func TestServerAuthMemoryIsCapped(t *testing.T) { |
| 433 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 434 | username, _ := creds(now(), "abc", "meet", "0123456789abcdef") |
| 435 | for i := 0; i < s.maxLastAuth+600; i++ { |
| 436 | src := &net.UDPAddr{IP: net.ParseIP("192.0.2.11"), Port: 1024 + i} |
| 437 | if _, ok := s.auth(username, "carlos", src); !ok { |
| 438 | t.Fatalf("source %d refused", i) |
| 439 | } |
| 440 | } |
| 441 | s.mu.Lock() |
| 442 | n := len(s.lastAuth) |
| 443 | s.mu.Unlock() |
| 444 | if n > s.maxLastAuth { |
| 445 | t.Fatalf("lastAuth = %d, want at most %d", n, s.maxLastAuth) |
| 446 | } |
| 447 | } |
| 448 | |
| 449 | // TestServerCountsRejectedPeerBytes is spec §3's `rejected`: peer bytes |
| 450 | // that reached a relay socket and were dropped for want of a permission. |
| 451 | func TestServerCountsRejectedPeerBytes(t *testing.T) { |
| 452 | meter := &fakeMeter{} |
| 453 | s, _, now, _, _ := newTestServer(t, "203.0.113.5", meter, nil) |
| 454 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 455 | client := turnClient(t, s.UDPAddr(), username, password) |
| 456 | relayed, err := client.Allocate() |
| 457 | if err != nil { |
| 458 | t.Fatalf("allocate: %v", err) |
| 459 | } |
| 460 | defer relayed.Close() |
| 461 | port := relayed.LocalAddr().(*net.UDPAddr).Port |
| 462 | |
| 463 | peer, err := net.ListenPacket("udp4", "127.0.0.1:0") |
| 464 | if err != nil { |
| 465 | t.Fatal(err) |
| 466 | } |
| 467 | defer peer.Close() |
| 468 | // No permission was ever created for this peer, so pion drops both. |
| 469 | to := &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: port} |
| 470 | for i := 0; i < 2; i++ { |
| 471 | if _, err := peer.WriteTo(make([]byte, 100), to); err != nil { |
| 472 | t.Fatalf("peer write: %v", err) |
| 473 | } |
| 474 | } |
| 475 | id := Identity{"abc", "meet"} |
| 476 | waitFor(t, func() bool { _, _, rejected := meter.totals(id); return rejected >= 100 }) |
| 477 | |
| 478 | s.EndAll("test") |
| 479 | waitFor(t, func() bool { return s.Live() == 0 }) |
| 480 | in, out, rejected := meter.totals(id) |
| 481 | if rejected != 200 { |
| 482 | t.Fatalf("rejected = %d, want 200", rejected) |
| 483 | } |
| 484 | if out != 0 { |
| 485 | t.Fatalf("out = %d, want 0: nothing was forwarded to the client", out) |
| 486 | } |
| 487 | if in != 0 { |
| 488 | t.Fatalf("in = %d, want 0", in) |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | // TestServerControlIsNotBucketed: at a saturated per-allocation bucket a |
| 493 | // client must still be able to allocate and refresh. Only relayed payload |
| 494 | // is bucketed and metered. |
| 495 | func TestServerControlIsNotBucketed(t *testing.T) { |
| 496 | meter := &fakeMeter{} |
| 497 | s, _, now, _, _ := newTestServer(t, "203.0.113.5", meter, func(cfg *ServerConfig) { |
| 498 | cfg.Limits.AllocBitsPerSec = 8 // one byte a second: every control response would be dropped |
| 499 | cfg.Limits.BoxBitsPerSec = 8 |
| 500 | }) |
| 501 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 502 | client := turnClient(t, s.UDPAddr(), username, password) |
| 503 | relayed, err := client.Allocate() |
| 504 | if err != nil { |
| 505 | t.Fatalf("allocate at a saturated bucket: %v", err) |
| 506 | } |
| 507 | defer relayed.Close() |
| 508 | // A second round trip on the same listener, after the allocation |
| 509 | // exists: its response must get through too. |
| 510 | if _, err := client.SendBindingRequest(); err != nil { |
| 511 | t.Fatalf("binding request at a saturated bucket: %v", err) |
| 512 | } |
| 513 | if in, out, rejected := meter.totals(Identity{"abc", "meet"}); in|out|rejected != 0 { |
| 514 | t.Fatalf("signalling was metered: in=%d out=%d rejected=%d", in, out, rejected) |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | // TestServerClosesOrphanedAllocation: if the pending socket is gone by the |
| 519 | // time pion reports the allocation, pion owns an allocation no deadline of |
| 520 | // ours can end. It must be ended on the spot. |
| 521 | func TestServerClosesOrphanedAllocation(t *testing.T) { |
| 522 | s, setNow, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 523 | src := &net.UDPAddr{IP: net.ParseIP("192.0.2.12"), Port: 7000} |
| 524 | |
| 525 | // The socket is still ours, but its pending record has gone. |
| 526 | _, addr, err := s.AllocatePacketConn("udp4", 0) |
| 527 | if err != nil { |
| 528 | t.Fatal(err) |
| 529 | } |
| 530 | port := addr.(*net.UDPAddr).Port |
| 531 | username, _ := creds(now(), "abc", "meet", "0123456789abcdef") |
| 532 | if _, ok := s.auth(username, "carlos", src); !ok { |
| 533 | t.Fatal("fixture credential refused") |
| 534 | } |
| 535 | s.mu.Lock() |
| 536 | delete(s.pending, port) |
| 537 | s.mu.Unlock() |
| 538 | s.created(src, nil, "udp", username, "carlos", addr, 0) |
| 539 | if s.Live() != 0 { |
| 540 | t.Fatalf("live = %d, want the orphan ended", s.Live()) |
| 541 | } |
| 542 | if s.ports.has(port) { |
| 543 | t.Fatal("the orphaned socket was left open") |
| 544 | } |
| 545 | |
| 546 | // And the real race: the sweep reclaimed the socket before pion |
| 547 | // reported the allocation. |
| 548 | _, addr2, err := s.AllocatePacketConn("udp4", 0) |
| 549 | if err != nil { |
| 550 | t.Fatal(err) |
| 551 | } |
| 552 | port2 := addr2.(*net.UDPAddr).Port |
| 553 | setNow(now().Add(pendingLife + time.Second)) |
| 554 | s.tick() |
| 555 | if s.ports.has(port2) { |
| 556 | t.Fatal("the sweep left the socket open") |
| 557 | } |
| 558 | username2, _ := creds(now(), "abc", "meet", "0123456789abcdef") |
| 559 | if _, ok := s.auth(username2, "carlos", src); !ok { |
| 560 | t.Fatal("fixture credential refused after the clock moved") |
| 561 | } |
| 562 | s.created(src, nil, "udp", username2, "carlos", addr2, 0) |
| 563 | if s.Live() != 0 { |
| 564 | t.Fatalf("live = %d, want no allocation for a reclaimed socket", s.Live()) |
| 565 | } |
| 566 | } |
| 567 | |
| 568 | // refusalIdentity is the identity a refusal is attributed to: the parsed |
| 569 | // username's, or the zero identity when the username would not parse. |
| 570 | func refusalIdentity(username string) Identity { |
| 571 | u, err := ParseUsername(username) |
| 572 | if err != nil { |
| 573 | return Identity{} |
| 574 | } |
| 575 | return Identity{u.Account, u.App} |
| 576 | } |
| 577 | |
| 578 | func TestServerPermit(t *testing.T) { |
| 579 | _, deny, err := net.ParseCIDR("172.31.0.0/16") |
| 580 | if err != nil { |
| 581 | t.Fatal(err) |
| 582 | } |
| 583 | s, err := NewServer(ServerConfig{ |
| 584 | Region: "test", RelayIP: net.ParseIP("203.0.113.5"), UDP: "127.0.0.1:0", |
| 585 | MinPort: 40200, MaxPort: 40300, DenyCIDRs: []*net.IPNet{deny}, |
| 586 | Masters: func() *Masters { return nil }, |
| 587 | Authority: func() *Authority { return nil }, |
| 588 | Lease: func(time.Time) bool { return true }, |
| 589 | Limits: DefaultLimits(), Log: t.Logf, |
| 590 | }) |
| 591 | if err != nil { |
| 592 | t.Fatal(err) |
| 593 | } |
| 594 | defer s.Close() |
| 595 | |
| 596 | client := &net.UDPAddr{IP: net.ParseIP("198.51.100.1"), Port: 1} |
| 597 | refused := []string{ |
| 598 | "127.0.0.1", "::1", "169.254.169.254", "fe80::1", "10.0.0.1", |
| 599 | "172.16.5.5", "192.168.1.1", "fd00::1", "100.64.0.1", "172.31.9.9", |
| 600 | "0.0.0.0", "224.0.0.1", |
| 601 | // Discussion #220: "this network" beyond the unspecified address, |
| 602 | // IETF protocol assignments, benchmarking. |
| 603 | "0.1.2.3", "0.255.255.255", "192.0.0.8", "198.18.0.1", "198.19.255.254", |
| 604 | // NAT64 and 6to4 judged by the IPv4 address they carry. |
| 605 | "64:ff9b::a00:1", "64:ff9b::7f00:1", "64:ff9b::a9fe:a9fe", "64:ff9b::ac1f:909", |
| 606 | "2002:a00:1::1", "2002:a9fe:a9fe::", "2002:c0a8:101::1", "2002:0:0::1", |
| 607 | // IPv4-mapped forms are matched by the IPv4 ranges already. |
| 608 | "::ffff:10.0.0.1", "::ffff:198.18.0.1", |
| 609 | } |
| 610 | for _, ip := range refused { |
| 611 | if s.permit(client, net.ParseIP(ip)) { |
| 612 | t.Fatalf("peer %s was permitted", ip) |
| 613 | } |
| 614 | } |
| 615 | for _, ip := range []string{ |
| 616 | "203.0.113.5", "198.51.100.9", "2001:db8::1", |
| 617 | // Neighbours of the new ranges stay reachable, and a public address |
| 618 | // behind NAT64 or 6to4 is an ordinary peer. |
| 619 | "1.0.0.1", "192.0.1.1", "198.17.255.255", "198.20.0.1", |
| 620 | "64:ff9b::808:808", "2002:808:808::1", |
| 621 | } { |
| 622 | if !s.permit(client, net.ParseIP(ip)) { |
| 623 | t.Fatalf("peer %s was refused", ip) |
| 624 | } |
| 625 | } |
| 626 | } |
| 627 | |
| 628 | // TestServerCloseKeepsReplacementBinding is the race the spec calls out: |
| 629 | // pion removes an allocation from its manager before closing its socket, so |
| 630 | // a replacement allocation on the same typed source can exist while the old |
| 631 | // wrapper is still closing. The old wrapper's close must not erase it. |
| 632 | func TestServerCloseKeepsReplacementBinding(t *testing.T) { |
| 633 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, nil) |
| 634 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 635 | client := turnClient(t, s.UDPAddr(), username, password) |
| 636 | if _, err := client.Allocate(); err != nil { |
| 637 | t.Fatalf("allocate: %v", err) |
| 638 | } |
| 639 | key := "" |
| 640 | var old *relayConn |
| 641 | s.mu.Lock() |
| 642 | for k, e := range s.allocs { |
| 643 | key, old = k, e.conn |
| 644 | } |
| 645 | s.mu.Unlock() |
| 646 | |
| 647 | // A replacement allocation lands for the same typed source while the |
| 648 | // old socket is still closing. |
| 649 | replacement := &Binding{ID: Identity{"abc", "meet"}, Src: key} |
| 650 | replConn := newRelayConn(&fakePacketConn{}, replacement, nil, s.ports, nil, nil, nil, nil, func(*relayConn) {}) |
| 651 | replEntry := &allocEntry{conn: replConn, id: replacement.ID, key: key, seq: 99} |
| 652 | s.mu.Lock() |
| 653 | s.bound[key] = replacement |
| 654 | s.allocs[key] = replEntry |
| 655 | s.owners[replConn] = replEntry |
| 656 | s.mu.Unlock() |
| 657 | |
| 658 | old.Close() |
| 659 | |
| 660 | s.mu.Lock() |
| 661 | gotBound, gotAlloc := s.bound[key], s.allocs[key] |
| 662 | _, oldOwned := s.owners[old] |
| 663 | s.mu.Unlock() |
| 664 | if gotBound != replacement { |
| 665 | t.Fatalf("close erased the replacement binding: %v", gotBound) |
| 666 | } |
| 667 | if gotAlloc != replEntry { |
| 668 | t.Fatalf("close erased the replacement allocation: %v", gotAlloc) |
| 669 | } |
| 670 | if oldOwned { |
| 671 | t.Fatal("the closed socket is still owned, so its admission never came back") |
| 672 | } |
| 673 | } |
| 674 | |
| 675 | // TestServerRefusesPermissionIntoConfiguredDenyCIDR covers the operator's |
| 676 | // half of the peer policy, which the built-in private ranges do not reach: |
| 677 | // CARLOS_RELAY_DENY_CIDRS carries the box's own VPC CIDR, and that CIDR is |
| 678 | // ordinary public-looking space as far as the built-in list is concerned. |
| 679 | // It is the one thing standing between a TURN allocation and the deployment's |
| 680 | // private network, and until now only the built-in ranges had a test |
| 681 | // (discussion #120). |
| 682 | func TestServerRefusesPermissionIntoConfiguredDenyCIDR(t *testing.T) { |
| 683 | if raceDetector { |
| 684 | t.Skip("pion/turn races on Permission.lifetimeTimer between AddPermission and Close; see race_on_test.go") |
| 685 | } |
| 686 | // 198.51.100.0/24 stands in for the VPC: globally routable as far as |
| 687 | // the built-in private list is concerned, denied only because the |
| 688 | // operator said so. |
| 689 | _, vpc, err := net.ParseCIDR("198.51.100.0/24") |
| 690 | if err != nil { |
| 691 | t.Fatal(err) |
| 692 | } |
| 693 | s, _, now, _, _ := newTestServer(t, "127.0.0.1", nil, func(cfg *ServerConfig) { |
| 694 | cfg.DenyCIDRs = append(cfg.DenyCIDRs, vpc) |
| 695 | }) |
| 696 | username, password := creds(now(), "abc", "meet", "0123456789abcdef") |
| 697 | client := turnClient(t, s.UDPAddr(), username, password) |
| 698 | if _, err := client.Allocate(); err != nil { |
| 699 | t.Fatalf("allocate: %v", err) |
| 700 | } |
| 701 | if err := client.CreatePermission(&net.UDPAddr{IP: net.ParseIP("198.51.100.9"), Port: 1}); err == nil { |
| 702 | t.Error("permission into the configured deny CIDR was granted — a TURN client could reach the VPC") |
| 703 | } |
| 704 | // The control: an address outside both the built-in list and the |
| 705 | // configured range is still permitted, so the test is not passing |
| 706 | // because permissions are broken outright. |
| 707 | if err := client.CreatePermission(&net.UDPAddr{IP: net.ParseIP("203.0.113.9"), Port: 1}); err != nil { |
| 708 | t.Errorf("permission to an ordinary public peer was refused: %v", err) |
| 709 | } |
| 710 | } |
| 711 | |