github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/server/meek_test.go (about) 1 /* 2 * Copyright (c) 2017, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package server 21 22 import ( 23 "bytes" 24 "context" 25 crypto_rand "crypto/rand" 26 "encoding/base64" 27 "fmt" 28 "io/ioutil" 29 "math/rand" 30 "net" 31 "path/filepath" 32 "sync" 33 "sync/atomic" 34 "syscall" 35 "testing" 36 "time" 37 38 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon" 39 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 40 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters" 41 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" 42 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol" 43 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tactics" 44 "golang.org/x/crypto/nacl/box" 45 ) 46 47 var KB = 1024 48 var MB = KB * KB 49 50 func TestCachedResponse(t *testing.T) { 51 52 rand.Seed(time.Now().Unix()) 53 54 testCases := []struct { 55 concurrentResponses int 56 responseSize int 57 bufferSize int 58 extendedBufferSize int 59 extendedBufferCount int 60 minBytesPerWrite int 61 maxBytesPerWrite int 62 copyPosition int 63 expectedSuccess bool 64 }{ 65 {1, 16, 16, 0, 0, 1, 1, 0, true}, 66 67 {1, 31, 16, 0, 0, 1, 1, 15, true}, 68 69 {1, 16, 2, 2, 7, 1, 1, 0, true}, 70 71 {1, 31, 15, 3, 5, 1, 1, 1, true}, 72 73 {1, 16, 16, 0, 0, 1, 1, 16, true}, 74 75 {1, 64*KB + 1, 64 * KB, 64 * KB, 1, 1, 1 * KB, 64 * KB, true}, 76 77 {1, 10 * MB, 64 * KB, 64 * KB, 158, 1, 32 * KB, 0, false}, 78 79 {1, 10 * MB, 64 * KB, 64 * KB, 159, 1, 32 * KB, 0, true}, 80 81 {1, 10 * MB, 64 * KB, 64 * KB, 160, 1, 32 * KB, 0, true}, 82 83 {1, 128 * KB, 64 * KB, 0, 0, 1, 1 * KB, 64 * KB, true}, 84 85 {1, 128 * KB, 64 * KB, 0, 0, 1, 1 * KB, 63 * KB, false}, 86 87 {1, 200 * KB, 64 * KB, 0, 0, 1, 1 * KB, 136 * KB, true}, 88 89 {10, 10 * MB, 64 * KB, 64 * KB, 1589, 1, 32 * KB, 0, false}, 90 91 {10, 10 * MB, 64 * KB, 64 * KB, 1590, 1, 32 * KB, 0, true}, 92 } 93 94 for _, testCase := range testCases { 95 description := fmt.Sprintf("test case: %+v", testCase) 96 t.Run(description, func(t *testing.T) { 97 98 pool := NewCachedResponseBufferPool(testCase.extendedBufferSize, testCase.extendedBufferCount) 99 100 responses := make([]*CachedResponse, testCase.concurrentResponses) 101 for i := 0; i < testCase.concurrentResponses; i++ { 102 responses[i] = NewCachedResponse(testCase.bufferSize, pool) 103 } 104 105 // Repeats exercise CachedResponse.Reset() and CachedResponseBufferPool replacement 106 for repeat := 0; repeat < 2; repeat++ { 107 108 t.Logf("repeat %d", repeat) 109 110 responseData := make([]byte, testCase.responseSize) 111 _, _ = rand.Read(responseData) 112 113 waitGroup := new(sync.WaitGroup) 114 115 // Goroutines exercise concurrent access to CachedResponseBufferPool 116 for _, response := range responses { 117 waitGroup.Add(1) 118 go func(response *CachedResponse) { 119 defer waitGroup.Done() 120 121 remainingSize := testCase.responseSize 122 for remainingSize > 0 { 123 124 writeSize := testCase.minBytesPerWrite 125 writeSize += rand.Intn(testCase.maxBytesPerWrite - testCase.minBytesPerWrite + 1) 126 if writeSize > remainingSize { 127 writeSize = remainingSize 128 } 129 130 offset := len(responseData) - remainingSize 131 response.Write(responseData[offset : offset+writeSize]) 132 remainingSize -= writeSize 133 } 134 }(response) 135 } 136 137 waitGroup.Wait() 138 139 atLeastOneFailure := false 140 141 for i, response := range responses { 142 143 cachedResponseData := new(bytes.Buffer) 144 145 n, err := response.CopyFromPosition(testCase.copyPosition, cachedResponseData) 146 147 if testCase.expectedSuccess { 148 if err != nil { 149 t.Fatalf("CopyFromPosition unexpectedly failed for response %d: %s", i, err) 150 } 151 if n != cachedResponseData.Len() || n > response.Available() { 152 t.Fatalf("cached response size mismatch for response %d", i) 153 } 154 if !bytes.Equal(responseData[testCase.copyPosition:], cachedResponseData.Bytes()) { 155 t.Fatalf("cached response data mismatch for response %d", i) 156 } 157 } else { 158 atLeastOneFailure = true 159 } 160 } 161 162 if !testCase.expectedSuccess && !atLeastOneFailure { 163 t.Fatalf("CopyFromPosition unexpectedly succeeded for all responses") 164 } 165 166 for _, response := range responses { 167 response.Reset() 168 } 169 } 170 }) 171 } 172 } 173 174 func TestMeekResiliency(t *testing.T) { 175 176 upstreamData := make([]byte, 5*MB) 177 _, _ = rand.Read(upstreamData) 178 179 downstreamData := make([]byte, 5*MB) 180 _, _ = rand.Read(downstreamData) 181 182 minWrite, maxWrite := 1, 128*KB 183 minRead, maxRead := 1, 128*KB 184 minWait, maxWait := 1*time.Millisecond, 500*time.Millisecond 185 186 sendFunc := func(name string, conn net.Conn, data []byte) { 187 for sent := 0; sent < len(data); { 188 wait := minWait + time.Duration(rand.Int63n(int64(maxWait-minWait)+1)) 189 time.Sleep(wait) 190 writeLen := minWrite + rand.Intn(maxWrite-minWrite+1) 191 writeLen = min(writeLen, len(data)-sent) 192 _, err := conn.Write(data[sent : sent+writeLen]) 193 if err != nil { 194 t.Errorf("conn.Write failed: %s", err) 195 return 196 } 197 sent += writeLen 198 fmt.Printf("%s sent %d/%d...\n", name, sent, len(data)) 199 } 200 fmt.Printf("%s send complete\n", name) 201 } 202 203 recvFunc := func(name string, conn net.Conn, expectedData []byte) { 204 data := make([]byte, len(expectedData)) 205 for received := 0; received < len(data); { 206 wait := minWait + time.Duration(rand.Int63n(int64(maxWait-minWait)+1)) 207 time.Sleep(wait) 208 readLen := minRead + rand.Intn(maxRead-minRead+1) 209 readLen = min(readLen, len(data)-received) 210 n, err := conn.Read(data[received : received+readLen]) 211 if err != nil { 212 t.Errorf("conn.Read failed: %s", err) 213 return 214 } 215 received += n 216 if !bytes.Equal(data[0:received], expectedData[0:received]) { 217 fmt.Printf("%s data check has failed...\n", name) 218 additionalInfo := "" 219 index := bytes.Index(expectedData, data[received-n:received]) 220 if index != -1 { 221 // Helpful for debugging missing or repeated data... 222 additionalInfo = fmt.Sprintf( 223 " (last read of %d appears at %d)", n, index) 224 } 225 t.Errorf("%s got unexpected data with %d/%d%s", 226 name, received, len(expectedData), additionalInfo) 227 return 228 } 229 fmt.Printf("%s received %d/%d...\n", name, received, len(expectedData)) 230 } 231 fmt.Printf("%s receive complete\n", name) 232 } 233 234 // Run meek server 235 236 rawMeekCookieEncryptionPublicKey, rawMeekCookieEncryptionPrivateKey, err := box.GenerateKey(crypto_rand.Reader) 237 if err != nil { 238 t.Fatalf("box.GenerateKey failed: %s", err) 239 } 240 meekCookieEncryptionPublicKey := base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPublicKey[:]) 241 meekCookieEncryptionPrivateKey := base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPrivateKey[:]) 242 meekObfuscatedKey := prng.HexString(SSH_OBFUSCATED_KEY_BYTE_LENGTH) 243 244 mockSupport := &SupportServices{ 245 Config: &Config{ 246 MeekObfuscatedKey: meekObfuscatedKey, 247 MeekCookieEncryptionPrivateKey: meekCookieEncryptionPrivateKey, 248 }, 249 TrafficRulesSet: &TrafficRulesSet{}, 250 } 251 mockSupport.GeoIPService, _ = NewGeoIPService([]string{}) 252 253 listener, err := net.Listen("tcp", "127.0.0.1:0") 254 if err != nil { 255 t.Fatalf("net.Listen failed: %s", err) 256 } 257 defer listener.Close() 258 259 serverAddress := listener.Addr().String() 260 261 relayWaitGroup := new(sync.WaitGroup) 262 263 var serverClientConn atomic.Value 264 265 clientHandler := func(_ string, conn net.Conn) { 266 serverClientConn.Store(conn) 267 name := "server" 268 relayWaitGroup.Add(1) 269 go func() { 270 defer relayWaitGroup.Done() 271 sendFunc(name, conn, downstreamData) 272 }() 273 relayWaitGroup.Add(1) 274 go func() { 275 defer relayWaitGroup.Done() 276 recvFunc(name, conn, upstreamData) 277 }() 278 } 279 280 stopBroadcast := make(chan struct{}) 281 282 useTLS := false 283 isFronted := false 284 useObfuscatedSessionTickets := false 285 286 server, err := NewMeekServer( 287 mockSupport, 288 listener, 289 "", 290 0, 291 useTLS, 292 isFronted, 293 useObfuscatedSessionTickets, 294 clientHandler, 295 stopBroadcast) 296 if err != nil { 297 t.Fatalf("NewMeekServer failed: %s", err) 298 } 299 300 serverWaitGroup := new(sync.WaitGroup) 301 302 serverWaitGroup.Add(1) 303 go func() { 304 defer serverWaitGroup.Done() 305 err := server.Run() 306 select { 307 case <-stopBroadcast: 308 return 309 default: 310 } 311 if err != nil { 312 t.Errorf("MeekServer.Run failed: %s", err) 313 } 314 }() 315 316 // Run meek client 317 318 dialConfig := &psiphon.DialConfig{ 319 DeviceBinder: new(fileDescriptorInterruptor), 320 ResolveIP: func(_ context.Context, host string) ([]net.IP, error) { 321 return []net.IP{net.ParseIP(host)}, nil 322 }, 323 } 324 325 params, err := parameters.NewParameters(nil) 326 if err != nil { 327 t.Fatalf("NewParameters failed: %s", err) 328 } 329 330 meekObfuscatorPaddingSeed, err := prng.NewSeed() 331 if err != nil { 332 t.Fatalf("prng.NewSeed failed: %s", err) 333 } 334 335 meekConfig := &psiphon.MeekConfig{ 336 Parameters: params, 337 DialAddress: serverAddress, 338 UseHTTPS: useTLS, 339 UseObfuscatedSessionTickets: useObfuscatedSessionTickets, 340 HostHeader: "example.com", 341 MeekCookieEncryptionPublicKey: meekCookieEncryptionPublicKey, 342 MeekObfuscatedKey: meekObfuscatedKey, 343 MeekObfuscatorPaddingSeed: meekObfuscatorPaddingSeed, 344 } 345 346 ctx, cancelFunc := context.WithTimeout( 347 context.Background(), time.Second*5) 348 defer cancelFunc() 349 350 clientConn, err := psiphon.DialMeek(ctx, meekConfig, dialConfig) 351 if err != nil { 352 t.Fatalf("psiphon.DialMeek failed: %s", err) 353 } 354 355 // Relay data through meek while interrupting underlying TCP connections 356 357 name := "client" 358 relayWaitGroup.Add(1) 359 go func() { 360 defer relayWaitGroup.Done() 361 sendFunc(name, clientConn, upstreamData) 362 }() 363 364 relayWaitGroup.Add(1) 365 go func() { 366 defer relayWaitGroup.Done() 367 recvFunc(name, clientConn, downstreamData) 368 }() 369 370 relayWaitGroup.Wait() 371 372 // Check for multiple underlying connections 373 374 metrics := serverClientConn.Load().(common.MetricsSource).GetMetrics() 375 count := metrics["meek_underlying_connection_count"].(int64) 376 if count <= 1 { 377 t.Fatalf("unexpected meek_underlying_connection_count: %d", count) 378 } 379 380 // Graceful shutdown 381 382 clientConn.Close() 383 384 listener.Close() 385 close(stopBroadcast) 386 387 // This wait will hang if shutdown is broken, and the test will ultimately panic 388 serverWaitGroup.Wait() 389 } 390 391 type fileDescriptorInterruptor struct { 392 } 393 394 func (interruptor *fileDescriptorInterruptor) BindToDevice(fileDescriptor int) (string, error) { 395 fdDup, err := syscall.Dup(fileDescriptor) 396 if err != nil { 397 return "", err 398 } 399 minAfter := 500 * time.Millisecond 400 maxAfter := 1 * time.Second 401 after := minAfter + time.Duration(rand.Int63n(int64(maxAfter-minAfter)+1)) 402 time.AfterFunc(after, func() { 403 syscall.Shutdown(fdDup, syscall.SHUT_RDWR) 404 syscall.Close(fdDup) 405 fmt.Printf("interrupted TCP connection\n") 406 }) 407 return "", nil 408 } 409 410 func TestMeekRateLimiter(t *testing.T) { 411 runTestMeekAccessControl(t, true, false) 412 runTestMeekAccessControl(t, false, false) 413 } 414 415 func TestMeekRestrictFrontingProviders(t *testing.T) { 416 runTestMeekAccessControl(t, false, true) 417 runTestMeekAccessControl(t, false, false) 418 } 419 420 func runTestMeekAccessControl(t *testing.T, rateLimit, restrictProvider bool) { 421 422 attempts := 10 423 424 allowedConnections := 5 425 426 if !rateLimit { 427 allowedConnections = 10 428 } 429 430 if restrictProvider { 431 allowedConnections = 0 432 } 433 434 // Configure tactics 435 436 frontingProviderID := prng.HexString(8) 437 438 tacticsConfigJSONFormat := ` 439 { 440 "RequestPublicKey" : "%s", 441 "RequestPrivateKey" : "%s", 442 "RequestObfuscatedKey" : "%s", 443 "DefaultTactics" : { 444 "TTL" : "60s", 445 "Probability" : 1.0, 446 "Parameters" : { 447 "RestrictFrontingProviderIDs" : ["%s"], 448 "RestrictFrontingProviderIDsServerProbability" : 1.0 449 } 450 } 451 } 452 ` 453 454 tacticsRequestPublicKey, tacticsRequestPrivateKey, tacticsRequestObfuscatedKey, err := 455 tactics.GenerateKeys() 456 if err != nil { 457 t.Fatalf("error generating tactics keys: %s", err) 458 } 459 460 restrictFrontingProviderID := "" 461 462 if restrictProvider { 463 restrictFrontingProviderID = frontingProviderID 464 } 465 466 tacticsConfigJSON := fmt.Sprintf( 467 tacticsConfigJSONFormat, 468 tacticsRequestPublicKey, tacticsRequestPrivateKey, tacticsRequestObfuscatedKey, 469 restrictFrontingProviderID) 470 471 tacticsConfigFilename := filepath.Join(testDataDirName, "tactics_config.json") 472 473 err = ioutil.WriteFile(tacticsConfigFilename, []byte(tacticsConfigJSON), 0600) 474 if err != nil { 475 t.Fatalf("error paving tactics config file: %s", err) 476 } 477 478 // Run meek server 479 480 rawMeekCookieEncryptionPublicKey, rawMeekCookieEncryptionPrivateKey, err := box.GenerateKey(crypto_rand.Reader) 481 if err != nil { 482 t.Fatalf("box.GenerateKey failed: %s", err) 483 } 484 meekCookieEncryptionPublicKey := base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPublicKey[:]) 485 meekCookieEncryptionPrivateKey := base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPrivateKey[:]) 486 meekObfuscatedKey := prng.HexString(SSH_OBFUSCATED_KEY_BYTE_LENGTH) 487 488 tunnelProtocol := protocol.TUNNEL_PROTOCOL_FRONTED_MEEK 489 490 meekRateLimiterTunnelProtocols := []string{tunnelProtocol} 491 if !rateLimit { 492 meekRateLimiterTunnelProtocols = []string{protocol.TUNNEL_PROTOCOL_FRONTED_MEEK} 493 } 494 495 mockSupport := &SupportServices{ 496 Config: &Config{ 497 MeekObfuscatedKey: meekObfuscatedKey, 498 MeekCookieEncryptionPrivateKey: meekCookieEncryptionPrivateKey, 499 TunnelProtocolPorts: map[string]int{tunnelProtocol: 0}, 500 frontingProviderID: frontingProviderID, 501 }, 502 TrafficRulesSet: &TrafficRulesSet{ 503 MeekRateLimiterHistorySize: allowedConnections, 504 MeekRateLimiterThresholdSeconds: attempts, 505 MeekRateLimiterTunnelProtocols: meekRateLimiterTunnelProtocols, 506 MeekRateLimiterGarbageCollectionTriggerCount: 1, 507 MeekRateLimiterReapHistoryFrequencySeconds: 1, 508 }, 509 } 510 mockSupport.GeoIPService, _ = NewGeoIPService([]string{}) 511 512 tacticsServer, err := tactics.NewServer(nil, nil, nil, tacticsConfigFilename) 513 if err != nil { 514 t.Fatalf("tactics.NewServer failed: %s", err) 515 } 516 517 mockSupport.TacticsServer = tacticsServer 518 mockSupport.ServerTacticsParametersCache = NewServerTacticsParametersCache(mockSupport) 519 520 listener, err := net.Listen("tcp", "127.0.0.1:0") 521 if err != nil { 522 t.Fatalf("net.Listen failed: %s", err) 523 } 524 defer listener.Close() 525 526 serverAddress := listener.Addr().String() 527 528 stopBroadcast := make(chan struct{}) 529 530 useTLS := false 531 isFronted := false 532 useObfuscatedSessionTickets := false 533 534 server, err := NewMeekServer( 535 mockSupport, 536 listener, 537 tunnelProtocol, 538 0, 539 useTLS, 540 isFronted, 541 useObfuscatedSessionTickets, 542 func(_ string, conn net.Conn) { 543 go func() { 544 for { 545 buffer := make([]byte, 1) 546 n, err := conn.Read(buffer) 547 if err == nil && n == 1 { 548 _, err = conn.Write(buffer) 549 } 550 if err != nil { 551 conn.Close() 552 break 553 } 554 } 555 }() 556 }, 557 stopBroadcast) 558 if err != nil { 559 t.Fatalf("NewMeekServer failed: %s", err) 560 } 561 562 serverWaitGroup := new(sync.WaitGroup) 563 564 serverWaitGroup.Add(1) 565 go func() { 566 defer serverWaitGroup.Done() 567 err := server.Run() 568 select { 569 case <-stopBroadcast: 570 return 571 default: 572 } 573 if err != nil { 574 t.Errorf("MeekServer.Run failed: %s", err) 575 } 576 }() 577 578 // Run meek clients: 579 // For 10 attempts, connect once per second vs. rate limit of 5-per-10 seconds, 580 // so about half of the connections should be rejected by the rate limiter. 581 582 totalConnections := 0 583 totalFailures := 0 584 585 for i := 0; i < attempts; i++ { 586 587 dialConfig := &psiphon.DialConfig{ 588 ResolveIP: func(_ context.Context, host string) ([]net.IP, error) { 589 return []net.IP{net.ParseIP(host)}, nil 590 }, 591 } 592 593 params, err := parameters.NewParameters(nil) 594 if err != nil { 595 t.Fatalf("NewParameters failed: %s", err) 596 } 597 598 meekObfuscatorPaddingSeed, err := prng.NewSeed() 599 if err != nil { 600 t.Fatalf("prng.NewSeed failed: %s", err) 601 } 602 603 meekConfig := &psiphon.MeekConfig{ 604 Parameters: params, 605 DialAddress: serverAddress, 606 HostHeader: "example.com", 607 MeekCookieEncryptionPublicKey: meekCookieEncryptionPublicKey, 608 MeekObfuscatedKey: meekObfuscatedKey, 609 MeekObfuscatorPaddingSeed: meekObfuscatorPaddingSeed, 610 } 611 612 ctx, cancelFunc := context.WithTimeout( 613 context.Background(), 500*time.Millisecond) 614 defer cancelFunc() 615 616 clientConn, err := psiphon.DialMeek(ctx, meekConfig, dialConfig) 617 618 if err == nil { 619 _, err = clientConn.Write([]byte{0}) 620 } 621 if err == nil { 622 buffer := make([]byte, 1) 623 _, err = clientConn.Read(buffer) 624 } 625 626 if clientConn != nil { 627 clientConn.Close() 628 } 629 630 if err != nil { 631 totalFailures += 1 632 } else { 633 totalConnections += 1 634 } 635 636 if i < attempts-1 { 637 time.Sleep(1 * time.Second) 638 } 639 } 640 641 if totalConnections != allowedConnections || 642 totalFailures != attempts-totalConnections { 643 644 t.Fatalf( 645 "Unexpected results: %d connections, %d failures, %d allowed", 646 totalConnections, totalFailures, allowedConnections) 647 } 648 649 // Graceful shutdown 650 651 listener.Close() 652 close(stopBroadcast) 653 654 // This wait will hang if shutdown is broken, and the test will ultimately panic 655 serverWaitGroup.Wait() 656 }