| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- package tuic
- import (
- "bytes"
- "context"
- "crypto/tls"
- "io"
- "net"
- "testing"
- "time"
- serverquic "github.com/apernet/quic-go"
- "github.com/google/uuid"
- clientquic "github.com/quic-go/quic-go"
- )
- func startLifecycleTestServer(t *testing.T, relayAddr, email string) (*Server, *clientquic.Conn, uuid.UUID, string) {
- t.Helper()
- cert, key := generateTestCert(t)
- clientID := uuid.New()
- password := "lifecycle-test-password"
- server, err := NewServer(Instance{
- Id: 99101, Tag: "lifecycle-test", Listen: "127.0.0.1", Port: 0,
- Certificate: string(cert), PrivateKey: string(key), ALPN: []string{"h3"},
- AuthenticationTimeout: 2, MaxIdleTime: 30,
- Clients: []TuicClientSettings{{UUID: clientID.String(), Password: password, Email: email}},
- }, &SocksRelay{Addr: relayAddr, Password: "lifecycle-socks-password"})
- if err != nil {
- t.Fatalf("create TUIC server: %v", err)
- }
- if err := server.Start(); err != nil {
- t.Fatalf("start TUIC server: %v", err)
- }
- t.Cleanup(func() { _ = server.Close() })
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- client, err := clientquic.DialAddr(ctx, server.packetConn.LocalAddr().String(),
- &tls.Config{InsecureSkipVerify: true, NextProtos: []string{"h3"}},
- &clientquic.Config{EnableDatagrams: true})
- if err != nil {
- t.Fatalf("dial TUIC server: %v", err)
- }
- t.Cleanup(func() { _ = client.CloseWithError(0, "test complete") })
- tlsState := client.ConnectionState().TLS
- token, err := tlsState.ExportKeyingMaterial(string(clientID[:]), []byte(password), 32)
- if err != nil {
- t.Fatalf("derive authentication token: %v", err)
- }
- stream, err := client.OpenUniStreamSync(ctx)
- if err != nil {
- t.Fatalf("open authentication stream: %v", err)
- }
- auth := append([]byte{ProtocolVersion, CmdAuthenticate}, clientID[:]...)
- auth = append(auth, token...)
- if _, err := stream.Write(auth); err != nil {
- t.Fatalf("write authentication: %v", err)
- }
- if err := stream.Close(); err != nil {
- t.Fatalf("close authentication stream: %v", err)
- }
- return server, client, clientID, password
- }
- func authenticatedServerConnection(t *testing.T, server *Server, clientID uuid.UUID) (*serverquic.Conn, *User) {
- t.Helper()
- id := [16]byte(clientID)
- deadline := time.Now().Add(2 * time.Second)
- for time.Now().Before(deadline) {
- server.activeConnsMu.Lock()
- for conn, user := range server.activeConns[id] {
- server.activeConnsMu.Unlock()
- return conn, user
- }
- server.activeConnsMu.Unlock()
- time.Sleep(time.Millisecond)
- }
- t.Fatal("server did not register authenticated QUIC connection")
- return nil, nil
- }
- func TestMalformedBiStreamDelayedFINReleasesReceiveCredit(t *testing.T) {
- _, client, _, _ := startLifecycleTestServer(t, "127.0.0.1:1", "bidi-lifecycle@x")
- ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
- defer cancel()
- for i := 0; i < 110; i++ {
- openCtx, openCancel := context.WithTimeout(ctx, 700*time.Millisecond)
- stream, err := client.OpenStreamSync(openCtx)
- openCancel()
- if err != nil {
- t.Fatalf("bidirectional stream %d blocked after unsupported commands: %v", i+1, err)
- }
- if _, err := stream.Write([]byte{ProtocolVersion, 0xff}); err != nil {
- t.Fatalf("write unsupported command %d: %v", i+1, err)
- }
- stream.SetReadDeadline(time.Now().Add(time.Second))
- if _, err := io.Copy(io.Discard, stream); err != nil {
- t.Fatalf("wait for unsupported stream %d to close: %v", i+1, err)
- }
- _ = stream.Close()
- }
- }
- func TestDownstreamUDPResponseRefreshesAssociationIdleTime(t *testing.T) {
- socksAddr, cleanup := startMockSocks5Server(t, "udp-lifecycle@x", "lifecycle-socks-password")
- defer cleanup()
- server, client, clientID, _ := startLifecycleTestServer(t, socksAddr, "udp-lifecycle@x")
- serverConn, user := authenticatedServerConnection(t, server, clientID)
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- session, err := server.relay.DialUDP(ctx, user.Email)
- if err != nil {
- t.Fatalf("open SOCKS UDP session: %v", err)
- }
- t.Cleanup(func() { _ = session.Close() })
- const associationID uint16 = 61244
- oldActive := time.Now().Add(-udpAssociationIdleTimeout - time.Second)
- association := &udpAssociation{
- responseTransport: packetTransportDatagram,
- relay: &udpRelaySession{relay: session, responseTransport: packetTransportDatagram},
- lastActive: oldActive,
- }
- registry := newUdpAssociationRegistry(maxUdpRelayPacketSize)
- registry.associations[associationID] = association
- responseDone := make(chan struct{})
- go func() {
- defer close(responseDone)
- server.relayUDPResponses(ctx, serverConn, user, associationID, association, registry, association.relay)
- }()
- target := &Address{Type: AddrTypeIPv4, IP: net.IPv4(8, 8, 8, 8), Port: 53}
- if _, err := session.Send(target, []byte("seed")); err != nil {
- t.Fatalf("send SOCKS seed datagram: %v", err)
- }
- response, err := client.ReceiveDatagram(ctx)
- if err != nil {
- t.Fatalf("receive echoed UDP response: %v", err)
- }
- if len(response) < 2 || response[0] != ProtocolVersion || response[1] != CmdPacket {
- t.Fatalf("unexpected TUIC UDP response: %x", response)
- }
- reader := bytes.NewReader(response[2:])
- header, err := ReadPacketHeader(reader)
- if err != nil {
- t.Fatalf("read response header: %v", err)
- }
- got, err := readPacketPayload(reader, header)
- if err != nil || !bytes.Equal(got, []byte("seed")) {
- t.Fatalf("echo response payload = %q, error=%v", got, err)
- }
- if header.AssocID != associationID {
- t.Fatalf("response association id = %d, want %d", header.AssocID, associationID)
- }
- registry.mu.Lock()
- refreshedAt := association.lastActive
- registry.mu.Unlock()
- if !refreshedAt.After(oldActive) {
- t.Fatal("successful downstream response did not refresh association activity")
- }
- registry.reapIdle(oldActive.Add(udpAssociationIdleTimeout + time.Second))
- registry.mu.Lock()
- remaining := registry.associations[associationID]
- registry.mu.Unlock()
- if remaining != association {
- t.Fatal("association was reaped despite a recently delivered downstream response")
- }
- _ = session.Close()
- select {
- case <-responseDone:
- case <-time.After(time.Second):
- t.Fatal("UDP response relay did not stop after SOCKS session closed")
- }
- }
- func TestUdpAssociationTouchDoesNotRefreshReusedID(t *testing.T) {
- registry := newUdpAssociationRegistry(maxUdpRelayPacketSize)
- addr := &Address{Type: AddrTypeIPv4, IP: net.IPv4(8, 8, 8, 8), Port: 53}
- header := &PacketHeader{AssocID: 17, PktID: 1, FragTotal: 1, FragID: 0, Size: 1, Addr: addr}
- old, _, _, complete := registry.feed(packetTransportDatagram, header, []byte("x"))
- if !complete {
- t.Fatal("failed to create first association generation")
- }
- oldTime := old.lastActive
- if !registry.dissociate(header.AssocID) {
- t.Fatal("failed to dissociate first association generation")
- }
- newGeneration, _, _, complete := registry.feed(packetTransportDatagram, header, []byte("x"))
- if !complete || newGeneration == old {
- t.Fatal("failed to create replacement association generation")
- }
- if registry.touch(header.AssocID, old, oldTime.Add(time.Hour)) {
- t.Fatal("late response refreshed a replacement association generation")
- }
- if !newGeneration.lastActive.Before(oldTime.Add(time.Hour)) {
- t.Fatal("replacement association timestamp changed after stale touch")
- }
- }
|