@@ -32,6 +32,7 @@ pub(crate) struct IcPipe<M: RingMem = GpadlRingMem> {
3232 pub pipe : MessagePipe < M > ,
3333 #[ inspect( skip) ]
3434 buf : Vec < u8 > ,
35+ pub transaction_id : u8 ,
3536}
3637
3738#[ derive( Inspect , Default ) ]
@@ -54,7 +55,11 @@ impl<M: RingMem> IcPipe<M> {
5455 pub fn new ( raw : RawAsyncChannel < M > ) -> Result < Self , std:: io:: Error > {
5556 let pipe = MessagePipe :: new ( raw) ?;
5657 let buf = vec ! [ 0 ; hyperv_ic_protocol:: MAX_MESSAGE_SIZE ] ;
57- Ok ( Self { pipe, buf } )
58+ Ok ( Self {
59+ pipe,
60+ buf,
61+ transaction_id : u8:: MAX ,
62+ } )
5863 }
5964
6065 pub async fn negotiate (
@@ -64,6 +69,7 @@ impl<M: RingMem> IcPipe<M> {
6469 ) -> anyhow:: Result < Option < Versions > > {
6570 match state {
6671 NegotiateState :: SendVersion => {
72+ let transaction_id = self . transaction_id . wrapping_add ( 1 ) ;
6773 let message = hyperv_ic_protocol:: NegotiateMessage {
6874 framework_version_count : FRAMEWORK_VERSIONS . len ( ) as u16 ,
6975 message_version_count : message_versions. len ( ) as u16 ,
@@ -76,7 +82,7 @@ impl<M: RingMem> IcPipe<M> {
7682 + size_of_val ( FRAMEWORK_VERSIONS )
7783 + size_of_val ( message_versions) ) as u16 ,
7884 status : Status :: SUCCESS ,
79- transaction_id : 0 ,
85+ transaction_id,
8086 flags : HeaderFlags :: new ( ) . with_transaction ( true ) . with_request ( true ) ,
8187 ..FromZeros :: new_zeroed ( )
8288 } ;
@@ -91,11 +97,14 @@ impl<M: RingMem> IcPipe<M> {
9197 . await
9298 . context ( "ring buffer error" ) ?;
9399
100+ self . transaction_id = transaction_id;
94101 * state = NegotiateState :: WaitVersion ;
95102 Ok ( None )
96103 }
97104 NegotiateState :: WaitVersion => {
98- let ( message_type, status, buf) = self . read_message ( ) . await ?;
105+ let Some ( ( message_type, status, buf) ) = self . read_message ( None ) . await ? else {
106+ return Ok ( None ) ;
107+ } ;
99108 // A response to a request issued before the channel was
100109 // reset (e.g. across save/restore) may still be in flight.
101110 // There is no one left to receive it, so drop it.
@@ -148,29 +157,40 @@ impl<M: RingMem> IcPipe<M> {
148157 flags : HeaderFlags ,
149158 message : & [ u8 ] ,
150159 ) -> anyhow:: Result < ( ) > {
160+ let transaction_id = self . transaction_id . wrapping_add ( 1 ) ;
151161 let header = hyperv_ic_protocol:: Header {
152162 framework_version : versions. framework_version ,
153163 message_type,
154164 message_size : message. len ( ) as u16 ,
155165 message_version : versions. message_version ,
156166 status : Status :: SUCCESS ,
157- transaction_id : 0 ,
167+ transaction_id,
158168 flags,
159169 ..FromZeros :: new_zeroed ( )
160170 } ;
161171
162172 self . pipe
163173 . send_vectored ( & [ IoSlice :: new ( header. as_bytes ( ) ) , IoSlice :: new ( message) ] )
164174 . await
165- . context ( "ring buffer error" )
175+ . context ( "ring buffer error" ) ?;
176+ self . transaction_id = transaction_id;
177+ Ok ( ( ) )
166178 }
167179
168- pub async fn read_response ( & mut self ) -> anyhow:: Result < ( Status , & [ u8 ] ) > {
169- let ( _message_type, status, buf) = self . read_message ( ) . await ?;
170- Ok ( ( status, buf) )
180+ pub async fn read_response (
181+ & mut self ,
182+ versions : & Versions ,
183+ ) -> anyhow:: Result < Option < ( Status , & [ u8 ] ) > > {
184+ Ok ( self
185+ . read_message ( Some ( versions) )
186+ . await ?
187+ . map ( |( _, status, buf) | ( status, buf) ) )
171188 }
172189
173- async fn read_message ( & mut self ) -> anyhow:: Result < ( MessageType , Status , & [ u8 ] ) > {
190+ pub async fn read_message (
191+ & mut self ,
192+ versions : Option < & Versions > ,
193+ ) -> anyhow:: Result < Option < ( MessageType , Status , & [ u8 ] ) > > {
174194 let n = self
175195 . pipe
176196 . recv ( & mut self . buf )
@@ -181,15 +201,42 @@ impl<M: RingMem> IcPipe<M> {
181201 . ok ( )
182202 . context ( "missing header" ) ?;
183203
184- if header . transaction_id != 0 || !header. flags . transaction ( ) || !header. flags . response ( ) {
204+ if !header. flags . transaction ( ) || !header. flags . response ( ) {
185205 anyhow:: bail!( "invalid transaction response" ) ;
186206 }
187207
208+ if header. transaction_id != self . transaction_id {
209+ tracelimit:: warn_ratelimited!(
210+ transaction_id = header. transaction_id,
211+ expected_transaction_id = self . transaction_id,
212+ "dropping response for an unexpected transaction"
213+ ) ;
214+ return Ok ( None ) ;
215+ }
216+
188217 let rest = rest
189218 . get ( ..header. message_size as usize )
190219 . context ( "missing message body" ) ?;
191220
192- Ok ( ( header. message_type , header. status , rest) )
221+ let valid_versions = if let Some ( versions) = versions {
222+ header. framework_version == versions. framework_version
223+ && header. message_version == versions. message_version
224+ } else if header. message_type == MessageType :: VERSION_NEGOTIATION {
225+ header. framework_version == Version :: new ( 0 , 0 )
226+ && header. message_version == Version :: new ( 0 , 0 )
227+ } else {
228+ true
229+ } ;
230+ if !valid_versions {
231+ tracelimit:: warn_ratelimited!(
232+ framework_version = %header. framework_version,
233+ message_version = %header. message_version,
234+ "dropping response with incorrect protocol versions"
235+ ) ;
236+ return Ok ( None ) ;
237+ }
238+
239+ Ok ( Some ( ( header. message_type , header. status , rest) ) )
193240 }
194241}
195242
@@ -265,6 +312,160 @@ mod tests {
265312 assert ! ( header. flags. request( ) ) ;
266313 }
267314
315+ #[ async_test]
316+ async fn transactions_advance_and_wrap_ids ( ) {
317+ let ( mut pipe, mut guest) = new_pipes ( ) ;
318+ let mut state = NegotiateState :: default ( ) ;
319+ send_version_request ( & mut pipe, & mut state, & mut guest) . await ;
320+ let negotiation_response = version_response ( FRAMEWORK_VERSION_3 , TEST_MESSAGE_VERSION ) ;
321+ guest. send ( & negotiation_response) . await . unwrap ( ) ;
322+ let versions = pipe
323+ . negotiate ( & mut state, TEST_MESSAGE_VERSIONS )
324+ . await
325+ . unwrap ( )
326+ . unwrap ( ) ;
327+ for expected_id in 1 ..=257u16 {
328+ pipe. write_message (
329+ & versions,
330+ MessageType :: SHUTDOWN ,
331+ HeaderFlags :: new ( ) . with_transaction ( true ) . with_request ( true ) ,
332+ & [ ] ,
333+ )
334+ . await
335+ . unwrap ( ) ;
336+ let mut buffer = [ 0 ; hyperv_ic_protocol:: MAX_MESSAGE_SIZE ] ;
337+ let size = guest. recv ( & mut buffer) . await . unwrap ( ) ;
338+ let ( mut header, _) = Header :: read_from_prefix ( & buffer[ ..size] ) . unwrap ( ) ;
339+ assert_eq ! ( header. transaction_id, expected_id as u8 ) ;
340+ if header. transaction_id == 0 {
341+ guest. send ( & negotiation_response) . await . unwrap ( ) ;
342+ assert ! ( pipe. read_response( & versions) . await . unwrap( ) . is_none( ) ) ;
343+ assert_eq ! ( pipe. transaction_id, header. transaction_id) ;
344+ }
345+ header. flags = HeaderFlags :: new ( )
346+ . with_transaction ( true )
347+ . with_response ( true ) ;
348+ guest. send ( header. as_bytes ( ) ) . await . unwrap ( ) ;
349+ let ( status, body) = pipe. read_response ( & versions) . await . unwrap ( ) . unwrap ( ) ;
350+ assert_eq ! ( status, Status :: SUCCESS ) ;
351+ assert ! ( body. is_empty( ) ) ;
352+ }
353+ }
354+
355+ #[ async_test]
356+ async fn responses_match_transaction_and_versions ( ) {
357+ let versions = Versions {
358+ framework_version : FRAMEWORK_VERSION_3 ,
359+ message_version : TEST_MESSAGE_VERSION ,
360+ } ;
361+ for message_type in [
362+ MessageType :: SHUTDOWN ,
363+ MessageType :: TIME_SYNC ,
364+ MessageType :: KVP_EXCHANGE ,
365+ ] {
366+ let ( mut pipe, mut guest) = new_pipes ( ) ;
367+ let mut buffer = [ 0 ; hyperv_ic_protocol:: MAX_MESSAGE_SIZE ] ;
368+ for expected_id in 0 ..2 {
369+ pipe. write_message (
370+ & versions,
371+ message_type,
372+ HeaderFlags :: new ( ) . with_transaction ( true ) . with_request ( true ) ,
373+ & [ ] ,
374+ )
375+ . await
376+ . unwrap ( ) ;
377+ let size = guest. recv ( & mut buffer) . await . unwrap ( ) ;
378+ let ( mut response, _) = Header :: read_from_prefix ( & buffer[ ..size] ) . unwrap ( ) ;
379+ assert_eq ! ( response. transaction_id, expected_id) ;
380+ response. flags = HeaderFlags :: new ( )
381+ . with_transaction ( true )
382+ . with_response ( true ) ;
383+
384+ for status in [ Status :: SUCCESS , Status :: FAIL ] {
385+ let mut negotiation_response =
386+ version_response ( FRAMEWORK_VERSION_3 , TEST_MESSAGE_VERSION ) ;
387+ let ( header, _) =
388+ Header :: mut_from_prefix ( negotiation_response. as_mut_slice ( ) ) . unwrap ( ) ;
389+ header. transaction_id = expected_id;
390+ header. status = status;
391+ guest. send ( & negotiation_response) . await . unwrap ( ) ;
392+ assert ! ( pipe. read_response( & versions) . await . unwrap( ) . is_none( ) ) ;
393+ assert_eq ! ( pipe. transaction_id, expected_id) ;
394+ }
395+
396+ if expected_id == 1 {
397+ for ( transaction_id, framework_version, message_version) in [
398+ ( 0 , versions. framework_version , versions. message_version ) ,
399+ ( 2 , versions. framework_version , versions. message_version ) ,
400+ ( 1 , Version :: new ( 0 , 0 ) , versions. message_version ) ,
401+ ( 1 , FRAMEWORK_VERSION_1 , versions. message_version ) ,
402+ ( 1 , Version :: new ( 3 , 1 ) , versions. message_version ) ,
403+ ( 1 , versions. framework_version , Version :: new ( 0 , 0 ) ) ,
404+ ( 1 , versions. framework_version , Version :: new ( 5 , 1 ) ) ,
405+ ] {
406+ let invalid = Header {
407+ transaction_id,
408+ framework_version,
409+ message_version,
410+ status : Status :: NOT_SUPPORTED ,
411+ ..response
412+ } ;
413+ guest. send ( invalid. as_bytes ( ) ) . await . unwrap ( ) ;
414+ assert ! ( pipe. read_response( & versions) . await . unwrap( ) . is_none( ) ) ;
415+ assert_eq ! ( pipe. transaction_id, expected_id) ;
416+ }
417+ response. status = Status :: FAIL ;
418+ }
419+
420+ let body = b"ack" ;
421+ response. message_size = body. len ( ) as u16 ;
422+ guest
423+ . send ( & [ response. as_bytes ( ) , body] . concat ( ) )
424+ . await
425+ . unwrap ( ) ;
426+ let ( status, received_body) = pipe. read_response ( & versions) . await . unwrap ( ) . unwrap ( ) ;
427+ assert_eq ! ( status, response. status) ;
428+ assert_eq ! ( received_body, body) ;
429+ }
430+ }
431+ }
432+
433+ #[ async_test]
434+ async fn negotiate_drops_unmatched_ids_and_invalid_header_versions ( ) {
435+ let ( mut pipe, mut guest) = new_pipes ( ) ;
436+ let mut state = NegotiateState :: default ( ) ;
437+ send_version_request ( & mut pipe, & mut state, & mut guest) . await ;
438+ for ( transaction_id, framework_version, message_version) in [
439+ ( 1 , Version :: new ( 0 , 0 ) , Version :: new ( 0 , 0 ) ) ,
440+ ( 0 , FRAMEWORK_VERSION_3 , Version :: new ( 0 , 0 ) ) ,
441+ ( 0 , Version :: new ( 0 , 0 ) , TEST_MESSAGE_VERSION ) ,
442+ ] {
443+ let mut response = version_response ( FRAMEWORK_VERSION_3 , TEST_MESSAGE_VERSION ) ;
444+ let ( header, _) = Header :: mut_from_prefix ( response. as_mut_slice ( ) ) . unwrap ( ) ;
445+ header. transaction_id = transaction_id;
446+ header. framework_version = framework_version;
447+ header. message_version = message_version;
448+ guest. send ( & response) . await . unwrap ( ) ;
449+ assert ! (
450+ pipe. negotiate( & mut state, TEST_MESSAGE_VERSIONS )
451+ . await
452+ . unwrap( )
453+ . is_none( )
454+ ) ;
455+ assert ! ( matches!( state, NegotiateState :: WaitVersion ) ) ;
456+ }
457+ guest
458+ . send ( & version_response ( FRAMEWORK_VERSION_3 , TEST_MESSAGE_VERSION ) )
459+ . await
460+ . unwrap ( ) ;
461+ assert ! (
462+ pipe. negotiate( & mut state, TEST_MESSAGE_VERSIONS )
463+ . await
464+ . unwrap( )
465+ . is_some( )
466+ ) ;
467+ }
468+
268469 #[ async_test]
269470 async fn negotiate ( ) {
270471 for & framework_version in FRAMEWORK_VERSIONS {
0 commit comments