├── .github └── workflows │ └── docker-publish.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.rst ├── agents ├── ack_agent.go ├── base_agent.go ├── buffer_agent.go ├── closing_agent.go ├── flow_control_agent.go ├── frame_queue_agent.go ├── handshake_agent.go ├── http09_agent.go ├── http3_agent.go ├── parse_agent.go ├── qlog_agent.go ├── qpack_agent.go ├── recovery_agent.go ├── rtt_agent.go ├── send_agent.go ├── socket_agent.go ├── stream_agent.go └── tls_agent.go ├── bin ├── http │ └── http_get.go └── test_suite │ ├── scenario_runner.go │ └── test_suite.go ├── common.go ├── compat ├── utils.go ├── utils_darwin.go └── utils_generic.go ├── connection.go ├── crypto.go ├── frames.go ├── headers.go ├── http3 └── http3.go ├── ietf_quic_hosts.txt ├── lib └── varint.go ├── packets.go ├── pcap_capture.go ├── qlog ├── frames.go ├── packets.go ├── qlog.go ├── qt2qlog │ └── qt2qlog.go └── recovery.go ├── scenarii ├── ack_ecn.go ├── ack_only.go ├── address_validation.go ├── closed_connection.go ├── connection_migration.go ├── connection_migration_v4_v6.go ├── flow_control.go ├── handshake.go ├── handshake_v6.go ├── http3_encoder_stream.go ├── http3_get.go ├── http3_reserved_frames.go ├── http3_reserved_streams.go ├── http3_uni_streams_limits.go ├── http_get_and_wait.go ├── http_get_on_uni_stream.go ├── key_update.go ├── multi_packet_client_hello.go ├── multi_stream.go ├── new_connection_id.go ├── padding.go ├── retire_connection_id.go ├── scenario.go ├── server_flow_control.go ├── spin_bit.go ├── stop_sending_frame_on_receive_stream.go ├── stream_opening_reordering.go ├── transport_parameters.go ├── unsupported_tls_version.go ├── version_negotiation.go ├── zero_length_cid.go └── zero_rtt.go ├── streams.go ├── streams_test.go ├── trace.go ├── transport_parameters.go └── utils.go /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build and push to Docker Hub 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | # Job name is Greeting 8 | name: Build and push 9 | # This job runs on Linux 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | 15 | - name: Build and push Docker images 16 | uses: docker/build-push-action@v1.1.0 17 | with: 18 | username: ${{ secrets.DOCKER_USERNAME }} 19 | password: ${{ secrets.DOCKER_PASSWORD }} 20 | repository: mpiraux/quictracker 21 | tags: latest 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__ 3 | quic_tracker/postprocess/output 4 | *.qlog -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | 5 | language: go 6 | go: 7 | - "1.9" 8 | - "1.10" 9 | - "1.11" 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - tcpdump 15 | - libpcap-dev 16 | - openssl 17 | - libssl-dev 18 | - faketime 19 | - libscope-guard-perl 20 | - libtest-tcp-perl 21 | homebrew: 22 | packages: 23 | - openssl 24 | - libpcap 25 | - tcpdump 26 | - libfaketime 27 | 28 | before_install: 29 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CMAKE_OPTS=" -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"; fi 30 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then curl -L https://cpanmin.us | sudo perl - App::cpanminus; fi 31 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo cpanm --notest Scope::Guard; fi 32 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo cpanm --notest Test::TCP; fi 33 | 34 | install: 35 | - go get -t -v ./... || true 36 | - cd $GOPATH/src/github.com/mpiraux/pigotls && make 37 | - cd $GOPATH/src/github.com/mpiraux/ls-qpack-go && make 38 | - cd $GOPATH/src/github.com/QUIC-Tracker/quic-tracker 39 | 40 | script: 41 | - go test 42 | - go build bin/test_suite/test_suite.go 43 | - go build bin/test_suite/scenario_runner.go 44 | - go build bin/http/http_get.go 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11-alpine 2 | RUN apk add --no-cache make cmake gcc g++ git openssl openssl-dev perl-test-harness-utils tcpdump libpcap libpcap-dev libbsd-dev perl-scope-guard perl-test-tcp python3 yarn && \ 3 | pip3 install --upgrade git+https://github.com/QUIC-Tracker/dissector git+https://github.com/QUIC-Tracker/web-app 4 | 5 | RUN echo "from quic_tracker.app import app as application" > /web-app.wsgi 6 | RUN ln -s /usr/lib/python3.7/site-packages/quic_tracker/traces /traces 7 | WORKDIR /usr/lib/python3.7/site-packages/quic_tracker/static 8 | RUN yarn install 9 | EXPOSE 5000 10 | ENV QUIC_TRACKER_ALLOW_UPLOAD 1 11 | ENV FLASK_APP=quic_tracker.app 12 | 13 | RUN mkdir -p /go/src/github.com/QUIC-Tracker/quic-tracker 14 | ADD . /go/src/github.com/QUIC-Tracker/quic-tracker 15 | WORKDIR /go/src/github.com/QUIC-Tracker/quic-tracker 16 | ENV GOPATH /go 17 | RUN go get -v || true 18 | WORKDIR /go/src/github.com/mpiraux/pigotls 19 | RUN make 20 | WORKDIR /go/src/github.com/mpiraux/ls-qpack-go 21 | RUN make 22 | WORKDIR /go/src/github.com/QUIC-Tracker/quic-tracker 23 | RUN go build -o /test_suite bin/test_suite/test_suite.go && \ 24 | go build -o /scenario_runner bin/test_suite/scenario_runner.go && \ 25 | go build -o /http_get bin/http/http_get.go 26 | CMD ["/test_suite"] 27 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | A test suite for QUIC 2 | ===================== 3 | 4 | .. image:: https://godoc.org/github.com/QUIC-Tracker/quic-tracker?status.svg 5 | :target: https://godoc.org/github.com/QUIC-Tracker/quic-tracker 6 | :alt: Documentation status 7 | 8 | 9 | The test suite comprises a minimal Go implementation of QUIC which is 10 | currently draft-29 and TLS-1.3 compatible, as well as several 11 | test scenarii built upon this implementation. The test suite outputs its 12 | result as JSON files, which contains the result, the decrypted packets 13 | exchanged, as well as a pcap file and exporter secrets. 14 | 15 | Installation 16 | ------------ 17 | 18 | You should have Go 1.9, tcpdump, libpcap libraries and headers as well as 19 | openssl headers installed before starting. 20 | 21 | :: 22 | 23 | go get -u github.com/QUIC-Tracker/quic-tracker # This will fail because of the missing dependencies that should be build using the 4 lines below 24 | cd $GOPATH/src/github.com/mpiraux/pigotls 25 | make 26 | cd $GOPATH/src/github.com/mpiraux/ls-qpack-go 27 | make 28 | 29 | The test suite is run using the scripts in ``bin/test_suite/``. For help 30 | about their usage see: 31 | 32 | :: 33 | 34 | go run bin/test_suite/scenario_runner.go -h 35 | go run bin/test_suite/test_suite.go -h 36 | 37 | 38 | Docker 39 | ------ 40 | 41 | Docker builds exist on `Docker Hub`_. 42 | 43 | :: 44 | 45 | docker run --network="host" mpiraux/quictracker /http_get -h 46 | docker run --network="host" mpiraux/quictracker /scenario_runner -h 47 | docker run --network="host" mpiraux/quictracker /test_suite -h 48 | # Running the web app and mounting traces files from the current directory 49 | docker run -v "$PWD:/traces" -p 5000:5000 mpiraux/quictracker flask run -h 0.0.0.0 50 | 51 | .. _Docker Hub: https://hub.docker.com/r/quictracker/quictracker/ 52 | -------------------------------------------------------------------------------- /agents/ack_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | "time" 6 | ) 7 | 8 | // The AckAgent is in charge of queuing ACK frames in response to receiving packets that need to be acknowledged as well 9 | // as answering to PATH_CHALLENGE frames. Both can be disabled independently for a finer control on its behaviour. 10 | type AckAgent struct { 11 | FrameProducingAgent 12 | DisableAcks map[PNSpace]bool 13 | TotalDataAcked map[PNSpace]uint64 14 | DisablePathResponse bool 15 | } 16 | 17 | func (a *AckAgent) Run(conn *Connection) { 18 | a.BaseAgent.Init("AckAgent", conn.OriginalDestinationCID) 19 | a.FrameProducingAgent.InitFPA(conn) 20 | if a.DisableAcks == nil { 21 | a.DisableAcks = make(map[PNSpace]bool) 22 | } 23 | a.TotalDataAcked = make(map[PNSpace]uint64) 24 | 25 | recvdTimestamps := make(map[PNSpace]map[PacketNumber]time.Time) 26 | for _, space := range []PNSpace{PNSpaceInitial, PNSpaceHandshake, PNSpaceAppData} { 27 | recvdTimestamps[space] = make(map[PacketNumber]time.Time) 28 | } 29 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 30 | 31 | go func() { 32 | defer a.Logger.Println("Agent terminated") 33 | defer close(a.closed) 34 | for { 35 | select { 36 | case i := <-incomingPackets: 37 | p := i.(Packet) 38 | if p.PNSpace() != PNSpaceNoSpace { 39 | pn := p.Header().PacketNumber() 40 | for _, number := range conn.AckQueue[p.PNSpace()] { 41 | if number == pn { 42 | a.Logger.Printf("Received duplicate packet number %d in PN space %s\n", pn, p.PNSpace().String()) 43 | // TODO: This should be flagged somewhere, and placed in a more general ErrorAgent 44 | } 45 | } 46 | 47 | conn.AckQueue[p.PNSpace()] = append(conn.AckQueue[p.PNSpace()], pn) 48 | recvdTimestamps[p.PNSpace()][p.Header().PacketNumber()] = p.ReceiveContext().Timestamp 49 | 50 | if framePacket, ok := p.(Framer); ok { 51 | if pathChallenge := framePacket.GetFirst(PathChallengeType); !a.DisablePathResponse && pathChallenge != nil { 52 | conn.FrameQueue.Submit(QueuedFrame{&PathResponse{pathChallenge.(*PathChallenge).Data}, p.EncryptionLevel()}) 53 | } 54 | } 55 | 56 | if !a.DisableAcks[p.PNSpace()] && p.ShouldBeAcknowledged() { 57 | a.conn.PreparePacket.Submit(p.EncryptionLevel()) 58 | a.TotalDataAcked[p.PNSpace()] += uint64(len(p.Encode(p.EncodePayload()))) // TODO: See following todo 59 | } 60 | } 61 | case args := <-a.requestFrame: // TODO: Keep track of the ACKs and their packet to shorten the ack blocks once received by the peer 62 | pnSpace := EncryptionLevelToPNSpace[args.level] 63 | if a.DisableAcks[pnSpace] || args.level == EncryptionLevelBest || args.level == EncryptionLevelBestAppData || args.level == EncryptionLevel0RTT { 64 | a.frames <- nil 65 | break 66 | } 67 | f := conn.GetAckFrame(pnSpace) 68 | if f != nil { 69 | lRTimestamp, ok := recvdTimestamps[pnSpace][f.LargestAcknowledged] 70 | if ok { 71 | f.AckDelay = uint64((time.Now().Sub(lRTimestamp).Round(time.Microsecond) / time.Microsecond) >> conn.TLSTPHandler.AckDelayExponent) 72 | } 73 | if args.availableSpace >= int(f.FrameLength()) { 74 | a.frames <- []Frame{f} 75 | } else { 76 | a.conn.PreparePacket.Submit(args.level) 77 | a.frames <- nil 78 | } 79 | } else { 80 | a.frames <- nil 81 | } 82 | case <-a.close: 83 | return 84 | } 85 | } 86 | }() 87 | } 88 | -------------------------------------------------------------------------------- /agents/base_agent.go: -------------------------------------------------------------------------------- 1 | // 2 | // This package contains pieces of behaviours that constitutes a QUIC client. 3 | // 4 | // Each agent is responsible for a limited part of the behaviour of a QUIC client. This allows modularity when defining 5 | // test scenarii with specific needs. Each agent is described in its type documentation. For more information on the 6 | // architecture of QUIC-Tracker, please consult the package quictracker documentation. 7 | // 8 | package agents 9 | 10 | import ( 11 | "encoding/hex" 12 | "fmt" 13 | . "github.com/QUIC-Tracker/quic-tracker" 14 | "log" 15 | "os" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | type Agent interface { 21 | Name() string 22 | Init(name string, ODCID ConnectionID) 23 | Run(conn *Connection) 24 | Stop() 25 | Restart() 26 | Join() 27 | } 28 | 29 | type RequestFrameArgs struct { 30 | availableSpace int 31 | level EncryptionLevel 32 | number PacketNumber 33 | } 34 | 35 | // All agents should embed this structure 36 | type BaseAgent struct { 37 | name string 38 | Logger *log.Logger 39 | close chan bool // true if should restart, false otherwise 40 | closed chan bool 41 | } 42 | 43 | func (a *BaseAgent) Name() string { return a.name } 44 | 45 | // All agents that embed this structure must call Init() as soon as their Run() method is called 46 | func (a *BaseAgent) Init(name string, ODCID ConnectionID) { 47 | a.name = name 48 | a.Logger = log.New(os.Stderr, fmt.Sprintf("[%s/%s] ", hex.EncodeToString(ODCID), a.Name()), log.Lshortfile) 49 | a.Logger.Println("Agent started") 50 | a.close = make(chan bool) 51 | a.closed = make(chan bool) 52 | } 53 | 54 | func (a *BaseAgent) Stop() { 55 | select { 56 | case <-a.close: 57 | default: 58 | close(a.close) 59 | } 60 | } 61 | 62 | func (a *BaseAgent) Restart() { 63 | select { 64 | case <-a.close: 65 | default: 66 | a.close <- true 67 | } 68 | } 69 | 70 | func (a *BaseAgent) Join() { 71 | <-a.closed 72 | } 73 | 74 | type FrameProducer interface { 75 | RequestFrames(availableSpace int, level EncryptionLevel, number PacketNumber) ([]Frame, bool) 76 | } 77 | 78 | type FrameProducingAgent struct { 79 | BaseAgent 80 | conn *Connection 81 | requestFrame chan RequestFrameArgs 82 | frames chan []Frame 83 | } 84 | 85 | func (a *FrameProducingAgent) InitFPA(conn *Connection) { 86 | a.conn = conn 87 | a.requestFrame = make(chan RequestFrameArgs) 88 | a.frames = make(chan []Frame) 89 | } 90 | 91 | func (a *FrameProducingAgent) RequestFrames(availableSpace int, level EncryptionLevel, number PacketNumber) ([]Frame, bool) { 92 | select { 93 | case a.requestFrame <- RequestFrameArgs{availableSpace, level, number}: 94 | select { 95 | case f := <-a.frames: 96 | return f, true 97 | } 98 | case <-a.close: 99 | return nil, false 100 | } 101 | } 102 | 103 | func (a *FrameProducingAgent) Run(conn *Connection) {} 104 | 105 | // Represents a set of agents that are attached to a particular connection 106 | type ConnectionAgents struct { 107 | conn *Connection 108 | agents map[string]Agent 109 | } 110 | 111 | func AttachAgentsToConnection(conn *Connection, agents ...Agent) *ConnectionAgents { 112 | c := ConnectionAgents{conn, make(map[string]Agent)} 113 | 114 | for _, a := range agents { 115 | c.Add(a) 116 | } 117 | 118 | go func() { 119 | for { 120 | select { 121 | case <-conn.ConnectionRestart: 122 | conn.Logger.Printf("Restarting all agents\n") 123 | for _, a := range agents { 124 | a.Restart() 125 | a.Join() 126 | } 127 | conn.ConnectionRestart = make(chan bool, 1) 128 | for _, a := range agents { 129 | a.Run(conn) 130 | } 131 | close(conn.ConnectionRestarted) 132 | conn.Logger.Printf("Restarting all agents: done\n") 133 | case <-conn.ConnectionClosed: 134 | return 135 | } 136 | } 137 | }() 138 | 139 | return &c 140 | } 141 | 142 | func (c *ConnectionAgents) Add(agent Agent) { 143 | agent.Run(c.conn) 144 | c.agents[agent.Name()] = agent 145 | } 146 | 147 | func (c *ConnectionAgents) Get(name string) Agent { 148 | return c.agents[name] 149 | } 150 | 151 | func (c *ConnectionAgents) Has(name string) (Agent, bool) { 152 | a, b := c.agents[name] 153 | return a, b 154 | } 155 | 156 | func (c *ConnectionAgents) GetFrameProducingAgents() []FrameProducer { 157 | var agents []FrameProducer 158 | for _, a := range c.agents { 159 | if fpa, ok := a.(FrameProducer); ok { 160 | agents = append(agents, fpa) 161 | } 162 | } 163 | return agents 164 | } 165 | 166 | func (c *ConnectionAgents) Stop(names ...string) { 167 | for _, n := range names { 168 | c.Get(n).Stop() 169 | c.Get(n).Join() 170 | } 171 | } 172 | 173 | func (c *ConnectionAgents) StopAll() { 174 | for _, a := range c.agents { 175 | a.Stop() 176 | a.Join() 177 | } 178 | } 179 | 180 | // This function sends an (CONNECTION|APPLICATION)_CLOSE frame and wait for it to be sent out. Then it stops all the 181 | // agents attached to this connection. 182 | func (c *ConnectionAgents) CloseConnection(quicLayer bool, errorCode uint64, reasonPhrase string) { 183 | var a Agent 184 | var present bool 185 | if a, present = c.Has("ClosingAgent"); !present { 186 | a = &ClosingAgent{} 187 | c.Add(a) 188 | } 189 | a.(*ClosingAgent).Close(quicLayer, errorCode, reasonPhrase) 190 | a.Join() 191 | c.StopAll() 192 | } 193 | 194 | func (c *ConnectionAgents) AddHTTPAgent() HTTPAgent { 195 | var agent HTTPAgent 196 | if strings.HasPrefix(c.conn.ALPN, "h3") { 197 | agent = &HTTP3Agent{DisableQPACKStreams: true} 198 | } else { 199 | agent = &HTTP09Agent{} 200 | } 201 | c.Add(agent) 202 | return agent 203 | } 204 | 205 | // Returns the agents needed for a basic QUIC connection to operate 206 | func GetDefaultAgents() []Agent { 207 | fc := &FlowControlAgent{} 208 | return []Agent{ 209 | &QLogAgent{}, 210 | &SocketAgent{}, 211 | &ParsingAgent{}, 212 | &BufferAgent{}, 213 | &TLSAgent{}, 214 | &AckAgent{}, 215 | &SendingAgent{MTU: 1200}, 216 | &RecoveryAgent{TimerValue: 500 * time.Millisecond}, 217 | &RTTAgent{}, 218 | &FrameQueueAgent{}, 219 | fc, 220 | &StreamAgent{FlowControlAgent: fc}, 221 | &ClosingAgent{}, 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /agents/buffer_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/qlog" 6 | qt2qlog "github.com/QUIC-Tracker/quic-tracker/qlog/qt2qlog" 7 | ) 8 | 9 | // The BufferAgent is in charge of waiting for a given decryption level to become available before putting 10 | // ciphertexts that require this level back into the decryption queue. 11 | type BufferAgent struct { 12 | BaseAgent 13 | } 14 | 15 | func (a *BufferAgent) Run(conn *Connection) { 16 | a.Init("BufferAgent", conn.OriginalDestinationCID) 17 | 18 | uPChan := conn.UnprocessedPayloads.RegisterNewChan(1000) 19 | eLChan := conn.EncryptionLevels.RegisterNewChan(1000) 20 | 21 | unprocessedPayloads := make(map[EncryptionLevel][]IncomingPayload) 22 | encryptionLevelsAvailable := make(map[EncryptionLevel]bool) 23 | 24 | go func() { 25 | defer a.Logger.Println("Agent terminated") 26 | defer close(a.closed) 27 | for { 28 | select { 29 | case i := <-uPChan: 30 | u := i.(UnprocessedPayload) 31 | if !encryptionLevelsAvailable[u.EncryptionLevel] { 32 | u.EncryptionLevel.String() 33 | conn.QLogEvents <- conn.QLogTrace.NewEvent(qlog.Categories.Transport.Category, qlog.Categories.Transport.PacketBuffered, qt2qlog.ConvertPacketBuffered(EncryptionLevelToPacketType[u.EncryptionLevel], "keys_unavailable")) 34 | unprocessedPayloads[u.EncryptionLevel] = append(unprocessedPayloads[u.EncryptionLevel], u.IncomingPayload) 35 | } else { 36 | conn.IncomingPayloads.Submit(u.IncomingPayload) 37 | } 38 | case i := <-eLChan: 39 | dEL := i.(DirectionalEncryptionLevel) 40 | if dEL.Available && dEL.Read { 41 | eL := dEL.EncryptionLevel 42 | encryptionLevelsAvailable[eL] = true 43 | if len(unprocessedPayloads[eL]) > 0 { 44 | a.Logger.Printf("Encryption level %s is available, putting back %d unprocessed payloads into the buffer", eL.String(), len(unprocessedPayloads[eL])) 45 | } 46 | for _, uP := range unprocessedPayloads[eL] { 47 | uP.WasBuffered = true 48 | conn.IncomingPayloads.Submit(uP) 49 | } 50 | unprocessedPayloads[eL] = nil 51 | } 52 | case <-a.close: 53 | return 54 | } 55 | } 56 | }() 57 | } 58 | -------------------------------------------------------------------------------- /agents/closing_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | "time" 6 | ) 7 | 8 | // The ClosingAgent is responsible for keeping track of events that can close the connection, such as the idle timeout. 9 | // It can queue an (CONNECTION|APPLICATION)_CLOSE frame and wait for it to be sent out. 10 | type ClosingAgent struct { 11 | BaseAgent 12 | closing bool 13 | conn *Connection 14 | IdleDuration time.Duration 15 | IdleTimeout *time.Timer 16 | } 17 | 18 | func (a *ClosingAgent) Run(conn *Connection) { // TODO: Observe incoming CC and AC 19 | a.Init("ClosingAgent", conn.OriginalDestinationCID) 20 | a.conn = conn 21 | a.IdleDuration = time.Duration(a.conn.TLSTPHandler.IdleTimeout) * time.Millisecond 22 | a.IdleTimeout = time.NewTimer(a.IdleDuration) 23 | 24 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 25 | outgoingPackets := conn.OutgoingPackets.RegisterNewChan(1000) 26 | 27 | go func() { 28 | defer a.Logger.Println("Agent terminated") 29 | defer close(a.closed) 30 | 31 | for { 32 | select { 33 | case <-incomingPackets: 34 | a.IdleTimeout.Reset(a.IdleDuration) 35 | case i := <-outgoingPackets: 36 | switch p := i.(type) { 37 | case Framer: 38 | if a.closing && (p.Contains(ConnectionCloseType) || p.Contains(ApplicationCloseType)) { 39 | close(a.conn.ConnectionClosed) 40 | return 41 | } 42 | } 43 | if p := i.(Packet); p.ShouldBeAcknowledged() { 44 | a.IdleTimeout.Reset(a.IdleDuration) 45 | } 46 | case <-a.IdleTimeout.C: 47 | a.closing = true 48 | a.Logger.Printf("Idle timeout of %v reached, closing\n", a.IdleDuration.String()) 49 | close(a.conn.ConnectionClosed) 50 | return 51 | case shouldRestart := <-a.close: 52 | if !shouldRestart { 53 | close(a.conn.ConnectionClosed) 54 | } 55 | return 56 | } 57 | } 58 | }() 59 | } 60 | 61 | func (a *ClosingAgent) Close(quicLayer bool, errorCode uint64, reasonPhrase string) { 62 | if !a.closing { 63 | a.closing = true 64 | a.conn.CloseConnection(quicLayer, errorCode, reasonPhrase) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /agents/frame_queue_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | "container/heap" 5 | . "github.com/QUIC-Tracker/quic-tracker" 6 | ) 7 | 8 | var FramePriority = map[FrameType]int{ 9 | ConnectionCloseType: 1, 10 | ApplicationCloseType: 2, 11 | PathResponseType: 3, 12 | AckType: 4, 13 | AckECNType: 5, 14 | CryptoType: 6, 15 | PingType: 7, 16 | NewConnectionIdType: 8, 17 | RetireConnectionIdType: 9, 18 | PathChallengeType: 10, 19 | ResetStreamType: 11, 20 | StopSendingType: 12, 21 | MaxDataType: 13, 22 | MaxStreamDataType: 14, 23 | MaxStreamsType: 15, 24 | MaxStreamsType + 1: 16, 25 | DataBlockedType: 17, 26 | StreamDataBlockedType: 18, 27 | StreamsBlockedType: 19, 28 | StreamsBlockedType + 1: 20, 29 | NewTokenType: 21, 30 | StreamType: 22, 31 | PaddingFrameType: 23, 32 | } 33 | 34 | type item struct { 35 | value Frame 36 | priority int 37 | index int 38 | } 39 | 40 | type FramePriorityQueue []*item 41 | 42 | func NewFramePriorityQueue() *FramePriorityQueue { 43 | pq := make(FramePriorityQueue, 0) 44 | heap.Init(&pq) 45 | return &pq 46 | } 47 | 48 | func (pq FramePriorityQueue) Len() int { return len(pq) } 49 | 50 | func (pq FramePriorityQueue) Less(i, j int) bool { 51 | return pq[i].priority < pq[j].priority 52 | } 53 | 54 | func (pq FramePriorityQueue) Swap(i, j int) { 55 | if pq.Len() < 2 { 56 | return 57 | } 58 | pq[i], pq[j] = pq[j], pq[i] 59 | pq[i].index = i 60 | pq[j].index = j 61 | } 62 | 63 | func (pq *FramePriorityQueue) Push(x interface{}) { 64 | n := len(*pq) 65 | item := new(item) 66 | f := x.(Frame) 67 | item.value = f 68 | item.priority = FramePriority[f.FrameType()] 69 | item.index = n 70 | *pq = append(*pq, item) 71 | } 72 | 73 | func (pq *FramePriorityQueue) Pop() interface{} { 74 | if pq.Len() < 1 { 75 | return nil 76 | } 77 | old := *pq 78 | n := len(old) 79 | item := old[n-1] 80 | item.index = -1 81 | *pq = old[0 : n-1] 82 | return item.value 83 | } 84 | 85 | func (pq *FramePriorityQueue) Peek() Frame { 86 | if pq.Len() < 1 { 87 | return nil 88 | } 89 | items := *pq 90 | return items[len(items)-1].value 91 | } 92 | 93 | type FrameQueueAgent struct { 94 | FrameProducingAgent 95 | } 96 | 97 | // The FrameQueueAgent collects all the frames that should be packed into packets and order them by frame type priority. 98 | // Each type of frame is given a level of priority as expressed in FramePriority. 99 | func (a *FrameQueueAgent) Run(conn *Connection) { 100 | a.BaseAgent.Init("FrameQueueAgent", conn.OriginalDestinationCID) 101 | a.FrameProducingAgent.InitFPA(conn) 102 | 103 | frameBuffer := map[EncryptionLevel]*FramePriorityQueue{ 104 | EncryptionLevelInitial: NewFramePriorityQueue(), 105 | EncryptionLevel0RTT: NewFramePriorityQueue(), 106 | EncryptionLevelHandshake: NewFramePriorityQueue(), 107 | EncryptionLevel1RTT: NewFramePriorityQueue(), 108 | EncryptionLevelBest: NewFramePriorityQueue(), 109 | EncryptionLevelBestAppData: NewFramePriorityQueue(), 110 | } 111 | 112 | incFrames := conn.FrameQueue.RegisterNewChan(1000) 113 | 114 | go func() { 115 | defer a.Logger.Println("Agent terminated") 116 | defer close(a.closed) 117 | for { 118 | select { 119 | case i := <-incFrames: 120 | qf := i.(QueuedFrame) 121 | heap.Push(frameBuffer[qf.EncryptionLevel], qf.Frame) 122 | a.Logger.Printf("Received a 0x%02x frame for encryption level %s\n", qf.FrameType(), qf.EncryptionLevel) 123 | conn.PreparePacket.Submit(qf.EncryptionLevel) 124 | case args := <-a.requestFrame: 125 | var frames []Frame 126 | buffer := frameBuffer[args.level] 127 | var i interface{} 128 | for i = heap.Pop(buffer); i != nil && args.availableSpace >= int(i.(Frame).FrameLength()); i = heap.Pop(buffer) { 129 | frames = append(frames, i.(Frame)) 130 | args.availableSpace -= int(i.(Frame).FrameLength()) 131 | } 132 | if i != nil { 133 | heap.Push(buffer, i) 134 | } 135 | 136 | if i != nil && args.availableSpace < int(i.(Frame).FrameLength()) { 137 | a.Logger.Printf("Unable to put %d-byte frame into %d-byte buffer\n", i.(Frame).FrameLength(), args.availableSpace) 138 | a.conn.PreparePacket.Submit(args.level) 139 | } 140 | a.frames <- frames 141 | case <-a.close: 142 | return 143 | } 144 | } 145 | }() 146 | } 147 | -------------------------------------------------------------------------------- /agents/handshake_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | . "github.com/QUIC-Tracker/quic-tracker" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type HandshakeStatus struct { 14 | Completed bool 15 | Packet 16 | Error error 17 | } 18 | 19 | func (s HandshakeStatus) String() string { 20 | return fmt.Sprintf("HandshakeStatus{Completed=%t, Error=%s}", s.Completed, s.Error) 21 | } 22 | 23 | // The HandshakeAgent is responsible for initiating the QUIC handshake and respond to the version negotiation process if 24 | // the server requires it. It reports the status of the handshake through the HandshakeStatus attribute. The status 25 | // should only be published once, reporting a failure or a success. 26 | type HandshakeAgent struct { 27 | BaseAgent 28 | TLSAgent *TLSAgent 29 | SocketAgent *SocketAgent 30 | HandshakeStatus Broadcaster //type: HandshakeStatus 31 | IgnoreRetry bool 32 | DontDropKeys bool 33 | sendInitial chan bool 34 | receivedRetry bool 35 | retrySource ConnectionID 36 | } 37 | 38 | func (a *HandshakeAgent) Run(conn *Connection) { 39 | a.Init("HandshakeAgent", conn.OriginalDestinationCID) 40 | a.HandshakeStatus = NewBroadcaster(10) 41 | a.sendInitial = make(chan bool, 1) 42 | 43 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 44 | outPackets := conn.OutgoingPackets.RegisterNewChan(1000) 45 | tlsStatus := a.TLSAgent.TLSStatus.RegisterNewChan(10) 46 | socketStatus := a.SocketAgent.SocketStatus.RegisterNewChan(10) 47 | 48 | firstInitialReceived := false 49 | tlsCompleted := false 50 | pingTimer := time.NewTimer(0) 51 | var tlsPacket Packet 52 | 53 | go func() { 54 | defer a.Logger.Println("Agent terminated") 55 | defer close(a.closed) 56 | for { 57 | select { 58 | case <-a.sendInitial: 59 | a.Logger.Println("Sending first Initial packet") 60 | conn.SendPacket.Submit(PacketToSend{Packet: conn.GetInitialPacket(), EncryptionLevel: EncryptionLevelInitial}) 61 | case p := <-incPackets: 62 | switch p := p.(type) { 63 | case *VersionNegotiationPacket: 64 | err := conn.ProcessVersionNegotation(p) 65 | if err != nil { 66 | a.HandshakeStatus.Submit(HandshakeStatus{false, p, err}) 67 | return 68 | } 69 | close(conn.ConnectionRestart) 70 | case *RetryPacket: 71 | // TODO: Validate this, https://tools.ietf.org/html/draft-ietf-quic-tls-27#section-5.8 72 | if !a.IgnoreRetry && !a.receivedRetry { 73 | a.Logger.Println("A Retry packet was received, restarting the connection") 74 | a.receivedRetry = true 75 | conn.DestinationCID = p.Header().(*LongHeader).SourceCID 76 | a.retrySource = p.Header().(*LongHeader).SourceCID 77 | tlsTP, alpn := conn.TLSTPHandler, conn.ALPN 78 | conn.TransitionTo(QuicVersion, alpn) 79 | conn.TLSTPHandler = tlsTP 80 | conn.Token = p.RetryToken 81 | close(conn.ConnectionRestart) 82 | } 83 | case Framer: 84 | if p.Contains(ConnectionCloseType) || p.Contains(ApplicationCloseType) { 85 | a.Logger.Println("The connection was closed before the handshake completed") 86 | a.HandshakeStatus.Submit(HandshakeStatus{false, p, errors.New("the connection was closed before the handshake completed")}) 87 | return 88 | } 89 | if _, ok := p.(*InitialPacket); ok && !firstInitialReceived { 90 | firstInitialReceived = true 91 | conn.DestinationCID = p.Header().(*LongHeader).SourceCID 92 | a.Logger.Printf("Received first Initial packet from server, switching DCID to %s\n", hex.EncodeToString(conn.DestinationCID)) 93 | } 94 | if p.Contains(HandshakeDoneType) { 95 | a.HandshakeStatus.Submit(HandshakeStatus{true, tlsPacket, nil}) 96 | conn.IncomingPackets.Unregister(incPackets) 97 | if !a.DontDropKeys { 98 | conn.EncryptionLevels.Submit(DirectionalEncryptionLevel{EncryptionLevel: EncryptionLevelInitial, Available: false}) 99 | conn.EncryptionLevels.Submit(DirectionalEncryptionLevel{EncryptionLevel: EncryptionLevelHandshake, Available: false}) 100 | // TODO: Drop crypto contexts accordingly 101 | } 102 | } 103 | default: 104 | a.HandshakeStatus.Submit(HandshakeStatus{false, p.(Packet), errors.New("received incorrect packet type during handshake")}) 105 | } 106 | pingTimer.Reset(time.Duration(conn.SmoothedRTT + conn.RTTVar) * time.Microsecond) 107 | case p := <-outPackets: 108 | if !tlsCompleted || conn.Version >= 0xff000019 { 109 | break 110 | } 111 | switch p := p.(type) { 112 | case *HandshakePacket: 113 | for _, f := range p.GetAll(CryptoType) { 114 | cf := f.(*CryptoFrame) 115 | if cf.CryptoData[0] == 0x14 { // TLS Finished 116 | a.HandshakeStatus.Submit(HandshakeStatus{true, tlsPacket, nil}) 117 | conn.IncomingPackets.Unregister(incPackets) 118 | conn.OutgoingPackets.Unregister(outPackets) 119 | return 120 | } 121 | } 122 | } 123 | case i := <-tlsStatus: 124 | s := i.(TLSStatus) 125 | if s.Error != nil { 126 | if s.Completed { 127 | if !bytes.Equal(conn.TLSTPHandler.ReceivedParameters.OriginalDestinationConnectionId, conn.OriginalDestinationCID) { 128 | a.Logger.Println("The server included an invalid original_destination_connection_id") 129 | s.Completed = false 130 | s.Error = errors.New(fmt.Sprint("invalid original_destination_connection_id")) 131 | } else if a.receivedRetry { 132 | if !bytes.Equal(conn.TLSTPHandler.ReceivedParameters.RetrySourceConnectionId, a.retrySource) { 133 | a.Logger.Println("The server include an invalid retry_source_connection_id after sending a Retry") 134 | s.Completed = false 135 | s.Error = errors.New(fmt.Sprint("invalid retry_source_connection_id")) 136 | } 137 | } else { 138 | if conn.TLSTPHandler.ReceivedParameters.RetrySourceConnectionId != nil { 139 | a.Logger.Println("The server included a retry_source_connection_id but did not send a Retry") 140 | s.Completed = false 141 | s.Error = errors.New(fmt.Sprint("invalid retry_source_connection_id")) 142 | } 143 | } 144 | } 145 | a.HandshakeStatus.Submit(HandshakeStatus{s.Completed, s.Packet, s.Error}) 146 | } 147 | tlsCompleted = s.Completed 148 | tlsPacket = s.Packet 149 | case i := <-socketStatus: 150 | if strings.Contains(i.(error).Error(), "connection refused") { 151 | a.HandshakeStatus.Submit(HandshakeStatus{false, nil , i.(error)}) 152 | return 153 | } 154 | case <-pingTimer.C: 155 | if firstInitialReceived { 156 | conn.PreparePacket.Submit(EncryptionLevelBest) 157 | } 158 | case <-conn.ConnectionRestarted: 159 | incPackets = conn.IncomingPackets.RegisterNewChan(1000) 160 | outPackets = conn.OutgoingPackets.RegisterNewChan(1000) 161 | tlsStatus = a.TLSAgent.TLSStatus.RegisterNewChan(10) 162 | socketStatus = a.SocketAgent.SocketStatus.RegisterNewChan(10) 163 | conn.ConnectionRestarted = make(chan bool, 1) 164 | conn.SendPacket.Submit(PacketToSend{Packet: conn.GetInitialPacket(), EncryptionLevel: EncryptionLevelInitial}) 165 | case <-a.close: 166 | return 167 | } 168 | } 169 | }() 170 | 171 | status := a.HandshakeStatus.RegisterNewChan(1) 172 | 173 | go func() { 174 | for { 175 | select { 176 | case i := <-status: 177 | a.Logger.Printf("New status %s\n", i.(HandshakeStatus).String()) 178 | case <-a.close: 179 | return 180 | } 181 | } 182 | }() 183 | } 184 | 185 | func (a *HandshakeAgent) InitiateHandshake() { 186 | a.sendInitial <- true 187 | } 188 | -------------------------------------------------------------------------------- /agents/http09_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | ) 6 | 7 | type HTTPAgent interface { 8 | Agent 9 | SendRequest(path, method, authority string, headers map[string]string) chan HTTPResponse 10 | HTTPResponseReceived() Broadcaster 11 | } 12 | 13 | type HTTPResponse interface { 14 | StreamID() uint64 15 | Headers() []HTTPHeader 16 | Body() []byte 17 | } 18 | 19 | type HTTP09Response struct { 20 | streamID uint64 21 | body []byte 22 | } 23 | 24 | func (r *HTTP09Response) StreamID() uint64 { return r.streamID } 25 | func (r *HTTP09Response) Headers() []HTTPHeader { return nil } 26 | func (r *HTTP09Response) Body() []byte { return r.body } 27 | 28 | type HTTP09Agent struct { 29 | BaseAgent 30 | conn *Connection 31 | nextRequestStream uint64 32 | httpResponseReceived Broadcaster 33 | } 34 | 35 | func (a *HTTP09Agent) Run(conn *Connection) { 36 | a.Init("HTTP09Agent", conn.OriginalDestinationCID) 37 | a.httpResponseReceived = NewBroadcaster(1000) 38 | a.conn = conn 39 | 40 | go func() { 41 | defer a.Logger.Println("Agent terminated") 42 | defer close(a.closed) 43 | <-a.close 44 | }() 45 | } 46 | 47 | func (a *HTTP09Agent) SendRequest(path, method, authority string, headers map[string]string) chan HTTPResponse { 48 | streamID := a.nextRequestStream 49 | a.conn.SendHTTP09GETRequest(path, streamID) 50 | responseStream := a.conn.Streams.Get(a.nextRequestStream).ReadChan.RegisterNewChan(1000) 51 | responseChan := make(chan HTTPResponse, 1) 52 | 53 | go func() { 54 | response := HTTP09Response{streamID: streamID} 55 | for i := range responseStream { 56 | data := i.([]byte) 57 | response.body = append(response.body, data...) 58 | } 59 | a.Logger.Printf("A %d-byte long response on stream %d is complete\n", len(response.body), response.streamID) 60 | responseChan <- &response 61 | a.httpResponseReceived.Submit(response) 62 | }() 63 | 64 | a.nextRequestStream += 4 65 | return responseChan 66 | } 67 | 68 | func (a *HTTP09Agent) HTTPResponseReceived() Broadcaster { return a.httpResponseReceived } 69 | -------------------------------------------------------------------------------- /agents/parse_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | "bytes" 5 | . "github.com/QUIC-Tracker/quic-tracker" 6 | "unsafe" 7 | ) 8 | 9 | // The ParsingAgent is responsible for decrypting and parsing the payloads received in UDP datagrams. It also decrypts 10 | // the packet number if needed. Payloads that require a decryption level that is not available are put back into the 11 | // UnprocessedPayloads queue. 12 | type ParsingAgent struct { 13 | BaseAgent 14 | conn *Connection 15 | } 16 | 17 | func (a *ParsingAgent) Run(conn *Connection) { 18 | a.conn = conn 19 | a.Init("ParsingAgent", conn.OriginalDestinationCID) 20 | 21 | incomingPayloads := a.conn.IncomingPayloads.RegisterNewChan(1000) 22 | 23 | go func() { 24 | defer a.Logger.Println("Agent terminated") 25 | defer close(a.closed) 26 | for { 27 | packetSelect: 28 | select { 29 | case i := <-incomingPayloads: 30 | ic := i.(IncomingPayload) 31 | var off int 32 | for off < len(ic.Payload) { 33 | ciphertext := ic.Payload[off:] 34 | 35 | if ciphertext[0] & 0x80 == 0x80 && bytes.Equal(ciphertext[1:5], []byte{0, 0, 0, 0}) { 36 | ctx := ic.PacketContext 37 | ctx.PacketSize = uint16(len(ciphertext)) 38 | packet := ReadVersionNegotationPacket(bytes.NewReader(ciphertext)) 39 | packet.SetReceiveContext(ctx) 40 | a.SaveCleartextPacket(ciphertext, packet.Pointer()) 41 | a.conn.IncomingPackets.Submit(packet) 42 | break packetSelect 43 | } 44 | 45 | header := ReadHeader(bytes.NewReader(ciphertext), a.conn) 46 | cryptoState := a.conn.CryptoState(header.EncryptionLevel()) 47 | 48 | switch header.PacketType() { 49 | case Initial, Handshake, ZeroRTTProtected, ShortHeaderPacket: // Decrypt PN 50 | if cryptoState != nil && cryptoState.HeaderRead != nil && cryptoState.Read != nil { 51 | a.Logger.Printf("Decrypting packet number of %s packet of length %d bytes", header.PacketType().String(), len(ciphertext)) 52 | 53 | firstByteMask := byte(0x1F) 54 | if ciphertext[0] & 0x80 == 0x80 { 55 | firstByteMask = 0x0F 56 | } 57 | 58 | sample, pnOffset := GetPacketSample(header, ciphertext) 59 | if sample == nil { 60 | a.Logger.Printf("Packet is too short to for header protection, dropping it\n") 61 | break packetSelect 62 | } 63 | mask := cryptoState.HeaderRead.Encrypt(sample, make([]byte, 5, 5)) 64 | ciphertext[0] ^= mask[0] & firstByteMask 65 | 66 | pnLength := int(ciphertext[0] & 0x3) + 1 67 | 68 | for i := 0; i < pnLength; i++ { 69 | ciphertext[pnOffset+i] ^= mask[1+i] 70 | } 71 | header = ReadHeader(bytes.NewReader(ciphertext), a.conn) // Update PN 72 | } else { 73 | a.Logger.Printf("Crypto state for %s packet of length %d bytes is not ready, putting it back in waiting buffer\n", header.PacketType().String(), len(ciphertext)) 74 | ic.Payload = ciphertext 75 | a.conn.UnprocessedPayloads.Submit(UnprocessedPayload{ic, header.EncryptionLevel()}) 76 | break packetSelect 77 | } 78 | } 79 | 80 | a.Logger.Printf("Successfully decrypted header {type=%s, number=%d}\n", header.PacketType().String(), header.PacketNumber()) 81 | 82 | hLen := header.HeaderLength() 83 | var packet Packet 84 | var cleartext []byte 85 | var consumed int 86 | switch header.PacketType() { 87 | case Handshake, Initial: 88 | lHeader := header.(*LongHeader) 89 | pLen := int(lHeader.Length.Value) - header.TruncatedPN().Length 90 | 91 | if hLen+pLen > len(ciphertext) { 92 | a.Logger.Printf("Payload length %d is past the %d received bytes, has PN decryption failed ? Aborting", hLen+pLen, len(ciphertext)) 93 | break packetSelect 94 | } 95 | 96 | payload := cryptoState.Read.Decrypt(ciphertext[hLen:hLen+pLen], uint64(header.PacketNumber()), ciphertext[:hLen]) 97 | if payload == nil { 98 | a.Logger.Printf("Could not decrypt packet {type=%s, number=%d}\n", header.PacketType().String(), header.PacketNumber()) 99 | break packetSelect 100 | } 101 | 102 | cleartext = append(append(cleartext, ciphertext[:hLen]...), payload...) 103 | 104 | if lHeader.PacketType() == Initial { 105 | packet = ReadInitialPacket(bytes.NewReader(cleartext), a.conn) 106 | } else { 107 | packet = ReadHandshakePacket(bytes.NewReader(cleartext), a.conn) 108 | } 109 | 110 | consumed = hLen + pLen 111 | case ShortHeaderPacket: // Packets with a short header always include a 1-RTT protected payload. 112 | payload := cryptoState.Read.Decrypt(ciphertext[hLen:], uint64(header.PacketNumber()), ciphertext[:hLen]) 113 | if payload == nil { 114 | a.Logger.Printf("Could not decrypt packet {type=%s, number=%d}\n", header.PacketType().String(), header.PacketNumber()) 115 | statelessResetToken := ciphertext[len(ciphertext)-16:] 116 | if bytes.Equal(statelessResetToken, conn.TLSTPHandler.ReceivedParameters.StatelessResetToken) { 117 | a.Logger.Println("Received a Stateless Reset packet") 118 | cleartext = ciphertext 119 | packet = ReadStatelessResetPacket(bytes.NewReader(ciphertext)) 120 | } else { 121 | break packetSelect 122 | } 123 | } else { 124 | cleartext = append(append(cleartext, ic.Payload[off:off+hLen]...), payload...) 125 | packet = ReadProtectedPacket(bytes.NewReader(cleartext), a.conn) 126 | } 127 | consumed = len(ic.Payload) 128 | case Retry: 129 | cleartext = ciphertext 130 | packet = ReadRetryPacket(bytes.NewReader(cleartext), a.conn) 131 | consumed = len(ic.Payload) 132 | default: 133 | a.Logger.Printf("Packet type is unknown, the first byte is %x\n", ciphertext[0]) 134 | break packetSelect 135 | } 136 | 137 | a.Logger.Printf("Successfully parsed packet {type=%s, number=%d, length=%d}\n", header.PacketType().String(), header.PacketNumber(), len(cleartext)) 138 | 139 | switch packet.(type) { 140 | case Framer: 141 | if packet.Header().PacketNumber() > conn.LargestPNsReceived[packet.PNSpace()] { 142 | conn.LargestPNsReceived[packet.PNSpace()] = packet.Header().PacketNumber() 143 | } 144 | } 145 | 146 | off += consumed 147 | 148 | ctx := ic.PacketContext 149 | ctx.PacketSize = uint16(consumed) 150 | packet.SetReceiveContext(ctx) 151 | a.conn.IncomingPackets.Submit(packet) 152 | a.SaveCleartextPacket(cleartext, packet.Pointer()) 153 | 154 | } 155 | case <-a.close: 156 | return 157 | } 158 | } 159 | }() 160 | } 161 | 162 | func (a *ParsingAgent) SaveCleartextPacket(cleartext []byte, unique unsafe.Pointer) { 163 | if a.conn.ReceivedPacketHandler != nil { 164 | a.conn.ReceivedPacketHandler(cleartext, unique) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /agents/qlog_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/qlog" 6 | "github.com/QUIC-Tracker/quic-tracker/qlog/qt2qlog" 7 | "time" 8 | ) 9 | 10 | type QLogAgent struct { 11 | BaseAgent 12 | } 13 | 14 | func (a *QLogAgent) Run(conn *Connection) { 15 | a.Init("QLogAgent", conn.OriginalDestinationCID) 16 | 17 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 18 | outgoingPackets := conn.OutgoingPackets.RegisterNewChan(1000) 19 | 20 | go func() { 21 | defer a.Logger.Println("Agent terminated") 22 | defer close(a.closed) 23 | for { 24 | select { 25 | case i := <-incomingPackets: 26 | p := i.(Packet) 27 | jp := qt2qlog.ConvertPacket(p) 28 | jp.Header.PacketSize = int(p.ReceiveContext().PacketSize) 29 | e := conn.QLogTrace.NewEvent(qlog.Categories.Transport.Category, qlog.Categories.Transport.PacketReceived, jp) 30 | if !p.ReceiveContext().WasBuffered { 31 | e.RelativeTime = uint64(p.ReceiveContext().Timestamp.Sub(conn.QLogTrace.ReferenceTime) / qlog.TimeUnits) 32 | } else { 33 | e.RelativeTime = uint64(time.Now().Sub(conn.QLogTrace.ReferenceTime) / qlog.TimeUnits) 34 | } 35 | conn.QLogEvents <- e 36 | case i := <-outgoingPackets: 37 | p := i.(Packet) 38 | jp := qt2qlog.ConvertPacket(p) 39 | jp.Header.PacketSize = int(i.(Packet).SendContext().PacketSize) 40 | e := conn.QLogTrace.NewEvent(qlog.Categories.Transport.Category, qlog.Categories.Transport.PacketSent, jp) 41 | e.RelativeTime = uint64(p.SendContext().Timestamp.Sub(conn.QLogTrace.ReferenceTime) / qlog.TimeUnits) 42 | conn.QLogEvents <- e 43 | case <-a.close: 44 | return 45 | } 46 | } 47 | }() 48 | 49 | } 50 | -------------------------------------------------------------------------------- /agents/qpack_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | "bytes" 5 | . "github.com/QUIC-Tracker/quic-tracker" 6 | "github.com/mpiraux/ls-qpack-go" 7 | "math" 8 | ) 9 | 10 | type HTTPHeader struct { 11 | Name, Value string 12 | } 13 | 14 | type DecodedHeaders struct { 15 | StreamID uint64 16 | Headers []HTTPHeader 17 | } 18 | 19 | type EncodedHeaders struct { 20 | StreamID uint64 21 | Headers []byte 22 | } 23 | 24 | // The QPACK Agent is TODO 25 | type QPACKAgent struct { 26 | BaseAgent 27 | EncoderStreamID uint64 28 | DecoderStreamID uint64 29 | DisableStreams bool 30 | DecodeHeaders chan EncodedHeaders 31 | DecodedHeaders Broadcaster //type: DecodedHeaders 32 | EncodeHeaders chan DecodedHeaders 33 | EncodedHeaders Broadcaster //type: EncodedHeaders 34 | encoder *ls_qpack_go.QPackEncoder 35 | decoder *ls_qpack_go.QPackDecoder 36 | } 37 | 38 | const ( 39 | QPACKNoStream uint64 = math.MaxUint64 40 | QPACKEncoderStreamValue = 0x2 41 | QPACKDecoderStreamValue = 0x3 42 | ) 43 | 44 | func (a *QPACKAgent) Run(conn *Connection) { 45 | a.Init("QPACKAgent", conn.OriginalDestinationCID) 46 | a.DecodedHeaders = NewBroadcaster(1000) 47 | a.EncodedHeaders = NewBroadcaster(1000) 48 | a.DecodeHeaders = make(chan EncodedHeaders, 1000) 49 | a.EncodeHeaders = make(chan DecodedHeaders, 1000) 50 | 51 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 52 | 53 | dynamicTableSize := uint(1024) 54 | if a.DisableStreams { 55 | dynamicTableSize = 0 56 | } 57 | 58 | a.encoder = ls_qpack_go.NewQPackEncoder(false) 59 | a.decoder = ls_qpack_go.NewQPackDecoder(dynamicTableSize, 100) 60 | 61 | peerEncoderStreamId := QPACKNoStream 62 | peerDecoderStreamId := QPACKNoStream 63 | peerEncoderStream := make(chan interface{}, 1000) 64 | peerDecoderStream := make(chan interface{}, 1000) 65 | 66 | checkForDecodedHeaders := func() { 67 | for _, dhb := range a.decoder.DecodedHeaderBlocks() { 68 | if len(dhb.Headers()) > 0 { 69 | headers := make([]HTTPHeader, len(dhb.Headers())) 70 | for i, h := range dhb.Headers() { 71 | headers[i] = HTTPHeader{h.Name, h.Value} 72 | } 73 | a.DecodedHeaders.Submit(DecodedHeaders{dhb.StreamID, headers}) 74 | if len(dhb.DecoderStream()) > 0 { 75 | conn.Streams.Send(a.DecoderStreamID, dhb.DecoderStream(), false) 76 | } 77 | a.Logger.Printf("Submitted %d decoded headers on stream %d\n", len(headers), dhb.StreamID) 78 | } 79 | } 80 | } 81 | 82 | go func() { 83 | defer a.Logger.Println("Agent terminated") 84 | defer close(a.closed) 85 | for { 86 | select { 87 | case i := <-incomingPackets: 88 | p := i.(Packet) 89 | if p.PNSpace() == PNSpaceAppData { 90 | for _, f := range p.(Framer).GetAll(StreamType) { 91 | s := f.(*StreamFrame) 92 | if s.Offset < 4 && IsUni(s.StreamId) && s.StreamId != peerEncoderStreamId && s.StreamId != peerDecoderStreamId { 93 | stream := conn.Streams.Get(s.StreamId) 94 | qpackStreamType, err := ReadVarInt(bytes.NewReader(stream.ReadData)) 95 | if err != nil { 96 | a.Logger.Printf("Error when parsing stream type: %s\n", err.Error()) 97 | } else if qpackStreamType.Value == QPACKEncoderStreamValue { 98 | if peerEncoderStreamId != QPACKNoStream { 99 | a.Logger.Printf("Peer attempted to open another encoder stream on stream %d\n", s.StreamId) 100 | continue 101 | } 102 | peerEncoderStreamId = s.StreamId 103 | 104 | a.Logger.Printf("Peer opened encoder stream on stream %d\n", s.StreamId) 105 | if len(stream.ReadData) > qpackStreamType.Length { 106 | peerEncoderStream <- stream.ReadData[qpackStreamType.Length:] 107 | } 108 | conn.Streams.Get(s.StreamId).ReadChan.Register(peerEncoderStream) 109 | } else if qpackStreamType.Value == QPACKDecoderStreamValue { 110 | if peerDecoderStreamId != QPACKNoStream { 111 | a.Logger.Printf("Peer attempted to open another decoder stream on stream %d\n", s.StreamId) 112 | continue 113 | } 114 | peerDecoderStreamId = s.StreamId 115 | stream := conn.Streams.Get(peerEncoderStreamId) 116 | a.Logger.Printf("Peer opened decoder stream on stream %d\n", s.StreamId) 117 | if len(stream.ReadData) > qpackStreamType.Length { 118 | peerDecoderStream <- stream.ReadData[qpackStreamType.Length:] 119 | } 120 | conn.Streams.Get(s.StreamId).ReadChan.Register(peerDecoderStream) 121 | } else { 122 | a.Logger.Printf("Unknown stream type %d, ignoring it\n", qpackStreamType.Value) 123 | } 124 | } 125 | } 126 | } 127 | case i := <-peerEncoderStream: 128 | data := i.([]byte) 129 | if a.decoder.EncoderIn(data) { 130 | a.Logger.Printf("Decoder failed on encoder stream input\n") 131 | return 132 | } 133 | a.Logger.Printf("Fed %d bytes from the encoder stream to the decoder\n", len(data)) 134 | checkForDecodedHeaders() 135 | case i := <-peerDecoderStream: 136 | data := i.([]byte) 137 | if a.encoder.DecoderIn(data) { 138 | a.Logger.Printf("Encoder failed on decoder stream input\n") 139 | return 140 | } 141 | a.Logger.Printf("Fed %d bytes from the decoder stream to the encoder\n", len(data)) 142 | checkForDecodedHeaders() 143 | case e := <-a.EncodeHeaders: 144 | if a.encoder.StartHeaderBlock(e.StreamID, /*TODO*/ 0) { 145 | a.Logger.Printf("Encoder failed to start header block\n") 146 | return 147 | } 148 | var encStream []byte 149 | var encHeaders []byte 150 | for _, h := range e.Headers { 151 | es, eh := a.encoder.Encode(h.Name, h.Value) 152 | encStream = append(encStream, es...) 153 | encHeaders = append(encHeaders, eh...) 154 | } 155 | hdp := a.encoder.EndHeaderBlock() 156 | payload := append(hdp, encHeaders...) 157 | a.EncodedHeaders.Submit(EncodedHeaders{e.StreamID, payload}) 158 | a.Logger.Printf("Encoded %d headers in %d bytes, with %d additional bytes on the encoder stream\n", len(e.Headers), len(payload), len(encStream)) 159 | if len(encStream) > 0 { 160 | conn.Streams.Send(a.EncoderStreamID, encStream, false) 161 | a.Logger.Printf("Enqueued %d bytes on the encoder stream\n", len(encStream)) 162 | } 163 | case d := <-a.DecodeHeaders: 164 | ret := a.decoder.HeaderIn(d.Headers, d.StreamID) 165 | if ret < len(d.Headers) { 166 | a.Logger.Printf("Decoder is blocked and waiting for encoder input before decoding the %d bytes remaining on stream %d\n", len(d.Headers) - ret, d.StreamID) 167 | } 168 | checkForDecodedHeaders() 169 | case <-a.close: 170 | return 171 | } 172 | } 173 | }() 174 | 175 | if !a.DisableStreams { 176 | conn.Streams.Send(a.EncoderStreamID, []byte{QPACKEncoderStreamValue}, false) 177 | conn.Streams.Send(a.DecoderStreamID, []byte{QPACKDecoderStreamValue}, false) 178 | } 179 | } 180 | func (a *QPACKAgent) InitEncoder(headerTableSize uint, dynamicTablesize uint, maxRiskedStreams uint, opts uint32) { 181 | a.encoder.Init(headerTableSize, dynamicTablesize, maxRiskedStreams, opts) 182 | a.Logger.Printf("Encoder initialized with HTS=%d, DTS=%d, MRS=%d and opts=%d\n", headerTableSize, dynamicTablesize, maxRiskedStreams, opts) 183 | } -------------------------------------------------------------------------------- /agents/rtt_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/qlog" 6 | "time" 7 | "math" 8 | ) 9 | 10 | type RTTAgent struct { 11 | BaseAgent 12 | conn *Connection 13 | MinRTT uint64 14 | LatestRTT uint64 15 | SmoothedRTT uint64 16 | RTTVar uint64 17 | MaxAckDelay uint64 18 | SentPackets map[PNSpace]map[PacketNumber]SentPacket 19 | LargestSentPackets map[PNSpace]PacketNumber 20 | } 21 | 22 | type SentPacket struct { 23 | sent time.Time 24 | ackOnly bool 25 | size int 26 | } 27 | 28 | func (a *RTTAgent) Run(conn *Connection) { 29 | a.Init("RTTAgent", conn.OriginalDestinationCID) 30 | a.conn = conn 31 | a.MinRTT = math.MaxUint64 32 | 33 | a.SentPackets = map[PNSpace]map[PacketNumber]SentPacket{ 34 | PNSpaceInitial: make(map[PacketNumber]SentPacket), 35 | PNSpaceHandshake: make(map[PacketNumber]SentPacket), 36 | PNSpaceAppData: make(map[PacketNumber]SentPacket), 37 | } 38 | 39 | a.LargestSentPackets = make(map[PNSpace]PacketNumber) 40 | 41 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 42 | outgoingPackets := conn.OutgoingPackets.RegisterNewChan(1000) 43 | 44 | go func() { // TODO: Support ACK_ECN 45 | defer a.Logger.Println("Agent terminated") 46 | defer close(a.closed) 47 | 48 | for { 49 | select { 50 | case i := <-outgoingPackets: 51 | switch p := i.(type) { 52 | case Framer: 53 | packetNumber := p.Header().PacketNumber() 54 | if packetNumber > PacketNumber(a.LargestSentPackets[p.PNSpace()]) { 55 | a.LargestSentPackets[p.PNSpace()] = packetNumber 56 | } 57 | a.SentPackets[p.PNSpace()][packetNumber] = SentPacket{time.Now(), p.OnlyContains(AckType), len(p.Encode(p.EncodePayload()))} 58 | } 59 | case i := <-incomingPackets: 60 | switch p := i.(type) { 61 | case Framer: 62 | for _, f := range p.GetAll(AckType) { 63 | ack := f.(*AckFrame) 64 | 65 | if sp, ok := a.SentPackets[p.PNSpace()][ack.LargestAcknowledged]; ok { 66 | var ackDelayExponent uint64 67 | if conn.TLSTPHandler.ReceivedParameters != nil { 68 | ackDelayExponent = conn.TLSTPHandler.ReceivedParameters.AckDelayExponent 69 | } 70 | if ackDelayExponent == 0 { 71 | ackDelayExponent = 3 72 | } 73 | a.LatestRTT = uint64(p.ReceiveContext().Timestamp.Sub(sp.sent).Nanoseconds() / int64(time.Microsecond)) 74 | a.UpdateRTT(ack.AckDelay * (2 << (ackDelayExponent - 1)), sp.ackOnly) 75 | } 76 | } 77 | 78 | } 79 | case <-a.close: 80 | return 81 | } 82 | } 83 | }() 84 | } 85 | 86 | func (a *RTTAgent) UpdateRTT(ackDelay uint64, ackOnly bool) { // TODO: https://tools.ietf.org/html/draft-ietf-quic-recovery-13#section-3.5.5 87 | if a.LatestRTT < a.MinRTT { 88 | a.MinRTT = a.LatestRTT 89 | } 90 | 91 | if a.LatestRTT - a.MinRTT > ackDelay { 92 | a.LatestRTT -= ackDelay 93 | } 94 | 95 | if !ackOnly && ackDelay > a.MaxAckDelay { 96 | a.MaxAckDelay = ackDelay 97 | } 98 | 99 | if a.SmoothedRTT == 0 { 100 | a.SmoothedRTT = a.LatestRTT 101 | a.RTTVar = a.LatestRTT / 2 102 | } else { 103 | var RTTVarSample uint64 104 | if a.SmoothedRTT < a.LatestRTT { 105 | RTTVarSample = -(a.SmoothedRTT - a.LatestRTT) 106 | } else { 107 | RTTVarSample = a.SmoothedRTT - a.LatestRTT 108 | } 109 | a.RTTVar = uint64(0.75 * float64(a.RTTVar) + 0.25 * float64(RTTVarSample)) 110 | a.SmoothedRTT = uint64(0.875 * float64(a.SmoothedRTT) + 0.125 * float64(a.LatestRTT)) 111 | } 112 | 113 | a.conn.MinRTT = a.MinRTT 114 | a.conn.SmoothedRTT = a.SmoothedRTT 115 | a.conn.RTTVar = a.RTTVar 116 | 117 | a.conn.QLogEvents <- a.conn.QLogTrace.NewEvent(qlog.Categories.Recovery.Category, qlog.Categories.Recovery.MetricsUpdated, qlog.MetricUpdate{ 118 | LatestRTT: a.LatestRTT / 1000, 119 | MaxAckDelay: a.MaxAckDelay / 1000, 120 | SmoothedRTT: a.conn.SmoothedRTT / 1000, 121 | RTTVariance: a.conn.RTTVar / 1000, 122 | MinRTT: a.conn.MinRTT / 1000, 123 | }) 124 | 125 | a.Logger.Printf("LatestRTT = %d, MinRTT = %d, SmoothedRTT = %d, RTTVar = %d", a.LatestRTT, a.MinRTT, a.SmoothedRTT, a.RTTVar) 126 | } 127 | -------------------------------------------------------------------------------- /agents/send_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | "time" 6 | ) 7 | 8 | // The SendingAgent is responsible of bundling frames for sending from other agents into packets. If the frames queued 9 | // for a given encryption level are smaller than a given MTU, it will wait a window of 5ms before sending them in the hope 10 | // that more will be queued. Frames that require an unavailable encryption level are queued until it is made available. 11 | // It also merge the ACK frames inside a given packet before sending. 12 | type SendingAgent struct { 13 | BaseAgent 14 | MTU uint16 15 | FrameProducer []FrameProducer 16 | DontCoalesceZeroRTT bool 17 | KeepDroppedEncryptionLevels bool 18 | } 19 | 20 | func (a *SendingAgent) Run(conn *Connection) { 21 | a.Init("SendingAgent", conn.OriginalDestinationCID) 22 | 23 | preparePacket := conn.PreparePacket.RegisterNewChan(100) 24 | sendPacket := conn.SendPacket.RegisterNewChan(100) 25 | elChan := conn.EncryptionLevels.RegisterNewChan(10) 26 | 27 | encryptionLevels := map[DirectionalEncryptionLevel]bool{ 28 | {EncryptionLevel: EncryptionLevelInitial, Available: true}: true, 29 | {EncryptionLevel: EncryptionLevelNone, Available: true}: true, 30 | {EncryptionLevel: EncryptionLevel0RTT, Available: false}: true, 31 | {EncryptionLevel: EncryptionLevelHandshake, Available: false}: true, 32 | {EncryptionLevel: EncryptionLevel1RTT, Available: false}: true, 33 | } 34 | bestEncryptionLevels := map[EncryptionLevel]EncryptionLevel{ 35 | EncryptionLevelBest: EncryptionLevelInitial, 36 | } 37 | timers := make(map[EncryptionLevel]*time.Timer) 38 | timersArmed := make(map[EncryptionLevel]bool) 39 | for dEL := range encryptionLevels { 40 | el := dEL.EncryptionLevel 41 | if dEL.EncryptionLevel != EncryptionLevelNone { 42 | timers[el] = time.NewTimer(0) 43 | timersArmed[el] = false 44 | if !timers[el].Stop() { 45 | <-timers[el].C 46 | } 47 | } 48 | } 49 | 50 | initialSent := false 51 | 52 | fillPacket := func(packet Framer, level EncryptionLevel) Framer { 53 | spaceLeft := int(a.MTU) - packet.Header().HeaderLength() - conn.CryptoState(level).Write.Overhead() 54 | 55 | addFrame: 56 | for i, fp := range a.FrameProducer { 57 | levels := []EncryptionLevel{level} 58 | for eL, bEL := range bestEncryptionLevels { 59 | if bEL == level { 60 | levels = append(levels, eL) 61 | } 62 | } 63 | for _, l := range levels { 64 | if spaceLeft < 1 { 65 | break addFrame 66 | } 67 | frames, more := fp.RequestFrames(spaceLeft, l, packet.Header().PacketNumber()) 68 | if !more { 69 | a.FrameProducer[i] = nil 70 | a.FrameProducer = append(a.FrameProducer[:i], a.FrameProducer[i+1:]...) 71 | break 72 | } 73 | for _, f := range frames { 74 | packet.AddFrame(f) 75 | spaceLeft -= int(f.FrameLength()) 76 | } 77 | } 78 | } 79 | 80 | if len(packet.GetFrames()) == 0 { 81 | a.Logger.Printf("Preparing a packet for encryption level %s resulted in an empty packet, discarding\n", level.String()) 82 | conn.PacketNumberLock.Lock() 83 | conn.PacketNumber[packet.PNSpace()]-- // Avoids PN skipping 84 | conn.PacketNumberLock.Unlock() 85 | return nil 86 | } 87 | return packet 88 | } 89 | 90 | go func() { 91 | defer a.Logger.Println("Agent terminated") 92 | defer close(a.closed) 93 | for { 94 | select { 95 | case i := <-preparePacket: 96 | eL := i.(EncryptionLevel) 97 | 98 | if eL == EncryptionLevelBest || eL == EncryptionLevelBestAppData { 99 | nEL := chooseBestEncryptionLevel(encryptionLevels, eL == EncryptionLevelBestAppData) 100 | bestEncryptionLevels[eL] = nEL 101 | a.Logger.Printf("Chose %s as new encryption level for %s\n", nEL, eL) 102 | eL = nEL 103 | } 104 | if encryptionLevels[DirectionalEncryptionLevel{EncryptionLevel: eL, Read: false, Available: true}] && !timersArmed[eL] { 105 | timers[eL].Reset(2 * time.Millisecond) 106 | timersArmed[eL] = true 107 | } 108 | case <-timers[EncryptionLevelInitial].C: 109 | p := fillPacket(NewInitialPacket(conn), EncryptionLevelInitial) 110 | if p != nil { 111 | var initialLength int 112 | if conn.UseIPv6 { 113 | initialLength = MinimumInitialLengthv6 114 | } else { 115 | initialLength = MinimumInitialLength 116 | } 117 | initialLength -= conn.CryptoState(EncryptionLevelInitial).Write.Overhead() 118 | p.PadTo(initialLength) 119 | initialSent = true 120 | conn.DoSendPacket(p, EncryptionLevelInitial) 121 | } 122 | timersArmed[EncryptionLevelInitial] = false 123 | case <-timers[EncryptionLevel0RTT].C: 124 | if initialSent { 125 | p := fillPacket(NewZeroRTTProtectedPacket(conn), EncryptionLevel0RTT) 126 | if p != nil { 127 | conn.DoSendPacket(p, EncryptionLevel0RTT) 128 | } 129 | } 130 | timersArmed[EncryptionLevel0RTT] = false 131 | case <-timers[EncryptionLevelHandshake].C: 132 | p := fillPacket(NewHandshakePacket(conn), EncryptionLevelHandshake) 133 | if p != nil { 134 | conn.DoSendPacket(p, EncryptionLevelHandshake) 135 | } 136 | timersArmed[EncryptionLevelHandshake] = false 137 | case <-timers[EncryptionLevel1RTT].C: 138 | p := fillPacket(NewProtectedPacket(conn), EncryptionLevel1RTT) 139 | if p != nil { 140 | conn.DoSendPacket(p, EncryptionLevel1RTT) 141 | } 142 | timersArmed[EncryptionLevel1RTT] = false 143 | case i := <-elChan: 144 | dEL := i.(DirectionalEncryptionLevel) 145 | if dEL.Read { 146 | continue 147 | } 148 | eL := dEL.EncryptionLevel 149 | t := timers[eL] 150 | if !dEL.Available && !a.KeepDroppedEncryptionLevels { 151 | a.Logger.Println("Dropping encryption level", eL.String()) 152 | encryptionLevels[dEL] = true 153 | t.Stop() 154 | } else if dEL.Available { 155 | encryptionLevels[dEL] = true 156 | dEL.Available = false 157 | delete(encryptionLevels, dEL) 158 | bestEncryptionLevels[EncryptionLevelBest] = chooseBestEncryptionLevel(encryptionLevels, false) 159 | bestEncryptionLevels[EncryptionLevelBestAppData] = chooseBestEncryptionLevel(encryptionLevels, true) 160 | t.Reset(2 * time.Millisecond) 161 | } 162 | case i := <-sendPacket: 163 | p := i.(PacketToSend) 164 | if p.EncryptionLevel == EncryptionLevelInitial && p.Packet.Header().PacketType() == Initial { 165 | initial := p.Packet.(*InitialPacket) 166 | if !a.DontCoalesceZeroRTT && bestEncryptionLevels[EncryptionLevelBestAppData] == EncryptionLevel0RTT { 167 | // Try to prepare a 0-RTT packet and squeeze it after the Initial 168 | zp := NewZeroRTTProtectedPacket(conn) 169 | fillPacket(zp, EncryptionLevel0RTT) 170 | if len(zp.GetFrames()) > 0 { 171 | zpBytes := conn.EncodeAndEncrypt(zp, EncryptionLevel0RTT) 172 | initialFrames := initial.GetFrames() 173 | initialLength := len(conn.EncodeAndEncrypt(initial, EncryptionLevelInitial)) 174 | initial.Frames = nil 175 | for _, f := range initialFrames { 176 | if f.FrameType() != PaddingFrameType { 177 | initial.Frames = append(initial.Frames, f) 178 | } 179 | } 180 | initial.PadTo(initialLength - len(zpBytes)) 181 | coalescedPackets := append(conn.EncodeAndEncrypt(initial, EncryptionLevelInitial), zpBytes...) 182 | conn.UdpConnection.Write(coalescedPackets) 183 | conn.PacketWasSent(initial) 184 | conn.PacketWasSent(zp) 185 | continue 186 | } 187 | } 188 | var initialLength int 189 | if conn.UseIPv6 { 190 | initialLength = MinimumInitialLengthv6 191 | } else { 192 | initialLength = MinimumInitialLength 193 | } 194 | initialLength -= conn.CryptoState(EncryptionLevelInitial).Write.Overhead() 195 | initial.PadTo(initialLength) 196 | initialSent = true 197 | } 198 | conn.DoSendPacket(p.Packet, p.EncryptionLevel) 199 | case <-a.close: 200 | return 201 | } 202 | } 203 | }() 204 | } 205 | 206 | var elOrder = []EncryptionLevel{EncryptionLevel1RTT, EncryptionLevelHandshake, EncryptionLevelInitial} 207 | var elAppDataOrder = []EncryptionLevel{EncryptionLevel1RTT, EncryptionLevel0RTT} 208 | 209 | func chooseBestEncryptionLevel(eLs map[DirectionalEncryptionLevel]bool, restrictAppData bool) EncryptionLevel { 210 | order := elOrder 211 | if restrictAppData { 212 | order = elAppDataOrder 213 | } 214 | for _, eL := range order { 215 | if eLs[DirectionalEncryptionLevel{EncryptionLevel: eL, Available: true}] { 216 | return eL 217 | } 218 | } 219 | if restrictAppData { 220 | return EncryptionLevel1RTT 221 | } 222 | return order[len(order)-1] 223 | } 224 | -------------------------------------------------------------------------------- /agents/socket_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | "errors" 5 | . "github.com/QUIC-Tracker/quic-tracker" 6 | "github.com/QUIC-Tracker/quic-tracker/compat" 7 | "syscall" 8 | "time" 9 | "unsafe" 10 | ) 11 | 12 | // The SocketAgent is responsible for receiving the UDP payloads off the socket and putting them in the decryption queue. 13 | // If configured using ConfigureECN(), it will also mark the packet as with ECN(0) and report the ECN status of 14 | // the corresponding IP packet received. 15 | type SocketAgent struct { 16 | BaseAgent 17 | conn *Connection 18 | ecn bool 19 | TotalDataReceived int 20 | DatagramsReceived int 21 | SocketStatus Broadcaster //type: err 22 | } 23 | 24 | func (a *SocketAgent) Run(conn *Connection) { 25 | a.Init("SocketAgent", conn.OriginalDestinationCID) 26 | a.conn = conn 27 | a.SocketStatus = NewBroadcaster(10) 28 | recChan := make(chan IncomingPayload) 29 | 30 | go func() { 31 | for { 32 | recBuf := make([]byte, MaxTheoreticUDPPayloadSize) 33 | oob := make([]byte, 128) // Find a reasonable upper-bound 34 | i, oobn, _, addr, err := conn.UdpConnection.ReadMsgUDP(recBuf, oob) 35 | 36 | if err != nil { 37 | a.Logger.Println("Closing UDP socket because of error", err.Error()) 38 | select { 39 | case <-recChan: 40 | return 41 | default: 42 | } 43 | close(recChan) 44 | a.SocketStatus.Submit(err) 45 | break 46 | } 47 | 48 | sm := IncomingPayload{} 49 | sm.Timestamp = time.Now() 50 | sm.Payload = make([]byte, i) 51 | copy(sm.Payload, recBuf[:i]) 52 | sm.RemoteAddr = addr 53 | sm.DatagramSize = uint16(len(sm.Payload)) 54 | 55 | if a.ecn { 56 | ecn, err := findECNValue(oob[:oobn]) 57 | if err != nil { 58 | a.Logger.Println(err.Error()) 59 | } 60 | ecn = ecn & 0x03 61 | a.Logger.Printf("Read ECN value %d\n", ecn) 62 | sm.ECNStatus = ECNStatus(ecn) 63 | } 64 | 65 | a.TotalDataReceived += i 66 | a.DatagramsReceived += 1 67 | a.Logger.Printf("Received %d bytes from UDP socket\n", i) 68 | select { 69 | case <-recChan: 70 | return 71 | default: 72 | } 73 | recChan <- sm 74 | } 75 | }() 76 | 77 | go func() { 78 | defer a.Logger.Println("Agent terminated") 79 | defer close(a.closed) 80 | for { 81 | select { 82 | case p, open := <-recChan: 83 | if !open { 84 | return 85 | } 86 | 87 | conn.IncomingPayloads.Submit(p) 88 | case <-a.close: 89 | conn.UdpConnection.Close() 90 | return 91 | } 92 | } 93 | }() 94 | } 95 | 96 | func (a *SocketAgent) ConfigureECN() error { 97 | s, err := a.conn.UdpConnection.SyscallConn() 98 | if err != nil { 99 | return err 100 | } 101 | f := func(fd uintptr) { 102 | var u *compat.Utils 103 | err = u.SetRECVTOS(int(fd)) 104 | if err != nil { 105 | a.ecn = false 106 | a.Logger.Printf("Error when setting RECVTOS: %s\n", err.Error()) 107 | return 108 | } 109 | err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, 2) //INET_ECN_ECT_0 // TODO: This should actually be the responsability of the SendingAgent 110 | if err != nil { 111 | a.Logger.Printf("Error when setting TOS: %s\n", err.Error()) 112 | } 113 | a.ecn = err == nil 114 | } 115 | err = s.Control(f) 116 | if err != nil { 117 | return err 118 | } 119 | if !a.ecn { 120 | return errors.New("could not configure ecn") 121 | } 122 | return nil 123 | } 124 | 125 | type cmsgHdr struct { 126 | cLength uint64 127 | cLevel int32 128 | cType int32 129 | } 130 | 131 | func findECNValue(oob []byte) (byte, error) { 132 | for len(oob) > 0 { 133 | hdr := (*cmsgHdr)(unsafe.Pointer(&oob[0])) 134 | if hdr.cLevel == 0 && hdr.cType == 1 { 135 | return oob[hdr.cLength - 1], nil 136 | } 137 | oob = oob[hdr.cLength:] 138 | } 139 | return 0, errors.New("could not find ecn control message") 140 | } -------------------------------------------------------------------------------- /agents/stream_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | "errors" 5 | . "github.com/QUIC-Tracker/quic-tracker" 6 | ) 7 | 8 | type StreamAgent struct { 9 | FrameProducingAgent 10 | conn *Connection 11 | FlowControlAgent *FlowControlAgent 12 | input chan interface{} 13 | streamBuffers map[uint64][]byte 14 | streamClosing map[uint64]bool 15 | } 16 | 17 | func (a *StreamAgent) Run(conn *Connection) { 18 | a.BaseAgent.Init("StreamAgent", conn.OriginalDestinationCID) 19 | a.FrameProducingAgent.InitFPA(conn) 20 | a.input = conn.StreamInput.RegisterNewChan(1000) 21 | a.conn = conn 22 | a.streamBuffers = make(map[uint64][]byte) 23 | a.streamClosing = make(map[uint64]bool) 24 | 25 | go func() { 26 | defer a.Logger.Println("Agent terminated") 27 | defer close(a.closed) 28 | for { 29 | select { 30 | case i := <-a.input: 31 | si := i.(StreamInput) 32 | if si.Reset { 33 | a.reset(si.StreamId, si.AppErrorCode) 34 | } 35 | if si.StopSending { 36 | a.stopSending(si.StreamId, si.AppErrorCode) 37 | } 38 | if len(si.Data) > 0 { 39 | a.send(si.StreamId, si.Data, si.Close) 40 | } else if si.Close { 41 | a.close(si.StreamId) 42 | } 43 | case args := <-a.requestFrame: 44 | if args.level != EncryptionLevel0RTT && args.level != EncryptionLevel1RTT && args.level != EncryptionLevelBestAppData { 45 | a.frames <- nil 46 | break 47 | } 48 | var frames []Frame 49 | for streamId, buf := range a.streamBuffers { 50 | stream := conn.Streams.Get(streamId) 51 | f := NewStreamFrame(streamId, stream.WriteOffset - uint64(len(buf)), nil, false) 52 | length := Min(len(buf) + int(f.FrameLength()), args.availableSpace) 53 | if length > int(f.FrameLength()) { 54 | f.StreamData = buf[:length-int(f.FrameLength())] 55 | f.LenBit = true 56 | f.Length = uint64(len(f.StreamData)) 57 | if len(buf) > length { 58 | a.streamBuffers[streamId] = buf[length:] 59 | } else { 60 | delete(a.streamBuffers, streamId) 61 | if a.streamClosing[streamId] { 62 | delete(a.streamClosing, streamId) 63 | f.FinBit = true 64 | } 65 | } 66 | args.availableSpace -= length 67 | frames = append(frames, f) 68 | } 69 | } 70 | a.frames <- frames 71 | case <-a.BaseAgent.close: 72 | return 73 | } 74 | } 75 | }() 76 | } 77 | 78 | func (a *StreamAgent) close(streamId uint64) error { 79 | s := a.conn.Streams.Get(streamId) 80 | if IsClient(streamId) || IsBidi(streamId) { 81 | if s.WriteClosed { 82 | return errors.New("cannot close already closed stream") 83 | } 84 | s.WriteCloseOffset = s.WriteOffset 85 | a.conn.FrameQueue.Submit(QueuedFrame{NewStreamFrame(streamId, s.WriteOffset, nil, true), EncryptionLevelBestAppData}) 86 | return nil 87 | } 88 | return errors.New("cannot close server uni stream") 89 | } 90 | 91 | func (a *StreamAgent) reset(streamId uint64, appErrorCode uint64) error { 92 | s := a.conn.Streams.Get(streamId) 93 | if IsClient(streamId) || IsBidi(streamId) { 94 | if s.WriteClosed { 95 | return errors.New("cannot reset already closed stream") 96 | } 97 | s.WriteCloseOffset = s.WriteOffset 98 | s.WriteClosed = true 99 | a.conn.FrameQueue.Submit(QueuedFrame{&ResetStream{streamId, appErrorCode, s.WriteOffset}, EncryptionLevelBestAppData}) 100 | return nil 101 | } 102 | return errors.New("cannot reset server uni stream") 103 | } 104 | 105 | func (a *StreamAgent) stopSending(streamId uint64, appErrorCode uint64) error { 106 | if IsServer(streamId) || IsBidi(streamId) { 107 | if _, present := a.conn.Streams.Has(streamId); !present && IsServer(streamId) { 108 | return errors.New("cannot ask to stop sending on non-ready server stream") 109 | } 110 | a.conn.FrameQueue.Submit(QueuedFrame{&StopSendingFrame{streamId, appErrorCode}, EncryptionLevelBestAppData}) 111 | return nil 112 | } 113 | return errors.New("cannot ask to stop sending on a client uni stream") 114 | } 115 | 116 | func (a *StreamAgent) send(streamId uint64, data []byte, close bool) error { 117 | s := a.conn.Streams.Get(streamId) 118 | 119 | if s.WriteClosed { 120 | return errors.New("cannot write on closed stream") 121 | } 122 | s.WriteOffset += uint64(len(data)) 123 | s.WriteClosed = close 124 | if s.WriteClosed { 125 | s.WriteCloseOffset = s.WriteOffset 126 | } 127 | a.streamBuffers[streamId] = append(a.streamBuffers[streamId], data...) 128 | if close { 129 | a.streamClosing[streamId] = true 130 | } 131 | a.conn.PreparePacket.Submit(EncryptionLevelBestAppData) 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /agents/tls_agent.go: -------------------------------------------------------------------------------- 1 | package agents 2 | 3 | import ( 4 | "encoding/hex" 5 | . "github.com/QUIC-Tracker/quic-tracker" 6 | ) 7 | 8 | type TLSStatus struct { 9 | Completed bool 10 | Packet 11 | Error error 12 | } 13 | 14 | // The TLSAgent is responsible of interacting with the TLS-1.3 stack. It waits on the CRYPTO streams for new data and 15 | // feed it to the TLS stack. Any response is queued in a corresponding CRYPTO frame, unless disabled using 16 | // DisableFrameSending. The TLSAgent will broadcast when new encryption or decryption levels are available. 17 | type TLSAgent struct { 18 | BaseAgent 19 | TLSStatus Broadcaster //type: TLSStatus 20 | ResumptionTicket Broadcaster //type: []byte 21 | DisableFrameSending bool 22 | } 23 | 24 | func (a *TLSAgent) Run(conn *Connection) { 25 | a.Init("TLSAgent", conn.OriginalDestinationCID) 26 | a.TLSStatus = NewBroadcaster(10) 27 | a.ResumptionTicket = NewBroadcaster(10) 28 | 29 | encryptionLevels := []*DirectionalEncryptionLevel{ 30 | {EncryptionLevel: EncryptionLevelHandshake}, 31 | {EncryptionLevel: EncryptionLevelHandshake, Read: true}, 32 | {EncryptionLevel: EncryptionLevel1RTT}, 33 | {EncryptionLevel: EncryptionLevel1RTT, Read: true}, 34 | } 35 | 36 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 37 | 38 | cryptoChans := map[PNSpace]chan interface{}{ 39 | PNSpaceInitial: make(chan interface{}, 1000), 40 | PNSpaceHandshake: make(chan interface{}, 1000), 41 | PNSpaceAppData: make(chan interface{}, 1000), 42 | } 43 | for space, channel := range cryptoChans { 44 | conn.CryptoStreams.Get(space).ReadChan.Register(channel) 45 | } 46 | 47 | var resumptionTicketSent bool 48 | 49 | go func() { 50 | defer a.Logger.Println("Agent terminated") 51 | defer close(a.closed) 52 | 53 | for { 54 | select { 55 | case i := <-incomingPackets: 56 | packet := i.(Packet) 57 | if _, ok := packet.(Framer); !ok { 58 | break 59 | } 60 | cryptoChan := cryptoChans[packet.PNSpace()] 61 | 62 | var handshakeData []byte 63 | 64 | forLoop: 65 | for { 66 | select { 67 | case i := <-cryptoChan: 68 | handshakeData = append(handshakeData, i.([]byte)...) 69 | default: 70 | break forLoop 71 | } 72 | } 73 | 74 | a.Logger.Printf("Received %s packet in PN space %s and %d bytes from the corresponding crypto stream\n", packet.Header().PacketType().String(), packet.PNSpace().String(), len(handshakeData)) 75 | 76 | switch packet.(type) { 77 | case Framer: 78 | if len(handshakeData) > 0 { 79 | tlsOutput, notCompleted, err := conn.Tls.HandleMessage(handshakeData, PNSpaceToEpoch[packet.PNSpace()]) 80 | 81 | if err != nil { 82 | a.Logger.Printf("TLS error occured: %s\n", err.Error()) 83 | a.TLSStatus.Submit(TLSStatus{false, packet, err}) 84 | } 85 | 86 | conn.CryptoStateLock.Lock() 87 | if conn.CryptoStates[EncryptionLevelHandshake] == nil { 88 | conn.CryptoStates[EncryptionLevelHandshake] = new(CryptoState) 89 | } 90 | 91 | if conn.CryptoStates[EncryptionLevelHandshake] != nil { 92 | if conn.CryptoStates[EncryptionLevelHandshake].HeaderRead == nil && len(conn.Tls.HandshakeReadSecret()) > 0 { 93 | a.Logger.Printf("Installing handshake read crypto with secret %s\n", hex.EncodeToString(conn.Tls.HandshakeReadSecret())) 94 | conn.CryptoStates[EncryptionLevelHandshake].InitRead(conn.Tls, conn.Tls.HandshakeReadSecret()) 95 | } 96 | if conn.CryptoStates[EncryptionLevelHandshake].HeaderWrite == nil && len(conn.Tls.HandshakeWriteSecret()) > 0 { 97 | a.Logger.Printf("Installing handshake write crypto with secret %s\n", hex.EncodeToString(conn.Tls.HandshakeWriteSecret())) 98 | conn.CryptoStates[EncryptionLevelHandshake].InitWrite(conn.Tls, conn.Tls.HandshakeWriteSecret()) 99 | } 100 | } 101 | 102 | if len(tlsOutput) > 0 && !a.DisableFrameSending { 103 | for _, m := range tlsOutput { 104 | conn.FrameQueue.Submit(QueuedFrame{NewCryptoFrame(conn.CryptoStreams.Get(EpochToPNSpace[m.Epoch]), m.Data), EpochToEncryptionLevel[m.Epoch]}) 105 | } 106 | } 107 | 108 | if !notCompleted && conn.CryptoStates[EncryptionLevel1RTT] == nil { 109 | a.Logger.Printf("Handshake has completed, installing protected crypto {read=%s, write=%s}\n", hex.EncodeToString(conn.Tls.ProtectedReadSecret()), hex.EncodeToString(conn.Tls.ProtectedWriteSecret())) 110 | conn.CryptoStates[EncryptionLevel1RTT] = NewProtectedCryptoState(conn.Tls, conn.Tls.ProtectedReadSecret(), conn.Tls.ProtectedWriteSecret()) 111 | 112 | // TODO: Check negotiated ALPN ? 113 | 114 | err = conn.TLSTPHandler.ReceiveExtensionData(conn.Tls.ReceivedQUICTransportParameters()) 115 | if err != nil { 116 | a.Logger.Printf("Failed to decode extension data: %s\n", err.Error()) 117 | a.TLSStatus.Submit(TLSStatus{false, packet, err}) 118 | } else { 119 | conn.TransportParameters.Submit(*conn.TLSTPHandler.ReceivedParameters) 120 | a.TLSStatus.Submit(TLSStatus{true, packet, err}) 121 | } 122 | } 123 | 124 | for _, e := range encryptionLevels { 125 | if !e.Available && conn.CryptoStates[e.EncryptionLevel] != nil && ((e.Read && conn.CryptoStates[e.EncryptionLevel].HeaderRead != nil) || (!e.Read && conn.CryptoStates[e.EncryptionLevel].HeaderWrite != nil)) { 126 | e.Available = true 127 | conn.EncryptionLevels.Submit(*e) 128 | } 129 | } 130 | conn.CryptoStateLock.Unlock() 131 | 132 | if !resumptionTicketSent && len(conn.Tls.ResumptionTicket()) > 0 { 133 | a.ResumptionTicket.Submit(conn.Tls.ResumptionTicket()) 134 | } 135 | } 136 | default: 137 | // The packet does not impact the TLS agent 138 | } 139 | case <-a.close: 140 | return 141 | } 142 | } 143 | }() 144 | } 145 | -------------------------------------------------------------------------------- /bin/http/http_get.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | qt "github.com/QUIC-Tracker/quic-tracker" 8 | "github.com/QUIC-Tracker/quic-tracker/agents" 9 | "github.com/davecgh/go-spew/spew" 10 | "log" 11 | "net/http" 12 | _ "net/http/pprof" 13 | "os" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | func main() { 19 | go func() { 20 | log.Println(http.ListenAndServe("localhost:6060", nil)) 21 | }() 22 | 23 | address := flag.String("address", "", "The address to connect to") 24 | useIPv6 := flag.Bool("6", false, "Use IPV6") 25 | path := flag.String("path", "/index.html", "The path to request") 26 | alpn := flag.String("alpn", "hq", "The ALPN prefix to use when connecting ot the endpoint.") 27 | qlog := flag.String("qlog", "", "The file to write the qlog output to.") 28 | netInterface := flag.String("interface", "", "The interface to listen to when capturing pcap") 29 | timeout := flag.Int("timeout", 10, "The number of seconds after which the program will timeout") 30 | h3 := flag.Bool("3", false, "Use HTTP/3 instead of HTTP/0.9") 31 | flag.Parse() 32 | 33 | t := time.NewTimer(time.Duration(*timeout) * time.Second) 34 | conn, err := qt.NewDefaultConnection(*address, (*address)[:strings.LastIndex(*address, ":")], nil, *useIPv6, *alpn, *h3) 35 | if err != nil { 36 | panic(err) 37 | } 38 | defer conn.Close() 39 | conn.QLog.Title = fmt.Sprintf("QUIC-Tracker HTTP GET %s%s", *address, *path) 40 | 41 | if *h3 { 42 | conn.TLSTPHandler.MaxUniStreams = 3 43 | } 44 | 45 | pcap, err := qt.StartPcapCapture(conn, *netInterface) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | trace := qt.NewTrace("http_get", 1, *address) 51 | trace.AttachTo(conn) 52 | defer func() { 53 | trace.Complete(conn) 54 | err = trace.AddPcap(conn, pcap) 55 | if err != nil { 56 | trace.Results["pcap_error"] = err.Error() 57 | } 58 | 59 | var t []qt.Trace 60 | t = append(t, *trace) 61 | out, err := json.Marshal(t) 62 | if err != nil { 63 | println(err) 64 | } 65 | println(string(out)) 66 | }() 67 | 68 | Agents := agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 69 | handshakeAgent := &agents.HandshakeAgent{TLSAgent: Agents.Get("TLSAgent").(*agents.TLSAgent), SocketAgent: Agents.Get("SocketAgent").(*agents.SocketAgent)} 70 | Agents.Add(handshakeAgent) 71 | Agents.Get("SendingAgent").(*agents.SendingAgent).FrameProducer = Agents.GetFrameProducingAgents() 72 | 73 | handshakeStatus := make(chan interface{}, 10) 74 | handshakeAgent.HandshakeStatus.Register(handshakeStatus) 75 | handshakeAgent.InitiateHandshake() 76 | 77 | select { 78 | case i := <-handshakeStatus: 79 | s := i.(agents.HandshakeStatus) 80 | if !s.Completed { 81 | Agents.StopAll() 82 | return 83 | } 84 | case <-t.C: 85 | Agents.StopAll() 86 | return 87 | } 88 | 89 | defer func() { 90 | conn.QLogTrace.Sort() 91 | trace.QLog = conn.QLog 92 | if *qlog != "" { 93 | outFile, err := os.OpenFile(*qlog, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) 94 | if err == nil { 95 | content, err := json.Marshal(conn.QLog) 96 | if err == nil { 97 | outFile.Write(content) 98 | outFile.Close() 99 | } 100 | } 101 | } 102 | }() 103 | defer conn.CloseConnection(false, 0, "") 104 | 105 | var httpAgent agents.HTTPAgent 106 | 107 | if !*h3 { 108 | httpAgent = &agents.HTTP09Agent{} 109 | } else { 110 | httpAgent = &agents.HTTP3Agent{} 111 | } 112 | Agents.Add(httpAgent) 113 | 114 | select { 115 | case r := <-httpAgent.SendRequest(*path, "GET", trace.Host, nil): 116 | spew.Dump(r) 117 | case <-t.C: 118 | return 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /bin/test_suite/scenario_runner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | qt "github.com/QUIC-Tracker/quic-tracker" 7 | s "github.com/QUIC-Tracker/quic-tracker/scenarii" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | host := flag.String("host", "", "The host endpoint to run the test against.") 16 | path := flag.String("path", "/index.html", "The path to request when performing tests that needs data to be sent.") 17 | alpn := flag.String("alpn", "hq", "The ALPN prefix to use when connecting ot the endpoint.") 18 | scenarioName := flag.String("scenario", "", "The particular scenario to run.") 19 | outputFile := flag.String("output", "", "The file to write the output to. Output to stdout if not set.") 20 | qlog := flag.String("qlog", "", "The file to write the qlog output to.") 21 | debug := flag.Bool("debug", false, "Enables debugging information to be printed.") 22 | nopcap := flag.Bool("nopcap", false, "Disables the pcap capture.") 23 | netInterface := flag.String("interface", "", "The interface to listen to when capturing pcap.") 24 | timeout := flag.Int("timeout", 10, "The amount of time in seconds spent when completing the test. Defaults to 10. When set to 0, the test ends as soon as possible.") 25 | flag.Parse() 26 | 27 | if *host == "" || *path == "" || *scenarioName == "" { 28 | println("Parameters host, path and scenario are required") 29 | os.Exit(-1) 30 | } 31 | 32 | scenario, ok := s.GetAllScenarii()[*scenarioName] 33 | if !ok { 34 | println("Unknown scenario", *scenarioName) 35 | return 36 | } 37 | 38 | trace := qt.NewTrace(scenario.Name(), scenario.Version(), *host) 39 | 40 | conn, err := qt.NewDefaultConnection(*host, strings.Split(*host, ":")[0], nil, scenario.IPv6(), *alpn, scenario.HTTP3()) // Raw IPv6 are not handled correctly 41 | 42 | if err == nil { 43 | conn.QLog.Title = "QUIC-Tracker scenario " + *scenarioName 44 | 45 | var pcap *exec.Cmd 46 | if !*nopcap { 47 | pcap, err = qt.StartPcapCapture(conn, *netInterface) 48 | if err != nil { 49 | trace.Results["pcap_start_error"] = err.Error() 50 | } 51 | } 52 | 53 | trace.AttachTo(conn) 54 | 55 | start := time.Now() 56 | scenario.SetTimer(time.Duration(*timeout) * time.Second) 57 | scenario.Run(conn, trace, *path, *debug) 58 | trace.Duration = uint64(time.Now().Sub(start).Seconds() * 1000) 59 | ip := strings.Replace(conn.ConnectedIp().String(), "[", "", -1) 60 | trace.Ip = ip[:strings.LastIndex(ip, ":")] 61 | trace.StartedAt = start.Unix() 62 | 63 | trace.Complete(conn) 64 | conn.Close() 65 | if pcap != nil { 66 | err = trace.AddPcap(conn, pcap) 67 | } 68 | if err != nil { 69 | trace.Results["pcap_completed_error"] = err.Error() 70 | } 71 | 72 | conn.QLogTrace.Sort() 73 | trace.QLog = conn.QLog 74 | if *qlog != "" { 75 | outFile, err := os.OpenFile(*qlog, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) 76 | if err == nil { 77 | content, err := json.Marshal(conn.QLog) 78 | if err == nil { 79 | outFile.Write(content) 80 | outFile.Close() 81 | } 82 | } 83 | } 84 | } else { 85 | trace.ErrorCode = 255 86 | trace.Results["udp_error"] = err.Error() 87 | } 88 | 89 | out, _ := json.Marshal(trace) 90 | if *outputFile != "" { 91 | os.Remove(*outputFile) 92 | outFile, err := os.OpenFile(*outputFile, os.O_CREATE|os.O_WRONLY, 0755) 93 | if err == nil { 94 | outFile.Write(out) 95 | outFile.Close() 96 | } else { 97 | println(err.Error()) 98 | } 99 | } else { 100 | println(string(out)) 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /bin/test_suite/test_suite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | qt "github.com/QUIC-Tracker/quic-tracker" 9 | "github.com/QUIC-Tracker/quic-tracker/scenarii" 10 | "io/ioutil" 11 | "os" 12 | "os/exec" 13 | p "path" 14 | "runtime" 15 | "sort" 16 | "strconv" 17 | "strings" 18 | "sync" 19 | "time" 20 | ) 21 | 22 | func main() { 23 | hostsFilename := flag.String("hosts", "", "A tab-separated file containing hosts, the paths used to request data to be sent and ports for negotiating h3.") 24 | scenarioName := flag.String("scenario", "", "A particular scenario to run. Run all of them if the parameter is missing.") 25 | outputFilename := flag.String("output", "", "The file to write the output to. Output to stdout if not set.") 26 | logsDirectory := flag.String("logs-directory", "/tmp", "Location of the logs.") 27 | netInterface := flag.String("interface", "", "The interface to listen to when capturing pcaps. Lets tcpdump decide if not set.") 28 | parallel := flag.Bool("parallel", false, "Runs each scenario against multiple hosts at the same time.") 29 | parallelScenarios := flag.Bool("parallel-scenarios", false, "Run multiple scenarios against multiple hosts in parallel. Enable this only if all the test servers can support multiple connections") 30 | maxInstances := flag.Int("max-instances", 10, "Limits the number of parallel scenario runs.") 31 | randomise := flag.Bool("randomise", false, "Randomise the execution order of scenarii") 32 | timeout := flag.Int("timeout", 10, "The amount of time in seconds spent when completing a test. Defaults to 10. When set to 0, each test ends as soon as possible.") 33 | debug := flag.Bool("debug", false, "Enables debugging information to be printed.") 34 | flag.Parse() 35 | 36 | _, filename, _, ok := runtime.Caller(0) 37 | if !ok { 38 | println("No caller information") 39 | os.Exit(-1) 40 | } 41 | scenarioRunnerFilename := p.Join(p.Dir(filename), "scenario_runner.go") 42 | 43 | if *hostsFilename == "" { 44 | println("The hosts parameter is required") 45 | os.Exit(-1) 46 | } 47 | 48 | file, err := os.Open(*hostsFilename) 49 | if err != nil { 50 | panic(err) 51 | } 52 | defer file.Close() 53 | 54 | scenariiInstances := scenarii.GetAllScenarii() 55 | 56 | var scenarioIds []string 57 | for scenarioId := range scenariiInstances { 58 | scenarioIds = append(scenarioIds, scenarioId) 59 | } 60 | if !*randomise { 61 | sort.Strings(scenarioIds) 62 | } 63 | 64 | if *scenarioName != "" && scenariiInstances[*scenarioName] == nil { 65 | println("Unknown scenario", *scenarioName) 66 | } 67 | 68 | var results Results 69 | result := make(chan *qt.Trace) 70 | resultsAgg := make(chan bool) 71 | 72 | go func() { 73 | for t := range result { 74 | results = append(results, *t) 75 | } 76 | close(resultsAgg) 77 | }() 78 | 79 | if !*parallel && !*parallelScenarios { 80 | *maxInstances = 1 81 | } 82 | 83 | semaphore := make(chan bool, *maxInstances) 84 | for i := 0; i < *maxInstances; i++ { 85 | semaphore <- true 86 | } 87 | wg := &sync.WaitGroup{} 88 | 89 | for _, id := range scenarioIds { 90 | if *scenarioName != "" && *scenarioName != id { 91 | continue 92 | } 93 | 94 | scenarioId := id 95 | scenario := scenariiInstances[scenarioId] 96 | 97 | os.MkdirAll(p.Join(*logsDirectory, scenarioId), os.ModePerm) 98 | 99 | scanner := bufio.NewScanner(file) 100 | for scanner.Scan() { 101 | line := strings.Split(scanner.Text(), "\t") 102 | host, path := line[0], line[1] 103 | h3port, err := strconv.Atoi(line[2]) 104 | if err != nil { 105 | println(err) 106 | continue 107 | } 108 | preferredALPN := line[3] 109 | 110 | if scenario.HTTP3() { 111 | split := strings.Split(host, ":") 112 | host, _ = split[0], split[1] 113 | host = fmt.Sprintf("%s:%d", host, h3port) 114 | } 115 | 116 | <-semaphore 117 | wg.Add(1) 118 | if *debug { 119 | fmt.Println("starting", scenario.Name(), "against", host) 120 | } 121 | 122 | go func() { 123 | defer func() { semaphore <- true }() 124 | defer wg.Done() 125 | 126 | outputFile, err := ioutil.TempFile("", "quic_tracker") 127 | if err != nil { 128 | println(err.Error()) 129 | return 130 | } 131 | outputFile.Close() 132 | 133 | logFile, err := os.Create(p.Join(*logsDirectory, scenarioId, host)) 134 | if err != nil { 135 | println(err.Error()) 136 | return 137 | } 138 | defer logFile.Close() 139 | 140 | crashTrace := GetCrashTrace(scenario, host) // Prepare one just in case 141 | start := time.Now() 142 | 143 | args := []string{"run", scenarioRunnerFilename, "-host", host, "-path", path, "-alpn", preferredALPN, "-scenario", scenarioId, "-interface", *netInterface, "-output", outputFile.Name(), "-timeout", strconv.Itoa(*timeout)} 144 | if *debug { 145 | args = append(args, "-debug") 146 | } 147 | 148 | c := exec.Command("go", args...) 149 | c.Stdout = logFile 150 | c.Stderr = logFile 151 | err = c.Run() 152 | if err != nil { 153 | println(err.Error()) 154 | } 155 | 156 | var trace qt.Trace 157 | outputFile, err = os.Open(outputFile.Name()) 158 | if err != nil { 159 | println(err) 160 | } 161 | defer outputFile.Close() 162 | defer os.Remove(outputFile.Name()) 163 | 164 | err = json.NewDecoder(outputFile).Decode(&trace) 165 | if err != nil { 166 | println(err.Error()) 167 | crashTrace.StartedAt = start.Unix() 168 | crashTrace.Duration = uint64(time.Now().Sub(start).Seconds() * 1000) 169 | result <- crashTrace 170 | return 171 | } 172 | result <- &trace 173 | }() 174 | } 175 | if !*parallelScenarios { 176 | wg.Wait() 177 | } 178 | file.Seek(0, 0) 179 | } 180 | 181 | wg.Wait() 182 | close(result) 183 | <-resultsAgg 184 | 185 | sort.Sort(results) 186 | out, _ := json.Marshal(results) 187 | if *outputFilename != "" { 188 | outFile, err := os.Create(*outputFilename) 189 | defer outFile.Close() 190 | if err == nil { 191 | outFile.Write(out) 192 | return 193 | } else { 194 | println(err.Error()) 195 | } 196 | } 197 | 198 | println(string(out)) 199 | } 200 | 201 | func GetCrashTrace(scenario scenarii.Scenario, host string) *qt.Trace { 202 | trace := qt.NewTrace(scenario.Name(), scenario.Version(), host) 203 | trace.ErrorCode = 254 204 | return trace 205 | } 206 | 207 | type Results []qt.Trace 208 | 209 | func (a Results) Less(i, j int) bool { 210 | if a[i].Scenario == a[j].Scenario { 211 | return a[i].Host < a[j].Host 212 | } 213 | return a[i].Scenario < a[j].Scenario 214 | } 215 | func (a Results) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 216 | func (a Results) Len() int { return len(a) } 217 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | // 2 | // QUIC-Tracker is a test suite for QUIC, built upon a minimal client implementation in Go. 3 | // It is currently draft-27 and TLS-1.3 compatible. 4 | // 5 | // The main package is a toolbox to parse and create QUIC packets of all types. More high-level client behaviours are 6 | // implemented in the package agents. Several test scenarii are implemented in the package scenarii. 7 | // 8 | // Architecture 9 | // 10 | // QUIC-Tracker is comprised of three parts. 11 | // 12 | // The first is this package, which contains types, methods and functions to 13 | // parse and create QUIC packets that can be easily manipulated. 14 | // 15 | // The second is the package agents, which implements all the features and behaviours of a QUIC client as asynchronous 16 | // message-passing objects. These agents exchange messages through the broadcasting channels defined in the Connection 17 | // struct. This allows additional behaviours to be hooked up and respond to several events that occur when the 18 | // connection is active. 19 | // 20 | // The third the package scenarii, which contains all the tests of the test suite. They can be ran using the scripts in 21 | // the package bin/test_suite. The tests results are produced in an unified JSON format. It is described in the Trace 22 | // type documentation. 23 | // 24 | // License and copyright 25 | // 26 | // QUIC-Tracker is licensed under the GNU Affero General Public License version 3. You can find its terms in the 27 | // LICENSE file, or at https://www.gnu.org/licenses/agpl.txt. 28 | // 29 | // Copyright (C) 2017-2021 Maxime Piraux 30 | // 31 | package quictracker 32 | 33 | import ( 34 | "bytes" 35 | "encoding/binary" 36 | "encoding/hex" 37 | . "github.com/QUIC-Tracker/quic-tracker/lib" 38 | _ "github.com/mpiraux/ls-qpack-go" 39 | "github.com/mpiraux/pigotls" 40 | "io" 41 | "math" 42 | "net" 43 | "time" 44 | ) 45 | 46 | // TODO: Reconsider the use of global variables 47 | var QuicVersion uint32 = 0xff00001d // See https://tools.ietf.org/html/draft-ietf-quic-transport-08#section-4 48 | var QuicALPNToken = "hq-29" // See https://www.ietf.org/mail-archive/web/quic/current/msg01882.html 49 | var QuicH3ALPNToken = "h3-29" // See https://tools.ietf.org/html/draft-ietf-quic-http-17#section-2.1 50 | 51 | const ( 52 | MinimumInitialLength = 1252 53 | MinimumInitialLengthv6 = 1232 54 | MaxTheoreticUDPPayloadSize = 65507 55 | MaximumVersion = 0xff00001d 56 | MinimumVersion = 0xff00001c 57 | ) 58 | 59 | // errors 60 | 61 | const ( 62 | ERR_STREAM_LIMIT_ERROR = 0x04 63 | ERR_STREAM_STATE_ERROR = 0x05 64 | ERR_PROTOCOL_VIOLATION = 0x0a 65 | ) 66 | 67 | type PacketNumber uint64 68 | 69 | func ReadPacketNumber (buffer *bytes.Reader) PacketNumber { 70 | v, _, _ := ReadVarIntValue(buffer) 71 | return PacketNumber(v) 72 | } 73 | 74 | func (p PacketNumber) Truncate(largestAcknowledged PacketNumber) TruncatedPN { 75 | if p < largestAcknowledged { 76 | panic("PNs should be truncated with a lower PN") 77 | } 78 | length := (int(math.Log2(float64(p - largestAcknowledged + 1))) / 8) + 1 // See: https://tools.ietf.org/html/draft-ietf-quic-transport-13#section-4.8 79 | if length > 4 { 80 | println("couldn't truncate", p, "with", largestAcknowledged) 81 | panic(length) 82 | } 83 | return TruncatedPN{uint32(p) & (0xFFFFFFFF >> (8 * (4 - uint(length)))), length} 84 | } 85 | 86 | type TruncatedPN struct { 87 | Value uint32 88 | Length int 89 | } 90 | 91 | func ReadTruncatedPN(buffer *bytes.Reader, length int) TruncatedPN { 92 | pn := TruncatedPN{Length: length} 93 | value := make([]byte, 4, 4) 94 | buffer.Read(value[4-length:]) 95 | pn.Value = binary.BigEndian.Uint32(value) 96 | return pn 97 | } 98 | 99 | func (t TruncatedPN) Encode() []byte { 100 | buffer := new(bytes.Buffer) 101 | switch t.Length { 102 | case 1: 103 | buffer.WriteByte(byte(t.Value)) 104 | case 2: 105 | buffer.Write(Uint16ToBEBytes(uint16(t.Value))) 106 | case 3: 107 | buffer.Write(Uint24ToBEBytes(t.Value)) 108 | case 4: 109 | buffer.Write(Uint32ToBEBytes(t.Value)) 110 | } 111 | return buffer.Bytes() 112 | } 113 | 114 | func (t TruncatedPN) Join(p PacketNumber) PacketNumber { 115 | return PacketNumber(uint64(p & (0xFFFFFFFFFFFFFFFF << uint(t.Length * 8))) | uint64(t.Value)) 116 | } 117 | 118 | func (t *TruncatedPN) SetLength(length int) { 119 | t.Length = length 120 | } 121 | 122 | type VarInt struct { 123 | Value uint64 124 | Length int 125 | } 126 | func NewVarInt(value uint64) VarInt { 127 | return VarInt{value, VarIntLen(value)} 128 | } 129 | func ReadVarInt(buffer io.ByteReader) (VarInt, error) { 130 | i, l, err := ReadVarIntValue(buffer) 131 | if err != nil { 132 | return VarInt{}, err 133 | } 134 | return VarInt{Value: i, Length: l}, nil 135 | } 136 | 137 | func (v VarInt) Encode() []byte { 138 | buffer := new(bytes.Buffer) 139 | WriteVarInt(buffer, v.Value) 140 | return buffer.Bytes() 141 | } 142 | 143 | type PNSpace int 144 | 145 | const ( 146 | PNSpaceInitial PNSpace = iota 147 | PNSpaceHandshake 148 | PNSpaceAppData 149 | PNSpaceNoSpace 150 | ) 151 | 152 | var PNSpaceToString = map[PNSpace]string{ 153 | PNSpaceInitial: "Initial", 154 | PNSpaceHandshake: "Handshake", 155 | PNSpaceAppData: "Application data", 156 | } 157 | 158 | var PNSpaceToEpoch = map[PNSpace]pigotls.Epoch{ 159 | PNSpaceInitial: pigotls.EpochInitial, 160 | PNSpaceHandshake: pigotls.EpochHandshake, 161 | PNSpaceAppData: pigotls.Epoch1RTT, 162 | } 163 | 164 | var PNSpaceToPacketType = map[PNSpace]PacketType{ 165 | PNSpaceInitial: Initial, 166 | PNSpaceHandshake: Handshake, 167 | PNSpaceAppData: ShortHeaderPacket, // TODO: Deal with O-RTT packets 168 | } 169 | 170 | var EpochToPNSpace = map[pigotls.Epoch]PNSpace { 171 | pigotls.EpochInitial: PNSpaceInitial, 172 | pigotls.EpochHandshake: PNSpaceHandshake, 173 | pigotls.Epoch0RTT: PNSpaceAppData, 174 | pigotls.Epoch1RTT: PNSpaceAppData, 175 | } 176 | 177 | func (pns PNSpace) String() string { 178 | return PNSpaceToString[pns] 179 | } 180 | 181 | func (pns PNSpace) Epoch() pigotls.Epoch { 182 | return PNSpaceToEpoch[pns] 183 | } 184 | 185 | func Uint32ToBEBytes(uint32 uint32) []byte { 186 | b := make([]byte, 4, 4) 187 | binary.BigEndian.PutUint32(b, uint32) 188 | return b 189 | } 190 | 191 | func Uint24ToBEBytes(uint32 uint32) []byte { 192 | b := make([]byte, 4, 4) 193 | binary.BigEndian.PutUint32(b, uint32) 194 | return b[1:] 195 | } 196 | 197 | func Uint16ToBEBytes(uint16 uint16) []byte { 198 | b := make([]byte, 2, 2) 199 | binary.BigEndian.PutUint16(b, uint16) 200 | return b 201 | } 202 | 203 | func Max(a, b int) int { if a < b { return b }; return a} 204 | func Min(a, b int) int { if a > b { return b }; return a} 205 | 206 | type PacketNumberQueue []PacketNumber 207 | func (a PacketNumberQueue) Less(i, j int) bool { return a[i] > a[j] } 208 | func (a PacketNumberQueue) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 209 | func (a PacketNumberQueue) Len() int { return len(a) } 210 | 211 | type ConnectionID []byte 212 | 213 | func (c ConnectionID) CIDL() uint8 { 214 | return uint8(len(c)) 215 | } 216 | 217 | func (c ConnectionID) WriteTo(buffer *bytes.Buffer) { 218 | buffer.WriteByte(c.CIDL()) 219 | buffer.Write(c) 220 | } 221 | 222 | func (c ConnectionID) String() string { 223 | return hex.EncodeToString(c) 224 | } 225 | 226 | func min(a, b uint64) uint64 { 227 | if a < b { 228 | return a 229 | } 230 | return b 231 | } 232 | 233 | func max(a, b uint64) uint64 { 234 | if a > b { 235 | return a 236 | } 237 | return b 238 | } 239 | 240 | type ECNStatus int 241 | 242 | const ( 243 | ECNStatusNonECT ECNStatus = 0 244 | ECNStatusECT_1 = 1 245 | ECNStatusECT_0 = 2 246 | ECNStatusCE = 3 247 | ) 248 | 249 | type PacketContext struct { 250 | Timestamp time.Time 251 | RemoteAddr net.Addr 252 | ECNStatus 253 | DatagramSize uint16 254 | PacketSize uint16 255 | WasBuffered bool 256 | } 257 | 258 | type IncomingPayload struct { 259 | PacketContext 260 | Payload []byte 261 | } 262 | 263 | type UnprocessedPayload struct { 264 | IncomingPayload 265 | EncryptionLevel 266 | } 267 | 268 | type QueuedFrame struct { 269 | Frame 270 | EncryptionLevel 271 | } 272 | 273 | type PacketAcknowledged struct { 274 | PacketNumber 275 | PNSpace 276 | } 277 | 278 | type PacketToSend struct { 279 | Packet 280 | EncryptionLevel 281 | } -------------------------------------------------------------------------------- /compat/utils.go: -------------------------------------------------------------------------------- 1 | package compat 2 | 3 | type UtilsInterface interface { 4 | SetRECVTOS(fd int) error 5 | } -------------------------------------------------------------------------------- /compat/utils_darwin.go: -------------------------------------------------------------------------------- 1 | package compat 2 | 3 | import "syscall" 4 | 5 | const IP_RECVTOS = 27 6 | 7 | type Utils byte 8 | 9 | func (u *Utils) SetRECVTOS(fd int) error { 10 | return syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, IP_RECVTOS, 1) 11 | } 12 | -------------------------------------------------------------------------------- /compat/utils_generic.go: -------------------------------------------------------------------------------- 1 | // +build !darwin 2 | 3 | package compat 4 | 5 | import "syscall" 6 | 7 | type Utils byte 8 | 9 | func (u *Utils) SetRECVTOS(fd int) error { 10 | return syscall.SetsockoptByte(int(fd), syscall.IPPROTO_IP, syscall.IP_RECVTOS, 1) 11 | } 12 | -------------------------------------------------------------------------------- /crypto.go: -------------------------------------------------------------------------------- 1 | package quictracker 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "github.com/mpiraux/pigotls" 7 | ) 8 | 9 | var quicVersionSalt = []byte{ // See https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 10 | 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 11 | 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 12 | 0x43, 0x90, 0xa8, 0x99, 13 | } 14 | 15 | const ( 16 | clientInitialLabel = "client in" 17 | serverInitialLabel = "server in" 18 | ) 19 | 20 | type EncryptionLevel int 21 | 22 | const ( 23 | EncryptionLevelNone EncryptionLevel = iota 24 | EncryptionLevelInitial 25 | EncryptionLevel0RTT 26 | EncryptionLevelHandshake 27 | EncryptionLevel1RTT 28 | EncryptionLevelBest // A special flag to indicate that the best encryption level available should be used 29 | EncryptionLevelBestAppData // A special flag to indicate that the best app data encryption level available should be used 30 | ) 31 | 32 | func (eL EncryptionLevel) String() string { 33 | return encryptionLevelToString[eL] 34 | } 35 | 36 | var encryptionLevelToString = map[EncryptionLevel]string { 37 | EncryptionLevelNone: "None", 38 | EncryptionLevelInitial: "Initial", 39 | EncryptionLevelHandshake: "Handshake", 40 | EncryptionLevel0RTT: "0RTT", 41 | EncryptionLevel1RTT: "1RTT", 42 | EncryptionLevelBest: "Best", 43 | EncryptionLevelBestAppData: "BestAppData", 44 | } 45 | 46 | var EncryptionLevelToPNSpace = map[EncryptionLevel]PNSpace { 47 | EncryptionLevelNone: PNSpaceNoSpace, 48 | EncryptionLevelInitial: PNSpaceInitial, 49 | EncryptionLevelHandshake: PNSpaceHandshake, 50 | EncryptionLevel0RTT: PNSpaceAppData, 51 | EncryptionLevel1RTT: PNSpaceAppData, 52 | EncryptionLevelBest: PNSpaceNoSpace, 53 | EncryptionLevelBestAppData: PNSpaceAppData, 54 | } 55 | 56 | var EncryptionLevelToPacketType = map[EncryptionLevel]PacketType{ 57 | EncryptionLevelInitial: Initial, 58 | EncryptionLevelHandshake: Handshake, 59 | EncryptionLevel0RTT: ZeroRTTProtected, 60 | EncryptionLevel1RTT: ShortHeaderPacket, 61 | } 62 | 63 | var packetTypeToEncryptionLevel = map[PacketType]EncryptionLevel{ 64 | Initial: EncryptionLevelInitial, 65 | Retry: EncryptionLevelNone, 66 | Handshake: EncryptionLevelHandshake, 67 | ZeroRTTProtected: EncryptionLevel0RTT, 68 | ShortHeaderPacket: EncryptionLevel1RTT, 69 | } 70 | 71 | var EpochToEncryptionLevel = map[pigotls.Epoch]EncryptionLevel { 72 | pigotls.EpochInitial: EncryptionLevelInitial, 73 | pigotls.Epoch0RTT: EncryptionLevel0RTT, 74 | pigotls.EpochHandshake: EncryptionLevelHandshake, 75 | pigotls.Epoch1RTT: EncryptionLevel1RTT, 76 | } 77 | 78 | type DirectionalEncryptionLevel struct { 79 | EncryptionLevel EncryptionLevel 80 | Read bool 81 | Available bool 82 | } 83 | 84 | type CryptoState struct { 85 | Read *pigotls.AEAD 86 | Write *pigotls.AEAD 87 | HeaderRead *pigotls.Cipher 88 | HeaderWrite *pigotls.Cipher 89 | } 90 | 91 | type RetryPseudoPacket struct { 92 | OriginalDestinationCID ConnectionID 93 | UnusedByte byte 94 | Version uint32 95 | DestinationCID ConnectionID 96 | SourceCID ConnectionID 97 | RetryToken []byte 98 | } 99 | 100 | func (r *RetryPseudoPacket) Encode() []byte { 101 | buf := bytes.NewBuffer(nil) 102 | r.OriginalDestinationCID.WriteTo(buf) 103 | buf.WriteByte(r.UnusedByte) 104 | binary.Write(buf, binary.BigEndian, &r.Version) 105 | r.DestinationCID.WriteTo(buf) 106 | r.SourceCID.WriteTo(buf) 107 | buf.Write(r.RetryToken) 108 | return buf.Bytes() 109 | } 110 | 111 | func (s *CryptoState) InitRead(tls *pigotls.Connection, readSecret []byte) { 112 | s.Read = tls.NewAEAD(readSecret, false) 113 | s.HeaderRead = tls.NewCipher(tls.HkdfExpandLabel(readSecret, "hp", nil, tls.AEADKeySize(), pigotls.QuicBaseLabel)) 114 | } 115 | 116 | func (s *CryptoState) InitWrite(tls *pigotls.Connection, writeSecret []byte) { 117 | s.Write = tls.NewAEAD(writeSecret, true) 118 | s.HeaderWrite = tls.NewCipher(tls.HkdfExpandLabel(writeSecret, "hp", nil, tls.AEADKeySize(), pigotls.QuicBaseLabel)) 119 | } 120 | 121 | func NewInitialPacketProtection(conn *Connection) *CryptoState { 122 | initialSecret := conn.Tls.HkdfExtract(quicVersionSalt, conn.DestinationCID) 123 | readSecret := conn.Tls.HkdfExpandLabel(initialSecret, serverInitialLabel, nil, conn.Tls.HashDigestSize(), pigotls.BaseLabel) 124 | writeSecret := conn.Tls.HkdfExpandLabel(initialSecret, clientInitialLabel, nil, conn.Tls.HashDigestSize(), pigotls.BaseLabel) 125 | return NewProtectedCryptoState(conn.Tls, readSecret, writeSecret) 126 | } 127 | 128 | func NewProtectedCryptoState(tls *pigotls.Connection, readSecret []byte, writeSecret []byte) *CryptoState { 129 | s := new(CryptoState) 130 | if len(readSecret) > 0 { 131 | s.InitRead(tls, readSecret) 132 | } 133 | if len(writeSecret) > 0 { 134 | s.InitWrite(tls, writeSecret) 135 | } 136 | return s 137 | } 138 | 139 | func GetPacketSample(header Header, packetBytes []byte) ([]byte, int) { 140 | var pnOffset int 141 | sampleLength := 16 142 | switch h := header.(type) { 143 | case *LongHeader: 144 | pnOffset = h.HeaderLength() - h.TruncatedPN().Length 145 | case *ShortHeader: 146 | pnOffset = 1 + len(h.DestinationCID) 147 | } 148 | 149 | sampleOffset := pnOffset + 4 150 | 151 | if sampleOffset+sampleLength > len(packetBytes) { 152 | // Packet is too short for sampling header protection, it must be padded first 153 | return nil, pnOffset 154 | } 155 | 156 | return packetBytes[sampleOffset:sampleOffset+sampleLength], pnOffset 157 | } 158 | -------------------------------------------------------------------------------- /headers.go: -------------------------------------------------------------------------------- 1 | package quictracker 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | ) 7 | 8 | type PacketType uint8 9 | 10 | const ( 11 | Initial PacketType = 0x0 12 | ZeroRTTProtected PacketType = 0x1 13 | Handshake PacketType = 0x2 14 | Retry PacketType = 0x3 15 | 16 | VersionNegotiation PacketType = 0xfe // TODO: Find a way around this 17 | ShortHeaderPacket PacketType = 0xff // TODO: Find a way around this 18 | ) 19 | 20 | var packetTypeToString = map[PacketType]string{ 21 | Initial: "Initial", 22 | Retry: "Retry", 23 | Handshake: "Handshake", 24 | ZeroRTTProtected: "0-RTT Protected", 25 | 26 | ShortHeaderPacket: "1-RTT Protected", 27 | } 28 | 29 | var packetTypeToPNSPace = map[PacketType]PNSpace { 30 | Initial: PNSpaceInitial, 31 | Retry: PNSpaceNoSpace, 32 | Handshake: PNSpaceHandshake, 33 | ZeroRTTProtected: PNSpaceAppData, 34 | 35 | ShortHeaderPacket: PNSpaceAppData, 36 | } 37 | 38 | type Header interface { 39 | PacketType() PacketType 40 | DestinationConnectionID() ConnectionID 41 | PacketNumber() PacketNumber 42 | TruncatedPN() TruncatedPN 43 | EncryptionLevel() EncryptionLevel 44 | Encode() []byte 45 | HeaderLength() int 46 | } 47 | func ReadHeader(buffer *bytes.Reader, conn *Connection) Header { 48 | var h Header 49 | typeByte, _ := buffer.ReadByte() 50 | buffer.UnreadByte() 51 | if typeByte & 0x80 == 0x80 { 52 | h = ReadLongHeader(buffer, conn) 53 | } else { 54 | h = ReadShortHeader(buffer, conn) 55 | } 56 | return h 57 | } 58 | 59 | type LongHeader struct { 60 | packetType PacketType 61 | lowerBits byte 62 | Version uint32 63 | DestinationCID ConnectionID 64 | SourceCID ConnectionID 65 | TokenLength VarInt 66 | Token []byte 67 | Length VarInt 68 | packetNumber PacketNumber 69 | truncatedPN TruncatedPN 70 | } 71 | func (h *LongHeader) Encode() []byte { 72 | buffer := new(bytes.Buffer) 73 | typeByte := uint8(0xC0) 74 | typeByte |= uint8(h.packetType) << 4 75 | typeByte |= uint8(h.truncatedPN.Length) - 1 76 | binary.Write(buffer, binary.BigEndian, typeByte) 77 | binary.Write(buffer, binary.BigEndian, h.Version) 78 | buffer.WriteByte(h.DestinationCID.CIDL()) 79 | binary.Write(buffer, binary.BigEndian, h.DestinationCID) 80 | buffer.WriteByte(h.SourceCID.CIDL()) 81 | binary.Write(buffer, binary.BigEndian, h.SourceCID) 82 | if h.packetType == Initial { 83 | buffer.Write(h.TokenLength.Encode()) 84 | buffer.Write(h.Token) 85 | } 86 | if h.packetType != Retry { 87 | buffer.Write(h.Length.Encode()) 88 | buffer.Write(h.truncatedPN.Encode()) 89 | } 90 | return buffer.Bytes() 91 | } 92 | func (h *LongHeader) PacketType() PacketType { return h.packetType } 93 | func (h *LongHeader) DestinationConnectionID() ConnectionID { return h.DestinationCID } 94 | func (h *LongHeader) PacketNumber() PacketNumber { return h.packetNumber } 95 | func (h *LongHeader) TruncatedPN() TruncatedPN { return h.truncatedPN } 96 | func (h *LongHeader) EncryptionLevel() EncryptionLevel { return packetTypeToEncryptionLevel[h.PacketType()] } 97 | func (h *LongHeader) HeaderLength() int { 98 | length := 7 + len(h.DestinationCID) + len(h.SourceCID) + h.Length.Length + h.truncatedPN.Length 99 | if h.packetType == Initial { 100 | length += h.TokenLength.Length + len(h.Token) 101 | } 102 | return length 103 | } 104 | func ReadLongHeader(buffer *bytes.Reader, conn *Connection) *LongHeader { 105 | h := new(LongHeader) 106 | typeByte, _ := buffer.ReadByte() 107 | h.lowerBits = typeByte & 0x0F 108 | h.packetType = PacketType(typeByte - 0xC0) >> 4 109 | binary.Read(buffer, binary.BigEndian, &h.Version) 110 | DCIL, _ := buffer.ReadByte() 111 | h.DestinationCID = make([]byte, DCIL, DCIL) 112 | binary.Read(buffer, binary.BigEndian, &h.DestinationCID) 113 | SCIL, _ := buffer.ReadByte() 114 | h.SourceCID = make([]byte, SCIL, SCIL) 115 | binary.Read(buffer, binary.BigEndian, &h.SourceCID) 116 | if h.packetType == Initial { 117 | h.TokenLength, _ = ReadVarInt(buffer) 118 | h.Token = make([]byte, h.TokenLength.Value) 119 | buffer.Read(h.Token) 120 | } 121 | if h.packetType != Retry { 122 | h.Length, _ = ReadVarInt(buffer) 123 | h.truncatedPN = ReadTruncatedPN(buffer, int(typeByte & 0x3) + 1) 124 | h.packetNumber = h.truncatedPN.Join(conn.LargestPNsReceived[h.packetType.PNSpace()]) 125 | } 126 | return h 127 | } 128 | func NewLongHeader(packetType PacketType, conn *Connection, space PNSpace) *LongHeader { 129 | h := new(LongHeader) 130 | h.packetType = packetType 131 | h.Version = conn.Version 132 | h.SourceCID = conn.SourceCID 133 | if packetType == ZeroRTTProtected { 134 | h.DestinationCID = conn.OriginalDestinationCID 135 | } else { 136 | h.DestinationCID = conn.DestinationCID 137 | } 138 | h.TokenLength = NewVarInt(0) 139 | h.packetNumber = conn.nextPacketNumber(space) 140 | h.truncatedPN = h.packetNumber.Truncate(conn.LargestPNsAcknowledged[h.packetType.PNSpace()]) 141 | return h 142 | } 143 | 144 | func (t PacketType) String() string { 145 | return packetTypeToString[t] 146 | } 147 | 148 | func (t PacketType) PNSpace() PNSpace { 149 | return packetTypeToPNSPace[t] 150 | } 151 | 152 | type ShortHeader struct { 153 | SpinBit SpinBit 154 | KeyPhase KeyPhaseBit 155 | DestinationCID ConnectionID 156 | truncatedPN TruncatedPN 157 | packetNumber PacketNumber 158 | } 159 | func (h *ShortHeader) Encode() []byte { 160 | buffer := new(bytes.Buffer) 161 | var typeByte uint8 162 | typeByte |= 0x40 163 | if h.SpinBit == SpinValueOne { 164 | typeByte |= 0x20 165 | } 166 | if h.KeyPhase == KeyPhaseOne { 167 | typeByte |= 0x04 168 | } 169 | typeByte |= uint8(h.truncatedPN.Length) - 1 170 | binary.Write(buffer, binary.BigEndian, typeByte) 171 | binary.Write(buffer, binary.BigEndian, h.DestinationCID) 172 | buffer.Write(h.truncatedPN.Encode()) 173 | 174 | return buffer.Bytes() 175 | } 176 | func (h *ShortHeader) PacketType() PacketType { return ShortHeaderPacket } 177 | func (h *ShortHeader) DestinationConnectionID() ConnectionID { return h.DestinationCID } 178 | func (h *ShortHeader) PacketNumber() PacketNumber { return h.packetNumber } 179 | func (h *ShortHeader) TruncatedPN() TruncatedPN { return h.truncatedPN } 180 | func (h *ShortHeader) EncryptionLevel() EncryptionLevel { return packetTypeToEncryptionLevel[h.PacketType()] } 181 | func (h *ShortHeader) HeaderLength() int { return 1 + len(h.DestinationCID) + h.truncatedPN.Length } 182 | func ReadShortHeader(buffer *bytes.Reader, conn *Connection) *ShortHeader { 183 | h := new(ShortHeader) 184 | typeByte, _ := buffer.ReadByte() 185 | h.SpinBit = (typeByte & 0x20) == 0x20 186 | h.KeyPhase = (typeByte & 0x04) == 0x04 187 | 188 | h.DestinationCID = make([]byte, len(conn.SourceCID)) 189 | buffer.Read(h.DestinationCID) 190 | h.truncatedPN = ReadTruncatedPN(buffer, int(typeByte&0x3) + 1) 191 | h.packetNumber = h.truncatedPN.Join(conn.LargestPNsReceived[PNSpaceAppData]) 192 | return h 193 | } 194 | func NewShortHeader(conn *Connection) *ShortHeader { 195 | h := new(ShortHeader) 196 | h.SpinBit = conn.SpinBit 197 | h.KeyPhase = conn.KeyPhaseIndex % 2 == 1 198 | h.DestinationCID = conn.DestinationCID 199 | h.packetNumber = conn.nextPacketNumber(PNSpaceAppData) 200 | h.truncatedPN = h.packetNumber.Truncate(conn.LargestPNsAcknowledged[PNSpaceAppData]) 201 | return h 202 | } 203 | 204 | type KeyPhaseBit bool 205 | const KeyPhaseZero KeyPhaseBit = false 206 | const KeyPhaseOne KeyPhaseBit = true 207 | 208 | type SpinBit bool 209 | const SpinValueZero SpinBit = false 210 | const SpinValueOne SpinBit = true 211 | -------------------------------------------------------------------------------- /ietf_quic_hosts.txt: -------------------------------------------------------------------------------- 1 | quic.rocks:4433 /index.html 4433 h3 2 | nghttp2.org:4433 /index.html 4433 h3 3 | quant.eggert.org:4433 /index.html 4433 hq 4 | h2o.examp1e.net:443 /notfound 443 h3 5 | quic.examp1e.net:4433 /notfound 4433 hq 6 | quic.westus.cloudapp.azure.com:4433 /index.html 443 hq 7 | fb.mvfst.net:4433 /index.html 4433 hq 8 | cloudflare-quic.com:443 /index.html 443 h3 9 | f5quic.com:4433 /t1.html 4433 h3 10 | test.privateoctopus.com:4433 / 4433 hq 11 | quicker.edm.uhasselt.be:4433 /index.html 4433 hq 12 | h3.stammw.eu:443 /index.html 443 hq 13 | http3-test.litespeedtech.com:4433 /index.html 4433 h3 14 | quic.tech:4433 /index.html 8443 hq 15 | quic.aiortc.org:443 / 443 hq 16 | quic.seemann.io:443 / 443 h3 17 | pandora.cm.in.tum.de:4433 /index.html 4433 hq 18 | test.pquic.org:443 / 443 hq 19 | mew.org:443 / 443 hq 20 | www.haproxy.org:443 / 443 h3 21 | -------------------------------------------------------------------------------- /lib/varint.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | /*This file has been adapted from https://github.com/lucas-clemente/quic-go and 4 | is subject to following license and copyright. 5 | 6 | ------------------------------------------------------------------------------- 7 | 8 | MIT License 9 | 10 | Copyright (c) 2016 the quic-go authors & Google, Inc. 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | 30 | */ 31 | 32 | import ( 33 | "bytes" 34 | "fmt" 35 | "io" 36 | ) 37 | 38 | // taken from the QUIC draft 39 | const ( 40 | maxVarInt1 = 63 41 | maxVarInt2 = 16383 42 | maxVarInt4 = 1073741823 43 | maxVarInt8 = 4611686018427387903 44 | ) 45 | 46 | // ReadVarIntValue reads a number in the QUIC varint format 47 | func ReadVarIntValue(b io.ByteReader) (uint64, int, error) { 48 | firstByte, err := b.ReadByte() 49 | if err != nil { 50 | return 0, 0, err 51 | } 52 | // the first two bits of the first byte encode the length 53 | len := 1 << ((firstByte & 0xc0) >> 6) 54 | b1 := firstByte & (0xff - 0xc0) 55 | if len == 1 { 56 | return uint64(b1), 1, nil 57 | } 58 | b2, err := b.ReadByte() 59 | if err != nil { 60 | return 0, 0, err 61 | } 62 | if len == 2 { 63 | return uint64(b2) + uint64(b1)<<8, 2, nil 64 | } 65 | b3, err := b.ReadByte() 66 | if err != nil { 67 | return 0, 0, err 68 | } 69 | b4, err := b.ReadByte() 70 | if err != nil { 71 | return 0, 0, err 72 | } 73 | if len == 4 { 74 | return uint64(b4) + uint64(b3)<<8 + uint64(b2)<<16 + uint64(b1)<<24, 4, nil 75 | } 76 | b5, err := b.ReadByte() 77 | if err != nil { 78 | return 0, 0, err 79 | } 80 | b6, err := b.ReadByte() 81 | if err != nil { 82 | return 0, 0, err 83 | } 84 | b7, err := b.ReadByte() 85 | if err != nil { 86 | return 0, 0, err 87 | } 88 | b8, err := b.ReadByte() 89 | if err != nil { 90 | return 0, 0, err 91 | } 92 | return uint64(b8) + uint64(b7)<<8 + uint64(b6)<<16 + uint64(b5)<<24 + uint64(b4)<<32 + uint64(b3)<<40 + uint64(b2)<<48 + uint64(b1)<<56, 8, nil 93 | } 94 | 95 | // WriteVarInt writes a number in the QUIC varint format 96 | func WriteVarInt(b *bytes.Buffer, i uint64) { 97 | b.Write(EncodeVarInt(i)) 98 | } 99 | 100 | func EncodeVarInt(i uint64) []byte { 101 | if i <= maxVarInt1 { 102 | return []byte{uint8(i)} 103 | } else if i <= maxVarInt2 { 104 | return []byte{uint8(i>>8) | 0x40, uint8(i)} 105 | } else if i <= maxVarInt4 { 106 | return []byte{uint8(i>>24) | 0x80, uint8(i >> 16), uint8(i >> 8), uint8(i)} 107 | } else if i <= maxVarInt8 { 108 | return []byte{ 109 | uint8(i>>56) | 0xc0, uint8(i >> 48), uint8(i >> 40), uint8(i >> 32), 110 | uint8(i >> 24), uint8(i >> 16), uint8(i >> 8), uint8(i), 111 | } 112 | } else { 113 | panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i)) 114 | } 115 | } 116 | 117 | // VarIntLen determines the number of bytes that will be needed to write a number 118 | func VarIntLen(i uint64) int { 119 | if i <= maxVarInt1 { 120 | return 1 121 | } 122 | if i <= maxVarInt2 { 123 | return 2 124 | } 125 | if i <= maxVarInt4 { 126 | return 4 127 | } 128 | if i <= maxVarInt8 { 129 | return 8 130 | } 131 | panic(fmt.Sprintf("%#x doesn't fit into 62 bits", i)) 132 | } -------------------------------------------------------------------------------- /pcap_capture.go: -------------------------------------------------------------------------------- 1 | package quictracker 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "io/ioutil" 7 | "syscall" 8 | "time" 9 | "os" 10 | "encoding/hex" 11 | ) 12 | 13 | func StartPcapCapture(conn *Connection, netInterface string) (*exec.Cmd, error) { 14 | bpfFilter := fmt.Sprintf("host %s and udp src or dst port %d", conn.Host.IP.String(), conn.Host.Port) 15 | var cmd *exec.Cmd 16 | if netInterface == "" { 17 | cmd = exec.Command("/usr/sbin/tcpdump", bpfFilter, "-w", "/tmp/pcap_" + hex.EncodeToString(conn.OriginalDestinationCID)) 18 | } else { 19 | cmd = exec.Command("/usr/sbin/tcpdump", bpfFilter, "-i", netInterface, "-w", "/tmp/pcap_" + hex.EncodeToString(conn.OriginalDestinationCID)) 20 | } 21 | err := cmd.Start() 22 | if err == nil { 23 | time.Sleep(1 * time.Second) 24 | } 25 | return cmd, err 26 | } 27 | 28 | func StopPcapCapture(conn *Connection, cmd *exec.Cmd) ([]byte, error) { 29 | time.Sleep(1 * time.Second) 30 | cmd.Process.Signal(syscall.SIGTERM) 31 | err := cmd.Wait() 32 | if err != nil { 33 | return nil, err 34 | } 35 | defer os.Remove("/tmp/pcap_" + hex.EncodeToString(conn.OriginalDestinationCID)) 36 | return ioutil.ReadFile("/tmp/pcap_" + hex.EncodeToString(conn.OriginalDestinationCID)) 37 | } -------------------------------------------------------------------------------- /qlog/frames.go: -------------------------------------------------------------------------------- 1 | package qlog 2 | 3 | type PingFrame struct { 4 | FrameType string `json:"frame_type"` 5 | } 6 | 7 | type AckFrame struct { 8 | FrameType string `json:"frame_type"` 9 | ACKDelay uint64 `json:"ack_delay,string"` 10 | ACKedRanges [][]uint64 `json:"acked_ranges"` 11 | 12 | ECT1 uint64 `json:"ect1,omitempty"` 13 | ECT0 uint64 `json:"ect0,omitempty"` 14 | CE uint64 `json:"ce,omitempty"` 15 | } 16 | 17 | type StreamFrame struct { 18 | FrameType string `json:"frame_type"` 19 | StreamID uint64 `json:"stream_id,string"` 20 | Offset uint64 `json:"offset,string"` 21 | Length uint64 `json:"length,string"` 22 | Fin bool `json:"fin,omitempty"` 23 | } 24 | 25 | type ResetStreamFrame struct { 26 | FrameType string `json:"frame_type"` 27 | StreamID uint64 `json:"stream_id,string"` 28 | ErrorCode uint64 `json:"error_code,string"` 29 | FinalOffset uint64 `json:"final_offset,string"` 30 | } 31 | 32 | type StopSendingFrame struct { 33 | FrameType string `json:"frame_type"` 34 | StreamID uint64 `json:"stream_id,string"` 35 | ErrorCode uint64 `json:"error_code,string"` 36 | } 37 | 38 | type CryptoFrame struct { 39 | FrameType string `json:"frame_type"` 40 | Offset uint64 `json:"offset,string"` 41 | Length uint64 `json:"length,string"` 42 | } 43 | 44 | type NewTokenFrame struct { 45 | FrameType string `json:"frame_type"` 46 | Length uint64 `json:"length,string"` 47 | Token string `json:"token"` 48 | } 49 | 50 | type ConnectionCloseFrame struct { 51 | FrameType string `json:"frame_type"` 52 | ErrorSpace string `json:"error_space"` 53 | ErrorCode uint64 `json:"error_code,string"` 54 | Reason string `json:"reason"` 55 | } 56 | 57 | type MaxDataFrame struct { 58 | FrameType string `json:"frame_type"` 59 | Maximum uint64 `json:"maximum,string"` 60 | } 61 | 62 | type MaxStreamDataFrame struct { 63 | FrameType string `json:"frame_type"` 64 | StreamID uint64 `json:"stream_id,string"` 65 | Maximum uint64 `json:"maximum,string"` 66 | } 67 | 68 | type MaxStreamsFrame struct { 69 | FrameType string `json:"frame_type"` 70 | StreamType `json:"stream_type"` 71 | Maximum uint64 `json:"maximum,string"` 72 | } 73 | 74 | type DataBlockedFrame struct { 75 | FrameType string `json:"frame_type"` 76 | Limit uint64 `json:"limit,string"` 77 | } 78 | 79 | type StreamDataBlockedFrame struct { 80 | FrameType string `json:"frame_type"` 81 | StreamID uint64 `json:"stream_id,string"` 82 | Limit uint64 `json:"limit,string"` 83 | } 84 | 85 | type StreamsBlockedFrame struct { 86 | FrameType string `json:"frame_type"` 87 | StreamType `json:"stream_type"` 88 | Limit uint64 `json:"limit,string"` 89 | } 90 | 91 | type NewConnectionIDFrame struct { 92 | FrameType string `json:"frame_type"` 93 | SequenceNumber uint64 `json:"sequence_number,string"` 94 | RetirePriorTo uint64 `json:"retire_prior_to,string"` 95 | Length uint8 `json:"length"` 96 | ConnectionID string `json:"connection_id"` 97 | ResetToken string `json:"reset_token"` 98 | } 99 | 100 | type RetireConnectionIDFrame struct { 101 | FrameType string `json:"frame_type"` 102 | SequenceNumber uint64 `json:"sequence_number,string"` 103 | } 104 | 105 | type PathChallengeFrame struct { 106 | FrameType string `json:"frame_type"` 107 | Data string `json:"data"` 108 | } 109 | 110 | type PathResponseFrame struct { 111 | FrameType string `json:"frame_type"` 112 | Data string `json:"data"` 113 | } 114 | 115 | type HandshakeDoneFrame struct { 116 | FrameType string `json:"frame_type"` 117 | } 118 | 119 | type UnknownFrame struct { 120 | FrameType string `json:"frame_type"` 121 | RawFrameType uint64 `json:"raw_frame_type,string"` 122 | } 123 | -------------------------------------------------------------------------------- /qlog/packets.go: -------------------------------------------------------------------------------- 1 | package qlog 2 | 3 | type PacketTrigger string 4 | 5 | const ( 6 | PacketTriggerReordering = "retransmit_reorder" 7 | PacketTriggerTimeout = "retransmit_timeout" 8 | PacketTriggerPTO = "pro_probe" 9 | PacketTriggerCrypto = "retransmit_crypto" 10 | PacketTriggerBWProbe = "cc_bandwidth_probe" 11 | ) 12 | 13 | type PacketHeader struct { 14 | PacketNumber uint64 `json:"packet_number,string"` 15 | PacketSize int `json:"packet_size,omitempty"` 16 | PayloadLength int `json:"payload_length,omitempty"` 17 | 18 | Version string `json:"version,omitempty"` 19 | SCIL string `json:"scil,omitempty"` 20 | DCIL string `json:"dcil,omitempty"` 21 | SCID string `json:"scid,omitempty"` 22 | DCID string `json:"dcid,omitempty"` 23 | } 24 | 25 | type Packet struct { 26 | PacketType string `json:"packet_type"` 27 | Header PacketHeader `json:"header"` 28 | Frames []interface{} `json:"frames,omitempty"` 29 | 30 | IsCoalesced bool `json:"is_coalesced,omitempty"` 31 | Trigger string `json:"trigger,omitempty"` 32 | } 33 | 34 | type PacketLost struct { 35 | PacketType string `json:"packet_type"` 36 | PacketNumber uint64 `json:"packet_number,string"` 37 | Frames []interface{} `json:"frames"` 38 | Trigger string `json:"trigger"` 39 | } 40 | 41 | type PacketBuffered struct { 42 | PacketType string `json:"packet_type"` 43 | Trigger string `json:"trigger"` 44 | } 45 | -------------------------------------------------------------------------------- /qlog/qlog.go: -------------------------------------------------------------------------------- 1 | package qlog 2 | 3 | import ( 4 | "encoding/json" 5 | "sort" 6 | "time" 7 | ) 8 | 9 | const ( 10 | TimeUnits = time.Microsecond 11 | TimeUnitsString = "us" 12 | ) 13 | 14 | type StreamType string 15 | 16 | const ( 17 | StreamTypeBidi StreamType = "bidirectional" 18 | StreamTypeUni StreamType = "unidirectional" 19 | ) 20 | 21 | var Categories = struct { 22 | Connectivity struct { 23 | Category string 24 | ServerListening string 25 | ConnectionStarted string 26 | ConnectionIDUpdated string 27 | SpinBitUpdated string 28 | ConnectionRetried string 29 | ConnectionStateUpdated string 30 | } 31 | Transport struct { 32 | Category string 33 | PacketSent string 34 | PacketReceived string 35 | PacketDropped string 36 | PacketBuffered string 37 | StreamStateUpdated string 38 | } 39 | Recovery struct { 40 | Category string 41 | MetricsUpdated string 42 | CongestionStateUpdated string 43 | LossTimerSet string 44 | LossTimerFired string 45 | PacketLost string 46 | MarkedForRetransmit string 47 | } 48 | }{ 49 | struct { 50 | Category string 51 | ServerListening string 52 | ConnectionStarted string 53 | ConnectionIDUpdated string 54 | SpinBitUpdated string 55 | ConnectionRetried string 56 | ConnectionStateUpdated string 57 | }{"connectivity", "server_listening", "connection_started", "connection_id_updated", "spin_bit_updated", "connection_retried", "connection_state_updated"}, 58 | struct { 59 | Category string 60 | PacketSent string 61 | PacketReceived string 62 | PacketDropped string 63 | PacketBuffered string 64 | StreamStateUpdated string 65 | }{"transport", "packet_sent", "packet_received", "packet_dropped", "packet_buffered", "stream_state_updated"}, 66 | struct { 67 | Category string 68 | MetricsUpdated string 69 | CongestionStateUpdated string 70 | LossTimerSet string 71 | LossTimerFired string 72 | PacketLost string 73 | MarkedForRetransmit string 74 | }{"recovery", "metrics_updated", "congestion_state_updated", "loss_timer_set", "loss_timer_fired", "packet_lost", "marked_for_retransmit"}, 75 | } 76 | 77 | type Event struct { 78 | RelativeTime uint64 79 | Category string 80 | Event string 81 | Data interface{} 82 | } 83 | 84 | func (e *Event) MarshalJSON() ([]byte, error) { 85 | return json.Marshal([]interface{}{e.RelativeTime, e.Category, e.Event, e.Data}) 86 | } 87 | 88 | func DefaultEventFields() []string { 89 | return []string{"relative_time", "category", "event", "data"} 90 | } 91 | 92 | type Trace struct { 93 | VantagePoint struct { 94 | Name string `json:"name"` 95 | Type string `json:"type"` 96 | } `json:"vantage_point"` 97 | Title string `json:"title"` 98 | Description string `json:"description"` 99 | Configuration struct { 100 | TimeOffset uint64 `json:"time_offset,string"` 101 | TimeUnits string `json:"time_units"` 102 | } `json:"configuration"` 103 | CommonFields map[string]interface{} `json:"common_fields"` 104 | EventFields []string `json:"event_fields"` 105 | Events []*Event `json:"events"` 106 | 107 | ReferenceTime time.Time `json:"-"` 108 | } 109 | 110 | func (t *Trace) NewEvent(category, eventType string, data interface{}) *Event { 111 | e := new(Event) 112 | e.RelativeTime = uint64(time.Now().Sub(t.ReferenceTime) / TimeUnits) 113 | e.Category = category 114 | e.Event = eventType 115 | e.Data = data 116 | return e 117 | } 118 | 119 | func (t *Trace) Add(e *Event) { 120 | t.Events = append(t.Events, e) 121 | } 122 | 123 | func (t *Trace) Sort() { 124 | sort.Slice(t.Events, func(i, j int) bool { 125 | return t.Events[i].RelativeTime < t.Events[j].RelativeTime 126 | }) 127 | } 128 | 129 | type QLog struct { 130 | Version string `json:"qlog_version"` 131 | Title string `json:"title"` 132 | Description string `json:"description"` 133 | Summary map[string]interface{} `json:"summary"` 134 | Traces []*Trace `json:"traces"` 135 | } 136 | -------------------------------------------------------------------------------- /qlog/qt2qlog/qt2qlog.go: -------------------------------------------------------------------------------- 1 | package qt2qlog 2 | 3 | import ( 4 | "encoding/hex" 5 | . "github.com/QUIC-Tracker/quic-tracker" 6 | "github.com/QUIC-Tracker/quic-tracker/qlog" 7 | "strconv" 8 | ) 9 | 10 | var qlogPacketType = map[PacketType]string{ 11 | Initial: "initial", 12 | Handshake: "handshake", 13 | ZeroRTTProtected: "0RTT", 14 | ShortHeaderPacket: "1RTT", 15 | Retry: "retry", 16 | VersionNegotiation: "version_negotiation", 17 | } 18 | 19 | func ConvertPacket(p Packet) *qlog.Packet { 20 | j := &qlog.Packet{} 21 | switch p.(type) { 22 | case *InitialPacket, *HandshakePacket, *ZeroRTTProtectedPacket, *ProtectedPacket: 23 | j.PacketType = qlogPacketType[p.Header().PacketType()] 24 | case *RetryPacket: 25 | j.PacketType = qlogPacketType[Retry] 26 | case *VersionNegotiationPacket: 27 | j.PacketType = qlogPacketType[VersionNegotiation] 28 | default: 29 | j.PacketType = "unknown" 30 | } 31 | // TODO handle PacketSize computation here 32 | switch p.(type) { 33 | case *VersionNegotiationPacket, *RetryPacket, *StatelessResetPacket: 34 | default: 35 | j.Header.PacketNumber = uint64(p.Header().PacketNumber()) 36 | } 37 | 38 | switch h := p.Header().(type) { 39 | case *ShortHeader: 40 | j.Header.DCIL = strconv.Itoa(int(h.DestinationCID.CIDL())) 41 | j.Header.DCID = h.DestinationCID.String() 42 | case *LongHeader: 43 | j.Header.PayloadLength = int(h.Length.Value) 44 | j.Header.SCIL = strconv.Itoa(int(h.SourceCID.CIDL())) 45 | j.Header.SCID = h.SourceCID.String() 46 | j.Header.DCIL = strconv.Itoa(int(h.DestinationCID.CIDL())) 47 | j.Header.DCID = h.DestinationCID.String() 48 | } 49 | 50 | switch fr := p.(type) { 51 | case Framer: 52 | j.Frames = convertFrames(fr.GetFrames()) 53 | } 54 | if p.ReceiveContext().WasBuffered { 55 | j.Trigger = "keys_unavailable" 56 | } 57 | return j 58 | } 59 | 60 | func convertFrames(frames []Frame) []interface{} { 61 | var ret []interface{} 62 | for _, f := range frames { 63 | var qf interface{} 64 | switch ft := f.(type) { 65 | case *PingFrame: 66 | qf = &qlog.PingFrame{"ping"} 67 | case *AckFrame: 68 | qf = ackFrameToQLog(ft) 69 | case *AckECNFrame: 70 | qf = ackECNFrameToQLog(ft) 71 | case *StreamFrame: 72 | qf = &qlog.StreamFrame{ 73 | FrameType: "stream", 74 | StreamID: ft.StreamId, 75 | Offset: ft.Offset, 76 | Length: ft.Length, 77 | Fin: ft.FinBit, 78 | } 79 | case *ResetStream: 80 | qf = &qlog.ResetStreamFrame{ 81 | FrameType: "reset_stream", 82 | StreamID: ft.StreamId, 83 | ErrorCode: ft.ApplicationErrorCode, 84 | FinalOffset: ft.FinalSize, 85 | } 86 | case *StopSendingFrame: 87 | qf = &qlog.StopSendingFrame{ 88 | FrameType: "stop_sending", 89 | StreamID: ft.StreamId, 90 | ErrorCode: ft.ApplicationErrorCode, 91 | } 92 | case *CryptoFrame: 93 | qf = &qlog.CryptoFrame{ 94 | FrameType: "crypto", 95 | Offset: ft.Offset, 96 | Length: ft.Length, 97 | } 98 | case *NewTokenFrame: 99 | qf = &qlog.NewTokenFrame{ 100 | FrameType: "new_token", 101 | Length: uint64(len(ft.Token)), 102 | Token: hex.EncodeToString(ft.Token), 103 | } 104 | case *ConnectionCloseFrame: 105 | qf = &qlog.ConnectionCloseFrame{FrameType: "connection_close", 106 | ErrorSpace: "transport", 107 | ErrorCode: ft.ErrorCode, 108 | Reason: ft.ReasonPhrase, 109 | } 110 | case *ApplicationCloseFrame: 111 | qf = &qlog.ConnectionCloseFrame{ 112 | FrameType: "connection_close", 113 | ErrorSpace: "application", 114 | ErrorCode: ft.ErrorCode, 115 | Reason: ft.ReasonPhrase, 116 | } 117 | case *MaxDataFrame: 118 | qf = &qlog.MaxDataFrame{ 119 | FrameType: "max_data", 120 | Maximum: ft.MaximumData, 121 | } 122 | case *MaxStreamDataFrame: 123 | qf = &qlog.MaxStreamDataFrame{ 124 | FrameType: "max_stream_data", 125 | StreamID: ft.StreamId, 126 | Maximum: ft.MaximumStreamData, 127 | } 128 | case *MaxStreamsFrame: 129 | qf = maxStreamsToQLog(ft) 130 | case *NewConnectionIdFrame: 131 | qf = &qlog.NewConnectionIDFrame{ 132 | FrameType: "new_connection_id", 133 | SequenceNumber: ft.Sequence, 134 | RetirePriorTo: ft.RetirePriorTo, 135 | Length: uint8(len(ft.ConnectionId)), 136 | ConnectionID: hex.EncodeToString(ft.ConnectionId), 137 | ResetToken: hex.EncodeToString(ft.StatelessResetToken[:]), 138 | } 139 | case *RetireConnectionId: 140 | qf = &qlog.RetireConnectionIDFrame{ 141 | FrameType: "retire_connection_id", 142 | SequenceNumber: ft.SequenceNumber, 143 | } 144 | case *PathChallenge: 145 | qf = &qlog.PathChallengeFrame{ 146 | FrameType: "path_challenge", 147 | Data: hex.EncodeToString(ft.Data[:]), 148 | } 149 | case *PathResponse: 150 | qf = &qlog.PathResponseFrame{ 151 | FrameType: "path_response", 152 | Data: hex.EncodeToString(ft.Data[:]), 153 | } 154 | case *HandshakeDoneFrame: 155 | qf = &qlog.HandshakeDoneFrame{ 156 | FrameType: "handshake_done", 157 | } 158 | case *PaddingFrame: 159 | continue 160 | default: 161 | qf = unknownFrameToQLog(ft) 162 | } 163 | ret = append(ret, qf) 164 | } 165 | return ret 166 | } 167 | 168 | func ackFrameToQLog(a *AckFrame) *qlog.AckFrame { 169 | q := qlog.AckFrame{FrameType: "ack", ACKDelay: a.AckDelay} 170 | 171 | largest := uint64(a.LargestAcknowledged) 172 | rang := a.AckRanges[0].AckRange 173 | 174 | q.ACKedRanges = append(q.ACKedRanges, []uint64{largest - rang, largest}) 175 | largest -= rang 176 | 177 | for _, ar := range a.AckRanges[1:] { 178 | q.ACKedRanges = append(q.ACKedRanges, []uint64{largest - ar.Gap - 1 - ar.AckRange, largest - ar.Gap - 1}) 179 | largest -= ar.Gap + 1 + ar.AckRange 180 | } 181 | return &q 182 | } 183 | 184 | func ackECNFrameToQLog(a *AckECNFrame) *qlog.AckFrame { 185 | q := ackFrameToQLog(&a.AckFrame) 186 | q.ECT0 = a.ECT0Count 187 | q.ECT1 = a.ECT1Count 188 | q.CE = a.ECTCECount 189 | return q 190 | } 191 | 192 | func maxStreamsToQLog(m *MaxStreamsFrame) *qlog.MaxStreamsFrame { 193 | sType := qlog.StreamTypeBidi 194 | if m.StreamsType == UniStreams { 195 | sType = qlog.StreamTypeUni 196 | } 197 | return &qlog.MaxStreamsFrame{FrameType: "max_streams", StreamType: sType, Maximum: m.MaximumStreams} 198 | } 199 | 200 | func unknownFrameToQLog(u Frame) *qlog.UnknownFrame { 201 | return &qlog.UnknownFrame{FrameType: "unknown", RawFrameType: uint64(u.FrameType())} 202 | } 203 | 204 | func ConvertPacketLost(packetType PacketType, number PacketNumber, frames []Frame, trigger string) *qlog.PacketLost { 205 | j := &qlog.PacketLost{Frames: convertFrames(frames), Trigger: trigger} 206 | if pType, ok := qlogPacketType[packetType]; ok { 207 | j.PacketType = pType 208 | } else { 209 | j.PacketType = "unknown" 210 | } 211 | j.PacketNumber = uint64(number) 212 | return j 213 | } 214 | 215 | func ConvertPacketBuffered(packetType PacketType, trigger string) *qlog.PacketBuffered { 216 | var typeStr string 217 | if pType, ok := qlogPacketType[packetType]; ok { 218 | typeStr = pType 219 | } else { 220 | typeStr = "unknown" 221 | } 222 | return &qlog.PacketBuffered{PacketType: typeStr, Trigger: trigger} 223 | } -------------------------------------------------------------------------------- /qlog/recovery.go: -------------------------------------------------------------------------------- 1 | package qlog 2 | 3 | type MetricUpdate struct { 4 | CongestionWindow uint64 `json:"congestion_window,omitempty"` 5 | BytesInFlight uint64 `json:"bytes_in_flight,omitempty"` 6 | MinRTT uint64 `json:"min_rtt,omitempty"` 7 | SmoothedRTT uint64 `json:"smoothed_rtt,omitempty"` 8 | LatestRTT uint64 `json:"latest_rtt,omitempty"` 9 | MaxAckDelay uint64 `json:"max_ack_delay,omitempty"` 10 | RTTVariance uint64 `json:"rtt_variance,omitempty"` 11 | SSThresh uint64 `json:"ssthresh,omitempty"` 12 | PacingRate uint64 `json:"pacing_rate,omitempty"` 13 | } 14 | -------------------------------------------------------------------------------- /scenarii/ack_ecn.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | ) 7 | 8 | const ( 9 | AE_TLSHandshakeFailed = 1 10 | AE_FailedToSetECN = 2 11 | AE_NonECN = 3 12 | AE_NoACKECNReceived = 4 13 | AE_NonECNButACKECN = 5 14 | ) 15 | 16 | type AckECNScenario struct { 17 | AbstractScenario 18 | } 19 | 20 | func NewAckECNScenario() *AckECNScenario { 21 | return &AckECNScenario{AbstractScenario{name: "ack_ecn", version: 1}} 22 | } 23 | func (s *AckECNScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 24 | connAgents := s.CompleteHandshake(conn, trace, AE_TLSHandshakeFailed) 25 | if connAgents == nil { 26 | return 27 | } 28 | defer connAgents.CloseConnection(false, 0, "") 29 | 30 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 31 | socketAgent := connAgents.Get("SocketAgent").(*agents.SocketAgent) 32 | 33 | err := socketAgent.ConfigureECN() 34 | if err != nil { 35 | trace.MarkError(AE_FailedToSetECN, err.Error(), nil) 36 | return 37 | } 38 | 39 | connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 40 | 41 | trace.ErrorCode = AE_NonECN 42 | for { 43 | select { 44 | case i := <-incPackets: 45 | switch p := i.(type) { 46 | case qt.Framer: 47 | if p.Contains(qt.AckECNType) { 48 | if trace.ErrorCode == AE_NonECN { 49 | trace.ErrorCode = AE_NonECNButACKECN 50 | } else if trace.ErrorCode == AE_NoACKECNReceived { 51 | trace.ErrorCode = 0 52 | } 53 | } 54 | } 55 | switch i.(qt.Packet).ReceiveContext().ECNStatus { 56 | case qt.ECNStatusNonECT: 57 | case qt.ECNStatusECT_0, qt.ECNStatusECT_1, qt.ECNStatusCE: 58 | if trace.ErrorCode == AE_NonECN { 59 | trace.ErrorCode = AE_NoACKECNReceived 60 | } else if trace.ErrorCode == AE_NonECNButACKECN { 61 | trace.ErrorCode = 0 62 | } 63 | } 64 | case <-conn.ConnectionClosed: 65 | return 66 | case <-s.Timeout(): 67 | return 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /scenarii/ack_only.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | ) 6 | 7 | const ( 8 | AO_TLSHandshakeFailed = 1 9 | AO_SentAOInResponseOfAO = 2 10 | ) 11 | 12 | type AckOnlyScenario struct { 13 | AbstractScenario 14 | } 15 | 16 | func NewAckOnlyScenario() *AckOnlyScenario { 17 | return &AckOnlyScenario{AbstractScenario{name: "ack_only", version: 1}} 18 | } 19 | func (s *AckOnlyScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 20 | connAgents := s.CompleteHandshake(conn, trace, AO_TLSHandshakeFailed) 21 | if connAgents == nil { 22 | return 23 | } 24 | defer connAgents.CloseConnection(false, 0, "") 25 | connAgents.Stop("AckAgent") 26 | 27 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 28 | 29 | connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 30 | 31 | var ackOnlyPackets []qt.PacketNumber 32 | 33 | for { 34 | select { 35 | case i := <-incPackets: 36 | p := i.(qt.Packet) 37 | if p.PNSpace() != qt.PNSpaceNoSpace { 38 | conn.AckQueue[p.PNSpace()] = append(conn.AckQueue[p.PNSpace()], p.Header().PacketNumber()) 39 | } 40 | if p.ShouldBeAcknowledged() { 41 | ackFrame := conn.GetAckFrame(p.PNSpace()) 42 | if ackFrame == nil { 43 | break 44 | } 45 | var packet qt.Framer 46 | switch p.PNSpace() { 47 | case qt.PNSpaceInitial: 48 | packet = qt.NewInitialPacket(conn) 49 | case qt.PNSpaceHandshake: 50 | packet = qt.NewHandshakePacket(conn) 51 | case qt.PNSpaceAppData: 52 | packet = qt.NewProtectedPacket(conn) 53 | } 54 | 55 | packet.AddFrame(ackFrame) 56 | conn.DoSendPacket(packet, packet.EncryptionLevel()) 57 | if p.PNSpace() == qt.PNSpaceAppData { 58 | ackOnlyPackets = append(ackOnlyPackets, packet.Header().PacketNumber()) 59 | } 60 | } else if packet, ok := p.(*qt.ProtectedPacket); ok && packet.Contains(qt.AckType) { 61 | ack := packet.GetFirst(qt.AckType).(*qt.AckFrame) 62 | if containsAll(ack.GetAckedPackets(), ackOnlyPackets) { 63 | trace.MarkError(AO_SentAOInResponseOfAO, "", p) 64 | return 65 | } 66 | } 67 | case <-conn.ConnectionClosed: 68 | return 69 | case <-s.Timeout(): 70 | return 71 | } 72 | } 73 | } 74 | 75 | func containsAll(a []qt.PacketNumber, b []qt.PacketNumber) bool { // Checks a \in b 76 | for _, i := range a { 77 | contains := false 78 | for _, j := range b { 79 | if i == j { 80 | contains = true 81 | break 82 | } 83 | } 84 | if !contains { 85 | return false 86 | } 87 | } 88 | return true 89 | } 90 | -------------------------------------------------------------------------------- /scenarii/address_validation.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | "time" 7 | ) 8 | 9 | const ( 10 | AV_TLSHandshakeFailed = 1 11 | AV_SentMoreThan3Datagrams = 2 12 | AV_SentMoreThan3TimesAmount = 3 13 | AV_HostTimedOut = 4 14 | ) 15 | 16 | type AddressValidationScenario struct { 17 | AbstractScenario 18 | } 19 | 20 | func NewAddressValidationScenario() *AddressValidationScenario { 21 | return &AddressValidationScenario{AbstractScenario{name: "address_validation", version: 3}} 22 | } 23 | func (s *AddressValidationScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 24 | connAgents := agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 25 | defer connAgents.StopAll() 26 | 27 | ackAgent := connAgents.Get("AckAgent").(*agents.AckAgent) 28 | ackAgent.DisableAcks[qt.PNSpaceInitial] = true 29 | ackAgent.DisableAcks[qt.PNSpaceHandshake] = true 30 | ackAgent.DisableAcks[qt.PNSpaceAppData] = true 31 | socketAgent := connAgents.Get("SocketAgent").(*agents.SocketAgent) 32 | tlsAgent := connAgents.Get("TLSAgent").(*agents.TLSAgent) 33 | tlsAgent.DisableFrameSending = true 34 | fcAgent := connAgents.Get("FlowControlAgent").(*agents.FlowControlAgent) 35 | fcAgent.DontSlideCreditWindow = true 36 | 37 | handshakeAgent := &agents.HandshakeAgent{TLSAgent: tlsAgent, SocketAgent: connAgents.Get("SocketAgent").(*agents.SocketAgent)} 38 | handshakeAgent.IgnoreRetry = true 39 | connAgents.Add(handshakeAgent) 40 | handshakeStatus := handshakeAgent.HandshakeStatus.RegisterNewChan(10) 41 | connAgents.Get("SendingAgent").(*agents.SendingAgent).FrameProducer = connAgents.GetFrameProducingAgents() 42 | 43 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 44 | outgoingPackets := conn.OutgoingPackets.RegisterNewChan(1000) 45 | 46 | handshakeAgent.InitiateHandshake() 47 | 48 | var arrivals []uint64 49 | var start time.Time 50 | var initialLength int 51 | var recvd bool 52 | 53 | trace.ErrorCode = 0 54 | 55 | forLoop: 56 | for { 57 | select { 58 | case i := <-incomingPackets: 59 | recvd = true 60 | var isRetransmit bool 61 | switch p := i.(type) { 62 | case *qt.InitialPacket: 63 | if p.Contains(qt.CryptoType) { 64 | for _, f := range p.GetAll(qt.CryptoType) { 65 | if f.(*qt.CryptoFrame).Offset == 0 { 66 | isRetransmit = true 67 | } 68 | } 69 | } 70 | } 71 | 72 | if isRetransmit { 73 | if start.IsZero() { 74 | start = time.Now() 75 | } 76 | arrivals = append(arrivals, uint64(time.Now().Sub(start).Seconds()*1000)) 77 | if len(arrivals) > 1 { 78 | trace.Results["arrival_times"] = arrivals 79 | } 80 | } 81 | 82 | if float32(socketAgent.TotalDataReceived) / float32(initialLength) > 3.5 { 83 | trace.MarkError(AV_SentMoreThan3TimesAmount, "", i.(qt.Packet)) 84 | } 85 | case i := <-outgoingPackets: 86 | p := i.(qt.Packet) 87 | if p.Header().PacketNumber() == 0 && p.Header().PacketType() == qt.Initial { 88 | initialLength = len(p.Encode(p.EncodePayload())) 89 | } 90 | case i := <-handshakeStatus: 91 | status := i.(agents.HandshakeStatus) 92 | if !status.Completed { 93 | trace.MarkError(AV_TLSHandshakeFailed, status.Error.Error(), status.Packet) 94 | break forLoop 95 | } else { 96 | defer connAgents.CloseConnection(false, 0, "") 97 | trace.ErrorCode = 0 98 | } 99 | case <-conn.ConnectionClosed: 100 | return 101 | case <-s.Timeout(): 102 | break forLoop 103 | } 104 | } 105 | 106 | if !recvd { 107 | trace.ErrorCode = AV_HostTimedOut 108 | } 109 | 110 | trace.Results["amplification_factor"] = float32(socketAgent.TotalDataReceived) / float32(initialLength) 111 | trace.Results["datagrams_received"] = socketAgent.DatagramsReceived 112 | trace.Results["total_data_received"] = socketAgent.TotalDataReceived 113 | } 114 | -------------------------------------------------------------------------------- /scenarii/closed_connection.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "time" 6 | ) 7 | 8 | const ( 9 | CCS_TLSHandshakeFailed = 1 10 | CCS_NoPacketsReceived = 2 11 | CSS_APacketWasReceived = 3 12 | ) 13 | 14 | type ClosedConnectionScenario struct { 15 | AbstractScenario 16 | } 17 | 18 | func NewClosedConnectionScenario() *ClosedConnectionScenario { 19 | return &ClosedConnectionScenario{AbstractScenario{name: "closed_connection", version: 1}} 20 | } 21 | func (s *ClosedConnectionScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 22 | connAgents := s.CompleteHandshake(conn, trace, CCS_TLSHandshakeFailed) 23 | if connAgents == nil { 24 | return 25 | } 26 | <-time.NewTimer(time.Duration(2 * conn.SmoothedRTT) * time.Microsecond).C 27 | conn.CloseConnection(false, 0, "") 28 | <-time.NewTimer(time.Duration(8 * conn.SmoothedRTT) * time.Microsecond).C 29 | 30 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 31 | 32 | for i := 0; i < 3; i++ { 33 | ping := qt.PingFrame(0) 34 | conn.FrameQueue.Submit(qt.QueuedFrame{&ping, qt.EncryptionLevel1RTT}) 35 | <-time.NewTimer(time.Duration(3 * conn.SmoothedRTT) * time.Microsecond).C 36 | } 37 | 38 | trace.ErrorCode = CCS_NoPacketsReceived 39 | for { 40 | select { 41 | case <-incomingPackets: 42 | trace.ErrorCode = CSS_APacketWasReceived 43 | case <-conn.ConnectionClosed: 44 | return 45 | case <-s.Timeout(): 46 | return 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /scenarii/connection_migration.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "bytes" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | "math/rand" 7 | 8 | "time" 9 | ) 10 | 11 | const ( 12 | CM_TLSHandshakeFailed = 1 13 | CM_UDPConnectionFailed = 2 14 | CM_HostDidNotMigrate = 3 15 | CM_HostDidNotValidateNewPath = 4 16 | CM_TooManyCIDs = 5 17 | ) 18 | 19 | type ConnectionMigrationScenario struct { 20 | AbstractScenario 21 | } 22 | 23 | func NewConnectionMigrationScenario() *ConnectionMigrationScenario { 24 | return &ConnectionMigrationScenario{AbstractScenario{name: "connection_migration", version: 1}} 25 | } 26 | func (s *ConnectionMigrationScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 27 | connAgents := s.CompleteHandshake(conn, trace, CM_TLSHandshakeFailed) 28 | if connAgents == nil { 29 | return 30 | } 31 | defer connAgents.CloseConnection(false, 0, "") 32 | 33 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 34 | t := time.NewTimer(3 * time.Second) 35 | 36 | scid := make([]byte, 8) 37 | var resetToken [16]byte 38 | rand.Read(scid) 39 | rand.Read(resetToken[:]) 40 | conn.FrameQueue.Submit(qt.QueuedFrame{&qt.NewConnectionIdFrame{1, 0, uint8(len(scid)), scid, resetToken}, qt.EncryptionLevelBest}) 41 | 42 | var ncid []byte 43 | wait: 44 | for { 45 | select { 46 | case i := <-incPackets: 47 | p := i.(qt.Packet) 48 | if fp, ok := p.(qt.Framer); ok && fp.Header().PacketType() == qt.ShortHeaderPacket && fp.Contains(qt.NewConnectionIdType) { 49 | ncids := fp.GetAll(qt.NewConnectionIdType) 50 | if len(ncids) > int(conn.TLSTPHandler.ActiveConnectionIdLimit) { 51 | trace.MarkError(CM_TooManyCIDs, "", p) 52 | return 53 | } 54 | ncid = ncids[0].(*qt.NewConnectionIdFrame).ConnectionId 55 | } 56 | case <-t.C: 57 | conn.IncomingPackets.Unregister(incPackets) 58 | incPackets = nil 59 | break wait 60 | } 61 | } 62 | 63 | connAgents.Stop("SocketAgent", "SendingAgent") 64 | 65 | newUdpConn, err := qt.EstablishUDPConnection(conn.Host) 66 | if err != nil { 67 | trace.ErrorCode = CM_UDPConnectionFailed 68 | return 69 | } 70 | 71 | conn.UdpConnection.Close() 72 | conn.UdpConnection = newUdpConn 73 | 74 | connAgents.Get("SocketAgent").Run(conn) 75 | connAgents.Get("SendingAgent").Run(conn) 76 | conn.EncryptionLevels.Submit(qt.DirectionalEncryptionLevel{EncryptionLevel: qt.EncryptionLevel1RTT, Available: true}) 77 | 78 | incPackets = conn.IncomingPackets.RegisterNewChan(1000) 79 | 80 | responseChan := connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 81 | trace.ErrorCode = CM_HostDidNotMigrate // Assume it until proven wrong 82 | 83 | for { 84 | select { 85 | case i := <-incPackets: 86 | p := i.(qt.Packet) 87 | if trace.ErrorCode == CM_HostDidNotMigrate { 88 | trace.ErrorCode = CM_HostDidNotValidateNewPath 89 | if bytes.Equal(p.Header().DestinationConnectionID(), scid) && ncid != nil { 90 | conn.SourceCID = scid 91 | conn.DestinationCID = ncid 92 | } 93 | } 94 | 95 | if fp, ok := p.(qt.Framer); ok && fp.Contains(qt.PathChallengeType) { 96 | trace.ErrorCode = 0 97 | } 98 | case <-responseChan: 99 | s.Finished() 100 | case <-conn.ConnectionClosed: 101 | return 102 | case <-s.Timeout(): 103 | return 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /scenarii/connection_migration_v4_v6.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "bytes" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | "math/rand" 7 | "net" 8 | "time" 9 | ) 10 | 11 | const ( 12 | CM46_TLSHandshakeFailed = 1 13 | CM46_UDPConnectionFailed = 2 14 | CM46_HostDidNotMigrate = 3 15 | CM46_HostDidNotValidateNewPath = 4 16 | CM46_NoNewCIDReceived = 5 17 | CM46_NoNewCIDUsed = 6 18 | CM46_MigrationIsDisabled = 7 19 | CM46_NoCIDAllowed = 8 20 | ) 21 | 22 | 23 | type ConnectionMigrationv4v6Scenario struct { 24 | AbstractScenario 25 | } 26 | 27 | func NewConnectionMigrationv4v6Scenario() *ConnectionMigrationv4v6Scenario { 28 | return &ConnectionMigrationv4v6Scenario{AbstractScenario{name: "connection_migration_v4_v6", version: 1}} 29 | } 30 | func (s *ConnectionMigrationv4v6Scenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 31 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 32 | 33 | connAgents := s.CompleteHandshake(conn, trace, CM46_TLSHandshakeFailed) 34 | if connAgents == nil { 35 | return 36 | } 37 | defer connAgents.CloseConnection(false, 0, "") 38 | 39 | if conn.TLSTPHandler.ReceivedParameters.DisableMigration { 40 | trace.ErrorCode = CM46_MigrationIsDisabled 41 | return 42 | } 43 | 44 | if conn.TLSTPHandler.ReceivedParameters.ActiveConnectionIdLimit < 0 { 45 | trace.ErrorCode = CM46_NoCIDAllowed 46 | return 47 | } 48 | 49 | scid := make([]byte, 8) 50 | var resetToken [16]byte 51 | rand.Read(scid) 52 | rand.Read(resetToken[:]) 53 | conn.FrameQueue.Submit(qt.QueuedFrame{&qt.NewConnectionIdFrame{1, 0, uint8(len(scid)), scid, resetToken}, qt.EncryptionLevelBest}) 54 | firstFlightTimer := time.NewTimer(3 * time.Second) 55 | 56 | var ncid qt.ConnectionID 57 | firstFlight: 58 | for { 59 | select { 60 | case i := <-incPackets: 61 | switch p := i.(type) { 62 | case *qt.ProtectedPacket: 63 | if p.Contains(qt.NewConnectionIdType) { 64 | ncid = p.GetFirst(qt.NewConnectionIdType).(*qt.NewConnectionIdFrame).ConnectionId 65 | break firstFlight 66 | } 67 | } 68 | case <-firstFlightTimer.C: 69 | trace.ErrorCode = CM46_NoNewCIDReceived 70 | return 71 | } 72 | } 73 | <-firstFlightTimer.C 74 | 75 | connAgents.Stop("SocketAgent", "SendingAgent") 76 | conn.DestinationCID = ncid 77 | conn.SourceCID = scid 78 | 79 | v6Addr, err := net.ResolveUDPAddr("udp6", trace.Host) 80 | if err != nil { 81 | trace.ErrorCode = CM46_UDPConnectionFailed 82 | trace.Results["ResolveUDPAddr"] = err.Error() 83 | return 84 | } 85 | udpConn, err := qt.EstablishUDPConnection(v6Addr) 86 | if err != nil { 87 | trace.ErrorCode = CM46_UDPConnectionFailed 88 | trace.Results["EstablishUDPConnection"] = err.Error() 89 | return 90 | } 91 | 92 | conn.UdpConnection.Close() 93 | conn.UdpConnection = udpConn 94 | 95 | connAgents.Get("SocketAgent").Run(conn) 96 | connAgents.Get("SendingAgent").Run(conn) 97 | conn.EncryptionLevels.Submit(qt.DirectionalEncryptionLevel{EncryptionLevel: qt.EncryptionLevel1RTT, Available: true}) 98 | 99 | incPackets = conn.IncomingPackets.RegisterNewChan(1000) 100 | 101 | responseChan := connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 102 | trace.ErrorCode = CM46_HostDidNotMigrate // Assume it until proven wrong 103 | 104 | for { 105 | select { 106 | case i := <-incPackets: 107 | p := i.(qt.Packet) 108 | if trace.ErrorCode == CM46_HostDidNotMigrate { 109 | trace.ErrorCode = CM46_HostDidNotValidateNewPath 110 | } 111 | 112 | if fp, ok := p.(qt.Framer); ok && fp.Contains(qt.PathChallengeType) && trace.ErrorCode == CM46_HostDidNotValidateNewPath { 113 | trace.ErrorCode = CM46_NoNewCIDUsed 114 | } 115 | 116 | if bytes.Equal(p.Header().DestinationConnectionID(), scid) && trace.ErrorCode == CM46_NoNewCIDUsed { 117 | trace.ErrorCode = 0 118 | } 119 | 120 | case <-responseChan: 121 | s.Finished() 122 | case <-conn.ConnectionClosed: 123 | return 124 | case <-s.Timeout(): 125 | return 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /scenarii/flow_control.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | FC_TLSHandshakeFailed = 1 11 | FC_HostSentMoreThanLimit = 2 12 | FC_HostDidNotResumeSending = 3 13 | FC_NotEnoughDataAvailable = 4 14 | FC_RespectedLimitsButNoBlocked = 5 // After discussing w/ the implementers, it is not reasonable to expect a STREAM_BLOCKED or a BLOCKED frame to be sent. 15 | // These frames should be sent to signal poor window size w.r.t. to the RTT 16 | FC_EndpointDoesNotSupportHQ = 6 17 | ) 18 | 19 | type FlowControlScenario struct { 20 | AbstractScenario 21 | } 22 | 23 | func NewFlowControlScenario() *FlowControlScenario { 24 | return &FlowControlScenario{AbstractScenario{name: "flow_control", version: 2}} 25 | } 26 | func (s *FlowControlScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 27 | if !strings.Contains(conn.ALPN, "hq") { 28 | trace.ErrorCode = FC_EndpointDoesNotSupportHQ 29 | return 30 | } 31 | 32 | conn.TLSTPHandler.MaxStreamDataBidiLocal = 80 33 | 34 | connAgents := s.CompleteHandshake(conn, trace, FC_TLSHandshakeFailed) 35 | if connAgents == nil { 36 | return 37 | } 38 | connAgents.Get("FlowControlAgent").(*agents.FlowControlAgent).DontSlideCreditWindow = true 39 | defer connAgents.CloseConnection(false, 0, "") 40 | 41 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 42 | 43 | conn.SendHTTP09GETRequest(preferredPath, 0) 44 | 45 | var shouldResume bool 46 | 47 | forLoop: 48 | for { 49 | select { 50 | case i := <-incPackets: 51 | p := i.(qt.Packet) 52 | if conn.Streams.Get(0).ReadOffset > uint64(conn.TLSTPHandler.MaxStreamDataBidiLocal) { 53 | trace.MarkError(FC_HostSentMoreThanLimit, "", p) 54 | } 55 | 56 | if conn.Streams.Get(0).ReadClosed { 57 | conn.IncomingPackets.Unregister(incPackets) 58 | s.Finished() 59 | break 60 | } 61 | 62 | readOffset := conn.Streams.Get(0).ReadOffset 63 | if readOffset == uint64(conn.TLSTPHandler.MaxStreamDataBidiLocal) && !shouldResume { 64 | conn.TLSTPHandler.MaxData *= 2 65 | conn.TLSTPHandler.MaxStreamDataBidiLocal *= 2 66 | conn.FrameQueue.Submit(qt.QueuedFrame{&qt.MaxDataFrame{uint64(conn.TLSTPHandler.MaxData)}, qt.EncryptionLevel1RTT}) 67 | conn.FrameQueue.Submit(qt.QueuedFrame{&qt.MaxStreamDataFrame{0, uint64(conn.TLSTPHandler.MaxStreamDataBidiLocal)}, qt.EncryptionLevel1RTT}) 68 | shouldResume = true 69 | } 70 | case <-conn.ConnectionClosed: 71 | return 72 | case <-s.Timeout(): 73 | break forLoop 74 | } 75 | } 76 | 77 | readOffset := conn.Streams.Get(0).ReadOffset 78 | if readOffset == uint64(conn.TLSTPHandler.MaxStreamDataBidiLocal) { 79 | trace.ErrorCode = 0 80 | } else if shouldResume && readOffset == uint64(conn.TLSTPHandler.MaxStreamDataBidiLocal)/2 { 81 | trace.ErrorCode = FC_HostDidNotResumeSending 82 | } else if readOffset < uint64(conn.TLSTPHandler.MaxStreamDataBidiLocal) { 83 | trace.ErrorCode = FC_NotEnoughDataAvailable 84 | } else if readOffset > uint64(conn.TLSTPHandler.MaxStreamDataBidiLocal) { 85 | trace.ErrorCode = FC_HostSentMoreThanLimit 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /scenarii/handshake.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | 6 | "github.com/QUIC-Tracker/quic-tracker/agents" 7 | ) 8 | 9 | const ( 10 | H_ReceivedUnexpectedPacketType = 1 11 | H_TLSHandshakeFailed = 2 12 | H_NoCompatibleVersionAvailable = 3 13 | H_Timeout = 4 14 | ) 15 | 16 | type HandshakeScenario struct { 17 | AbstractScenario 18 | } 19 | 20 | func NewHandshakeScenario() *HandshakeScenario { 21 | return &HandshakeScenario{AbstractScenario{name: "handshake", version: 2}} 22 | } 23 | func (s *HandshakeScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 24 | connAgents := agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 25 | handshakeAgent := &agents.HandshakeAgent{TLSAgent: connAgents.Get("TLSAgent").(*agents.TLSAgent), SocketAgent: connAgents.Get("SocketAgent").(*agents.SocketAgent)} 26 | connAgents.Add(handshakeAgent) 27 | connAgents.Get("SendingAgent").(*agents.SendingAgent).FrameProducer = connAgents.GetFrameProducingAgents() 28 | 29 | handshakeStatus := handshakeAgent.HandshakeStatus.RegisterNewChan(1000) 30 | handshakeAgent.InitiateHandshake() 31 | 32 | var status agents.HandshakeStatus 33 | for { 34 | select { 35 | case i := <-handshakeStatus: 36 | status = i.(agents.HandshakeStatus) 37 | if !status.Completed { 38 | switch status.Error.Error() { 39 | case "no appropriate version found": 40 | trace.MarkError(H_NoCompatibleVersionAvailable, status.Error.Error(), status.Packet) 41 | case "received incorrect packet type during handshake": 42 | trace.MarkError(H_ReceivedUnexpectedPacketType, "", status.Packet) 43 | default: 44 | trace.MarkError(H_TLSHandshakeFailed, status.Error.Error(), status.Packet) 45 | } 46 | } else { 47 | trace.Results["negotiated_version"] = conn.Version 48 | } 49 | handshakeAgent.HandshakeStatus.Unregister(handshakeStatus) 50 | s.Finished() 51 | case <-conn.ConnectionClosed: 52 | return 53 | case <-s.Timeout(): 54 | if !status.Completed { 55 | if trace.ErrorCode == 0 { 56 | trace.MarkError(H_Timeout, "", nil) 57 | } 58 | connAgents.StopAll() 59 | } else { 60 | connAgents.CloseConnection(false, 0, "") 61 | } 62 | return 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /scenarii/handshake_v6.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | ) 6 | 7 | type Handshakev6Scenario struct { 8 | AbstractScenario 9 | } 10 | 11 | func NewHandshakev6Scenario() *Handshakev6Scenario { 12 | return &Handshakev6Scenario{AbstractScenario{name: "handshake_v6", version: 2, ipv6: true}} 13 | } 14 | func (s *Handshakev6Scenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 15 | h := NewHandshakeScenario() 16 | h.duration = s.duration 17 | h.timeout = s.timeout 18 | h.Run(conn, trace, preferredPath, debug) 19 | } 20 | -------------------------------------------------------------------------------- /scenarii/http3_encoder_stream.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | "github.com/QUIC-Tracker/quic-tracker/http3" 7 | "github.com/mpiraux/ls-qpack-go" 8 | "time" 9 | ) 10 | 11 | const ( 12 | H3ES_TLSHandshakeFailed = 1 13 | H3ES_RequestTimeout = 2 14 | H3ES_NotEnoughStreamsAvailable = 3 15 | H3ES_SETTINGSNotSent = 4 16 | ) 17 | 18 | type HTTP3EncoderStreamScenario struct { 19 | AbstractScenario 20 | } 21 | 22 | func NewHTTP3EncoderStreamScenario() *HTTP3EncoderStreamScenario { 23 | return &HTTP3EncoderStreamScenario{AbstractScenario{name: "http3_encoder_stream", version: 1, http3: true}} 24 | } 25 | func (s *HTTP3EncoderStreamScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 26 | conn.TLSTPHandler.MaxUniStreams = 3 27 | 28 | http := agents.HTTP3Agent{QPACKEncoderOpts: ls_qpack_go.LSQPackEncOptIxAggr} 29 | connAgents := s.CompleteHandshake(conn, trace, H3ES_TLSHandshakeFailed, &http) 30 | if connAgents == nil { 31 | return 32 | } 33 | defer connAgents.CloseConnection(false, 0, "") 34 | frameReceived := http.FrameReceived.RegisterNewChan(1000) 35 | 36 | if conn.TLSTPHandler.ReceivedParameters.MaxUniStreams < 3 || conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams == 0 { 37 | trace.ErrorCode = H3ES_NotEnoughStreamsAvailable 38 | trace.Results["max_uni_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxUniStreams 39 | trace.Results["max_bidi_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams 40 | return 41 | } 42 | 43 | if http.ReceivedSettings == nil { 44 | forLoop: 45 | for { 46 | select { 47 | case i := <-frameReceived: 48 | fr := i.(agents.HTTP3FrameReceived) 49 | switch fr.Frame.(type) { 50 | case *http3.SETTINGS: 51 | break forLoop 52 | } 53 | case <-conn.ConnectionClosed: 54 | return 55 | case <-s.Timeout(): 56 | trace.ErrorCode = H3ES_SETTINGSNotSent 57 | return 58 | } 59 | } 60 | } 61 | 62 | <-time.NewTimer(200 * time.Millisecond).C 63 | responseReceived := http.SendRequest(preferredPath, "GET", trace.Host, nil) 64 | 65 | trace.ErrorCode = H3ES_RequestTimeout 66 | select { 67 | case <-responseReceived: 68 | trace.ErrorCode = 0 69 | s.Finished() 70 | <-s.Timeout() 71 | case <-conn.ConnectionClosed: 72 | return 73 | case <-s.Timeout(): 74 | return 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /scenarii/http3_get.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | ) 7 | 8 | const ( 9 | H3G_TLSHandshakeFailed = 1 10 | H3G_RequestTimeout = 2 11 | H3G_NotEnoughStreamsAvailable = 3 12 | ) 13 | 14 | type HTTP3GETScenario struct { 15 | AbstractScenario 16 | } 17 | 18 | func NewHTTP3GETScenario() *HTTP3GETScenario { 19 | return &HTTP3GETScenario{AbstractScenario{name: "http3_get", version: 1, http3: true}} 20 | } 21 | func (s *HTTP3GETScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 22 | conn.TLSTPHandler.MaxUniStreams = 3 23 | 24 | http := agents.HTTP3Agent{} 25 | connAgents := s.CompleteHandshake(conn, trace, H3G_TLSHandshakeFailed, &http) 26 | if connAgents == nil { 27 | return 28 | } 29 | defer connAgents.CloseConnection(false, 0, "") 30 | 31 | if conn.TLSTPHandler.ReceivedParameters.MaxUniStreams < 3 || conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams == 0 { 32 | trace.ErrorCode = H3G_NotEnoughStreamsAvailable 33 | trace.Results["max_uni_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxUniStreams 34 | trace.Results["max_bidi_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams 35 | return 36 | } 37 | 38 | responseReceived := http.SendRequest(preferredPath, "GET", trace.Host, nil) 39 | 40 | trace.ErrorCode = H3G_RequestTimeout 41 | select { 42 | case <-responseReceived: 43 | trace.ErrorCode = 0 44 | s.Finished() 45 | <-s.Timeout() 46 | case <-conn.ConnectionClosed: 47 | return 48 | case <-s.Timeout(): 49 | return 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /scenarii/http3_reserved_frames.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "bytes" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | "github.com/QUIC-Tracker/quic-tracker/agents" 7 | "github.com/QUIC-Tracker/quic-tracker/http3" 8 | ) 9 | 10 | const ( 11 | H3RF_TLSHandshakeFailed = 1 12 | H3RF_RequestTimeout = 2 13 | H3RF_NotEnoughStreamsAvailable = 3 14 | ) 15 | 16 | type HTTP3ReservedFramesScenario struct { 17 | AbstractScenario 18 | } 19 | 20 | func NewHTTP3ReservedFramesScenario() *HTTP3ReservedFramesScenario { 21 | return &HTTP3ReservedFramesScenario{AbstractScenario{name: "http3_reserved_frames", version: 1, ipv6: false, http3: true}} 22 | } 23 | func (s *HTTP3ReservedFramesScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 24 | conn.TLSTPHandler.MaxUniStreams = 3 25 | 26 | http := agents.HTTP3Agent{} 27 | connAgents := s.CompleteHandshake(conn, trace, H3RF_TLSHandshakeFailed, &http) 28 | if connAgents == nil { 29 | return 30 | } 31 | defer connAgents.CloseConnection(false, 0, "") 32 | 33 | if conn.TLSTPHandler.ReceivedParameters.MaxUniStreams < 3 || conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams == 0 { 34 | trace.ErrorCode = H3RF_NotEnoughStreamsAvailable 35 | trace.Results["max_uni_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxUniStreams 36 | trace.Results["max_bidi_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams 37 | return 38 | } 39 | 40 | payload := []byte("Hello, world!") 41 | unknownFrame1 := http3.UnknownFrame{HTTPFrameHeader: http3.HTTPFrameHeader{Type: qt.NewVarInt(0x21), Length: qt.NewVarInt(uint64(len(payload)))}, OpaquePayload: payload} 42 | unknownFrame2 := http3.UnknownFrame{HTTPFrameHeader: http3.HTTPFrameHeader{Type: qt.NewVarInt(0x21 + 0x1f), Length: qt.NewVarInt(0)}} 43 | 44 | buf := new(bytes.Buffer) 45 | unknownFrame1.WriteTo(buf) 46 | unknownFrame2.WriteTo(buf) 47 | 48 | conn.Streams.Send(0, buf.Bytes(), false) 49 | 50 | responseReceived := http.SendRequest(preferredPath, "GET", trace.Host, nil) 51 | 52 | trace.ErrorCode = H3RF_RequestTimeout 53 | select { 54 | case <-responseReceived: 55 | trace.ErrorCode = 0 56 | s.Finished() 57 | <-s.Timeout() 58 | case <-conn.ConnectionClosed: 59 | return 60 | case <-s.Timeout(): 61 | return 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scenarii/http3_reserved_streams.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "bytes" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | "github.com/QUIC-Tracker/quic-tracker/agents" 7 | "github.com/QUIC-Tracker/quic-tracker/http3" 8 | ) 9 | 10 | const ( 11 | H3RS_TLSHandshakeFailed = 1 12 | H3RS_RequestTimeout = 2 13 | H3RS_NotEnoughStreamsAvailable = 3 14 | ) 15 | 16 | type HTTP3ReservedStreamsScenario struct { 17 | AbstractScenario 18 | } 19 | 20 | func NewHTTP3ReservedStreamsScenario() *HTTP3ReservedStreamsScenario { 21 | return &HTTP3ReservedStreamsScenario{AbstractScenario{name: "http3_reserved_streams", version: 1, ipv6: false, http3: true}} 22 | } 23 | func (s *HTTP3ReservedStreamsScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 24 | conn.TLSTPHandler.MaxUniStreams = 3 25 | 26 | http := agents.HTTP3Agent{DisableQPACKStreams: true} 27 | connAgents := s.CompleteHandshake(conn, trace, H3RS_TLSHandshakeFailed, &http) 28 | if connAgents == nil { 29 | return 30 | } 31 | defer connAgents.CloseConnection(false, 0, "") 32 | 33 | if conn.TLSTPHandler.ReceivedParameters.MaxUniStreams < 3 || conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams == 0 { 34 | trace.ErrorCode = H3RS_NotEnoughStreamsAvailable 35 | trace.Results["max_uni_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxUniStreams 36 | trace.Results["max_bidi_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams 37 | return 38 | } 39 | 40 | payload := []byte("Hello, world!") 41 | unknownFrame1 := http3.UnknownFrame{HTTPFrameHeader: http3.HTTPFrameHeader{Type: qt.NewVarInt(0x21), Length: qt.NewVarInt(uint64(len(payload)))}, OpaquePayload: payload} 42 | unknownFrame2 := http3.UnknownFrame{HTTPFrameHeader: http3.HTTPFrameHeader{Type: qt.NewVarInt(0x21 + 0x1f), Length: qt.NewVarInt(0)}} 43 | 44 | buf := new(bytes.Buffer) 45 | unknownFrame1.WriteTo(buf) 46 | unknownFrame2.WriteTo(buf) 47 | 48 | conn.Streams.Send(6, []byte{0x21}, false) 49 | conn.Streams.Send(10, []byte{0x21 + (0x1f * 3)}, false) 50 | conn.Streams.Send(6, buf.Bytes(), false) 51 | conn.Streams.Send(10, buf.Bytes(), false) 52 | 53 | responseReceived := http.SendRequest(preferredPath, "GET", trace.Host, nil) 54 | 55 | trace.ErrorCode = H3RS_RequestTimeout 56 | select { 57 | case <-responseReceived: 58 | trace.ErrorCode = 0 59 | s.Finished() 60 | <-s.Timeout() 61 | case <-conn.ConnectionClosed: 62 | return 63 | case <-s.Timeout(): 64 | return 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /scenarii/http3_uni_streams_limits.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | ) 7 | 8 | const ( 9 | H3USFC_TLSHandshakeFailed = 1 10 | H3USFC_RequestTimeout = 2 11 | H3USFC_NotEnoughStreamsAvailable = 3 12 | H3USFC_StreamIDError = 4 13 | ) 14 | 15 | type HTTP3UniStreamsLimitsScenario struct { 16 | AbstractScenario 17 | } 18 | 19 | func NewHTTP3UniStreamsLimitsScenario() *HTTP3UniStreamsLimitsScenario { 20 | return &HTTP3UniStreamsLimitsScenario{AbstractScenario{name: "http3_uni_streams_limits", version: 1, http3: true}} 21 | } 22 | func (s *HTTP3UniStreamsLimitsScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 23 | conn.TLSTPHandler.MaxUniStreams = 1 24 | 25 | http := agents.HTTP3Agent{} 26 | connAgents := s.CompleteHandshake(conn, trace, H3USFC_TLSHandshakeFailed, &http) 27 | if connAgents == nil { 28 | return 29 | } 30 | defer connAgents.CloseConnection(false, 0, "") 31 | 32 | if conn.TLSTPHandler.ReceivedParameters.MaxUniStreams < 3 || conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams == 0 { 33 | trace.ErrorCode = H3USFC_NotEnoughStreamsAvailable 34 | trace.Results["max_uni_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxUniStreams 35 | trace.Results["max_bidi_streams"] = conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams 36 | return 37 | } 38 | 39 | responseReceived := http.SendRequest(preferredPath, "GET", trace.Host, nil) 40 | 41 | trace.ErrorCode = H3USFC_RequestTimeout 42 | select { 43 | case <-responseReceived: 44 | trace.ErrorCode = 0 45 | s.Finished() 46 | <-s.Timeout() 47 | case <-conn.ConnectionClosed: 48 | case <-s.Timeout(): 49 | } 50 | 51 | if conn.Streams.NumberOfServerStreamsOpen() > 1 { 52 | trace.ErrorCode = H3USFC_StreamIDError 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scenarii/http_get_and_wait.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "strings" 6 | 7 | "fmt" 8 | ) 9 | 10 | const ( 11 | SGW_TLSHandshakeFailed = 1 12 | SGW_EmptyStreamFrameNoFinBit = 2 13 | SGW_RetransmittedAck = 3 // This could affect performance, but we don't check it anymore 14 | SGW_WrongStreamIDReceived = 4 15 | SGW_UnknownError = 5 16 | SGW_DidNotCloseTheConnection = 6 17 | SGW_MultipleErrors = 7 18 | SGW_TooLowStreamIdBidiToSendRequest = 8 19 | SGW_DidntReceiveTheRequestedData = 9 20 | SGW_AnsweredOnUnannouncedStream = 10 21 | SGW_EndpointDoesNotSupportHQ = 11 22 | ) 23 | 24 | type SimpleGetAndWaitScenario struct { 25 | AbstractScenario 26 | } 27 | 28 | func NewSimpleGetAndWaitScenario() *SimpleGetAndWaitScenario { 29 | return &SimpleGetAndWaitScenario{AbstractScenario{name: "http_get_and_wait", version: 1}} 30 | } 31 | 32 | func (s *SimpleGetAndWaitScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 33 | if !strings.Contains(conn.ALPN, "hq") { 34 | trace.ErrorCode = SGW_EndpointDoesNotSupportHQ 35 | return 36 | } 37 | 38 | conn.TLSTPHandler.MaxBidiStreams = 0 39 | conn.TLSTPHandler.MaxUniStreams = 0 40 | 41 | connAgents := s.CompleteHandshake(conn, trace, SGW_TLSHandshakeFailed) 42 | if connAgents == nil { 43 | return 44 | } 45 | defer connAgents.CloseConnection(false, 0, "") 46 | 47 | if conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams == 0 { 48 | trace.MarkError(SGW_TooLowStreamIdBidiToSendRequest, "cannot open bidi stream", nil) 49 | } 50 | 51 | errors := make(map[uint8]string) 52 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 53 | 54 | responseChan := connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 55 | 56 | var connectionCloseReceived bool 57 | 58 | forLoop: 59 | for { 60 | select { 61 | case i := <-incomingPackets: 62 | switch p := i.(type) { 63 | case *qt.ProtectedPacket: 64 | for _, f := range p.GetFrames() { 65 | switch f := f.(type) { 66 | case *qt.StreamFrame: 67 | if f.StreamId != 0 { 68 | errors[SGW_WrongStreamIDReceived] = fmt.Sprintf("received StreamID %d", f.StreamId) 69 | trace.MarkError(SGW_WrongStreamIDReceived, "", p) 70 | } 71 | if f.Length == 0 && !f.FinBit { 72 | errors[SGW_EmptyStreamFrameNoFinBit] = fmt.Sprintf("received an empty STREAM frame with no FIN bit set for stream %d", f.StreamId) 73 | trace.MarkError(SGW_EmptyStreamFrameNoFinBit, "", p) 74 | } 75 | case *qt.ConnectionCloseFrame, *qt.ApplicationCloseFrame: 76 | connectionCloseReceived = true 77 | s.Finished() 78 | } 79 | } 80 | } 81 | case <-responseChan: 82 | break forLoop 83 | case <-conn.ConnectionClosed: 84 | break forLoop 85 | case <-s.Timeout(): 86 | break forLoop 87 | } 88 | } 89 | 90 | if conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams == 0 { 91 | if conn.Streams.Get(0).ReadOffset > 0 { 92 | errors[SGW_AnsweredOnUnannouncedStream] = "data was received on stream 0 despite not being announced in TP" 93 | } else if !connectionCloseReceived { 94 | errors[SGW_DidNotCloseTheConnection] = "" 95 | } 96 | } else if !conn.Streams.Get(0).ReadClosed || conn.Streams.Get(0).ReadOffset == 0 { 97 | errors[SGW_DidntReceiveTheRequestedData] = "the response to the request was not complete" 98 | } 99 | 100 | if len(errors) == 1 { 101 | for e, s := range errors { 102 | trace.ErrorCode = e 103 | trace.Results["error"] = s 104 | } 105 | } else if len(errors) > 1 { 106 | trace.ErrorCode = SGW_MultipleErrors 107 | trace.Results["error"] = errors 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /scenarii/http_get_on_uni_stream.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | GS2_TLSHandshakeFailed = 1 10 | GS2_TooLowStreamIdUniToSendRequest = 2 11 | GS2_ReceivedDataOnStream2 = 3 12 | GS2_ReceivedDataOnUnauthorizedStream = 4 13 | GS2_AnswersToARequestOnAForbiddenStreamID = 5 // This is hard to disambiguate sometimes, we don't check anymore 14 | GS2_DidNotCloseTheConnection = 6 15 | GS2_EndpointDoesNotSupportHQ = 7 16 | ) 17 | 18 | type GetOnStream2Scenario struct { 19 | AbstractScenario 20 | } 21 | 22 | func NewGetOnStream2Scenario() *GetOnStream2Scenario { 23 | return &GetOnStream2Scenario{AbstractScenario{name: "http_get_on_uni_stream", version: 1}} 24 | } 25 | 26 | func (s *GetOnStream2Scenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 27 | conn.TLSTPHandler.MaxBidiStreams = 1 28 | conn.TLSTPHandler.MaxUniStreams = 1 29 | 30 | if !strings.Contains(conn.ALPN, "hq") { 31 | trace.ErrorCode = GS2_EndpointDoesNotSupportHQ 32 | return 33 | } 34 | 35 | connAgents := s.CompleteHandshake(conn, trace, GS2_TLSHandshakeFailed) 36 | if connAgents == nil { 37 | return 38 | } 39 | defer connAgents.CloseConnection(false, 0, "") 40 | 41 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 42 | 43 | trace.Results["received_transport_parameters"] = conn.TLSTPHandler.ReceivedParameters.ToJSON 44 | if conn.TLSTPHandler.ReceivedParameters.MaxUniStreams == 0 { 45 | trace.ErrorCode = GS2_DidNotCloseTheConnection 46 | } 47 | 48 | conn.SendHTTP09GETRequest(preferredPath, 2) 49 | 50 | for { 51 | select { 52 | case i := <-incPackets: 53 | switch p := i.(type) { 54 | case qt.Framer: 55 | for _, f := range p.GetFrames() { 56 | switch f := f.(type) { 57 | case *qt.StreamFrame: 58 | if f.StreamId == 2 && f.Length > 0 { 59 | trace.MarkError(GS2_ReceivedDataOnStream2, "", p) 60 | s.Finished() 61 | } else if f.StreamId > 3 { 62 | trace.MarkError(GS2_ReceivedDataOnUnauthorizedStream, "", p) 63 | s.Finished() 64 | } 65 | case *qt.ConnectionCloseFrame: 66 | if trace.ErrorCode == GS2_DidNotCloseTheConnection && (f.ErrorCode == qt.ERR_STREAM_LIMIT_ERROR || f.ErrorCode == qt.ERR_PROTOCOL_VIOLATION) { 67 | trace.ErrorCode = GS2_TooLowStreamIdUniToSendRequest 68 | } 69 | s.Finished() 70 | } 71 | } 72 | } 73 | case <-conn.ConnectionClosed: 74 | return 75 | case <-s.Timeout(): 76 | return 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /scenarii/key_update.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/mpiraux/pigotls" 6 | ) 7 | 8 | const ( 9 | KU_TLSHandshakeFailed = 1 10 | KU_HostDidNotRespond = 2 11 | ) 12 | 13 | type KeyUpdateScenario struct { 14 | AbstractScenario 15 | } 16 | 17 | func NewKeyUpdateScenario() *KeyUpdateScenario { 18 | return &KeyUpdateScenario{AbstractScenario{name: "key_update", version: 1}} 19 | } 20 | func (s *KeyUpdateScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 21 | connAgents := s.CompleteHandshake(conn, trace, KU_TLSHandshakeFailed) 22 | if connAgents == nil { 23 | return 24 | } 25 | defer connAgents.CloseConnection(false, 0, "") 26 | 27 | conn.FrameQueue.Submit(qt.QueuedFrame{Frame: new(qt.PingFrame), EncryptionLevel: qt.EncryptionLevel1RTT}) 28 | ackedPackets := conn.PacketAcknowledged.RegisterNewChan(10) 29 | forLoop1: 30 | for { 31 | select { 32 | case i := <-ackedPackets: 33 | switch a := i.(type) { 34 | case qt.PacketAcknowledged: 35 | if a.PNSpace == qt.PNSpaceAppData { 36 | conn.PacketAcknowledged.Unregister(ackedPackets) 37 | break forLoop1 38 | } 39 | } 40 | case <-conn.ConnectionClosed: 41 | trace.ErrorCode = KU_TLSHandshakeFailed 42 | return 43 | case <-s.Timeout(): 44 | trace.ErrorCode = KU_TLSHandshakeFailed 45 | return 46 | } 47 | } 48 | 49 | // TODO: Move this to crypto.go 50 | readSecret := conn.Tls.HkdfExpandLabel(conn.Tls.ProtectedReadSecret(), "ku", nil, conn.Tls.HashDigestSize(), pigotls.QuicBaseLabel) 51 | writeSecret := conn.Tls.HkdfExpandLabel(conn.Tls.ProtectedWriteSecret(), "ku", nil, conn.Tls.HashDigestSize(), pigotls.QuicBaseLabel) 52 | 53 | conn.CryptoStateLock.Lock() 54 | oldState := conn.CryptoStates[qt.EncryptionLevel1RTT] 55 | 56 | conn.CryptoStates[qt.EncryptionLevel1RTT] = qt.NewProtectedCryptoState(conn.Tls, readSecret, writeSecret) 57 | conn.CryptoStates[qt.EncryptionLevel1RTT].HeaderRead = oldState.HeaderRead 58 | conn.CryptoStates[qt.EncryptionLevel1RTT].HeaderWrite = oldState.HeaderWrite 59 | conn.KeyPhaseIndex++ 60 | conn.CryptoStateLock.Unlock() 61 | 62 | responseChan := connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 63 | 64 | forLoop2: 65 | for { 66 | select { 67 | case <-responseChan: 68 | s.Finished() 69 | case <-conn.ConnectionClosed: 70 | break forLoop2 71 | case <-s.Timeout(): 72 | break forLoop2 73 | } 74 | } 75 | 76 | if !conn.Streams.Get(0).ReadClosed { 77 | trace.ErrorCode = KU_HostDidNotRespond 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /scenarii/multi_packet_client_hello.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | "time" 7 | ) 8 | 9 | const ( 10 | MPCH_TLSHandshakeFailed = 1 11 | MPCH_RequestFailed = 2 12 | ) 13 | 14 | type MultiPacketClientHello struct { 15 | AbstractScenario 16 | } 17 | 18 | func NewMultiPacketClientHello() *MultiPacketClientHello { 19 | return &MultiPacketClientHello{AbstractScenario{name: "multi_packet_client_hello", version: 1}} 20 | } 21 | 22 | func (s *MultiPacketClientHello) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 23 | connAgents := agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 24 | handshakeAgent := &agents.HandshakeAgent{TLSAgent: connAgents.Get("TLSAgent").(*agents.TLSAgent), SocketAgent: connAgents.Get("SocketAgent").(*agents.SocketAgent)} 25 | connAgents.Add(handshakeAgent) 26 | connAgents.Get("SendingAgent").(*agents.SendingAgent).FrameProducer = connAgents.GetFrameProducingAgents() 27 | 28 | handshakeStatus := handshakeAgent.HandshakeStatus.RegisterNewChan(10) 29 | 30 | originalPacket := conn.GetInitialPacket() 31 | originalLen := len(originalPacket.Encode(originalPacket.EncodePayload())) 32 | f := originalPacket.GetFirst(qt.CryptoType).(*qt.CryptoFrame) 33 | secondPacket := qt.NewInitialPacket(conn) 34 | secondPacket.AddFrame(&qt.CryptoFrame{Offset: f.Length / 2, Length: f.Length - (f.Length / 2), CryptoData:f.CryptoData[f.Length/2:]}) 35 | secondPacket.PadTo(originalLen) 36 | f.CryptoData = f.CryptoData[:f.Length/2] 37 | f.Length /= 2 38 | originalPacket.PadTo(originalLen) 39 | 40 | conn.DoSendPacket(secondPacket, qt.EncryptionLevelInitial) 41 | <-time.NewTimer(1 * time.Millisecond).C 42 | conn.DoSendPacket(originalPacket, qt.EncryptionLevelInitial) 43 | 44 | select { 45 | case i := <-handshakeStatus: 46 | status := i.(agents.HandshakeStatus) 47 | if !status.Completed { 48 | trace.MarkError(MPCH_TLSHandshakeFailed, status.Error.Error(), status.Packet) 49 | connAgents.StopAll() 50 | return 51 | } else { 52 | defer connAgents.CloseConnection(false, 0, "") 53 | } 54 | case <-conn.ConnectionClosed: 55 | trace.MarkError(MPCH_TLSHandshakeFailed, "connection closed", nil) 56 | connAgents.StopAll() 57 | return 58 | case <-s.Timeout(): 59 | trace.MarkError(MPCH_TLSHandshakeFailed, "handshake timeout", nil) 60 | connAgents.StopAll() 61 | return 62 | } 63 | 64 | connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 65 | 66 | <-s.Timeout() 67 | 68 | if !conn.Streams.Get(0).ReadClosed { 69 | trace.ErrorCode = MPCH_RequestFailed 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /scenarii/multi_stream.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "fmt" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | _ "github.com/davecgh/go-spew/spew" 7 | ) 8 | 9 | const ( 10 | MS_TLSHandshakeFailed = 1 11 | MS_NoTPReceived = 2 // We don't distinguish the two first cases anymore 12 | MS_NotAllStreamsWereClosed = 3 13 | ) 14 | 15 | type MultiStreamScenario struct { 16 | AbstractScenario 17 | } 18 | 19 | func NewMultiStreamScenario() *MultiStreamScenario { 20 | return &MultiStreamScenario{AbstractScenario{name: "multi_stream", version: 1}} 21 | } 22 | func (s *MultiStreamScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 23 | conn.TLSTPHandler.MaxData = 1024 * 1024 24 | conn.TLSTPHandler.MaxStreamDataBidiLocal = 1024 * 1024 / 10 25 | 26 | allClosed := true 27 | connAgents := s.CompleteHandshake(conn, trace, MS_TLSHandshakeFailed) 28 | if connAgents == nil { 29 | return 30 | } 31 | defer connAgents.CloseConnection(false, 0, "") 32 | 33 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 34 | 35 | httpAgent := connAgents.AddHTTPAgent() 36 | for i := uint64(0); i < conn.TLSTPHandler.ReceivedParameters.MaxBidiStreams && i < 4; i++ { 37 | httpAgent.SendRequest(preferredPath, "GET", trace.Host, nil) 38 | } 39 | 40 | forLoop: 41 | for { 42 | select { 43 | case <-incPackets: 44 | for streamId, stream := range conn.Streams.GetAll() { 45 | if qt.IsBidi(streamId) && !stream.ReadClosed { 46 | allClosed = false 47 | break 48 | } 49 | } 50 | 51 | if allClosed { 52 | s.Finished() 53 | } 54 | case <-conn.ConnectionClosed: 55 | break forLoop 56 | case <-s.Timeout(): 57 | break forLoop 58 | } 59 | } 60 | 61 | allClosed = true 62 | for streamId, stream := range conn.Streams.GetAll() { 63 | if qt.IsBidi(streamId) && !stream.ReadClosed { 64 | allClosed = false 65 | break 66 | } 67 | } 68 | 69 | if !allClosed { 70 | trace.ErrorCode = MS_NotAllStreamsWereClosed 71 | for streamId, stream := range conn.Streams.GetAll() { 72 | if qt.IsBidi(streamId) { 73 | trace.Results[fmt.Sprintf("stream_%d_rec_offset", streamId)] = stream.ReadOffset 74 | trace.Results[fmt.Sprintf("stream_%d_snd_offset", streamId)] = stream.WriteOffset 75 | trace.Results[fmt.Sprintf("stream_%d_snd_closed", streamId)] = stream.WriteClosed 76 | trace.Results[fmt.Sprintf("stream_%d_rec_closed", streamId)] = stream.ReadClosed 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /scenarii/new_connection_id.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | qt "github.com/QUIC-Tracker/quic-tracker" 7 | 8 | "crypto/rand" 9 | "encoding/hex" 10 | ) 11 | 12 | const ( 13 | NCI_TLSHandshakeFailed = 1 14 | NCI_HostDidNotProvideCID = 2 15 | NCI_HostDidNotAnswerToNewCID = 3 16 | NCI_HostDidNotAdaptCID = 4 17 | NCI_HostSentInvalidCIDLength = 5 18 | NCI_NoCIDAllowed = 6 19 | ) 20 | 21 | type NewConnectionIDScenario struct { 22 | AbstractScenario 23 | } 24 | 25 | func NewNewConnectionIDScenario() *NewConnectionIDScenario { 26 | return &NewConnectionIDScenario{AbstractScenario{name: "new_connection_id", version: 2}} 27 | } 28 | func (s *NewConnectionIDScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 29 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 30 | 31 | connAgents := s.CompleteHandshake(conn, trace, NCI_TLSHandshakeFailed) 32 | if connAgents == nil { 33 | return 34 | } 35 | defer connAgents.CloseConnection(false, 0, "") 36 | 37 | if conn.TLSTPHandler.ReceivedParameters.ActiveConnectionIdLimit < 2 { 38 | trace.ErrorCode = NCI_NoCIDAllowed 39 | return 40 | } 41 | 42 | trace.ErrorCode = NCI_HostDidNotProvideCID 43 | 44 | var expectingResponse bool 45 | var alternativeConnectionIDs []string 46 | defer func() { trace.Results["new_connection_ids"] = alternativeConnectionIDs }() 47 | 48 | scid := make([]byte, 8) 49 | var resetToken [16]byte 50 | rand.Read(scid) 51 | rand.Read(resetToken[:]) 52 | 53 | for { 54 | select { 55 | case i := <-incPackets: 56 | p := i.(qt.Packet) 57 | if expectingResponse { 58 | if !bytes.Equal(p.Header().DestinationConnectionID(), conn.SourceCID) { 59 | trace.MarkError(NCI_HostDidNotAdaptCID, "", p) 60 | } else if conn.Streams.Get(0).ReadClosed { 61 | trace.ErrorCode = 0 62 | s.Finished() 63 | } 64 | break 65 | } 66 | 67 | if pp, ok := p.(*qt.ProtectedPacket); ok { 68 | for _, frame := range pp.GetAll(qt.NewConnectionIdType) { 69 | nci := frame.(*qt.NewConnectionIdFrame) 70 | 71 | if nci.Length < 4 || nci.Length > 18 { 72 | err := fmt.Sprintf("Connection ID length must be comprised between 4 and 18, it was %d", nci.Length) 73 | trace.MarkError(NCI_HostSentInvalidCIDLength, err, pp) 74 | } 75 | 76 | alternativeConnectionIDs = append(alternativeConnectionIDs, hex.EncodeToString(nci.ConnectionId)) 77 | 78 | if !expectingResponse { 79 | trace.ErrorCode = NCI_HostDidNotAnswerToNewCID // Assume it did not answer until proven otherwise 80 | conn.DestinationCID = nci.ConnectionId 81 | conn.SourceCID = scid 82 | conn.FrameQueue.Submit(qt.QueuedFrame{&qt.NewConnectionIdFrame{1, 0, uint8(len(scid)), scid, resetToken}, qt.EncryptionLevelBest}) 83 | conn.SendHTTP09GETRequest(preferredPath, 0) 84 | expectingResponse = true 85 | } 86 | } 87 | } 88 | case <-conn.ConnectionClosed: 89 | return 90 | case <-s.Timeout(): 91 | return 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /scenarii/padding.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "github.com/QUIC-Tracker/quic-tracker/agents" 6 | . "github.com/QUIC-Tracker/quic-tracker/lib" 7 | ) 8 | 9 | const ( 10 | P_VNDidNotComplete = 1 11 | P_ReceivedSmth = 2 12 | ) 13 | 14 | type PaddingScenario struct { 15 | AbstractScenario 16 | } 17 | 18 | func NewPaddingScenario() *PaddingScenario { 19 | return &PaddingScenario{AbstractScenario{name: "padding", version: 1}} 20 | } 21 | func (s *PaddingScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 22 | connAgents := agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 23 | defer connAgents.StopAll() 24 | 25 | sendEmptyInitialPacket := func() { 26 | var initialLength int 27 | if conn.UseIPv6 { 28 | initialLength = qt.MinimumInitialLengthv6 29 | } else { 30 | initialLength = qt.MinimumInitialLength 31 | } 32 | 33 | initialPacket := qt.NewInitialPacket(conn) 34 | payloadLen := len(initialPacket.EncodePayload()) 35 | paddingLength := initialLength - (len(initialPacket.Header().Encode()) + int(VarIntLen(uint64(payloadLen))) + payloadLen + conn.CryptoState(qt.EncryptionLevelInitial).Write.Overhead()) 36 | for i := 0; i < paddingLength; i++ { 37 | initialPacket.Frames = append(initialPacket.Frames, new(qt.PaddingFrame)) 38 | } 39 | 40 | conn.DoSendPacket(initialPacket, qt.EncryptionLevelInitial) 41 | } 42 | 43 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 44 | 45 | sendEmptyInitialPacket() 46 | 47 | select { 48 | case i := <-incPackets: 49 | packet := i.(qt.Packet) 50 | if vn, ok := packet.(*qt.VersionNegotiationPacket); ok { 51 | if err := conn.ProcessVersionNegotation(vn); err != nil { 52 | trace.MarkError(P_VNDidNotComplete, err.Error(), vn) 53 | return 54 | } 55 | sendEmptyInitialPacket() 56 | } else { 57 | trace.MarkError(P_ReceivedSmth, "", packet) 58 | s.Finished() 59 | } 60 | case <-conn.ConnectionClosed: 61 | return 62 | case <-s.Timeout(): 63 | return 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /scenarii/retire_connection_id.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "fmt" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | 7 | "encoding/hex" 8 | ) 9 | 10 | const ( 11 | RCI_TLSHandshakeFailed = 1 12 | RCI_HostDidNotProvideCID = 2 13 | RCI_HostDidNotProvideNewCID = 3 14 | RCI_HostSentInvalidCIDLength = 4 15 | ) 16 | 17 | type RetireConnectionIDScenario struct { 18 | AbstractScenario 19 | } 20 | 21 | func NewRetireConnectionIDScenario() *RetireConnectionIDScenario { 22 | return &RetireConnectionIDScenario{AbstractScenario{name: "retire_connection_id", version: 1}} 23 | } 24 | func (s *RetireConnectionIDScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 25 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 26 | 27 | connAgents := s.CompleteHandshake(conn, trace, RCI_TLSHandshakeFailed) 28 | if connAgents == nil { 29 | return 30 | } 31 | defer connAgents.CloseConnection(false, 0, "") 32 | 33 | trace.ErrorCode = RCI_HostDidNotProvideCID 34 | 35 | var alternativeConnectionIDs []string 36 | defer func() { trace.Results["new_connection_ids"] = alternativeConnectionIDs }() 37 | 38 | var hasRetiredCIDs bool 39 | var highestSeq uint64 40 | for { 41 | select { 42 | case i := <-incPackets: 43 | p := i.(qt.Packet) 44 | 45 | if pp, ok := p.(*qt.ProtectedPacket); ok { 46 | for _, frame := range pp.GetAll(qt.NewConnectionIdType) { 47 | nci := frame.(*qt.NewConnectionIdFrame) 48 | 49 | if nci.Length < 4 || nci.Length > 18 { 50 | err := fmt.Sprintf("Connection ID length must be comprised between 4 and 18, it was %d", nci.Length) 51 | trace.MarkError(RCI_HostSentInvalidCIDLength, err, pp) 52 | } 53 | 54 | alternativeConnectionIDs = append(alternativeConnectionIDs, hex.EncodeToString(nci.ConnectionId)) 55 | 56 | if !hasRetiredCIDs { 57 | conn.FrameQueue.Submit(qt.QueuedFrame{&qt.RetireConnectionId{nci.Sequence}, qt.EncryptionLevel1RTT}) 58 | if highestSeq < nci.Sequence { 59 | highestSeq = nci.Sequence 60 | } 61 | } else if nci.Sequence > highestSeq { 62 | trace.ErrorCode = 0 63 | s.Finished() 64 | } 65 | } 66 | if !hasRetiredCIDs && pp.Contains(qt.NewConnectionIdType) { 67 | hasRetiredCIDs = true 68 | trace.ErrorCode = RCI_HostDidNotProvideNewCID 69 | } 70 | } 71 | case <-conn.ConnectionClosed: 72 | return 73 | case <-s.Timeout(): 74 | return 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /scenarii/scenario.go: -------------------------------------------------------------------------------- 1 | // 2 | // This package contains all the test scenarii that are part of the test suite. Each of them is executed in a separate 3 | // connection. For executing scenarii, use the scripts in the bin/test_suite package. 4 | // 5 | // When adding a new scenario, one should comply with the following requirements: 6 | // 7 | // Its Name() must match its source code file without the extension. 8 | // It must be registered in the GetAllScenarii() function. 9 | // It must define an upper bound on its completion time. It should use the Timeout() function to achieve this. 10 | // 11 | // 12 | package scenarii 13 | 14 | import ( 15 | qt "github.com/QUIC-Tracker/quic-tracker" 16 | 17 | "github.com/QUIC-Tracker/quic-tracker/agents" 18 | "time" 19 | ) 20 | 21 | type Scenario interface { 22 | Name() string 23 | Version() int 24 | IPv6() bool 25 | HTTP3() bool 26 | Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) 27 | SetTimer(d time.Duration) 28 | Timeout() <-chan time.Time 29 | Finished() 30 | } 31 | 32 | // Each scenario should embed this structure 33 | type AbstractScenario struct { 34 | name string 35 | version int 36 | ipv6 bool 37 | http3 bool 38 | duration time.Duration 39 | timeout *time.Timer 40 | } 41 | 42 | func (s *AbstractScenario) Name() string { 43 | return s.name 44 | } 45 | func (s *AbstractScenario) Version() int { 46 | return s.version 47 | } 48 | func (s *AbstractScenario) IPv6() bool { 49 | return s.ipv6 50 | } 51 | func (s *AbstractScenario) HTTP3() bool { 52 | return s.http3 53 | } 54 | func (s *AbstractScenario) SetTimer(d time.Duration) { 55 | s.timeout = time.NewTimer(d) 56 | if d == 0 { 57 | <-s.timeout.C 58 | } 59 | s.duration = d 60 | } 61 | func (s *AbstractScenario) Timeout() <-chan time.Time { 62 | return s.timeout.C 63 | } 64 | func (s *AbstractScenario) Finished() { 65 | if s.duration == 0 { 66 | s.timeout.Reset(0) 67 | } 68 | } 69 | 70 | // Useful helper for scenarii that requires the handshake to complete before executing their test and don't want to 71 | // discern the cause of its failure. 72 | func (s *AbstractScenario) CompleteHandshake(conn *qt.Connection, trace *qt.Trace, handshakeErrorCode uint8, additionalAgents ...agents.Agent) *agents.ConnectionAgents { 73 | connAgents := agents.AttachAgentsToConnection(conn, append(agents.GetDefaultAgents(), additionalAgents...)...) 74 | handshakeAgent := &agents.HandshakeAgent{TLSAgent: connAgents.Get("TLSAgent").(*agents.TLSAgent), SocketAgent: connAgents.Get("SocketAgent").(*agents.SocketAgent)} 75 | connAgents.Add(handshakeAgent) 76 | connAgents.Get("SendingAgent").(*agents.SendingAgent).FrameProducer = connAgents.GetFrameProducingAgents() 77 | 78 | handshakeStatus := handshakeAgent.HandshakeStatus.RegisterNewChan(10) 79 | handshakeAgent.InitiateHandshake() 80 | 81 | select { 82 | case i := <-handshakeStatus: 83 | status := i.(agents.HandshakeStatus) 84 | if !status.Completed { 85 | trace.MarkError(handshakeErrorCode, status.Error.Error(), status.Packet) 86 | connAgents.StopAll() 87 | return nil 88 | } 89 | case <-conn.ConnectionClosed: 90 | trace.MarkError(handshakeErrorCode, "connection closed", nil) 91 | connAgents.StopAll() 92 | return nil 93 | case <-s.Timeout(): 94 | trace.MarkError(handshakeErrorCode, "handshake timeout", nil) 95 | connAgents.StopAll() 96 | return nil 97 | } 98 | return connAgents 99 | } 100 | 101 | func GetAllScenarii() map[string]Scenario { 102 | return map[string]Scenario{ 103 | "zero_rtt": NewZeroRTTScenario(), 104 | "connection_migration": NewConnectionMigrationScenario(), 105 | "unsupported_tls_version": NewUnsupportedTLSVersionScenario(), 106 | "stream_opening_reordering": NewStreamOpeningReorderingScenario(), 107 | "multi_stream": NewMultiStreamScenario(), 108 | "new_connection_id": NewNewConnectionIDScenario(), 109 | "version_negotiation": NewVersionNegotiationScenario(), 110 | "handshake": NewHandshakeScenario(), 111 | "handshake_v6": NewHandshakev6Scenario(), 112 | "transport_parameters": NewTransportParameterScenario(), 113 | "address_validation": NewAddressValidationScenario(), 114 | "padding": NewPaddingScenario(), 115 | "flow_control": NewFlowControlScenario(), 116 | "ack_only": NewAckOnlyScenario(), 117 | "ack_ecn": NewAckECNScenario(), 118 | "stop_sending": NewStopSendingOnReceiveStreamScenario(), 119 | "http_get_and_wait": NewSimpleGetAndWaitScenario(), 120 | "http_get_on_uni_stream": NewGetOnStream2Scenario(), 121 | "key_update": NewKeyUpdateScenario(), 122 | "retire_connection_id": NewRetireConnectionIDScenario(), 123 | "http3_get": NewHTTP3GETScenario(), 124 | "http3_encoder_stream": NewHTTP3EncoderStreamScenario(), 125 | "http3_uni_streams_limits": NewHTTP3UniStreamsLimitsScenario(), 126 | "http3_reserved_frames": NewHTTP3ReservedFramesScenario(), 127 | "http3_reserved_streams": NewHTTP3ReservedStreamsScenario(), 128 | "spin_bit": NewSpinBitScenario(), 129 | "server_flow_control": NewServerFlowControlScenario(), 130 | "connection_migration_v4_v6": NewConnectionMigrationv4v6Scenario(), 131 | "zero_length_cid": NewZeroLengthCID(), 132 | "multi_packet_client_hello": NewMultiPacketClientHello(), 133 | "closed_connection": NewClosedConnectionScenario(), 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /scenarii/server_flow_control.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "fmt" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | ) 7 | 8 | const ( 9 | SFC_TLSHandshakeFailed = 1 10 | SFC_DidNotClose = 2 11 | ) 12 | 13 | type ServerFlowControlScenario struct { 14 | AbstractScenario 15 | } 16 | 17 | func NewServerFlowControlScenario() *ServerFlowControlScenario { 18 | return &ServerFlowControlScenario{AbstractScenario{name: "server_flow_control", version: 1}} 19 | } 20 | func (s *ServerFlowControlScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 21 | connAgents := s.CompleteHandshake(conn, trace, SFC_TLSHandshakeFailed) 22 | if connAgents == nil { 23 | return 24 | } 25 | defer connAgents.CloseConnection(false, 0, "") 26 | 27 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 28 | 29 | data := []byte(fmt.Sprintf("GET %s\r\n", preferredPath)) 30 | f := qt.StreamFrame{ 31 | FinBit: true, LenBit: true, OffBit: true, 32 | StreamId: 0, 33 | Offset: conn.TLSTPHandler.ReceivedParameters.MaxStreamDataBidiRemote - uint64(len(data)) + 1, 34 | Length: uint64(len(data)), StreamData: data, 35 | } 36 | 37 | conn.FrameQueue.Submit(qt.QueuedFrame{&f, qt.EncryptionLevelBestAppData}) 38 | 39 | trace.ErrorCode = SFC_DidNotClose 40 | for { 41 | select { 42 | case i := <-incPackets: 43 | if p, ok := i.(qt.Framer); ok && (p.Contains(qt.ConnectionCloseType)) { 44 | cc := p.GetFirst(qt.ConnectionCloseType).(*qt.ConnectionCloseFrame) 45 | if cc.ErrorCode == 0x3 { 46 | trace.ErrorCode = 0 47 | } else if cc.ErrorCode & 0x100 == 0x100 { 48 | trace.ErrorCode = SFC_TLSHandshakeFailed 49 | } 50 | s.Finished() 51 | } 52 | case <-conn.ConnectionClosed: 53 | return 54 | case <-s.Timeout(): 55 | return 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scenarii/spin_bit.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | . "github.com/QUIC-Tracker/quic-tracker" 5 | ) 6 | 7 | const ( 8 | SB_TLSHandshakeFailed = 1 9 | SB_DoesNotSpin = 2 10 | ) 11 | 12 | type SpinBitScenario struct { 13 | AbstractScenario 14 | } 15 | 16 | func NewSpinBitScenario() *SpinBitScenario { 17 | return &SpinBitScenario{AbstractScenario{name: "spin_bit", version: 1, ipv6: false}} 18 | } 19 | func (s *SpinBitScenario) Run(conn *Connection, trace *Trace, preferredPath string, debug bool) { 20 | connAgents := s.CompleteHandshake(conn, trace, SB_TLSHandshakeFailed) 21 | if connAgents == nil { 22 | return 23 | } 24 | defer connAgents.CloseConnection(false, 0, "") 25 | 26 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 27 | 28 | http := connAgents.AddHTTPAgent() 29 | responseChan := http.SendRequest(preferredPath, "GET", trace.Host, nil) 30 | 31 | var lastServerSpin SpinBit 32 | spins := 0 33 | 34 | forLoop: 35 | for { 36 | select { 37 | case i := <-incomingPackets: 38 | switch p := i.(type) { 39 | case *ProtectedPacket: 40 | hdr := p.Header().(*ShortHeader) 41 | if hdr.PacketNumber() >= conn.LastSpinNumber { 42 | if hdr.SpinBit != lastServerSpin { 43 | lastServerSpin = hdr.SpinBit 44 | spins++ 45 | } 46 | conn.SpinBit = !hdr.SpinBit 47 | conn.LastSpinNumber = hdr.PacketNumber() 48 | } 49 | if conn.Streams.Get(0).ReadClosed && !conn.Streams.Get(4).WriteClosed { 50 | http.SendRequest(preferredPath, "GET", trace.Host, nil) 51 | } 52 | } 53 | case r := <-responseChan: 54 | if r != nil { 55 | http.SendRequest(preferredPath, "GET", trace.Host, nil) 56 | } 57 | case <-conn.ConnectionClosed: 58 | break forLoop 59 | case <-s.Timeout(): 60 | break forLoop 61 | } 62 | } 63 | 64 | if spins <= 1 { 65 | trace.ErrorCode = SB_DoesNotSpin 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /scenarii/stop_sending_frame_on_receive_stream.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "fmt" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | ) 7 | 8 | const ( 9 | SSRS_TLSHandshakeFailed = 1 10 | SSRS_DidNotCloseTheConnection = 2 11 | SSRS_CloseTheConnectionWithWrongError = 3 12 | SSRS_MaxStreamUniTooLow = 4 13 | SSRS_UnknownError = 5 14 | ) 15 | 16 | type StopSendingOnReceiveStreamScenario struct { 17 | AbstractScenario 18 | } 19 | 20 | func NewStopSendingOnReceiveStreamScenario() *StopSendingOnReceiveStreamScenario { 21 | return &StopSendingOnReceiveStreamScenario{AbstractScenario{name: "stop_sending_frame_on_receive_stream", version: 1}} 22 | } 23 | 24 | func (s *StopSendingOnReceiveStreamScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 25 | connAgents := s.CompleteHandshake(conn, trace, SSRS_TLSHandshakeFailed) 26 | if connAgents == nil { 27 | return 28 | } 29 | defer connAgents.CloseConnection(false, 0, "") 30 | 31 | if conn.TLSTPHandler.ReceivedParameters.MaxUniStreams == 0 { 32 | trace.MarkError(SSRS_MaxStreamUniTooLow, "", nil) 33 | return 34 | } 35 | 36 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 37 | 38 | conn.SendHTTP09GETRequest(preferredPath, 2) 39 | conn.FrameQueue.Submit(qt.QueuedFrame{&qt.StopSendingFrame{2, 0}, qt.EncryptionLevel1RTT}) 40 | 41 | trace.ErrorCode = SSRS_DidNotCloseTheConnection 42 | for { 43 | select { 44 | case i := <-incPackets: 45 | switch p := i.(type) { 46 | case qt.Framer: 47 | if p.Contains(qt.ConnectionCloseType) { 48 | cc := p.GetFirst(qt.ConnectionCloseType).(*qt.ConnectionCloseFrame) 49 | if cc.ErrorCode != qt.ERR_STREAM_STATE_ERROR && cc.ErrorCode != qt.ERR_PROTOCOL_VIOLATION { 50 | trace.MarkError(SSRS_CloseTheConnectionWithWrongError, fmt.Sprintf("Expected 0x%02x, got 0x%02x", qt.ERR_STREAM_STATE_ERROR, cc.ErrorCode), p) 51 | trace.Results["connection_closed_error_code"] = fmt.Sprintf("0x%x", cc.ErrorCode) 52 | return 53 | } 54 | trace.ErrorCode = 0 55 | return 56 | } 57 | } 58 | case <-conn.ConnectionClosed: 59 | return 60 | case <-s.Timeout(): 61 | return 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /scenarii/stream_opening_reordering.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "fmt" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | "strings" 7 | 8 | "time" 9 | ) 10 | 11 | const ( 12 | SOR_TLSHandshakeFailed = 1 13 | SOR_HostDidNotRespond = 2 14 | SOR_EndpointDoesNotSupportHQ = 3 15 | ) 16 | 17 | type StreamOpeningReorderingScenario struct { 18 | AbstractScenario 19 | } 20 | 21 | func NewStreamOpeningReorderingScenario() *StreamOpeningReorderingScenario { 22 | return &StreamOpeningReorderingScenario{AbstractScenario{name: "stream_opening_reordering", version: 2}} 23 | } 24 | func (s *StreamOpeningReorderingScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 25 | if !strings.Contains(conn.ALPN, "hq") { 26 | trace.ErrorCode = SOR_EndpointDoesNotSupportHQ 27 | return 28 | } 29 | 30 | connAgents := s.CompleteHandshake(conn, trace, SOR_TLSHandshakeFailed) 31 | if connAgents == nil { 32 | return 33 | } 34 | defer connAgents.CloseConnection(false, 0, "") 35 | 36 | incomingPackets := conn.IncomingPackets.RegisterNewChan(1000) 37 | 38 | <-time.NewTimer(20 * time.Millisecond).C // Simulates the SendingAgent behaviour 39 | 40 | payload := []byte(fmt.Sprintf("GET %s\r\n", preferredPath)) 41 | 42 | pp1 := qt.NewProtectedPacket(conn) 43 | pp1.Frames = append(pp1.Frames, qt.NewStreamFrame(0, 0, payload, false)) 44 | 45 | pp2 := qt.NewProtectedPacket(conn) 46 | pp2.Frames = append(pp2.Frames, qt.NewStreamFrame(0, uint64(len(payload)), []byte{}, true)) 47 | 48 | conn.DoSendPacket(pp2, qt.EncryptionLevel1RTT) 49 | conn.DoSendPacket(pp1, qt.EncryptionLevel1RTT) 50 | 51 | forLoop: 52 | for { 53 | select { 54 | case <-incomingPackets: 55 | if conn.Streams.Get(0).ReadClosed { 56 | s.Finished() 57 | } 58 | case <-conn.ConnectionClosed: 59 | break forLoop 60 | case <-s.Timeout(): 61 | break forLoop 62 | } 63 | } 64 | 65 | if !conn.Streams.Get(0).ReadClosed { 66 | trace.ErrorCode = SOR_HostDidNotRespond 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /scenarii/transport_parameters.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "encoding/binary" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | "time" 7 | ) 8 | 9 | const ( 10 | TP_NoTPReceived = 1 11 | TP_TPResentAfterVN = 2 // All others error code are now handled by the handshake scenario 12 | TP_HandshakeDidNotComplete = 3 13 | TP_MissingParameters = 4 // Since draft-15, there are no parameters that MUST be present 14 | ) 15 | 16 | type TransportParameterScenario struct { 17 | AbstractScenario 18 | } 19 | 20 | func NewTransportParameterScenario() *TransportParameterScenario { 21 | return &TransportParameterScenario{AbstractScenario{name: "transport_parameters", version: 4}} 22 | } 23 | func (s *TransportParameterScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 24 | s.timeout = time.NewTimer(10 * time.Second) 25 | for i := uint16(0xff00); i <= 0xff08; i++ { 26 | p := qt.TransportParameter{qt.TransportParametersType(i), qt.NewVarInt(uint64(i)).Encode()} 27 | conn.TLSTPHandler.AdditionalParameters.AddParameter(p) 28 | } 29 | 30 | for i := 0; i < 8; i++ { 31 | t := uint16((31 * i) + 27) 32 | p := qt.TransportParameter{ParameterType: qt.TransportParametersType(t)} 33 | p.Value = make([]byte, 2, 2) 34 | binary.BigEndian.PutUint16(p.Value, t) 35 | conn.TLSTPHandler.AdditionalParameters.AddParameter(p) 36 | } 37 | 38 | connAgents := s.CompleteHandshake(conn, trace, TP_HandshakeDidNotComplete) 39 | if connAgents == nil { 40 | return 41 | } 42 | s.Finished() 43 | <-s.timeout.C 44 | defer connAgents.CloseConnection(false, 0, "") 45 | 46 | trace.Results["transport_parameters"] = conn.TLSTPHandler.EncryptedExtensionsTransportParameters 47 | trace.Results["decoded_parameters"] = conn.TLSTPHandler.ReceivedParameters.ToJSON 48 | } -------------------------------------------------------------------------------- /scenarii/unsupported_tls_version.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | "bytes" 5 | qt "github.com/QUIC-Tracker/quic-tracker" 6 | 7 | "github.com/QUIC-Tracker/quic-tracker/agents" 8 | ) 9 | 10 | const ( 11 | UTS_NoConnectionCloseSent = 1 12 | UTS_WrongErrorCodeIsUsed = 2 // See https://tools.ietf.org/html/draft-ietf-quic-tls-10#section-11 13 | UTS_VNDidNotComplete = 3 14 | UTS_ReceivedUnexpectedPacketType = 4 15 | ) 16 | 17 | type UnsupportedTLSVersionScenario struct { 18 | AbstractScenario 19 | } 20 | 21 | func NewUnsupportedTLSVersionScenario() *UnsupportedTLSVersionScenario { 22 | return &UnsupportedTLSVersionScenario{AbstractScenario{name: "unsupported_tls_version", version: 1}} 23 | } 24 | func (s *UnsupportedTLSVersionScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 25 | connAgents := agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 26 | connAgents.Get("TLSAgent").(*agents.TLSAgent).DisableFrameSending = true 27 | connAgents.Get("SendingAgent").(*agents.SendingAgent).FrameProducer = connAgents.GetFrameProducingAgents() 28 | defer connAgents.StopAll() 29 | 30 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 31 | 32 | sendUnsupportedInitial(conn) 33 | 34 | var connectionClosed bool 35 | forLoop: 36 | for { 37 | select { 38 | case i := <-incPackets: 39 | switch p := i.(type) { 40 | case *qt.VersionNegotiationPacket: 41 | if err := conn.ProcessVersionNegotation(p); err != nil { 42 | trace.MarkError(UTS_VNDidNotComplete, err.Error(), p) 43 | return 44 | } 45 | sendUnsupportedInitial(conn) 46 | case *qt.RetryPacket: 47 | conn.DestinationCID = p.Header().(*qt.LongHeader).SourceCID 48 | conn.TransitionTo(qt.QuicVersion, qt.QuicALPNToken) 49 | conn.Token = p.RetryToken 50 | sendUnsupportedInitial(conn) 51 | case qt.Framer: 52 | for _, frame := range p.GetFrames() { 53 | if cc, ok := frame.(*qt.ConnectionCloseFrame); ok { // See https://tools.ietf.org/html/draft-ietf-quic-tls-10#section-11 54 | if cc.ErrorCode != 0x146 { // TLS Alert: procotol_version 55 | trace.MarkError(UTS_WrongErrorCodeIsUsed, "", p) 56 | } 57 | trace.Results["connection_reason_phrase"] = cc.ReasonPhrase 58 | connectionClosed = true 59 | s.Finished() 60 | } 61 | } 62 | } 63 | case <-conn.ConnectionClosed: 64 | break forLoop 65 | case <-s.Timeout(): 66 | if !connectionClosed { 67 | trace.ErrorCode = UTS_NoConnectionCloseSent 68 | } 69 | break forLoop 70 | } 71 | } 72 | } 73 | 74 | func sendUnsupportedInitial(conn *qt.Connection) { 75 | initialPacket := conn.GetInitialPacket() 76 | for _, f := range initialPacket.Frames { // Advertise support of TLS 1.3 draft-00 only 77 | if frame, ok := f.(*qt.CryptoFrame); ok { 78 | frame.CryptoData = bytes.Replace(frame.CryptoData, []byte{0x0, 0x2b, 0x0, 0x03, 0x2, 0x03, 0x04}, []byte{0x0, 0x2b, 0x0, 0x03, 0x2, 0x7f, 0x00}, 1) 79 | } 80 | } 81 | conn.DoSendPacket(initialPacket, qt.EncryptionLevelInitial) 82 | } 83 | -------------------------------------------------------------------------------- /scenarii/version_negotiation.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | 6 | "github.com/QUIC-Tracker/quic-tracker/agents" 7 | ) 8 | 9 | const ( 10 | VN_NotAnsweringToVN = 1 11 | VN_DidNotEchoVersion = 2 // draft-07 and below were stating that VN packets should echo the version of the client. It is not used anymore 12 | VN_LastTwoVersionsAreActuallySeal = 3 // draft-05 and below used AEAD to seal cleartext packets, VN packets should not be sealed, but some implementations did anyway. 13 | VN_Timeout = 4 14 | VN_UnusedFieldIsIdentical = 5 // See https://github.com/quicwg/base-drafts/issues/963 15 | ) 16 | 17 | const ForceVersionNegotiation = 0x1a2a3a4a 18 | 19 | type VersionNegotiationScenario struct { 20 | AbstractScenario 21 | } 22 | 23 | func NewVersionNegotiationScenario() *VersionNegotiationScenario { 24 | return &VersionNegotiationScenario{AbstractScenario{name: "version_negotiation", version: 2}} 25 | } 26 | func (s *VersionNegotiationScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 27 | connAgents := agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 28 | defer connAgents.StopAll() 29 | 30 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 31 | 32 | conn.Version = ForceVersionNegotiation 33 | trace.ErrorCode = VN_Timeout 34 | initial := conn.GetInitialPacket() 35 | conn.DoSendPacket(initial, qt.EncryptionLevelInitial) 36 | 37 | threshold := 3 38 | vnCount := 0 39 | var unusedField byte 40 | for { 41 | select { 42 | case i := <-incPackets: 43 | switch p := i.(type) { 44 | case *qt.VersionNegotiationPacket: 45 | vnCount++ 46 | if vnCount > 1 && unusedField != p.UnusedField { 47 | trace.ErrorCode = 0 48 | s.Finished() 49 | } else if vnCount == threshold { 50 | trace.ErrorCode = VN_UnusedFieldIsIdentical 51 | s.Finished() 52 | } else { 53 | unusedField = p.UnusedField 54 | trace.Results["supported_versions"] = p.SupportedVersions // TODO: Compare versions announced ? 55 | newInitial := qt.NewInitialPacket(conn) 56 | newInitial.Frames = initial.Frames 57 | conn.DoSendPacket(newInitial, qt.EncryptionLevelInitial) 58 | } 59 | case qt.Packet: 60 | trace.MarkError(VN_NotAnsweringToVN, "", p) 61 | trace.Results["received_packet_type"] = p.Header().PacketType() 62 | } 63 | case <-conn.ConnectionClosed: 64 | return 65 | case <-s.Timeout(): 66 | return 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /scenarii/zero_length_cid.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import qt "github.com/QUIC-Tracker/quic-tracker" 4 | 5 | const ( 6 | ZLCID_TLSHandshakeFailed = 1 7 | ZLCID_RequestFailed = 2 8 | ) 9 | 10 | type ZeroLengthCID struct { 11 | AbstractScenario 12 | } 13 | 14 | func NewZeroLengthCID() *ZeroLengthCID { 15 | return &ZeroLengthCID{AbstractScenario{name: "zero_length_cid", version: 1}} 16 | } 17 | 18 | func (s *ZeroLengthCID) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 19 | conn.SourceCID = nil 20 | conn.TLSTPHandler.InitialSourceConnectionId = nil 21 | connAgents := s.CompleteHandshake(conn, trace, ZLCID_TLSHandshakeFailed) 22 | if connAgents == nil { 23 | return 24 | } 25 | 26 | connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 27 | 28 | <-s.Timeout() 29 | 30 | if !conn.Streams.Get(0).ReadClosed { 31 | trace.ErrorCode = ZLCID_RequestFailed 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /scenarii/zero_rtt.go: -------------------------------------------------------------------------------- 1 | package scenarii 2 | 3 | import ( 4 | qt "github.com/QUIC-Tracker/quic-tracker" 5 | "strings" 6 | 7 | "time" 8 | "github.com/QUIC-Tracker/quic-tracker/agents" 9 | ) 10 | 11 | const ( 12 | ZR_TLSHandshakeFailed = 1 13 | ZR_NoResumptionSecret = 2 14 | ZR_ZeroRTTFailed = 3 15 | ZR_DidntReceiveTheRequestedData = 4 16 | ) 17 | 18 | type ZeroRTTScenario struct { 19 | AbstractScenario 20 | } 21 | 22 | func NewZeroRTTScenario() *ZeroRTTScenario { 23 | return &ZeroRTTScenario{AbstractScenario{name: "zero_rtt", version: 1}} 24 | } 25 | func (s *ZeroRTTScenario) Run(conn *qt.Connection, trace *qt.Trace, preferredPath string, debug bool) { 26 | connAgents := s.CompleteHandshake(conn, trace, ZR_TLSHandshakeFailed) 27 | if connAgents == nil { 28 | return 29 | } 30 | 31 | incPackets := conn.IncomingPackets.RegisterNewChan(1000) 32 | 33 | resumptionTicket := connAgents.Get("TLSAgent").(*agents.TLSAgent).ResumptionTicket.RegisterNewChan(10) 34 | 35 | ticket := conn.Tls.ResumptionTicket() 36 | 37 | if len(ticket) <= 0 { 38 | getTicket: 39 | for { 40 | select { 41 | case i := <-resumptionTicket: 42 | ticket = i.([]byte) 43 | break getTicket 44 | case <-conn.ConnectionClosed: 45 | trace.MarkError(ZR_NoResumptionSecret, "", nil) 46 | connAgents.CloseConnection(false, 0, "") 47 | return 48 | case <-s.Timeout(): 49 | trace.MarkError(ZR_NoResumptionSecret, "", nil) 50 | connAgents.CloseConnection(false, 0, "") 51 | return 52 | } 53 | } 54 | } 55 | connAgents.CloseConnection(false, 0, "") 56 | 57 | <-time.NewTimer(3 * time.Second).C 58 | 59 | rh, sh, token := conn.ReceivedPacketHandler, conn.SentPacketHandler, conn.Token 60 | 61 | var err error 62 | conn, err = qt.NewDefaultConnection(conn.Host.String(), conn.ServerName, ticket, s.ipv6, "hq", strings.Contains(conn.ALPN, "h3")) 63 | conn.ReceivedPacketHandler = rh 64 | conn.SentPacketHandler = sh 65 | conn.Token = token 66 | if err != nil { 67 | trace.MarkError(ZR_ZeroRTTFailed, err.Error(), nil) 68 | return 69 | } 70 | 71 | connAgents = agents.AttachAgentsToConnection(conn, agents.GetDefaultAgents()...) 72 | connAgents.Stop("RecoveryAgent") 73 | handshakeAgent := &agents.HandshakeAgent{TLSAgent: connAgents.Get("TLSAgent").(*agents.TLSAgent), SocketAgent: connAgents.Get("SocketAgent").(*agents.SocketAgent)} 74 | connAgents.Add(handshakeAgent) 75 | connAgents.Get("SendingAgent").(*agents.SendingAgent).FrameProducer = connAgents.GetFrameProducingAgents() 76 | defer connAgents.CloseConnection(false, 0, "") 77 | defer trace.Complete(conn) 78 | 79 | incPackets = conn.IncomingPackets.RegisterNewChan(1000) 80 | encryptionLevelsAvailable := conn.EncryptionLevels.RegisterNewChan(10) 81 | 82 | responseChan := connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) // TODO: Verify that this get effectively sent in a 0-RTT packet 83 | handshakeAgent.InitiateHandshake() // TODO: Handle stateless connection 84 | 85 | if !s.waitFor0RTT(conn, trace, encryptionLevelsAvailable) { 86 | return 87 | } 88 | 89 | trace.ErrorCode = ZR_DidntReceiveTheRequestedData 90 | for { 91 | select { 92 | case i := <-incPackets: 93 | switch i.(type) { 94 | case *qt.RetryPacket: 95 | if !s.waitFor0RTT(conn, trace, encryptionLevelsAvailable) { 96 | return 97 | } 98 | responseChan = connAgents.AddHTTPAgent().SendRequest(preferredPath, "GET", trace.Host, nil) 99 | } 100 | case <-responseChan: 101 | trace.ErrorCode = 0 102 | case <-conn.ConnectionClosed: 103 | return 104 | case <-s.Timeout(): 105 | return 106 | } 107 | } 108 | } 109 | 110 | func (s *ZeroRTTScenario) waitFor0RTT(conn *qt.Connection, trace *qt.Trace, encryptionLevelsAvailable chan interface{}) bool { 111 | for { 112 | select { 113 | case i := <-encryptionLevelsAvailable: 114 | eL := i.(qt.DirectionalEncryptionLevel) 115 | if eL.EncryptionLevel == qt.EncryptionLevel0RTT && !eL.Read { 116 | return true 117 | } 118 | case <-conn.ConnectionClosed: 119 | trace.ErrorCode = ZR_ZeroRTTFailed 120 | trace.Results["error"] = "0-RTT encryption was not available after feeding in the ticket" 121 | return false 122 | case <-s.Timeout(): 123 | trace.ErrorCode = ZR_ZeroRTTFailed 124 | trace.Results["error"] = "0-RTT encryption was not available after feeding in the ticket" 125 | return false 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /streams_test.go: -------------------------------------------------------------------------------- 1 | package quictracker 2 | 3 | import ( 4 | "testing" 5 | "bytes" 6 | "github.com/davecgh/go-spew/spew" 7 | ) 8 | 9 | func TestByteIntervalList_Add(t *testing.T) { 10 | l := NewbyteIntervalList() 11 | l.Add(byteInterval{1, 2}) 12 | l.Add(byteInterval{4, 5}) 13 | 14 | if ok, a, e := iterEquals(l, []byteInterval{{1, 2}, {4, 5}}); !ok { 15 | l.Println() 16 | if a != e { 17 | t.Error("Expected ", e, "got ", a) 18 | } else { 19 | t.Error("Lengthes differ") 20 | } 21 | } 22 | 23 | l.Add(byteInterval{2, 4}) 24 | 25 | if ok, a, e := iterEquals(l, []byteInterval{{1, 5}}); !ok { 26 | l.Println() 27 | if a != e { 28 | t.Error("Expected ", e, "got ", a) 29 | } else { 30 | t.Error("Lengthes differ") 31 | } 32 | } 33 | 34 | l.Add(byteInterval{6, 8}) 35 | if ok, a, e := iterEquals(l, []byteInterval{{1, 5}, {6, 8}}); !ok { 36 | l.Println() 37 | if a != e { 38 | t.Error("Expected ", e, "got ", a) 39 | } else { 40 | t.Error("Lengthes differ") 41 | } 42 | } 43 | } 44 | 45 | func TestByteIntervalList_Fill(t *testing.T) { 46 | l := NewbyteIntervalList() 47 | l.Add(byteInterval{1, 2}) 48 | l.Add(byteInterval{4, 6}) 49 | 50 | l.Fill(byteInterval{4,5}) 51 | 52 | if ok, a, e := iterEquals(l, []byteInterval{{1, 2}, {5, 6}}); !ok { 53 | l.Println() 54 | if a != e { 55 | t.Error("Expected ", e, "got ", a) 56 | } else { 57 | t.Error("Lengthes differ") 58 | } 59 | } 60 | 61 | l.Add(byteInterval{2, 5}) 62 | if ok, a, e := iterEquals(l, []byteInterval{{1, 6}}); !ok { 63 | l.Println() 64 | if a != e { 65 | t.Error("Expected ", e, "got ", a) 66 | } else { 67 | t.Error("Lengthes differ") 68 | } 69 | } 70 | 71 | l.Fill(byteInterval{3,4}) 72 | 73 | if ok, a, e := iterEquals(l, []byteInterval{{1, 2}, {5, 6}}); !ok { 74 | l.Println() 75 | if a != e { 76 | t.Error("Expected ", e, "got ", a) 77 | } else { 78 | t.Error("Lengthes differ") 79 | } 80 | } 81 | 82 | l.Fill(byteInterval{1,2}) 83 | 84 | if ok, a, e := iterEquals(l, []byteInterval{{5, 6}}); !ok { 85 | l.Println() 86 | if a != e { 87 | t.Error("Expected ", e, "got ", a) 88 | } else { 89 | t.Error("Lengthes differ") 90 | } 91 | } 92 | 93 | l = NewbyteIntervalList() 94 | 95 | } 96 | 97 | func TestStreamAddToRead (t *testing.T) { 98 | s := NewStream() 99 | 100 | readChan := make(chan interface{}, 10) 101 | s.ReadChan.Register(readChan) 102 | s.addToRead(&StreamFrame{Offset: 4, Length: 4, StreamData:[]byte{4, 5, 6, 7}}) 103 | 104 | select { 105 | case _ = <- readChan: 106 | t.Error("Should not return data") 107 | default: 108 | } 109 | 110 | s.addToRead(&StreamFrame{Offset: 0, Length: 4, StreamData: []byte{0, 1, 2, 3}}) 111 | 112 | var dataRead []byte 113 | 114 | read: 115 | for { 116 | select { 117 | case i := <- readChan: 118 | data := i.([]byte) 119 | dataRead = append(dataRead, data...) 120 | default: 121 | break read 122 | } 123 | } 124 | 125 | if !bytes.Equal(dataRead, []byte{0, 1, 2, 3, 4, 5, 6, 7}) { 126 | spew.Dump(dataRead) 127 | t.Error("Should be equal") 128 | } 129 | 130 | s.addToRead(&StreamFrame{Offset: 12, Length: 4, StreamData: []byte{4, 5, 6, 7}}) 131 | s.addToRead(&StreamFrame{Offset: 16, Length: 4, StreamData: []byte{8, 9, 10, 11}, FinBit: true}) 132 | 133 | if s.ReadClosed { 134 | t.Error("Should not be closed yet") 135 | } 136 | 137 | s.addToRead(&StreamFrame{Offset: 8, Length: 4, StreamData: []byte{0, 1, 2, 3}}) 138 | 139 | if !s.ReadClosed { 140 | t.Error("Should be closed now") 141 | } 142 | 143 | read2: 144 | for { 145 | select { 146 | case i := <- readChan: 147 | data := i.([]byte) 148 | dataRead = append(dataRead, data...) 149 | default: 150 | break read2 151 | } 152 | } 153 | 154 | if !bytes.Equal(dataRead, []byte{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}) { 155 | spew.Dump(dataRead) 156 | t.Error("Should be equal") 157 | } 158 | } 159 | 160 | func iterEquals(l *byteIntervalList, expected []byteInterval) (bool, *byteInterval, *byteInterval) { 161 | i := 0 162 | if l.Len() != len(expected) { 163 | return false, nil, nil 164 | } 165 | for n := l.Front(); n != nil && i < len(expected); n = n.Next() { 166 | if n.Value != expected[i] { 167 | return false, &n.Value, &expected[i] 168 | } 169 | i++ 170 | } 171 | return true, nil, nil 172 | } 173 | -------------------------------------------------------------------------------- /trace.go: -------------------------------------------------------------------------------- 1 | package quictracker 2 | 3 | import ( 4 | "os/exec" 5 | "time" 6 | "strings" 7 | "unsafe" 8 | "github.com/mpiraux/pigotls" 9 | ) 10 | 11 | // Contains the result of a test run against a given host. 12 | type Trace struct { 13 | Commit string `json:"commit"` // The git commit that versions the code that produced the trace 14 | Scenario string `json:"scenario"` // The id of the scenario that produced the trace 15 | ScenarioVersion int `json:"scenario_version"` 16 | Host string `json:"host"` // The host against which the scenario was run 17 | Ip string `json:"ip"` // The IP that was resolved for the given host 18 | Results map[string]interface{} `json:"results"` // A dictionary that allows to report scenario-specific results 19 | StartedAt int64 `json:"started_at"` // The time at which the scenario started in epoch seconds 20 | Duration uint64 `json:"duration"` // Its duration in epoch milliseconds 21 | ErrorCode uint8 `json:"error_code"` // A scenario-specific error code that reports its verdict 22 | Stream []TracePacket `json:"stream"` // A clear-text copy of the packets that were sent and received 23 | Pcap []byte `json:"pcap"` // The packet capture file associated with the trace 24 | QLog interface{} `json:"qlog"` // The QLog trace captured during the test run 25 | ClientRandom []byte `json:"client_random"` 26 | Secrets map[pigotls.Epoch]Secrets `json:"secrets"` 27 | } 28 | 29 | type Secrets struct { 30 | Epoch pigotls.Epoch `json:"epoch"` 31 | Read []byte `json:"read"` 32 | Write []byte `json:"write"` 33 | } 34 | 35 | func NewTrace(scenarioName string, scenarioVersion int, host string) *Trace { 36 | trace := Trace{ 37 | Scenario: scenarioName, 38 | ScenarioVersion: scenarioVersion, 39 | Commit: GitCommit(), 40 | Host: host, 41 | StartedAt: time.Now().Unix(), 42 | Results: make(map[string]interface{}), 43 | } 44 | 45 | return &trace 46 | } 47 | 48 | func (t *Trace) AddPcap(conn *Connection, cmd *exec.Cmd) error { 49 | content, err := StopPcapCapture(conn, cmd) 50 | if err != nil { 51 | return err 52 | } 53 | t.Pcap = content 54 | return err 55 | } 56 | 57 | func (t *Trace) MarkError(error uint8, message string, packet Packet) { 58 | t.ErrorCode = error 59 | if message != "" { 60 | t.Results["error"] = message 61 | } 62 | if packet == nil { 63 | return 64 | } 65 | for _, tp := range t.Stream { 66 | if tp.Pointer == packet.Pointer() { 67 | tp.IsOfInterest = true 68 | return 69 | } 70 | } 71 | } 72 | 73 | func (t *Trace) AttachTo(conn *Connection) { 74 | conn.ReceivedPacketHandler = func(data []byte, origin unsafe.Pointer) { 75 | t.Stream = append(t.Stream, TracePacket{Direction: ToClient, Timestamp: time.Now().UnixNano() / 1e6, Data: data, Pointer: origin}) 76 | } 77 | conn.SentPacketHandler = func(data []byte, origin unsafe.Pointer) { 78 | t.Stream = append(t.Stream, TracePacket{Direction: ToServer, Timestamp: time.Now().UnixNano() / 1e6, Data: data, Pointer: origin}) 79 | } 80 | } 81 | 82 | func (t *Trace) Complete(conn *Connection) { 83 | if len(t.ClientRandom) == 0 { 84 | t.ClientRandom = conn.Tls.ClientRandom() 85 | } 86 | if t.Secrets == nil { 87 | t.Secrets = make(map[pigotls.Epoch]Secrets) 88 | } 89 | if _, ok := t.Secrets[pigotls.Epoch0RTT]; !ok && len(conn.Tls.ZeroRTTSecret()) > 0 { 90 | t.Secrets[pigotls.Epoch0RTT] = Secrets{Epoch: pigotls.Epoch0RTT, Write: conn.Tls.ZeroRTTSecret()} 91 | } 92 | if _, ok := t.Secrets[pigotls.EpochHandshake]; !ok && len(conn.Tls.HandshakeReadSecret()) > 0 || len(conn.Tls.HandshakeWriteSecret()) > 0 { 93 | t.Secrets[pigotls.EpochHandshake] = Secrets{Epoch: pigotls.EpochHandshake, Read: conn.Tls.HandshakeReadSecret(), Write: conn.Tls.HandshakeWriteSecret()} 94 | } 95 | if _, ok := t.Secrets[pigotls.Epoch1RTT]; !ok && len(conn.Tls.ProtectedReadSecret()) > 0 || len(conn.Tls.ProtectedWriteSecret()) > 0 { 96 | t.Secrets[pigotls.Epoch1RTT] = Secrets{Epoch: pigotls.Epoch1RTT, Read: conn.Tls.ProtectedReadSecret(), Write: conn.Tls.ProtectedWriteSecret()} 97 | } 98 | } 99 | 100 | type Direction string 101 | 102 | const ToServer Direction = "to_server" 103 | const ToClient Direction = "to_client" 104 | 105 | type TracePacket struct { 106 | Direction Direction `json:"direction"` 107 | Timestamp int64 `json:"timestamp"` 108 | Data []byte `json:"data"` 109 | IsOfInterest bool `json:"is_of_interest"` 110 | Pointer unsafe.Pointer `json:"-"` 111 | } 112 | 113 | func GitCommit() string { 114 | var ( 115 | cmdOut []byte 116 | err error 117 | ) 118 | cmdName := "git" 119 | cmdArgs := []string{"rev-parse", "--verify", "HEAD"} 120 | if cmdOut, err = exec.Command(cmdName, cmdArgs...).Output(); err != nil { 121 | return "" 122 | } 123 | return strings.TrimSpace(string(cmdOut)) 124 | } -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package quictracker 2 | 3 | import "github.com/dustin/go-broadcast" 4 | 5 | type Broadcaster struct { 6 | broadcast.Broadcaster 7 | channels []chan interface{} 8 | isClosed bool 9 | } 10 | 11 | func NewBroadcaster(buflen int) Broadcaster { 12 | return Broadcaster{Broadcaster: broadcast.NewBroadcaster(buflen)} 13 | } 14 | 15 | func (b *Broadcaster) RegisterNewChan(size int) chan interface{} { 16 | c := make(chan interface{}, size) 17 | b.channels = append(b.channels, c) 18 | b.Register(c) 19 | return c 20 | } 21 | 22 | func (b *Broadcaster) Close() error { 23 | if b.isClosed { 24 | return nil 25 | } 26 | b.isClosed = true 27 | for _, c := range b.channels { 28 | close(c) 29 | } 30 | return b.Broadcaster.Close() 31 | } 32 | func (b *Broadcaster) IsClosed() bool { 33 | return b.isClosed 34 | } --------------------------------------------------------------------------------