From 121795977d06c4fd6b83a906797cd4d21521ee5a Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 29 Oct 2019 17:58:46 +0700 Subject: [PATCH] implement issuing and retiring of connection IDs --- conn_id_generator.go | 86 +++++++++++++++++++++++++++++++++ conn_id_generator_test.go | 95 +++++++++++++++++++++++++++++++++++++ internal/protocol/params.go | 3 ++ 3 files changed, 184 insertions(+) create mode 100644 conn_id_generator.go create mode 100644 conn_id_generator_test.go diff --git a/conn_id_generator.go b/conn_id_generator.go new file mode 100644 index 00000000..caec6b01 --- /dev/null +++ b/conn_id_generator.go @@ -0,0 +1,86 @@ +package quic + +import ( + "fmt" + + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/qerr" + "github.com/lucas-clemente/quic-go/internal/utils" + "github.com/lucas-clemente/quic-go/internal/wire" +) + +type connIDGenerator struct { + connIDLen int + highestSeq uint64 + + activeSrcConnIDs map[uint64]protocol.ConnectionID + + addConnectionID func(protocol.ConnectionID) [16]byte + retireConnectionID func(protocol.ConnectionID) + queueControlFrame func(wire.Frame) +} + +func newConnIDGenerator( + initialConnectionID protocol.ConnectionID, + addConnectionID func(protocol.ConnectionID) [16]byte, + retireConnectionID func(protocol.ConnectionID), + queueControlFrame func(wire.Frame), +) *connIDGenerator { + m := &connIDGenerator{ + connIDLen: initialConnectionID.Len(), + activeSrcConnIDs: make(map[uint64]protocol.ConnectionID), + addConnectionID: addConnectionID, + retireConnectionID: retireConnectionID, + queueControlFrame: queueControlFrame, + } + m.activeSrcConnIDs[0] = initialConnectionID + return m +} + +func (m *connIDGenerator) SetMaxActiveConnIDs(limit uint64) error { + if m.connIDLen == 0 { + return nil + } + // The active_connection_id_limit transport parameter is the number of + // connection IDs issued in NEW_CONNECTION_IDs frame that the peer will store. + for i := uint64(0); i < utils.MinUint64(limit, protocol.MaxIssuedConnectionIDs); i++ { + if err := m.issueNewConnID(); err != nil { + return err + } + } + return nil +} + +func (m *connIDGenerator) Retire(seq uint64) error { + if seq > m.highestSeq { + return qerr.Error(qerr.ProtocolViolation, fmt.Sprintf("tried to retire connection ID %d. Highest issued: %d", seq, m.highestSeq)) + } + connID, ok := m.activeSrcConnIDs[seq] + // We might already have deleted this connection ID, if this is a duplicate frame. + if !ok { + return nil + } + m.retireConnectionID(connID) + delete(m.activeSrcConnIDs, seq) + // Don't issue a replacement for the initial connection ID. + if seq == 0 { + return nil + } + return m.issueNewConnID() +} + +func (m *connIDGenerator) issueNewConnID() error { + connID, err := protocol.GenerateConnectionID(m.connIDLen) + if err != nil { + return err + } + m.activeSrcConnIDs[m.highestSeq+1] = connID + token := m.addConnectionID(connID) + m.queueControlFrame(&wire.NewConnectionIDFrame{ + SequenceNumber: m.highestSeq + 1, + ConnectionID: connID, + StatelessResetToken: token, + }) + m.highestSeq++ + return nil +} diff --git a/conn_id_generator_test.go b/conn_id_generator_test.go new file mode 100644 index 00000000..5d3cec91 --- /dev/null +++ b/conn_id_generator_test.go @@ -0,0 +1,95 @@ +package quic + +import ( + "github.com/lucas-clemente/quic-go/internal/protocol" + "github.com/lucas-clemente/quic-go/internal/wire" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Connection ID Generator", func() { + var ( + addedConnIDs []protocol.ConnectionID + retiredConnIDs []protocol.ConnectionID + queuedFrames []wire.Frame + g *connIDGenerator + ) + + BeforeEach(func() { + addedConnIDs = nil + retiredConnIDs = nil + queuedFrames = nil + g = newConnIDGenerator( + protocol.ConnectionID{1, 2, 3, 4, 5, 6, 7}, + func(c protocol.ConnectionID) [16]byte { + addedConnIDs = append(addedConnIDs, c) + l := uint8(len(addedConnIDs)) + return [16]byte{l, l, l, l, l, l, l, l, l, l, l, l, l, l, l, l} + }, + func(c protocol.ConnectionID) { retiredConnIDs = append(retiredConnIDs, c) }, + func(f wire.Frame) { queuedFrames = append(queuedFrames, f) }, + ) + }) + + It("issues new connection IDs", func() { + Expect(g.SetMaxActiveConnIDs(4)).To(Succeed()) + Expect(retiredConnIDs).To(BeEmpty()) + Expect(addedConnIDs).To(HaveLen(4)) + for i := 0; i < len(addedConnIDs)-1; i++ { + Expect(addedConnIDs[i]).ToNot(Equal(addedConnIDs[i+1])) + } + Expect(queuedFrames).To(HaveLen(4)) + for i := 0; i < 4; i++ { + f := queuedFrames[i] + Expect(f).To(BeAssignableToTypeOf(&wire.NewConnectionIDFrame{})) + nf := f.(*wire.NewConnectionIDFrame) + Expect(nf.SequenceNumber).To(BeEquivalentTo(i + 1)) + Expect(nf.ConnectionID.Len()).To(Equal(7)) + j := uint8(i + 1) + Expect(nf.StatelessResetToken).To(Equal([16]byte{j, j, j, j, j, j, j, j, j, j, j, j, j, j, j, j})) + } + }) + + It("limits the number of connection IDs that it issues", func() { + Expect(g.SetMaxActiveConnIDs(9999999)).To(Succeed()) + Expect(retiredConnIDs).To(BeEmpty()) + Expect(addedConnIDs).To(HaveLen(protocol.MaxIssuedConnectionIDs)) + Expect(queuedFrames).To(HaveLen(protocol.MaxIssuedConnectionIDs)) + }) + + It("errors if the peers tries to retire a connection ID that wasn't yet issued", func() { + Expect(g.Retire(1)).To(MatchError("PROTOCOL_VIOLATION: tried to retire connection ID 1. Highest issued: 0")) + }) + + It("issues new connection IDs, when old ones are retired", func() { + Expect(g.SetMaxActiveConnIDs(5)).To(Succeed()) + queuedFrames = nil + Expect(retiredConnIDs).To(BeEmpty()) + Expect(g.Retire(3)).To(Succeed()) + Expect(queuedFrames).To(HaveLen(1)) + Expect(queuedFrames[0]).To(BeAssignableToTypeOf(&wire.NewConnectionIDFrame{})) + nf := queuedFrames[0].(*wire.NewConnectionIDFrame) + Expect(nf.SequenceNumber).To(BeEquivalentTo(6)) + Expect(nf.ConnectionID.Len()).To(Equal(7)) + }) + + It("retires the initial connection ID", func() { + Expect(g.Retire(0)).To(Succeed()) + Expect(retiredConnIDs).To(HaveLen(1)) + Expect(retiredConnIDs[0]).To(Equal(protocol.ConnectionID{1, 2, 3, 4, 5, 6, 7})) + Expect(addedConnIDs).To(BeEmpty()) + }) + + It("handles duplicate retirements", func() { + Expect(g.SetMaxActiveConnIDs(11)).To(Succeed()) + queuedFrames = nil + Expect(retiredConnIDs).To(BeEmpty()) + Expect(g.Retire(5)).To(Succeed()) + Expect(retiredConnIDs).To(HaveLen(1)) + Expect(queuedFrames).To(HaveLen(1)) + Expect(g.Retire(5)).To(Succeed()) + Expect(retiredConnIDs).To(HaveLen(1)) + Expect(queuedFrames).To(HaveLen(1)) + }) +}) diff --git a/internal/protocol/params.go b/internal/protocol/params.go index f94ef690..4e4bc00c 100644 --- a/internal/protocol/params.go +++ b/internal/protocol/params.go @@ -136,6 +136,9 @@ const DefaultConnectionIDLength = 4 // MaxActiveConnectionIDs is the number of connection IDs that we're storing. const MaxActiveConnectionIDs = 4 +// MaxIssuedConnectionIDs is the maximum number of connection IDs that we're issuing at the same time. +const MaxIssuedConnectionIDs = 6 + // PacketsPerConnectionID is the number of packets we send using one connection ID. // If the peer provices us with enough new connection IDs, we switch to a new connection ID. const PacketsPerConnectionID = 10000