@@ -33,7 +33,7 @@ use std::{
33
33
pin:: { Pin , pin} ,
34
34
sync:: {
35
35
Arc ,
36
- atomic:: { AtomicBool , Ordering } ,
36
+ atomic:: { AtomicBool , AtomicUsize , Ordering } ,
37
37
} ,
38
38
} ;
39
39
@@ -149,6 +149,8 @@ struct ActiveRelayActor {
149
149
/// last datagram sent to the relay, received datagrams will trigger QUIC ACKs which is
150
150
/// sufficient to keep active connections open.
151
151
inactive_timeout : Pin < Box < time:: Sleep > > ,
152
+ /// The last known value for the magic socket's `AsyncUdpSocket::max_receive_segments`.
153
+ max_receive_segments : Arc < AtomicUsize > ,
152
154
/// Token indicating the [`ActiveRelayActor`] should stop.
153
155
stop_token : CancellationToken ,
154
156
metrics : Arc < MagicsockMetrics > ,
@@ -193,6 +195,7 @@ struct ActiveRelayActorOptions {
193
195
relay_datagrams_send : mpsc:: Receiver < RelaySendItem > ,
194
196
relay_datagrams_recv : mpsc:: Sender < RelayRecvDatagram > ,
195
197
connection_opts : RelayConnectionOptions ,
198
+ max_receive_segments : Arc < AtomicUsize > ,
196
199
stop_token : CancellationToken ,
197
200
metrics : Arc < MagicsockMetrics > ,
198
201
}
@@ -276,6 +279,7 @@ impl ActiveRelayActor {
276
279
relay_datagrams_send,
277
280
relay_datagrams_recv,
278
281
connection_opts,
282
+ max_receive_segments,
279
283
stop_token,
280
284
metrics,
281
285
} = opts;
@@ -289,6 +293,7 @@ impl ActiveRelayActor {
289
293
relay_client_builder,
290
294
is_home_relay : false ,
291
295
inactive_timeout : Box :: pin ( time:: sleep ( RELAY_INACTIVE_CLEANUP_TIME ) ) ,
296
+ max_receive_segments,
292
297
stop_token,
293
298
metrics,
294
299
}
@@ -624,7 +629,7 @@ impl ActiveRelayActor {
624
629
metrics. send_relay. inc_by( item. datagrams. contents. len( ) as _) ;
625
630
Ok ( ClientToRelayMsg :: Datagrams {
626
631
dst_node_id: item. remote_node,
627
- datagrams: item. datagrams
632
+ datagrams: item. datagrams,
628
633
} )
629
634
} ) ;
630
635
let mut packet_stream = n0_future:: stream:: iter( packet_iter) ;
@@ -678,12 +683,28 @@ impl ActiveRelayActor {
678
683
state. last_packet_src = Some ( remote_node_id) ;
679
684
state. nodes_present . insert ( remote_node_id) ;
680
685
}
681
- if let Err ( err) = self . relay_datagrams_recv . try_send ( RelayRecvDatagram {
682
- url : self . url . clone ( ) ,
683
- src : remote_node_id,
686
+
687
+ let max_segments = self
688
+ . max_receive_segments
689
+ . load ( std:: sync:: atomic:: Ordering :: Relaxed ) ;
690
+ // We might receive a datagram batch that's bigger than our magic socket's
691
+ // `AsyncUdpSocket::max_receive_segments`, if the other endpoint behind the relay
692
+ // has a higher `AsyncUdpSocket::max_transmit_segments` than we do.
693
+ // This happens e.g. when a linux machine (max transmit segments is usually 64)
694
+ // talks to a windows machine or a macos machine (max transmit segments is usually 1).
695
+ let re_batched = DatagramReBatcher {
696
+ max_segments,
684
697
datagrams,
685
- } ) {
686
- warn ! ( "Dropping received relay packet: {err:#}" ) ;
698
+ } ;
699
+ for datagrams in re_batched {
700
+ if let Err ( err) = self . relay_datagrams_recv . try_send ( RelayRecvDatagram {
701
+ url : self . url . clone ( ) ,
702
+ src : remote_node_id,
703
+ datagrams,
704
+ } ) {
705
+ warn ! ( "Dropping received relay packet: {err:#}" ) ;
706
+ break ; // No need to hot-loop in that case.
707
+ }
687
708
}
688
709
}
689
710
RelayToClientMsg :: NodeGone ( node_id) => {
@@ -859,6 +880,8 @@ pub struct Config {
859
880
pub proxy_url : Option < Url > ,
860
881
/// If the last net_report report, reports IPv6 to be available.
861
882
pub ipv6_reported : Arc < AtomicBool > ,
883
+ /// The last known return value of the magic socket's `AsyncUdpSocket::max_receive_segments` value
884
+ pub max_receive_segments : Arc < AtomicUsize > ,
862
885
#[ cfg( any( test, feature = "test-utils" ) ) ]
863
886
pub insecure_skip_relay_cert_verify : bool ,
864
887
pub metrics : Arc < MagicsockMetrics > ,
@@ -1114,6 +1137,7 @@ impl RelayActor {
1114
1137
relay_datagrams_send : send_datagram_rx,
1115
1138
relay_datagrams_recv : self . relay_datagram_recv_queue . clone ( ) ,
1116
1139
connection_opts,
1140
+ max_receive_segments : self . config . max_receive_segments . clone ( ) ,
1117
1141
stop_token : self . cancel_token . child_token ( ) ,
1118
1142
metrics : self . config . metrics . clone ( ) ,
1119
1143
} ;
@@ -1224,13 +1248,35 @@ pub(crate) struct RelayRecvDatagram {
1224
1248
pub ( crate ) src : NodeId ,
1225
1249
pub ( crate ) datagrams : Datagrams ,
1226
1250
}
1251
+
1252
+ /// Turns a datagrams batch into multiple datagram batches of maximum `max_segments` size.
1253
+ ///
1254
+ /// If the given datagram isn't batched, it just returns that datagram once.
1255
+ struct DatagramReBatcher {
1256
+ max_segments : usize ,
1257
+ datagrams : Datagrams ,
1258
+ }
1259
+
1260
+ impl Iterator for DatagramReBatcher {
1261
+ type Item = Datagrams ;
1262
+
1263
+ fn next ( & mut self ) -> Option < Self :: Item > {
1264
+ self . datagrams . take_segments ( self . max_segments )
1265
+ }
1266
+ }
1267
+
1227
1268
#[ cfg( test) ]
1228
1269
mod tests {
1229
1270
use std:: {
1230
- sync:: { Arc , atomic:: AtomicBool } ,
1271
+ num:: NonZeroU16 ,
1272
+ sync:: {
1273
+ Arc ,
1274
+ atomic:: { AtomicBool , AtomicUsize } ,
1275
+ } ,
1231
1276
time:: Duration ,
1232
1277
} ;
1233
1278
1279
+ use bytes:: Bytes ;
1234
1280
use iroh_base:: { NodeId , RelayUrl , SecretKey } ;
1235
1281
use iroh_relay:: { PingTracker , protos:: relay:: Datagrams } ;
1236
1282
use n0_snafu:: { Error , Result , ResultExt } ;
@@ -1244,7 +1290,9 @@ mod tests {
1244
1290
RELAY_INACTIVE_CLEANUP_TIME , RelayConnectionOptions , RelayRecvDatagram , RelaySendItem ,
1245
1291
UNDELIVERABLE_DATAGRAM_TIMEOUT ,
1246
1292
} ;
1247
- use crate :: { dns:: DnsResolver , test_utils} ;
1293
+ use crate :: {
1294
+ dns:: DnsResolver , magicsock:: transports:: relay:: actor:: DatagramReBatcher , test_utils,
1295
+ } ;
1248
1296
1249
1297
/// Starts a new [`ActiveRelayActor`].
1250
1298
#[ allow( clippy:: too_many_arguments) ]
@@ -1271,6 +1319,7 @@ mod tests {
1271
1319
prefer_ipv6 : Arc :: new ( AtomicBool :: new ( true ) ) ,
1272
1320
insecure_skip_cert_verify : true ,
1273
1321
} ,
1322
+ max_receive_segments : Arc :: new ( AtomicUsize :: new ( 1 ) ) ,
1274
1323
stop_token,
1275
1324
metrics : Default :: default ( ) ,
1276
1325
} ;
@@ -1569,4 +1618,39 @@ mod tests {
1569
1618
let res = tokio:: time:: timeout ( Duration :: from_secs ( 10 ) , tracker. timeout ( ) ) . await ;
1570
1619
assert ! ( res. is_err( ) , "ping timeout should only happen once" ) ;
1571
1620
}
1621
+
1622
+ fn run_datagram_re_batcher ( max_segments : usize , expected_lengths : Vec < usize > ) {
1623
+ let contents = Bytes :: from_static (
1624
+ b"Hello world! There's lots of stuff to talk about when you need a big buffer." ,
1625
+ ) ;
1626
+ let datagrams = Datagrams {
1627
+ contents : contents. clone ( ) ,
1628
+ ecn : None ,
1629
+ segment_size : NonZeroU16 :: new ( 10 ) ,
1630
+ } ;
1631
+
1632
+ let re_batched_lengths = DatagramReBatcher {
1633
+ datagrams,
1634
+ max_segments,
1635
+ }
1636
+ . map ( |d| d. contents . len ( ) )
1637
+ . collect :: < Vec < _ > > ( ) ;
1638
+
1639
+ assert_eq ! ( expected_lengths, re_batched_lengths) ;
1640
+ }
1641
+
1642
+ #[ test]
1643
+ fn test_datagram_re_batcher_small_batches ( ) {
1644
+ run_datagram_re_batcher ( 3 , vec ! [ 30 , 30 , 16 ] ) ;
1645
+ }
1646
+
1647
+ #[ test]
1648
+ fn test_datagram_re_batcher_batch_full ( ) {
1649
+ run_datagram_re_batcher ( 10 , vec ! [ 76 ] ) ;
1650
+ }
1651
+
1652
+ #[ test]
1653
+ fn test_datagram_re_batcher_unbatch ( ) {
1654
+ run_datagram_re_batcher ( 1 , vec ! [ 10 , 10 , 10 , 10 , 10 , 10 , 10 , 6 ] ) ;
1655
+ }
1572
1656
}
0 commit comments