packages feed

quic 0.3.9 → 0.3.10

raw patch · 12 files changed

+151/−28 lines, 12 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Network.QUIC.Internal: [prevInitialKeys] :: Connection -> IORef (Maybe (Coder, Protector))
+ Network.QUIC.Internal: getPrevInitialKeys :: Connection -> IO (Maybe (Coder, Protector))
+ Network.QUIC.Internal: keepInitialKeys :: Connection -> IO ()
- Network.QUIC.Internal: Connection :: ConnState -> DebugLogger -> QLogger -> Hooks -> ~Send -> ~Recv -> RecvQ -> DatagramQ -> IORef Socket -> (CID -> StatelessResetToken) -> IORef (Map Word64 (Weak ThreadId)) -> ThreadId -> Rate -> IORef RoleInfo -> IORef VersionInfo -> VersionInfo -> Parameters -> IORef CIDDB -> IORef Parameters -> TVar CIDDB -> IORef PeerInfo -> InputQ -> CryptoQ -> OutputQ -> Rate -> Shared -> IORef Int -> IORef (IO ()) -> IORef PacketNumber -> IORef StreamTable -> TVar Concurrency -> TVar Concurrency -> IORef Concurrency -> IORef Concurrency -> IORef OpenedStreams -> IORef OpenedStreams -> TVar TxFlow -> IORef RxFlow -> TVar MigrationState -> IORef Bool -> IORef Microseconds -> IORef Int -> IORef Int -> TVar Bool -> Array EncryptionLevel (TVar [ReceivedPacket]) -> IOArray EncryptionLevel Cipher -> IOArray EncryptionLevel Coder -> IOArray Bool Coder1RTT -> IOArray EncryptionLevel Protector -> IORef (Bool, PacketNumber) -> IORef Negotiated -> IORef AuthCIDs -> IORef AuthCIDs -> Buffer -> SizedBuffer -> Buffer -> IORef (IO ()) -> LDCC -> Connection
+ Network.QUIC.Internal: Connection :: ConnState -> DebugLogger -> QLogger -> Hooks -> ~Send -> ~Recv -> RecvQ -> DatagramQ -> IORef Socket -> (CID -> StatelessResetToken) -> IORef (Map Word64 (Weak ThreadId)) -> ThreadId -> Rate -> IORef RoleInfo -> IORef VersionInfo -> VersionInfo -> Parameters -> IORef CIDDB -> IORef Parameters -> TVar CIDDB -> IORef PeerInfo -> InputQ -> CryptoQ -> OutputQ -> Rate -> Shared -> IORef Int -> IORef (IO ()) -> IORef PacketNumber -> IORef StreamTable -> TVar Concurrency -> TVar Concurrency -> IORef Concurrency -> IORef Concurrency -> IORef OpenedStreams -> IORef OpenedStreams -> TVar TxFlow -> IORef RxFlow -> TVar MigrationState -> IORef Bool -> IORef Microseconds -> IORef Int -> IORef Int -> TVar Bool -> Array EncryptionLevel (TVar [ReceivedPacket]) -> IOArray EncryptionLevel Cipher -> IOArray EncryptionLevel Coder -> IOArray Bool Coder1RTT -> IOArray EncryptionLevel Protector -> IORef (Maybe (Coder, Protector)) -> IORef (Bool, PacketNumber) -> IORef Negotiated -> IORef AuthCIDs -> IORef AuthCIDs -> Buffer -> SizedBuffer -> Buffer -> IORef (IO ()) -> LDCC -> Connection

Files

