From f72fbc57a9c97229b786ba3617dc4563a2542bb8 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Fri, 9 Dec 2016 17:42:26 +0700 Subject: [PATCH] send connection parameters in CHLO --- flowcontrol/flow_controller_test.go | 5 +- handshake/connection_parameters_manager.go | 52 +++++++++++++-- .../connection_parameters_manager_test.go | 64 ++++++++++++++++--- handshake/crypto_setup_client.go | 22 ++++--- handshake/crypto_setup_client_test.go | 14 +++- handshake/crypto_setup_server.go | 5 +- handshake/crypto_setup_server_test.go | 2 +- session.go | 16 ++--- streams_map_test.go | 5 +- 9 files changed, 150 insertions(+), 35 deletions(-) diff --git a/flowcontrol/flow_controller_test.go b/flowcontrol/flow_controller_test.go index 4473ec71..9afc92d8 100644 --- a/flowcontrol/flow_controller_test.go +++ b/flowcontrol/flow_controller_test.go @@ -20,7 +20,10 @@ type mockConnectionParametersManager struct { func (m *mockConnectionParametersManager) SetFromMap(map[handshake.Tag][]byte) error { panic("not implemented") } -func (m *mockConnectionParametersManager) GetSHLOMap() map[handshake.Tag][]byte { +func (m *mockConnectionParametersManager) GetSHLOMap() (map[handshake.Tag][]byte, error) { + panic("not implemented") +} +func (m *mockConnectionParametersManager) GetCHLOMap() (map[handshake.Tag][]byte, error) { panic("not implemented") } func (m *mockConnectionParametersManager) GetSendStreamFlowControlWindow() protocol.ByteCount { diff --git a/handshake/connection_parameters_manager.go b/handshake/connection_parameters_manager.go index 582680be..959eb5bc 100644 --- a/handshake/connection_parameters_manager.go +++ b/handshake/connection_parameters_manager.go @@ -14,7 +14,8 @@ import ( // ConnectionParametersManager negotiates and stores the connection parameters type ConnectionParametersManager interface { SetFromMap(map[Tag][]byte) error - GetSHLOMap() map[Tag][]byte + GetSHLOMap() (map[Tag][]byte, error) + GetCHLOMap() (map[Tag][]byte, error) GetSendStreamFlowControlWindow() protocol.ByteCount GetSendConnectionFlowControlWindow() protocol.ByteCount @@ -29,7 +30,8 @@ type ConnectionParametersManager interface { type connectionParametersManager struct { mutex sync.RWMutex - version protocol.VersionNumber + version protocol.VersionNumber + perspective protocol.Perspective flowControlNegotiated bool hasReceivedMaxIncomingDynamicStreams bool @@ -55,8 +57,9 @@ var ( ) // NewConnectionParamatersManager creates a new connection parameters manager -func NewConnectionParamatersManager(v protocol.VersionNumber) ConnectionParametersManager { +func NewConnectionParamatersManager(pers protocol.Perspective, v protocol.VersionNumber) ConnectionParametersManager { return &connectionParametersManager{ + perspective: pers, version: v, idleConnectionStateLifetime: protocol.DefaultIdleTimeout, sendStreamFlowControlWindow: protocol.InitialStreamFlowControlWindow, // can only be changed by the client @@ -142,8 +145,13 @@ func (h *connectionParametersManager) negotiateIdleConnectionStateLifetime(clien return utils.MinDuration(clientValue, protocol.MaxIdleTimeout) } -// GetSHLOMap gets all values (except crypto values) needed for the SHLO -func (h *connectionParametersManager) GetSHLOMap() map[Tag][]byte { +// GetSHLOMap gets all parameters needed for the SHLO +// if the client sent us parameters earlier, these are the negotiated values +func (h *connectionParametersManager) GetSHLOMap() (map[Tag][]byte, error) { + if h.perspective != protocol.PerspectiveServer { + return nil, errors.New("ConnectionParametersManager BUG: GetSHLOMap should only be called for a server") + } + sfcw := bytes.NewBuffer([]byte{}) utils.WriteUint32(sfcw, uint32(h.GetReceiveStreamFlowControlWindow())) cfcw := bytes.NewBuffer([]byte{}) @@ -166,7 +174,39 @@ func (h *connectionParametersManager) GetSHLOMap() map[Tag][]byte { tags[TagMIDS] = mids.Bytes() } - return tags + return tags, nil +} + +// GetCHLOMap gets all parameters needed for the CHLO +// these are the values the client is suggesting to the server. The negotiation is done by the server +func (h *connectionParametersManager) GetCHLOMap() (map[Tag][]byte, error) { + if h.perspective != protocol.PerspectiveClient { + return nil, errors.New("ConnectionParametersManager BUG: GetCHLOMap should only be called for a client") + } + + sfcw := bytes.NewBuffer([]byte{}) + utils.WriteUint32(sfcw, uint32(protocol.InitialStreamFlowControlWindow)) + cfcw := bytes.NewBuffer([]byte{}) + utils.WriteUint32(cfcw, uint32(protocol.InitialConnectionFlowControlWindow)) + mspc := bytes.NewBuffer([]byte{}) + utils.WriteUint32(mspc, protocol.MaxStreamsPerConnection) + icsl := bytes.NewBuffer([]byte{}) + utils.WriteUint32(icsl, uint32(protocol.DefaultIdleTimeout/time.Second)) + + tags := map[Tag][]byte{ + TagICSL: icsl.Bytes(), + TagMSPC: mspc.Bytes(), + TagCFCW: cfcw.Bytes(), + TagSFCW: sfcw.Bytes(), + } + + if h.version > protocol.Version34 { + mids := bytes.NewBuffer([]byte{}) + utils.WriteUint32(mids, protocol.MaxIncomingDynamicStreamsPerConnection) + tags[TagMIDS] = mids.Bytes() + } + + return tags, nil } // GetSendStreamFlowControlWindow gets the size of the stream-level flow control window for sending data diff --git a/handshake/connection_parameters_manager_test.go b/handshake/connection_parameters_manager_test.go index 8c94f956..a4478ba4 100644 --- a/handshake/connection_parameters_manager_test.go +++ b/handshake/connection_parameters_manager_test.go @@ -1,6 +1,7 @@ package handshake import ( + "encoding/binary" "time" "github.com/lucas-clemente/quic-go/protocol" @@ -11,12 +12,13 @@ import ( var _ = Describe("ConnectionsParameterManager", func() { var cpm *connectionParametersManager BeforeEach(func() { - cpm = NewConnectionParamatersManager(protocol.Version36).(*connectionParametersManager) + cpm = NewConnectionParamatersManager(protocol.PerspectiveServer, protocol.Version36).(*connectionParametersManager) }) Context("SHLO", func() { It("returns all parameters necessary for the SHLO", func() { - entryMap := cpm.GetSHLOMap() + entryMap, err := cpm.GetSHLOMap() + Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagICSL)) Expect(entryMap).To(HaveKey(TagMSPC)) Expect(entryMap).To(HaveKey(TagMIDS)) @@ -24,27 +26,31 @@ var _ = Describe("ConnectionsParameterManager", func() { It("doesn't add the MaximumIncomingDynamicStreams tag for QUIC 34", func() { cpm.version = protocol.Version34 - entryMap := cpm.GetSHLOMap() + entryMap, err := cpm.GetSHLOMap() + Expect(err).ToNot(HaveOccurred()) Expect(entryMap).ToNot(HaveKey(TagMIDS)) }) It("sets the stream-level flow control windows in SHLO", func() { cpm.receiveStreamFlowControlWindow = 0xDEADBEEF - entryMap := cpm.GetSHLOMap() + entryMap, err := cpm.GetSHLOMap() + Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagSFCW)) Expect(entryMap[TagSFCW]).To(Equal([]byte{0xEF, 0xBE, 0xAD, 0xDE})) }) It("sets the connection-level flow control windows in SHLO", func() { cpm.receiveConnectionFlowControlWindow = 0xDECAFBAD - entryMap := cpm.GetSHLOMap() + entryMap, err := cpm.GetSHLOMap() + Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagCFCW)) Expect(entryMap[TagCFCW]).To(Equal([]byte{0xAD, 0xFB, 0xCA, 0xDE})) }) It("sets the connection-level flow control windows in SHLO", func() { cpm.idleConnectionStateLifetime = 0xDECAFBAD * time.Second - entryMap := cpm.GetSHLOMap() + entryMap, err := cpm.GetSHLOMap() + Expect(err).ToNot(HaveOccurred()) Expect(entryMap).To(HaveKey(TagICSL)) Expect(entryMap[TagICSL]).To(Equal([]byte{0xAD, 0xFB, 0xCA, 0xDE})) }) @@ -54,16 +60,58 @@ var _ = Describe("ConnectionsParameterManager", func() { Expect(val).To(BeNumerically("<", protocol.MaxStreamsPerConnection)) err := cpm.SetFromMap(map[Tag][]byte{TagMSPC: []byte{byte(val), 0, 0, 0}}) Expect(err).ToNot(HaveOccurred()) - entryMap := cpm.GetSHLOMap() + entryMap, err := cpm.GetSHLOMap() + Expect(err).ToNot(HaveOccurred()) Expect(entryMap[TagMSPC]).To(Equal([]byte{byte(val), 0, 0, 0})) }) It("always sends its own value for the maximum incoming dynamic streams in the SHLO", func() { err := cpm.SetFromMap(map[Tag][]byte{TagMIDS: []byte{5, 0, 0, 0}}) Expect(err).ToNot(HaveOccurred()) - entryMap := cpm.GetSHLOMap() + entryMap, err := cpm.GetSHLOMap() + Expect(err).ToNot(HaveOccurred()) Expect(entryMap[TagMIDS]).To(Equal([]byte{byte(protocol.MaxIncomingDynamicStreamsPerConnection), 0, 0, 0})) }) + + It("errors if called from a client", func() { + cpm.perspective = protocol.PerspectiveClient + _, err := cpm.GetSHLOMap() + Expect(err).To(HaveOccurred()) + }) + }) + + Context("CHLO", func() { + BeforeEach(func() { + cpm.perspective = protocol.PerspectiveClient + }) + + It("has the right values", func() { + entryMap, err := cpm.GetCHLOMap() + Expect(err).ToNot(HaveOccurred()) + Expect(entryMap).To(HaveKey(TagICSL)) + Expect(binary.LittleEndian.Uint32(entryMap[TagICSL])).To(Equal(uint32(protocol.DefaultIdleTimeout / time.Second))) + Expect(entryMap).To(HaveKey(TagMSPC)) + Expect(binary.LittleEndian.Uint32(entryMap[TagMSPC])).To(Equal(uint32(protocol.MaxStreamsPerConnection))) + Expect(entryMap).To(HaveKey(TagMIDS)) + Expect(binary.LittleEndian.Uint32(entryMap[TagMIDS])).To(Equal(uint32(protocol.MaxIncomingDynamicStreamsPerConnection))) + Expect(entryMap).To(HaveKey(TagSFCW)) + Expect(binary.LittleEndian.Uint32(entryMap[TagSFCW])).To(Equal(uint32(protocol.InitialStreamFlowControlWindow))) + Expect(entryMap).To(HaveKey(TagCFCW)) + Expect(binary.LittleEndian.Uint32(entryMap[TagCFCW])).To(Equal(uint32(protocol.InitialConnectionFlowControlWindow))) + }) + + It("doesn't add the MIDS tag for QUIC 34", func() { + cpm.version = protocol.Version34 + entryMap, err := cpm.GetCHLOMap() + Expect(err).ToNot(HaveOccurred()) + Expect(entryMap).ToNot(HaveKey(TagMIDS)) + }) + + It("errors if called from a server", func() { + cpm.perspective = protocol.PerspectiveServer + _, err := cpm.GetCHLOMap() + Expect(err).To(HaveOccurred()) + }) }) Context("Truncated connection IDs", func() { diff --git a/handshake/crypto_setup_client.go b/handshake/crypto_setup_client.go index 41bdb3ab..49c014fb 100644 --- a/handshake/crypto_setup_client.go +++ b/handshake/crypto_setup_client.go @@ -40,6 +40,8 @@ type cryptoSetupClient struct { receivedSecurePacket bool secureAEAD crypto.AEAD forwardSecureAEAD crypto.AEAD + + connectionParameters ConnectionParametersManager } var _ crypto.AEAD = &cryptoSetupClient{} @@ -57,15 +59,16 @@ func NewCryptoSetupClient( connID protocol.ConnectionID, version protocol.VersionNumber, cryptoStream utils.Stream, + connectionParameters ConnectionParametersManager, ) (CryptoSetup, error) { return &cryptoSetupClient{ - hostname: hostname, - connID: connID, - version: version, - cryptoStream: cryptoStream, - certManager: crypto.NewCertManager(), - - keyDerivation: crypto.DeriveKeysAESGCM, + hostname: hostname, + connID: connID, + version: version, + cryptoStream: cryptoStream, + certManager: crypto.NewCertManager(), + connectionParameters: connectionParameters, + keyDerivation: crypto.DeriveKeysAESGCM, }, nil } @@ -310,7 +313,10 @@ func (h *cryptoSetupClient) sendCHLO() error { } func (h *cryptoSetupClient) getTags() (map[Tag][]byte, error) { - tags := make(map[Tag][]byte) + tags, err := h.connectionParameters.GetCHLOMap() + if err != nil { + return nil, err + } tags[TagSNI] = []byte(h.hostname) tags[TagPDMD] = []byte("X509") diff --git a/handshake/crypto_setup_client_test.go b/handshake/crypto_setup_client_test.go index d269952a..c3612685 100644 --- a/handshake/crypto_setup_client_test.go +++ b/handshake/crypto_setup_client_test.go @@ -120,7 +120,8 @@ var _ = Describe("Crypto setup", func() { stream = &mockStream{} certManager = &mockCertManager{} - csInt, err := NewCryptoSetupClient("hostname", 0, protocol.Version36, stream) + version := protocol.Version36 + csInt, err := NewCryptoSetupClient("hostname", 0, version, stream, NewConnectionParamatersManager(protocol.PerspectiveClient, version)) Expect(err).ToNot(HaveOccurred()) cs = csInt.(*cryptoSetupClient) cs.certManager = certManager @@ -420,6 +421,17 @@ var _ = Describe("Crypto setup", func() { Expect(tags[TagCCS]).To(Equal(certManager.commonCertificateHashes)) }) + It("adds the tags returned from the connectionParametersManager to the CHLO", func() { + cpmTags, err := cs.connectionParameters.GetCHLOMap() + Expect(err).ToNot(HaveOccurred()) + Expect(cpmTags).ToNot(BeEmpty()) + tags, err := cs.getTags() + Expect(err).ToNot(HaveOccurred()) + for t := range cpmTags { + Expect(tags).To(HaveKey(t)) + } + }) + It("doesn't send a CCS if there are no common certificate sets available", func() { certManager.commonCertificateHashes = nil tags, err := cs.getTags() diff --git a/handshake/crypto_setup_server.go b/handshake/crypto_setup_server.go index 0d85c8fa..a1d3ee26 100644 --- a/handshake/crypto_setup_server.go +++ b/handshake/crypto_setup_server.go @@ -340,7 +340,10 @@ func (h *cryptoSetupServer) handleCHLO(sni string, data []byte, cryptoData map[T return nil, err } - replyMap := h.connectionParameters.GetSHLOMap() + replyMap, err := h.connectionParameters.GetSHLOMap() + if err != nil { + return nil, err + } // add crypto parameters replyMap[TagPUBS] = ephermalKex.PublicKey() replyMap[TagSNO] = serverNonce diff --git a/handshake/crypto_setup_server_test.go b/handshake/crypto_setup_server_test.go index a64bdb19..6a6af472 100644 --- a/handshake/crypto_setup_server_test.go +++ b/handshake/crypto_setup_server_test.go @@ -171,7 +171,7 @@ var _ = Describe("Crypto setup", func() { Expect(err).NotTo(HaveOccurred()) scfg.stkSource = &mockStkSource{} v := protocol.SupportedVersions[len(protocol.SupportedVersions)-1] - cpm = NewConnectionParamatersManager(protocol.VersionWhatever) + cpm = NewConnectionParamatersManager(protocol.PerspectiveServer, protocol.VersionWhatever) csInt, err := NewCryptoSetup(protocol.ConnectionID(42), ip, v, scfg, stream, cpm, aeadChanged) Expect(err).NotTo(HaveOccurred()) cs = csInt.(*cryptoSetupServer) diff --git a/session.go b/session.go index 434067d4..162c98dd 100644 --- a/session.go +++ b/session.go @@ -102,8 +102,9 @@ func newSession(conn connection, v protocol.VersionNumber, connectionID protocol perspective: protocol.PerspectiveServer, version: v, - streamCallback: streamCallback, - closeCallback: closeCallback, + streamCallback: streamCallback, + closeCallback: closeCallback, + connectionParameters: handshake.NewConnectionParamatersManager(protocol.PerspectiveServer, v), } session.setup() @@ -127,8 +128,9 @@ func newClientSession(conn *net.UDPConn, addr *net.UDPAddr, hostname string, v p perspective: protocol.PerspectiveClient, version: v, - streamCallback: streamCallback, - closeCallback: closeCallback, + streamCallback: streamCallback, + closeCallback: closeCallback, + connectionParameters: handshake.NewConnectionParamatersManager(protocol.PerspectiveClient, v), } session.receivedPacketHandler = ackhandler.NewReceivedPacketHandler(session.ackAlarmChanged) @@ -136,7 +138,7 @@ func newClientSession(conn *net.UDPConn, addr *net.UDPAddr, hostname string, v p cryptoStream, _ := session.GetOrOpenStream(1) var err error - session.cryptoSetup, err = handshake.NewCryptoSetupClient(hostname, connectionID, v, cryptoStream) + session.cryptoSetup, err = handshake.NewCryptoSetupClient(hostname, connectionID, v, cryptoStream, session.connectionParameters) if err != nil { return nil, err } @@ -150,15 +152,13 @@ func newClientSession(conn *net.UDPConn, addr *net.UDPAddr, hostname string, v p // setup is called from newSession and newClientSession and initializes values that are independent of the perspective func (s *Session) setup() { s.rttStats = &congestion.RTTStats{} - connectionParameters := handshake.NewConnectionParamatersManager(s.version) - flowControlManager := flowcontrol.NewFlowControlManager(connectionParameters, s.rttStats) + flowControlManager := flowcontrol.NewFlowControlManager(s.connectionParameters, s.rttStats) var sentPacketHandler ackhandler.SentPacketHandler sentPacketHandler = ackhandler.NewSentPacketHandler(s.rttStats) now := time.Now() - s.connectionParameters = connectionParameters s.sentPacketHandler = sentPacketHandler s.flowControlManager = flowControlManager s.receivedPacketHandler = ackhandler.NewReceivedPacketHandler(s.ackAlarmChanged) diff --git a/streams_map_test.go b/streams_map_test.go index 300e7c92..48ab611d 100644 --- a/streams_map_test.go +++ b/streams_map_test.go @@ -21,7 +21,10 @@ type mockConnectionParametersManager struct { func (m *mockConnectionParametersManager) SetFromMap(map[handshake.Tag][]byte) error { panic("not implemented") } -func (m *mockConnectionParametersManager) GetSHLOMap() map[handshake.Tag][]byte { +func (m *mockConnectionParametersManager) GetSHLOMap() (map[handshake.Tag][]byte, error) { + panic("not implemented") +} +func (m *mockConnectionParametersManager) GetCHLOMap() (map[handshake.Tag][]byte, error) { panic("not implemented") } func (m *mockConnectionParametersManager) GetSendStreamFlowControlWindow() protocol.ByteCount {