| // Copyright 2025 The BoringSSL Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package runner |
| |
| import ( |
| "slices" |
| "strconv" |
| "time" |
| ) |
| |
| func addDTLSReplayTests() { |
| for _, vers := range allVersions(dtls) { |
| // Test that sequence number replays are detected. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Replay-" + vers.name, |
| config: Config{ |
| MaxVersion: vers.version, |
| }, |
| messageCount: 200, |
| replayWrites: true, |
| }) |
| |
| // Test the incoming sequence number skipping by values larger |
| // than the retransmit window. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Replay-LargeGaps-" + vers.name, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| SequenceNumberMapping: func(in uint64) uint64 { |
| return in * 1023 |
| }, |
| }, |
| }, |
| messageCount: 200, |
| replayWrites: true, |
| }) |
| |
| // Test the incoming sequence number changing non-monotonically. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Replay-NonMonotonic-" + vers.name, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| SequenceNumberMapping: func(in uint64) uint64 { |
| // This mapping has numbers counting backwards in groups |
| // of 256, and then jumping forwards 511 numbers. |
| return in ^ 255 |
| }, |
| }, |
| }, |
| // This messageCount is large enough to make sure that the SequenceNumberMapping |
| // will reach the point where it jumps forwards after stepping backwards. |
| messageCount: 500, |
| replayWrites: true, |
| }) |
| } |
| } |
| |
| // timeouts is the default retransmit schedule for BoringSSL. It doubles and |
| // caps at 60 seconds. On the 13th timeout, it gives up. |
| var timeouts = []time.Duration{ |
| 400 * time.Millisecond, |
| 800 * time.Millisecond, |
| 1600 * time.Millisecond, |
| 3200 * time.Millisecond, |
| 6400 * time.Millisecond, |
| 12800 * time.Millisecond, |
| 25600 * time.Millisecond, |
| 51200 * time.Millisecond, |
| 60 * time.Second, |
| 60 * time.Second, |
| 60 * time.Second, |
| 60 * time.Second, |
| 60 * time.Second, |
| } |
| |
| // shortTimeouts is an alternate set of timeouts which would occur if the |
| // initial timeout duration was set to 250ms. |
| var shortTimeouts = []time.Duration{ |
| 250 * time.Millisecond, |
| 500 * time.Millisecond, |
| 1 * time.Second, |
| 2 * time.Second, |
| 4 * time.Second, |
| 8 * time.Second, |
| 16 * time.Second, |
| 32 * time.Second, |
| 60 * time.Second, |
| 60 * time.Second, |
| 60 * time.Second, |
| 60 * time.Second, |
| 60 * time.Second, |
| } |
| |
| // dtlsPrevEpochExpiration is how long before the shim releases old epochs. Add |
| // an extra second to allow the shim to be less precise. |
| const dtlsPrevEpochExpiration = 4*time.Minute + 1*time.Second |
| |
| func addDTLSRetransmitTests() { |
| for _, shortTimeout := range []bool{false, true} { |
| for _, vers := range allVersions(dtls) { |
| suffix := "-" + vers.name |
| flags := []string{"-async"} // Retransmit tests require async. |
| useTimeouts := timeouts |
| if shortTimeout { |
| suffix += "-Short" |
| flags = append(flags, "-initial-timeout-duration-ms", "250") |
| useTimeouts = shortTimeouts |
| } |
| |
| // Testing NewSessionTicket is tricky. First, BoringSSL sends two |
| // tickets in a row. These are conceptually separate flights, but we |
| // test them as one flight. Second, these tickets are sent |
| // concurrently with the runner's first test message. The shim's |
| // reply will come in before any retransmit challenges. |
| // handleNewSessionTicket corrects for both effects. |
| handleNewSessionTicket := func(f ACKFlightFunc) ACKFlightFunc { |
| if vers.version < VersionTLS13 { |
| return f |
| } |
| return func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| // BoringSSL sends two NewSessionTickets in a row. |
| if received[0].Type == typeNewSessionTicket && len(received) < 2 { |
| c.MergeIntoNextFlight() |
| return |
| } |
| // NewSessionTicket is sent in parallel with the runner's |
| // first application data. Consume the shim's reply. |
| testMessage := makeTestMessage(0, 32) |
| if received[0].Type == typeNewSessionTicket { |
| c.ReadAppData(c.InEpoch(), expectedReply(testMessage)) |
| } |
| // Run the test, without any stray messages in the way. |
| f(c, prev, received, records) |
| // The test loop is expecting a reply to the first message. |
| // Prime the shim to send it again. |
| if received[0].Type == typeNewSessionTicket { |
| c.WriteAppData(c.OutEpoch(), testMessage) |
| } |
| } |
| } |
| |
| // In all versions, the sender will retransmit the whole flight if |
| // it times out and hears nothing. |
| writeFlightBasic := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| // Exercise every timeout but the last one (which would fail the |
| // connection). |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| c.ExpectNextTimeout(t) |
| c.AdvanceClock(t) |
| c.ReadRetransmit() |
| } |
| c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1]) |
| } |
| // Finally release the whole flight to the shim. |
| c.WriteFlight(next) |
| } |
| ackFlightBasic := handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if vers.version >= VersionTLS13 { |
| // In DTLS 1.3, final flights (either handshake or post-handshake) |
| // are retransmited until ACKed. Exercise every timeout but |
| // the last one (which would fail the connection). |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| c.ExpectNextTimeout(t) |
| c.AdvanceClock(t) |
| c.ReadRetransmit() |
| } |
| c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1]) |
| // Finally ACK the flight. |
| c.WriteACK(c.OutEpoch(), records) |
| return |
| } |
| // In DTLS 1.2, the final flight is retransmitted on receipt of |
| // the previous flight. Test the peer is willing to retransmit |
| // it several times. |
| for i := 0; i < 5; i++ { |
| c.WriteFlight(prev) |
| c.ReadRetransmit() |
| } |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Client-Basic" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: writeFlightBasic, |
| ACKFlightDTLS: ackFlightBasic, |
| }, |
| }, |
| resumeSession: true, |
| flags: flags, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-Basic" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: writeFlightBasic, |
| ACKFlightDTLS: ackFlightBasic, |
| }, |
| }, |
| resumeSession: true, |
| flags: flags, |
| }) |
| |
| if vers.version <= VersionTLS12 { |
| // In DTLS 1.2, receiving a part of the next flight should not stop |
| // the retransmission timer. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-PartialProgress" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| // Send a portion of the first message. The rest was lost. |
| msg := next[0] |
| split := len(msg.Data) / 2 |
| c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)}) |
| // If we time out, the shim should still retransmit. It knows |
| // we received the whole flight, but the shim should use a |
| // retransmit to request the runner try again. |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| // "Retransmit" the rest of the flight. The shim should remember |
| // the portion that was already sent. |
| rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)} |
| for _, m := range next[1:] { |
| rest = append(rest, m.Fragment(0, len(m.Data))) |
| } |
| c.WriteFragments(rest) |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| } else { |
| // In DTLS 1.3, receiving a part of the next flight implicitly ACKs |
| // the previous flight. |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| protocol: dtls, |
| name: "DTLS-Retransmit-PartialProgress-Server" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| DefaultCurves: []CurveID{}, // Force HelloRetryRequest. |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) == 0 && next[0].Type == typeClientHello { |
| // Send the initial ClientHello as-is. |
| c.WriteFlight(next) |
| return |
| } |
| |
| // Send a portion of the first message. The rest was lost. |
| msg := next[0] |
| split := len(msg.Data) / 2 |
| c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)}) |
| // After waiting the current timeout, the shim should ACK |
| // the partial flight. |
| c.ExpectNextTimeout(useTimeouts[0] / 4) |
| c.AdvanceClock(useTimeouts[0] / 4) |
| c.ReadACK(c.InEpoch()) |
| // The partial flight is enough to ACK the previous flight. |
| // The shim should stop retransmitting and even stop the |
| // retransmit timer. |
| c.ExpectNoNextTimeout() |
| for _, t := range useTimeouts { |
| c.AdvanceClock(t) |
| } |
| // "Retransmit" the rest of the flight. The shim should remember |
| // the portion that was already sent. |
| rest := []DTLSFragment{msg.Fragment(split, len(msg.Data)-split)} |
| for _, m := range next[1:] { |
| rest = append(rest, m.Fragment(0, len(m.Data))) |
| } |
| c.WriteFragments(rest) |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| |
| // When the shim is a client, receiving fragments before the version is |
| // known does not trigger this behavior. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-PartialProgress-Client" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| msg := next[0] |
| if msg.Type != typeServerHello { |
| // Post-handshake is tested separately. |
| c.WriteFlight(next) |
| return |
| } |
| // Send a portion of the ServerHello. The rest was lost. |
| split := len(msg.Data) / 2 |
| c.WriteFragments([]DTLSFragment{msg.Fragment(0, split)}) |
| |
| // The shim did not know this was DTLS 1.3, so it still |
| // retransmits ClientHello. |
| c.ExpectNextTimeout(useTimeouts[0]) |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| |
| // Finish the ServerHello. The version is still not known, |
| // at the time the ServerHello fragment is processed, This |
| // is not as efficient as we could be; we could go back and |
| // implicitly ACK once the version is known. But the last |
| // byte of ServerHello will almost certainly be in the same |
| // packet as EncryptedExtensions, which will trigger the case |
| // below. |
| c.WriteFragments([]DTLSFragment{msg.Fragment(split, len(msg.Data)-split)}) |
| c.ExpectNextTimeout(useTimeouts[1]) |
| c.AdvanceClock(useTimeouts[1]) |
| c.ReadRetransmit() |
| |
| // Send EncryptedExtensions. The shim now knows the version. |
| c.WriteFragments([]DTLSFragment{next[1].Fragment(0, len(next[1].Data))}) |
| |
| // The shim should ACK the partial flight. The shim hasn't |
| // gotten to epoch 3 yet, so the ACK will come in epoch 2. |
| c.AdvanceClock(useTimeouts[2] / 4) |
| c.ReadACK(uint16(encryptionHandshake)) |
| |
| // This is enough to ACK the previous flight. The shim |
| // should stop retransmitting and even stop the timer. |
| c.ExpectNoNextTimeout() |
| for _, t := range useTimeouts[2:] { |
| c.AdvanceClock(t) |
| } |
| |
| // "Retransmit" the rest of the flight. The shim should remember |
| // the portion that was already sent. |
| var rest []DTLSFragment |
| for _, m := range next[2:] { |
| rest = append(rest, m.Fragment(0, len(m.Data))) |
| } |
| c.WriteFragments(rest) |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| } |
| |
| // Test that exceeding the timeout schedule hits a read |
| // timeout. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Timeout" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| c.ExpectNextTimeout(t) |
| c.AdvanceClock(t) |
| c.ReadRetransmit() |
| } |
| c.ExpectNextTimeout(useTimeouts[len(useTimeouts)-1]) |
| c.AdvanceClock(useTimeouts[len(useTimeouts)-1]) |
| // The shim should give up at this point. |
| }, |
| }, |
| }, |
| resumeSession: true, |
| flags: flags, |
| shouldFail: true, |
| expectedError: ":READ_TIMEOUT_EXPIRED:", |
| }) |
| |
| // Test that timeout handling has a fudge factor, due to API |
| // problems. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Fudge" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| c.ExpectNextTimeout(useTimeouts[0]) |
| c.AdvanceClock(useTimeouts[0] - 10*time.Millisecond) |
| c.ReadRetransmit() |
| } |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| resumeSession: true, |
| flags: flags, |
| }) |
| |
| // Test that the shim can retransmit at different MTUs. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-ChangeMTU" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| // Request a client certificate, so the shim has more to send. |
| ClientAuth: RequireAnyClientCert, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| for i, mtu := range []int{300, 301, 302, 303, 299, 298, 297} { |
| c.SetMTU(mtu) |
| c.AdvanceClock(useTimeouts[i]) |
| c.ReadRetransmit() |
| } |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| shimCertificate: &rsaChainCertificate, |
| flags: flags, |
| }) |
| |
| // DTLS 1.3 uses explicit ACKs. |
| if vers.version >= VersionTLS13 { |
| // The two server flights (HelloRetryRequest and ServerHello..Finished) |
| // happen after the shim has learned the version, so they are more |
| // straightforward. In these tests, we trigger HelloRetryRequest, |
| // and also use ML-KEM with rsaChainCertificate and a limited MTU, |
| // to increase the number of records and exercise more complex |
| // ACK patterns. |
| |
| // After ACKing everything, the shim should stop retransmitting. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKEverything" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Credential: &rsaChainCertificate, |
| CurvePreferences: []CurveID{CurveX25519MLKEM768}, |
| DefaultCurves: []CurveID{}, // Force HelloRetryRequest. |
| Bugs: ProtocolBugs{ |
| // Send smaller packets to exercise more ACK cases. |
| MaxPacketLength: 512, |
| MaxHandshakeRecordLength: 512, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| ackEpoch := received[len(received)-1].Epoch |
| c.ExpectNextTimeout(useTimeouts[0]) |
| c.WriteACK(ackEpoch, records) |
| // After everything is ACKed, the shim should stop the timer |
| // and wait for the next flight. |
| c.ExpectNoNextTimeout() |
| for _, t := range useTimeouts { |
| c.AdvanceClock(t) |
| } |
| } |
| c.WriteFlight(next) |
| }, |
| ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| ackEpoch := received[len(received)-1].Epoch |
| c.ExpectNextTimeout(useTimeouts[0]) |
| c.WriteACK(ackEpoch, records) |
| // After everything is ACKed, the shim should stop the timer. |
| c.ExpectNoNextTimeout() |
| for _, t := range useTimeouts { |
| c.AdvanceClock(t) |
| } |
| }), |
| SequenceNumberMapping: func(in uint64) uint64 { |
| // Perturb sequence numbers to test that ACKs are sorted. |
| return in ^ 63 |
| }, |
| }, |
| }, |
| shimCertificate: &rsaChainCertificate, |
| flags: slices.Concat(flags, []string{ |
| "-mtu", "512", |
| "-curves", strconv.Itoa(int(CurveX25519MLKEM768)), |
| // Request a client certificate so the client final flight is |
| // larger. |
| "-require-any-client-certificate", |
| }), |
| }) |
| |
| // ACK packets one by one, in reverse. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKReverse" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| CurvePreferences: []CurveID{CurveX25519MLKEM768}, |
| DefaultCurves: []CurveID{}, // Force HelloRetryRequest. |
| Bugs: ProtocolBugs{ |
| MaxPacketLength: 512, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| ackEpoch := received[len(received)-1].Epoch |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| if len(records) > 0 { |
| c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]}) |
| } |
| c.AdvanceClock(t) |
| records = c.ReadRetransmit() |
| } |
| } |
| c.WriteFlight(next) |
| }, |
| ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| ackEpoch := received[len(received)-1].Epoch |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| if len(records) > 0 { |
| c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[len(records)-1]}) |
| } |
| c.AdvanceClock(t) |
| records = c.ReadRetransmit() |
| } |
| }), |
| }, |
| }, |
| shimCertificate: &rsaChainCertificate, |
| flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}), |
| }) |
| |
| // ACK packets one by one, forwards. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKForwards" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| CurvePreferences: []CurveID{CurveX25519MLKEM768}, |
| DefaultCurves: []CurveID{}, // Force HelloRetryRequest. |
| Bugs: ProtocolBugs{ |
| MaxPacketLength: 512, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| ackEpoch := received[len(received)-1].Epoch |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| if len(records) > 0 { |
| c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]}) |
| } |
| c.AdvanceClock(t) |
| records = c.ReadRetransmit() |
| } |
| } |
| c.WriteFlight(next) |
| }, |
| ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| ackEpoch := received[len(received)-1].Epoch |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| if len(records) > 0 { |
| c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]}) |
| } |
| c.AdvanceClock(t) |
| records = c.ReadRetransmit() |
| } |
| }), |
| }, |
| }, |
| shimCertificate: &rsaChainCertificate, |
| flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}), |
| }) |
| |
| // ACK 1/3 the packets each time. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKIterate" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| CurvePreferences: []CurveID{CurveX25519MLKEM768}, |
| DefaultCurves: []CurveID{}, // Force HelloRetryRequest. |
| Bugs: ProtocolBugs{ |
| MaxPacketLength: 512, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| ackEpoch := received[len(received)-1].Epoch |
| for i, t := range useTimeouts[:len(useTimeouts)-1] { |
| if len(records) > 0 { |
| ack := make([]DTLSRecordNumberInfo, 0, (len(records)+2)/3) |
| for i := 0; i < len(records); i += 3 { |
| ack = append(ack, records[i]) |
| } |
| c.WriteACK(ackEpoch, ack) |
| } |
| // Change the MTU every iteration, to make the fragment |
| // patterns more complex. |
| c.SetMTU(512 + i) |
| c.AdvanceClock(t) |
| records = c.ReadRetransmit() |
| } |
| } |
| c.WriteFlight(next) |
| }, |
| ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| ackEpoch := received[len(received)-1].Epoch |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| if len(records) > 0 { |
| c.WriteACK(ackEpoch, []DTLSRecordNumberInfo{records[0]}) |
| } |
| c.AdvanceClock(t) |
| records = c.ReadRetransmit() |
| } |
| }), |
| }, |
| }, |
| shimCertificate: &rsaChainCertificate, |
| flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}), |
| }) |
| |
| // ACKing packets that have already been ACKed is a no-op. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKDuplicate" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| SendHelloRetryRequestCookie: []byte("cookie"), |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| ackEpoch := received[len(received)-1].Epoch |
| // Keep ACKing the same record over and over. |
| c.WriteACK(ackEpoch, records[:1]) |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| c.WriteACK(ackEpoch, records[:1]) |
| c.AdvanceClock(useTimeouts[1]) |
| c.ReadRetransmit() |
| } |
| c.WriteFlight(next) |
| }, |
| ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| ackEpoch := received[len(received)-1].Epoch |
| // Keep ACKing the same record over and over. |
| c.WriteACK(ackEpoch, records[:1]) |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| c.WriteACK(ackEpoch, records[:1]) |
| c.AdvanceClock(useTimeouts[1]) |
| c.ReadRetransmit() |
| // ACK everything to clear the timer. |
| c.WriteACK(ackEpoch, records) |
| }), |
| }, |
| }, |
| flags: flags, |
| }) |
| |
| // When ACKing ServerHello..Finished, the ServerHello might be |
| // ACKed at epoch 0 or epoch 2, depending on how far the client |
| // received. Test that epoch 0 is allowed by ACKing each packet |
| // at the record it was received. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKMatchingEpoch" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| if len(records) > 0 { |
| c.WriteACK(uint16(records[0].Epoch), []DTLSRecordNumberInfo{records[0]}) |
| } |
| c.AdvanceClock(t) |
| records = c.ReadRetransmit() |
| } |
| } |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| |
| // However, records in the handshake may not be ACKed at lower |
| // epoch than they were received. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKBadEpoch" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) == 0 { |
| // Send the ClientHello. |
| c.WriteFlight(next) |
| } else { |
| // Try to ACK ServerHello..Finished at epoch 0. The shim should reject this. |
| c.WriteACK(0, records) |
| } |
| }, |
| }, |
| }, |
| flags: flags, |
| shouldFail: true, |
| expectedError: ":DECODE_ERROR:", |
| }) |
| |
| // The bad epoch check should notice when the epoch number |
| // would overflow 2^16. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKEpochOverflow" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) == 0 { |
| // Send the ClientHello. |
| c.WriteFlight(next) |
| } else { |
| r := records[0] |
| r.Epoch += 1 << 63 |
| c.WriteACK(0, []DTLSRecordNumberInfo{r}) |
| } |
| }, |
| }, |
| }, |
| flags: flags, |
| shouldFail: true, |
| expectedError: ":DECODE_ERROR:", |
| }) |
| |
| // ACK some records from the first transmission, trigger a |
| // retransmit, but then ACK the rest of the first transmission. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKOldRecords" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| CurvePreferences: []CurveID{CurveX25519MLKEM768}, |
| Bugs: ProtocolBugs{ |
| MaxPacketLength: 512, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| ackEpoch := received[len(received)-1].Epoch |
| c.WriteACK(ackEpoch, records[len(records)/2:]) |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| c.WriteACK(ackEpoch, records[:len(records)/2]) |
| // Everything should be ACKed now. The shim should not |
| // retransmit anything. |
| c.AdvanceClock(useTimeouts[1]) |
| c.AdvanceClock(useTimeouts[2]) |
| c.AdvanceClock(useTimeouts[3]) |
| } |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}), |
| }) |
| |
| // If the shim sends too many records, it will eventually forget them. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKForgottenRecords" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| CurvePreferences: []CurveID{CurveX25519MLKEM768}, |
| Bugs: ProtocolBugs{ |
| MaxPacketLength: 256, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) > 0 { |
| // Make the peer retransmit many times, with a small MTU. |
| for _, t := range useTimeouts[:len(useTimeouts)-2] { |
| c.AdvanceClock(t) |
| c.ReadRetransmit() |
| } |
| // ACK the first record the shim ever sent. It will have |
| // fallen off the queue by now, so it is expected to not |
| // impact the shim's retransmissions. |
| c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: records[0].DTLSRecordNumber}}) |
| c.AdvanceClock(useTimeouts[len(useTimeouts)-2]) |
| c.ReadRetransmit() |
| } |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| flags: slices.Concat(flags, []string{"-mtu", "256", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}), |
| }) |
| |
| // The shim should ignore ACKs for a previous flight, and not get its |
| // internal state confused. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKPreviousFlight" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| DefaultCurves: []CurveID{}, // Force a HelloRetryRequest. |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if next[len(next)-1].Type == typeFinished { |
| // We are now sending client Finished, in response |
| // to the shim's ServerHello. ACK the shim's first |
| // record, which would have been part of |
| // HelloRetryRequest. This should not impact retransmit. |
| c.WriteACK(c.OutEpoch(), []DTLSRecordNumberInfo{{DTLSRecordNumber: DTLSRecordNumber{Epoch: 0, Sequence: 0}}}) |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| } |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| |
| // Records that contain a mix of discarded and processed fragments should |
| // not be ACKed. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-DoNotACKDiscardedFragments" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| DefaultCurves: []CurveID{}, // Force a HelloRetryRequest. |
| Bugs: ProtocolBugs{ |
| PackHandshakeFragments: 4096, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| // Send the flight, but combine every fragment with a far future |
| // fragment, which the shim will discard. During the handshake, |
| // the shim has enough information to reject this entirely, but |
| // that would require coordinating with the handshake state |
| // machine. Instead, BoringSSL discards the fragment and skips |
| // ACKing the packet. |
| // |
| // runner implicitly tests that the shim ACKs the Finished flight |
| // (or, in case, that it is does not), so this exercises the final |
| // ACK. |
| for _, msg := range next { |
| shouldDiscard := DTLSFragment{Epoch: msg.Epoch, Sequence: 1000, ShouldDiscard: true} |
| c.WriteFragments([]DTLSFragment{shouldDiscard, msg.Fragment(0, len(msg.Data))}) |
| // The shim has nothing to ACK and thus no ACK timer (which |
| // would be 1/4 of this value). |
| c.ExpectNextTimeout(useTimeouts[0]) |
| } |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| |
| // The server must continue to ACK the Finished flight even after |
| // receiving application data from the client. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-Retransmit-Server-ACKFinishedAfterAppData" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| // WriteFlightDTLS will handle consuming ACKs. |
| SkipImplicitACKRead: true, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if next[len(next)-1].Type != typeFinished { |
| c.WriteFlight(next) |
| return |
| } |
| |
| // Write Finished. The shim should ACK it immediately. |
| c.WriteFlight(next) |
| c.ReadACK(c.InEpoch()) |
| |
| // Exchange some application data. |
| msg := []byte("hello") |
| c.WriteAppData(c.OutEpoch(), msg) |
| c.ReadAppData(c.InEpoch(), expectedReply(msg)) |
| |
| // Act as if the ACK was dropped and retransmit Finished. |
| // The shim should process the retransmit from epoch 2 and |
| // ACK, although it has already received data at epoch 3. |
| c.WriteFlight(next) |
| ackTimeout := useTimeouts[0] / 4 |
| c.AdvanceClock(ackTimeout) |
| c.ReadACK(c.InEpoch()) |
| |
| // Partially retransmit Finished. The shim should continue |
| // to ACK. |
| c.WriteFragments([]DTLSFragment{next[0].Fragment(0, 1)}) |
| c.WriteFragments([]DTLSFragment{next[0].Fragment(1, 1)}) |
| c.AdvanceClock(ackTimeout) |
| c.ReadACK(c.InEpoch()) |
| |
| // Eventually, the shim assumes we have received the ACK |
| // and drops epoch 2. Retransmits now go unanswered. |
| c.AdvanceClock(dtlsPrevEpochExpiration) |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| // Disable tickets on the shim to avoid NewSessionTicket |
| // interfering with the test callback. |
| flags: slices.Concat(flags, []string{"-no-ticket"}), |
| }) |
| |
| // As a client, the shim must tolerate ACKs in response to its |
| // initial ClientHello, but it will not process them because the |
| // version is not yet known. The second ClientHello, in response |
| // to HelloRetryRequest, however, is ACKed. |
| // |
| // The shim must additionally process ACKs and retransmit its |
| // Finished flight, possibly interleaved with application data. |
| // (The server may send half-RTT data without Finished.) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Client" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| // Require a client certificate, so the Finished flight |
| // is large. |
| ClientAuth: RequireAnyClientCert, |
| Bugs: ProtocolBugs{ |
| SendHelloRetryRequestCookie: []byte("cookie"), // Send HelloRetryRequest |
| MaxPacketLength: 512, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if len(received) == 0 || received[0].Type != typeClientHello { |
| // We test post-handshake flights separately. |
| c.WriteFlight(next) |
| return |
| } |
| |
| // This is either HelloRetryRequest in response to ClientHello1, |
| // or ServerHello..Finished in response to ClientHello2. |
| first := records[0] |
| if len(prev) == 0 { |
| // This is HelloRetryRequest in response to ClientHello1. The client |
| // will accept the ACK, but it will ignore it. Do not expect |
| // retransmits to be impacted. |
| first.MessageStartSequence = 0 |
| first.MessageStartOffset = 0 |
| first.MessageEndSequence = 0 |
| first.MessageEndOffset = 0 |
| } |
| c.WriteACK(0, []DTLSRecordNumberInfo{first}) |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| c.WriteFlight(next) |
| }, |
| ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| // The shim will process application data without an ACK. |
| msg := []byte("hello") |
| c.WriteAppData(c.OutEpoch(), msg) |
| c.ReadAppData(c.InEpoch(), expectedReply(msg)) |
| |
| // After a timeout, the shim will retransmit Finished. |
| c.AdvanceClock(useTimeouts[0]) |
| c.ReadRetransmit() |
| |
| // Application data still flows. |
| c.WriteAppData(c.OutEpoch(), msg) |
| c.ReadAppData(c.InEpoch(), expectedReply(msg)) |
| |
| // ACK part of the flight and check that retransmits |
| // are updated. |
| c.WriteACK(c.OutEpoch(), records[len(records)/3:2*len(records)/3]) |
| c.AdvanceClock(useTimeouts[1]) |
| records = c.ReadRetransmit() |
| |
| // ACK the rest. Retransmits should stop. |
| c.WriteACK(c.OutEpoch(), records) |
| for _, t := range useTimeouts[2:] { |
| c.AdvanceClock(t) |
| } |
| }, |
| }, |
| }, |
| shimCertificate: &rsaChainCertificate, |
| flags: slices.Concat(flags, []string{"-mtu", "512", "-curves", strconv.Itoa(int(CurveX25519MLKEM768))}), |
| }) |
| |
| // If the client never receives an ACK for the Finished flight, it |
| // is eventually fatal. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Client-FinishedTimeout" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| c.AdvanceClock(t) |
| c.ReadRetransmit() |
| } |
| c.AdvanceClock(useTimeouts[len(useTimeouts)-1]) |
| }, |
| }, |
| }, |
| flags: flags, |
| shouldFail: true, |
| expectedError: ":READ_TIMEOUT_EXPIRED:", |
| }) |
| |
| // Neither post-handshake messages nor application data implicitly |
| // ACK the Finished flight. The server may have sent either in |
| // half-RTT data. Test that the client continues to retransmit |
| // despite this. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Client-NoImplictACKFinished" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| // Merge the Finished flight into the NewSessionTicket. |
| c.MergeIntoNextFlight() |
| }, |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if next[0].Type != typeNewSessionTicket { |
| c.WriteFlight(next) |
| return |
| } |
| if len(received) == 0 || received[0].Type != typeFinished { |
| panic("Finished should be merged with NewSessionTicket") |
| } |
| // Merge NewSessionTicket into the KeyUpdate. |
| if next[len(next)-1].Type != typeKeyUpdate { |
| c.MergeIntoNextFlight() |
| return |
| } |
| |
| // Write NewSessionTicket and the KeyUpdate and |
| // read the ACK. |
| c.WriteFlight(next) |
| ackTimeout := useTimeouts[0] / 4 |
| c.AdvanceClock(ackTimeout) |
| c.ReadACK(c.InEpoch()) |
| |
| // The retransmit timer is still running. |
| c.AdvanceClock(useTimeouts[0] - ackTimeout) |
| c.ReadRetransmit() |
| |
| // Application data can flow at the old epoch. |
| msg := []byte("test") |
| c.WriteAppData(c.OutEpoch()-1, msg) |
| c.ReadAppData(c.InEpoch(), expectedReply(msg)) |
| |
| // The retransmit timer is still running. |
| c.AdvanceClock(useTimeouts[1]) |
| c.ReadRetransmit() |
| |
| // Advance the shim to the next epoch. |
| c.WriteAppData(c.OutEpoch(), msg) |
| c.ReadAppData(c.InEpoch(), expectedReply(msg)) |
| |
| // The retransmit timer is still running. The shim |
| // actually could implicitly ACK at this point, but |
| // RFC 9147 does not list this as an implicit ACK. |
| c.AdvanceClock(useTimeouts[2]) |
| c.ReadRetransmit() |
| |
| // Finally ACK the final flight. Now the shim will |
| // stop the timer. |
| c.WriteACK(c.OutEpoch(), records) |
| c.ExpectNoNextTimeout() |
| }, |
| }, |
| }, |
| sendKeyUpdates: 1, |
| keyUpdateRequest: keyUpdateNotRequested, |
| flags: flags, |
| }) |
| |
| // If the server never receives an ACK for NewSessionTicket, it |
| // is eventually fatal. |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| protocol: dtls, |
| name: "DTLS-Retransmit-Server-NewSessionTicketTimeout" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| ACKFlightDTLS: handleNewSessionTicket(func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if received[0].Type != typeNewSessionTicket { |
| c.WriteACK(c.OutEpoch(), records) |
| return |
| } |
| // Time the peer out. |
| for _, t := range useTimeouts[:len(useTimeouts)-1] { |
| c.AdvanceClock(t) |
| c.ReadRetransmit() |
| } |
| c.AdvanceClock(useTimeouts[len(useTimeouts)-1]) |
| }), |
| }, |
| }, |
| flags: flags, |
| shouldFail: true, |
| expectedError: ":READ_TIMEOUT_EXPIRED:", |
| }) |
| |
| // If generating the reply to a flight takes time (generating a |
| // CertificateVerify for a client certificate), the shim should |
| // send an ACK. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-SlowReplyGeneration" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| ClientAuth: RequireAnyClientCert, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| c.WriteFlight(next) |
| if next[0].Type == typeServerHello { |
| // The shim will reply with Certificate..Finished, but |
| // take time to do so. In that time, it should schedule |
| // an ACK so the runner knows not to retransmit. |
| c.ReadACK(c.InEpoch()) |
| } |
| }, |
| }, |
| }, |
| shimCertificate: &rsaCertificate, |
| // Simulate it taking time to generate the reply. |
| flags: slices.Concat(flags, []string{"-private-key-delay-ms", strconv.Itoa(int(useTimeouts[0].Milliseconds()))}), |
| }) |
| |
| // BoringSSL's ACK policy may schedule both retransmit and ACK |
| // timers in parallel. |
| // |
| // TODO(crbug.com/42290594): This is only possible during the |
| // handshake because we're willing to ACK old flights without |
| // trying to distinguish these cases. However, post-handshake |
| // messages will exercise this, so that may be a better version |
| // of this test. In-handshake, it's kind of a waste to ACK this, |
| // so maybe we should stop. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-BothTimers" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| // Arrange for there to be two server flights. |
| SendHelloRetryRequestCookie: []byte("cookie"), |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if next[0].Sequence == 0 || next[0].Type != typeServerHello { |
| // Send the first flight (HelloRetryRequest) as-is, |
| // as well as any post-handshake flights. |
| c.WriteFlight(next) |
| return |
| } |
| |
| // The shim just send the ClientHello2 and is |
| // waiting for ServerHello..Finished. If it hears |
| // nothing, it will retransmit ClientHello2 on the |
| // assumption the packet was lost. |
| c.ExpectNextTimeout(useTimeouts[0]) |
| |
| // Retransmit a portion of HelloRetryRequest. |
| c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)}) |
| |
| // The shim does not actually need to ACK this, |
| // but BoringSSL does. Now both timers are active. |
| // Fire the first... |
| c.ExpectNextTimeout(useTimeouts[0] / 4) |
| c.AdvanceClock(useTimeouts[0] / 4) |
| c.ReadACK(0) |
| |
| // ...followed by the second. |
| c.ExpectNextTimeout(3 * useTimeouts[0] / 4) |
| c.AdvanceClock(3 * useTimeouts[0] / 4) |
| c.ReadRetransmit() |
| |
| // The shim is now set for the next retransmit. |
| c.ExpectNextTimeout(useTimeouts[1]) |
| |
| // Start the ACK timer again. |
| c.WriteFragments([]DTLSFragment{prev[0].Fragment(0, 1)}) |
| c.ExpectNextTimeout(useTimeouts[1] / 4) |
| |
| // Expire both timers at once. |
| c.AdvanceClock(useTimeouts[1]) |
| c.ReadACK(0) |
| c.ReadRetransmit() |
| |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Client-ACKPostHandshake" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if next[0].Type != typeNewSessionTicket { |
| c.WriteFlight(next) |
| return |
| } |
| |
| // The test should try to send two NewSessionTickets in a row. |
| if len(next) != 2 { |
| panic("unexpected message count") |
| } |
| |
| // Send part of first ticket post-handshake message. |
| first0, second0 := next[0].Split(len(next[0].Data) / 2) |
| first1, second1 := next[1].Split(len(next[1].Data) / 2) |
| c.WriteFragments([]DTLSFragment{first0}) |
| |
| // The shim should ACK on a timer. |
| c.ExpectNextTimeout(useTimeouts[0] / 4) |
| c.AdvanceClock(useTimeouts[0] / 4) |
| c.ReadACK(c.InEpoch()) |
| |
| // The shim is just waiting for us to retransmit. |
| c.ExpectNoNextTimeout() |
| |
| // Send some more fragments. |
| c.WriteFragments([]DTLSFragment{first0, second1}) |
| |
| // The shim should ACK, again on a timer. |
| c.ExpectNextTimeout(useTimeouts[0] / 4) |
| c.AdvanceClock(useTimeouts[0] / 4) |
| c.ReadACK(c.InEpoch()) |
| c.ExpectNoNextTimeout() |
| |
| // Finish up both messages. We implicitly test if shim |
| // processed these messages by checking that it returned a new |
| // session. |
| c.WriteFragments([]DTLSFragment{first1, second0}) |
| |
| // The shim should ACK again, once the timer expires. |
| // |
| // TODO(crbug.com/42290594): Should the shim ACK immediately? |
| // Otherwise KeyUpdates are delayed, which will complicated |
| // downstream testing. |
| c.ExpectNextTimeout(useTimeouts[0] / 4) |
| c.AdvanceClock(useTimeouts[0] / 4) |
| c.ReadACK(c.InEpoch()) |
| c.ExpectNoNextTimeout() |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "DTLS-Retransmit-Client-ACKPostHandshakeTwice" + suffix, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| if next[0].Type != typeNewSessionTicket { |
| c.WriteFlight(next) |
| return |
| } |
| |
| // The test should try to send two NewSessionTickets in a row. |
| if len(next) != 2 { |
| panic("unexpected message count") |
| } |
| |
| // Send the flight. The shim should ACK it. |
| c.WriteFlight(next) |
| c.AdvanceClock(useTimeouts[0] / 4) |
| c.ReadACK(c.InEpoch()) |
| c.ExpectNoNextTimeout() |
| |
| // Retransmit the flight, as if we lost the ACK. The shim should |
| // ACK again. |
| c.WriteFlight(next) |
| c.AdvanceClock(useTimeouts[0] / 4) |
| c.ReadACK(c.InEpoch()) |
| c.ExpectNoNextTimeout() |
| }, |
| }, |
| }, |
| flags: flags, |
| }) |
| } |
| } |
| } |
| |
| // Test that the final Finished retransmitting isn't |
| // duplicated if the peer badly fragments everything. |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| protocol: dtls, |
| name: "DTLS-RetransmitFinished-Fragmented", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| MaxHandshakeRecordLength: 2, |
| ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) { |
| c.WriteFlight(prev) |
| c.ReadRetransmit() |
| }, |
| }, |
| }, |
| flags: []string{"-async"}, |
| }) |
| |
| // If the shim sends the last Finished (server full or client resume |
| // handshakes), it must retransmit that Finished when it sees a |
| // post-handshake penultimate Finished from the runner. The above tests |
| // cover this. Conversely, if the shim sends the penultimate Finished |
| // (client full or server resume), test that it does not retransmit. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: clientTest, |
| name: "DTLS-StrayRetransmitFinished-ClientFull", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| c.WriteFlight(next) |
| for _, msg := range next { |
| if msg.Type == typeFinished { |
| c.WriteFlight([]DTLSMessage{msg}) |
| } |
| } |
| }, |
| }, |
| }, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "DTLS-StrayRetransmitFinished-ServerResume", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| c.WriteFlight(next) |
| for _, msg := range next { |
| if msg.Type == typeFinished { |
| c.WriteFlight([]DTLSMessage{msg}) |
| } |
| } |
| }, |
| }, |
| }, |
| resumeSession: true, |
| }) |
| } |
| |
| func addDTLSReorderTests() { |
| for _, vers := range allVersions(dtls) { |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "ReorderHandshakeFragments-Small-DTLS-" + vers.name, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| ReorderHandshakeFragments: true, |
| // Small enough that every handshake message is |
| // fragmented. |
| MaxHandshakeRecordLength: 2, |
| }, |
| }, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "ReorderHandshakeFragments-Large-DTLS-" + vers.name, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| ReorderHandshakeFragments: true, |
| // Large enough that no handshake message is |
| // fragmented. |
| MaxHandshakeRecordLength: 2048, |
| }, |
| }, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "MixCompleteMessageWithFragments-DTLS-" + vers.name, |
| config: Config{ |
| MaxVersion: vers.version, |
| Bugs: ProtocolBugs{ |
| ReorderHandshakeFragments: true, |
| MixCompleteMessageWithFragments: true, |
| MaxHandshakeRecordLength: 2, |
| }, |
| }, |
| }) |
| } |
| } |