ChangeLog.md view
@@ -1,5 +1,41 @@ # ChangeLog +## 0.3.10++Two for the server: one that answered on a stream it should never have+been given, and one that made a lost flight cost seconds.++* Hand the application a stream only once its first frame is read.+  `openStream` created the stream and gave it to the application in one+  step, before the frame that opened it had been looked over, so a first+  STREAM frame past the flow control limit was answered by the+  application in the moment before the connection was closed over it.+  An HTTP/3 server sent a response on a stream the peer had never+  opened, and the peer called that a STREAM_STATE_ERROR, as RFC 9000 Sec+  19.8 says to, before the FLOW_CONTROL_ERROR arrived; h3spec's "MUST+  send FLOW_CONTROL_ERROR if a STREAM frame with a large offset is+  received" failed for that reason.  The stream now reaches the+  application after the frame has been checked, so a frame that closes+  the connection closes it with the application none the wiser.+  [#131](https://github.com/kazu-yamamoto/quic/pull/131)++* Keep the Initial keys of the version the client addressed us in.  A+  server that settles on a compatible version answers in it and replaces+  its Initial keys with the ones for that version (RFC 9368), but the+  client hears of the choice only when the answer arrives and until then+  retransmits in the version it started with.  Without the keys for that+  version the server could not pick the handshake up from those and+  waited out its own PTO instead -- a second, then two, then four, with+  the client's retransmissions falling into it unread.  On the test that+  loses the server's whole first flight, the handshake is done at about+  1.4 seconds where it took about 7.0.  This is the other half of #128,+  which stopped the same sequence from hanging for good; what was left+  was the waiting.+  [#132](https://github.com/kazu-yamamoto/quic/pull/132)++* The library builds without a warning: two imports left over from the+  crypton 2.1.0 work are gone.+ ## 0.3.9  A stream limit the peer could walk past, two ways for a connection to
Network/QUIC/Connection/Crypto.hs view
@@ -13,6 +13,8 @@     setNegotiated,     --     dropSecrets,+    keepInitialKeys,+    getPrevInitialKeys,     --     initializeCoder,     initializeCoder1RTT,@@ -95,6 +97,26 @@ dropSecrets Connection{..} lvl = do     writeArray coders lvl initialCoder     writeArray protectors lvl initialProtector+    when (lvl == InitialLevel) $ writeIORef prevInitialKeys Nothing++-- | Keeping the Initial keys we have, before they are replaced with the ones+--   for a version we chose ourselves.+--+-- A server that settles on a compatible version (RFC 9368) answers in it, but+-- the client hears of that only when the answer arrives.  Until then it+-- retransmits its Initial packets in the version it started with, and without+-- the keys for that version they are so much noise: the server cannot pick up+-- the handshake from them, and has to wait out its own PTO instead -- a+-- second, then two, then four, with the client's retransmissions falling into+-- it unread.+keepInitialKeys :: Connection -> IO ()+keepInitialKeys conn@Connection{..} = do+    coder <- getCoder conn InitialLevel False+    protector <- getProtector conn InitialLevel+    writeIORef prevInitialKeys $ Just (coder, protector)++getPrevInitialKeys :: Connection -> IO (Maybe (Coder, Protector))+getPrevInitialKeys Connection{..} = readIORef prevInitialKeys  ---------------------------------------------------------------- 
Network/QUIC/Connection/Stream.hs view
@@ -62,8 +62,8 @@ -- -- A MAX_STREAMS that does not raise it is ignored (RFC 9000, section 4.6). -- One of them reordered on the way, or retransmitted after a newer one has--- gone out, would otherwise lower the limit.  'setTxMaxData' and--- 'setTxMaxStreamData' guard the limits on data the same way.+-- gone out, would otherwise lower the limit.  @setTxMaxData@ and+-- @setTxMaxStreamData@ guard the limits on data the same way. set :: TVar Concurrency -> Int -> IO () set tvar mx = atomically $ modifyTVar' tvar raise   where
Network/QUIC/Connection/Types.hs view
@@ -310,6 +310,9 @@     , coders            :: IOArray EncryptionLevel Coder     , coders1RTT        :: IOArray Bool Coder1RTT     , protectors        :: IOArray EncryptionLevel Protector+    , prevInitialKeys   :: IORef (Maybe (Coder, Protector))+    -- ^ The Initial keys for the version the peer addressed us in, kept+    --   over a compatible version change (RFC 9368)     , currentKeyPhase   :: IORef (Bool, PacketNumber)     , negotiated        :: IORef Negotiated     , connMyAuthCIDs    :: IORef AuthCIDs@@ -410,6 +413,7 @@     coders            <- newArray (InitialLevel, HandshakeLevel) initialCoder     coders1RTT        <- newArray (False, True) initialCoder1RTT     protectors        <- newArray (InitialLevel, RTT1Level) initialProtector+    prevInitialKeys   <- newIORef Nothing     currentKeyPhase   <- newIORef (False, 0)     negotiated        <- newIORef initialNegotiated     connMyAuthCIDs    <- newIORef myAuthCIDs@@ -463,9 +467,10 @@ clientConnection ClientConfig{..} verInfo myAuthCIDs peerAuthCIDs =     newConnection Client ccParameters' verInfo myAuthCIDs peerAuthCIDs   where-    ccParameters' = ccParameters-      { maxDatagramFrameSize = ccMaxDatagramFrameSize-      }+    ccParameters' =+        ccParameters+            { maxDatagramFrameSize = ccMaxDatagramFrameSize+            }  serverConnection     :: ServerConfig@@ -486,9 +491,10 @@ serverConnection ServerConfig{..} verInfo myAuthCIDs peerAuthCIDs =     newConnection Server scParameters' verInfo myAuthCIDs peerAuthCIDs   where-    scParameters' = scParameters-      { maxDatagramFrameSize = scMaxDatagramFrameSize-      }+    scParameters' =+        scParameters+            { maxDatagramFrameSize = scMaxDatagramFrameSize+            }  ---------------------------------------------------------------- 
Network/QUIC/Crypto/Nite.hs view
@@ -24,7 +24,6 @@ import Crypto.Cipher.ChaChaPoly1305 (aeadChacha20poly1305Init) import Crypto.Cipher.Types hiding (Cipher, IV) import Crypto.Error (maybeCryptoError)-import Data.IORef (IORef, newIORef, readIORef, writeIORef) import qualified Data.ByteArray as Byte (ByteArrayAccess (..), convert) import qualified Data.ByteString as BS import qualified Data.ByteString.Internal as BS
Network/QUIC/Crypto/Utils.hs view
@@ -11,7 +11,6 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Short as Short import Network.TLS hiding (Version)-import Network.TLS.Extra.Cipher  import Network.QUIC.Crypto.Nite import Network.QUIC.Crypto.Types
Network/QUIC/Handshake.hs view
@@ -4,8 +4,8 @@ module Network.QUIC.Handshake where  import qualified Control.Exception as E-import Data.List (intersect) import qualified Data.ByteString.Short as Short+import Data.List (intersect) import qualified Network.TLS as TLS import Network.TLS.QUIC @@ -107,11 +107,13 @@     qlogParamsSet conn (ccParameters conf, "local") -- fixme     handshakeClient' conf' conn myAuthCIDs <$> getVersion conn <*> newHndStateRef   where-    conf' = conf-      { ccParameters = (ccParameters conf)-        { maxDatagramFrameSize = ccMaxDatagramFrameSize conf-        }-      }+    conf' =+        conf+            { ccParameters =+                (ccParameters conf)+                    { maxDatagramFrameSize = ccMaxDatagramFrameSize conf+                    }+            }  handshakeClient'     :: ClientConfig -> Connection -> AuthCIDs -> Version -> IORef HndState -> IO ()@@ -342,6 +344,10 @@         case myVers `intersect` peerVers of             vers@(serverVer : _)                 | clientVer /= serverVer -> do+                    -- Before the keys below replace them: the client keeps+                    -- retransmitting in the version it started with until+                    -- our answer reaches it.+                    keepInitialKeys conn                     setVersionInfo conn $ VersionInfo serverVer vers                     dcid <- getClientDstCID conn                     initializeCoder conn InitialLevel $ initialSecrets serverVer dcid
Network/QUIC/Packet/Decrypt.hs view
@@ -21,9 +21,37 @@ ----------------------------------------------------------------  decryptCrypt :: Connection -> Crypt -> EncryptionLevel -> IO (Maybe Plain)-decryptCrypt conn Crypt{..} lvl = do-    cipher <- getCipher conn lvl+decryptCrypt conn crypt lvl = do     protector <- getProtector conn lvl+    mplain <- decryptCryptWith conn crypt lvl protector $ getCoder conn lvl+    case mplain of+        Just _ -> return mplain+        Nothing+            | lvl == InitialLevel -> do+                -- An Initial we cannot read may be one in the version the+                -- peer addressed us in, from before we settled on a+                -- compatible one of our own (RFC 9368).  The peer goes on+                -- retransmitting in that version until our answer reaches+                -- it, and those carry the handshake just as well.  Both the+                -- header protection and the payload keys differ by version,+                -- so the whole of it is tried again, not the payload alone.+                mkeys <- getPrevInitialKeys conn+                case mkeys of+                    Nothing -> return Nothing+                    Just (coder, protector') ->+                        decryptCryptWith conn crypt lvl protector' $+                            \_ -> return coder+            | otherwise -> return Nothing++decryptCryptWith+    :: Connection+    -> Crypt+    -> EncryptionLevel+    -> Protector+    -> (Bool -> IO Coder)+    -> IO (Maybe Plain)+decryptCryptWith conn Crypt{..} lvl protector getCoder' = do+    cipher <- getCipher conn lvl     let proFlags = Flags (cryptPacket `BS.index` 0)         sampleOffset = cryptPktNumOffset + 4         sampleLen = sampleLength cipher@@ -59,7 +87,7 @@             let keyPhase                     | lvl == RTT1Level = flags `testBit` 2                     | otherwise = False-            coder <- getCoder conn lvl keyPhase+            coder <- getCoder' keyPhase             siz <- decrypt coder (decryptBuf conn) ciphertext (AssDat header) pn             let rrMask                     | lvl == RTT1Level = 0x18
Network/QUIC/Receiver.hs view
@@ -244,16 +244,29 @@             closeConnection conn StreamStateError emsg streamNotCreatedYet _ _ _ = return () --- | Opening a stream for a STREAM frame that found none, unless the stream---   was open once and has been closed since.  'guardStream' has already---   refused one of ours that we have not created yet.+-- | Opening a stream for a frame that found none, unless the stream was open+--   once and has been closed since.  'guardStream' has already refused one of+--   ours that we have not created yet.+--+-- The application is not given the stream here; 'deliverStream' does that,+-- once the frame that opened it has been looked over.  Handed the stream+-- first, the application could answer on it in the moment before a first+-- STREAM frame past the flow control limit closed the connection.  An HTTP/3+-- server did: it sent a response on a stream the peer had never opened, and+-- the peer called that a STREAM_STATE_ERROR, as RFC 9000 Sec 19.8 says to,+-- before our own FLOW_CONTROL_ERROR had reached it. openStream :: Connection -> StreamId -> IO (Maybe Stream) openStream conn sid     | isInitiated conn sid = return Nothing     | otherwise = do         new <- claimPeerStream conn sid-        if new then Just <$> createStream conn sid else return Nothing+        if new then Just <$> addStream conn sid else return Nothing +-- | Handing a stream the peer opened to the application, if it is new to us.+deliverStream :: Connection -> Maybe Stream -> Stream -> IO ()+deliverStream conn found strm =+    when (isNothing found) $ putInput conn $ InpStream strm+ processFrame :: Connection -> EncryptionLevel -> Frame -> IO () processFrame _ _ Padding{} = return () processFrame conn lvl Ping = do@@ -306,6 +319,7 @@     case mstrm of         Nothing -> return ()         Just strm -> do+            deliverStream conn mstrm0 strm             onResetStreamReceived (connHooks conn) strm aerr             -- Before the pseudo FIN below, so that whoever reads it can             -- tell it was not a real one.@@ -394,6 +408,7 @@                         conn                         FlowControlError                         "Flow control error for connection in 0-RTT"+        deliverStream conn mstrm strm processFrame conn RTT1Level (StreamF sid _ [""] False) = do     -- FLOW CONTROL: MAX_STREAMS: recv: rejecting if over my limit     ok <- checkRxMaxStreams conn sid@@ -438,6 +453,7 @@                         conn                         FlowControlError                         "Flow control error for connection in 1-RTT"+        deliverStream conn mstrm strm processFrame conn lvl (MaxData n) = do     when (lvl == InitialLevel || lvl == HandshakeLevel) $         closeConnection conn ProtocolViolation "MAX_DATA in Initial or Handshake"
quic.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.0 name:               quic-version:            0.3.9+version:            0.3.10 license:            BSD3 license-file:       LICENSE maintainer:         kazu@iij.ad.jp
test/ErrorSpec.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ module ErrorSpec where  import Control.Concurrent@@ -23,7 +25,14 @@     threadDelay 500000 -- give enough time to the server     return tid   where-    loop conn = forever $ void $ acceptStream conn+    -- Answering on what it is given.  A server that only accepts cannot+    -- catch a stream handed to the application before the frame that opened+    -- it was looked over -- the answer is what reaches the peer, for a+    -- stream the peer never opened, and the peer calls that a+    -- STREAM_STATE_ERROR before our FLOW_CONTROL_ERROR arrives.+    loop conn = forever $ do+        strm <- acceptStream conn+        void $ forkIO $ sendStream strm "x"  teardown :: ThreadId -> IO () teardown = killThread
test/TransportError.hs view
@@ -340,9 +340,11 @@  ---------------------------------------------------------------- --- Stream 0 is not created internally.  It is assumed that a server--- send CC without sending back Stream 0.  If the server send back any--- data for Stream 0, `streamNotCreatedYet` throws an exception, sigh.+-- Stream 0, which the client has not opened.  The server must answer the+-- offset with FLOW_CONTROL_ERROR and nothing else: anything it sends back on+-- stream 0 is, to a client that never opened it, a STREAM_STATE_ERROR+-- (RFC 9000 Sec 19.8), and the client closes the connection before the error+-- we are waiting for arrives. largeOffset :: EncryptionLevel -> Plain -> Plain largeOffset lvl plain     | lvl == RTT1Level = plain{plainFrames = fake : plainFrames plain}