2525// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2626
2727use h3i:: quiche;
28+ use tokio_quiche:: quic:: SimpleConnectionIdGenerator ;
29+ use tokio_quiche:: ConnectionIdGenerator as _;
2830
2931use crate :: fixtures:: * ;
3032
3133#[ tokio:: test]
32- /// Tests that client can migrate passively.
34+ async fn test_passive_migration ( ) {
35+ run_migration_test ( false ) . await ;
36+ }
37+
38+ #[ tokio:: test]
39+ async fn test_active_migration ( ) {
40+ run_migration_test ( true ) . await ;
41+ }
42+
43+ /// Tests that the client can migrate either actively or passively.
3344///
34- /// This means that the client's address somehow changes, but the client is not
35- /// necessarily aware of it happening (e.g. due to NAT rebinding). This differs
36- /// from "active" migration because in that case the client would explicitly
37- /// provide a new connection ID that the server is then expected to use for the
38- /// new path.
45+ /// Active migration means the client intentionally chooses a new local address
46+ /// to switch to. In this case, it must select a new DCID that was previously
47+ /// issued to it by the server. The server then also switches to a new DCID
48+ /// that was generated by the client.
49+ ///
50+ /// Passive migration occurs when the client's address changes while keeping
51+ /// the same source and destination CIDs. The client is not necessarily aware
52+ /// of the change (e.g. due to NAT rebinding). Under these circumstances, the
53+ /// endpoints are allowed to keep talking to each other with the existing
54+ /// DCIDs.
3955///
4056/// The test simply binds a UDP socket on one address which is then used to
41- /// complete the handshake and send an initial HTTP/3 request, then uses a new
42- /// socket bound to a different port to send an additional HTTP/3 request, if
43- /// both requests complete that means that the client was successfully migrated
44- /// to the new address.
57+ /// complete the handshake and send an initial HTTP/3 request. Next, it
58+ /// switches (actively or passively) to a new socket bound to a different port
59+ /// and sends an additional HTTP/3 request. If both requests complete that
60+ /// means that the client was successfully migrated to the new address.
4561///
4662/// This requires using "plain" quiche as a client to properly control when and
4763/// where packets are sent to, which is not possible using h3i.
48- async fn test_passive_migration ( ) {
64+ async fn run_migration_test ( active : bool ) {
4965 let mut quic_settings = QuicSettings :: default ( ) ;
5066 quic_settings. active_connection_id_limit = 2 ;
51- quic_settings. disable_active_migration = true ;
67+ quic_settings. disable_active_migration = !active ;
5268 quic_settings. disable_dcid_reuse = false ;
5369
54- let hook = TestConnectionHook :: new ( ) ;
55-
5670 let url = start_server_with_settings (
5771 quic_settings,
5872 Http3Settings :: default ( ) ,
59- hook ,
73+ TestConnectionHook :: new ( ) ,
6074 handle_connection,
6175 ) ;
62-
63- let url = url:: Url :: parse ( & url) . unwrap ( ) ;
64-
65- let server_addr = match url. host ( ) . unwrap ( ) {
66- url:: Host :: Ipv4 ( addr) => std:: net:: SocketAddr :: new (
67- std:: net:: IpAddr :: V4 ( addr) ,
68- url. port ( ) . unwrap ( ) ,
69- ) ,
70-
71- _ => panic ! ( "invalid server address" ) ,
72- } ;
76+ let server_addr = extract_host_ipv4 ( & url) ;
7377
7478 let mut client_config =
7579 quiche:: Config :: new ( quiche:: PROTOCOL_VERSION ) . unwrap ( ) ;
@@ -80,15 +84,15 @@ async fn test_passive_migration() {
8084 client_config. set_initial_max_stream_data_uni ( 1500 ) ;
8185 client_config. set_initial_max_streams_bidi ( 10 ) ;
8286 client_config. set_initial_max_streams_uni ( 3 ) ;
87+ client_config. set_active_connection_id_limit ( 2 ) ;
88+ // We don't need to allow the server to initiate connection migration
8389 client_config. set_disable_active_migration ( true ) ;
8490 client_config. verify_peer ( false ) ;
8591
86- let mut client_scid = [ 0 ; quiche:: MAX_CONN_ID_LEN ] ;
87- boring:: rand:: rand_bytes ( & mut client_scid[ ..] ) . unwrap ( ) ;
88- let client_scid = quiche:: ConnectionId :: from_ref ( & client_scid) ;
89- let client_addr = "127.0.0.1:12345" . parse ( ) . unwrap ( ) ;
92+ let client_scid = SimpleConnectionIdGenerator . new_connection_id ( ) ;
9093
91- let socket = tokio:: net:: UdpSocket :: bind ( client_addr) . await . unwrap ( ) ;
94+ let socket = tokio:: net:: UdpSocket :: bind ( "127.0.0.1:0" ) . await . unwrap ( ) ;
95+ let client_addr = socket. local_addr ( ) . unwrap ( ) ;
9296
9397 let mut conn = quiche:: connect (
9498 Some ( "test.com" ) ,
@@ -99,10 +103,16 @@ async fn test_passive_migration() {
99103 )
100104 . unwrap ( ) ;
101105
106+ if active {
107+ // Supply a second SCID to the server to facilitate active migration
108+ let extra_scid = SimpleConnectionIdGenerator . new_connection_id ( ) ;
109+ conn. new_scid ( & extra_scid, 0xAABBCCDDEEFF0123454678 , false )
110+ . unwrap ( ) ;
111+ }
112+
102113 // Handshake.
103114 while !conn. is_established ( ) {
104115 emit_flight ( & socket, & mut conn) . await ;
105-
106116 process_flight ( & socket, client_addr, & mut conn) . await ;
107117 }
108118
@@ -126,16 +136,28 @@ async fn test_passive_migration() {
126136
127137 assert_eq ! ( process_h3_events( & mut h3_conn, & mut conn) , ( true , true ) ) ;
128138
129- // Client "migrates" to new address.
130- let migrated_addr: std:: net:: SocketAddr = "127.0.0.1:54321" . parse ( ) . unwrap ( ) ;
139+ // Client migrates to new address.
131140 let migrated_socket =
132- tokio:: net:: UdpSocket :: bind ( migrated_addr) . await . unwrap ( ) ;
141+ tokio:: net:: UdpSocket :: bind ( "127.0.0.1:0" ) . await . unwrap ( ) ;
142+ let migrated_addr = migrated_socket. local_addr ( ) . unwrap ( ) ;
143+ assert_ne ! (
144+ client_addr, migrated_addr,
145+ "migrated socket must use a different local address" ,
146+ ) ;
147+
148+ let client_addr = if active {
149+ // We actively switch the connection to `migrated_addr` and report that
150+ // address for all packets we receive from now on.
151+ conn. migrate_source ( migrated_addr)
152+ . expect ( "active migration should succeed" ) ;
153+ migrated_addr
154+ } else {
155+ // We keep using the original client address to simulate the fact that the
156+ // client doesn't know that the path changes (e.g. due to NAT rebinding).
157+ client_addr
158+ } ;
133159
134160 // Client sends second request on the new address.
135- //
136- // Note that even though we use the `migrated_socket`, we still use the
137- // original client address (`client_addr`) to simulate the fact that the
138- // client doesn't know that the path changes (e.g. due to NAT rebinding).
139161 h3_conn. send_request ( & mut conn, & req, true ) . unwrap ( ) ;
140162 emit_flight ( & migrated_socket, & mut conn) . await ;
141163
@@ -181,18 +203,12 @@ async fn process_flight(
181203) {
182204 let mut buf = [ 0 ; 65535 ] ;
183205
184- let mut did_recv = false ;
206+ socket . readable ( ) . await . unwrap ( ) ;
185207
186208 loop {
187- if !did_recv {
188- socket. readable ( ) . await . unwrap ( ) ;
189- }
190-
191209 let ( len, from) = match socket. try_recv_from ( & mut buf) {
192210 Ok ( v) => v,
193-
194211 Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: WouldBlock => break ,
195-
196212 Err ( e) => panic ! ( "failed to receive packets: {e:?}" ) ,
197213 } ;
198214
@@ -206,8 +222,6 @@ async fn process_flight(
206222
207223 // Process potentially coalesced packets.
208224 let _ = conn. recv ( & mut buf[ ..len] , recv_info) . unwrap ( ) ;
209-
210- did_recv = true ;
211225 }
212226}
213227
@@ -224,7 +238,7 @@ fn process_h3_events(
224238
225239 Ok ( ( stream_id, quiche:: h3:: Event :: Data ) ) => {
226240 // Drain stream and drop the data.
227- while let Ok ( _ ) = h3_conn. recv_body ( conn, stream_id, & mut buf) { }
241+ while h3_conn. recv_body ( conn, stream_id, & mut buf) . is_ok ( ) { }
228242 } ,
229243
230244 Ok ( ( _, quiche:: h3:: Event :: Finished ) ) => {
0 commit comments