1#[cfg(feature = "dev-context-only-utils")]
2use tokio::net::UdpSocket as TokioUdpSocket;
3use {
4 crate::PortRange,
5 log::warn,
6 socket2::{Domain, SockAddr, Socket, Type},
7 std::{
8 io,
9 net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, UdpSocket},
10 ops::Range,
11 sync::atomic::{AtomicU16, Ordering},
12 },
13};
14pub(crate) const UNIQUE_ALLOC_BASE_PORT: u16 = 2000;
16const SLICE_PER_PROCESS: u16 = (u16::MAX - UNIQUE_ALLOC_BASE_PORT) / 64;
19#[allow(clippy::arithmetic_side_effects)]
27pub fn unique_port_range_for_tests(size: u16) -> Range<u16> {
28 static SLICE: AtomicU16 = AtomicU16::new(0);
29 let offset = SLICE.fetch_add(size, Ordering::SeqCst);
30 let start = offset
31 + match std::env::var("NEXTEST_TEST_GLOBAL_SLOT") {
32 Ok(slot) => {
33 let slot: u16 = slot.parse().unwrap();
34 assert!(
35 offset < SLICE_PER_PROCESS,
36 "Overrunning into the port range of another test! Consider using fewer ports \
37 per test."
38 );
39 UNIQUE_ALLOC_BASE_PORT + slot * SLICE_PER_PROCESS
40 }
41 Err(_) => UNIQUE_ALLOC_BASE_PORT,
42 };
43 assert!(start < u16::MAX - size, "Ran out of port numbers!");
44 start..start + size
45}
46
47pub fn localhost_port_range_for_tests() -> (u16, u16) {
57 let pr = unique_port_range_for_tests(25);
58 (pr.start, pr.end)
59}
60
61pub fn bind_to_localhost_unique() -> io::Result<UdpSocket> {
63 bind_to(
64 IpAddr::V4(Ipv4Addr::LOCALHOST),
65 unique_port_range_for_tests(1).start,
66 )
67}
68
69pub fn bind_gossip_port_in_range(
70 gossip_addr: &SocketAddr,
71 port_range: PortRange,
72 bind_ip_addr: IpAddr,
73) -> (u16, (UdpSocket, TcpListener)) {
74 let config = SocketConfiguration::default();
75 if gossip_addr.port() != 0 {
76 (
77 gossip_addr.port(),
78 bind_common_with_config(bind_ip_addr, gossip_addr.port(), config).unwrap_or_else(|e| {
79 panic!("gossip_addr bind_to port {}: {}", gossip_addr.port(), e)
80 }),
81 )
82 } else {
83 bind_common_in_range_with_config(bind_ip_addr, port_range, config).expect("Failed to bind")
84 }
85}
86
87pub(crate) const PLATFORM_SUPPORTS_SOCKET_CONFIGS: bool =
89 cfg!(not(any(windows, target_os = "ios")));
90
91#[derive(Clone, Copy, Debug, Default)]
92pub struct SocketConfiguration {
93 reuseport: bool, recv_buffer_size: Option<usize>,
95 send_buffer_size: Option<usize>,
96 non_blocking: bool,
97}
98
99impl SocketConfiguration {
100 pub fn recv_buffer_size(mut self, size: usize) -> Self {
107 self.recv_buffer_size = Some(size);
108 self
109 }
110
111 pub fn send_buffer_size(mut self, size: usize) -> Self {
118 self.send_buffer_size = Some(size);
119 self
120 }
121
122 pub fn set_non_blocking(mut self, non_blocking: bool) -> Self {
124 self.non_blocking = non_blocking;
125 self
126 }
127}
128
129#[allow(deprecated)]
130impl From<crate::SocketConfig> for SocketConfiguration {
131 fn from(value: crate::SocketConfig) -> Self {
132 Self {
133 reuseport: value.reuseport,
134 recv_buffer_size: value.recv_buffer_size,
135 send_buffer_size: value.send_buffer_size,
136 non_blocking: false,
137 }
138 }
139}
140
141#[cfg(any(windows, target_os = "ios"))]
142fn set_reuse_port<T>(_socket: &T) -> io::Result<()> {
143 Ok(())
144}
145
146#[cfg(not(any(windows, target_os = "ios")))]
148fn set_reuse_port<T>(socket: &T) -> io::Result<()>
149where
150 T: std::os::fd::AsFd,
151{
152 use nix::sys::socket::{setsockopt, sockopt::ReusePort};
153 setsockopt(socket, ReusePort, &true).map_err(io::Error::from)
154}
155
156pub(crate) fn udp_socket_with_config(config: SocketConfiguration) -> io::Result<Socket> {
157 let SocketConfiguration {
158 reuseport,
159 recv_buffer_size,
160 send_buffer_size,
161 non_blocking,
162 } = config;
163 let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
164 if PLATFORM_SUPPORTS_SOCKET_CONFIGS {
165 if let Some(recv_buffer_size) = recv_buffer_size {
167 sock.set_recv_buffer_size(recv_buffer_size)?;
168 }
169 if let Some(send_buffer_size) = send_buffer_size {
170 sock.set_send_buffer_size(send_buffer_size)?;
171 }
172
173 if reuseport {
174 set_reuse_port(&sock)?;
175 }
176 }
177 sock.set_nonblocking(non_blocking)?;
178 Ok(sock)
179}
180
181pub fn bind_common_in_range_with_config(
183 ip_addr: IpAddr,
184 range: PortRange,
185 config: SocketConfiguration,
186) -> io::Result<(u16, (UdpSocket, TcpListener))> {
187 for port in range.0..range.1 {
188 if let Ok((sock, listener)) = bind_common_with_config(ip_addr, port, config) {
189 return Result::Ok((sock.local_addr().unwrap().port(), (sock, listener)));
190 }
191 }
192
193 Err(io::Error::other(format!(
194 "No available TCP/UDP ports in {range:?}"
195 )))
196}
197
198pub fn bind_in_range_with_config(
199 ip_addr: IpAddr,
200 range: PortRange,
201 config: SocketConfiguration,
202) -> io::Result<(u16, UdpSocket)> {
203 let socket = udp_socket_with_config(config)?;
204
205 for port in range.0..range.1 {
206 let addr = SocketAddr::new(ip_addr, port);
207
208 if socket.bind(&SockAddr::from(addr)).is_ok() {
209 let udp_socket: UdpSocket = socket.into();
210 return Result::Ok((udp_socket.local_addr().unwrap().port(), udp_socket));
211 }
212 }
213
214 Err(io::Error::other(format!(
215 "No available UDP ports in {range:?}"
216 )))
217}
218
219#[deprecated(since = "3.0.0", note = "Please bind to specific ports instead")]
220#[allow(deprecated)]
221pub fn bind_with_any_port_with_config(
222 ip_addr: IpAddr,
223 config: SocketConfiguration,
224) -> io::Result<UdpSocket> {
225 _bind_with_any_port_with_config(ip_addr, config)
226}
227
228fn _bind_with_any_port_with_config(
231 ip_addr: IpAddr,
232 config: SocketConfiguration,
233) -> io::Result<UdpSocket> {
234 let sock = udp_socket_with_config(config)?;
235 let addr = SocketAddr::new(ip_addr, 0);
236 let bind = sock.bind(&SockAddr::from(addr));
237 match bind {
238 Ok(_) => Result::Ok(sock.into()),
239 Err(err) => Err(io::Error::other(format!("No available UDP port: {err}"))),
240 }
241}
242
243pub fn multi_bind_in_range_with_config(
245 ip_addr: IpAddr,
246 range: PortRange,
247 config: SocketConfiguration,
248 mut num: usize,
249) -> io::Result<(u16, Vec<UdpSocket>)> {
250 if !PLATFORM_SUPPORTS_SOCKET_CONFIGS && num != 1 {
251 warn!(
253 "multi_bind_in_range_with_config() only supports 1 socket on this platform ({num} \
254 requested)"
255 );
256 num = 1;
257 }
258 let (port, socket) = bind_in_range_with_config(ip_addr, range, config)?;
259 let sockets = bind_more_with_config(socket, num, config)?;
260 Ok((port, sockets))
261}
262
263pub fn bind_to(ip_addr: IpAddr, port: u16) -> io::Result<UdpSocket> {
264 let config = SocketConfiguration {
265 ..Default::default()
266 };
267 bind_to_with_config(ip_addr, port, config)
268}
269
270#[cfg(feature = "dev-context-only-utils")]
271pub async fn bind_to_async(ip_addr: IpAddr, port: u16) -> io::Result<TokioUdpSocket> {
272 let config = SocketConfiguration {
273 non_blocking: true,
274 ..Default::default()
275 };
276 let socket = bind_to_with_config(ip_addr, port, config)?;
277 TokioUdpSocket::from_std(socket)
278}
279
280#[cfg(feature = "dev-context-only-utils")]
281pub async fn bind_to_localhost_async() -> io::Result<TokioUdpSocket> {
282 let port = unique_port_range_for_tests(1).start;
283 bind_to_async(IpAddr::V4(Ipv4Addr::LOCALHOST), port).await
284}
285
286#[cfg(feature = "dev-context-only-utils")]
287pub async fn bind_to_unspecified_async() -> io::Result<TokioUdpSocket> {
288 let port = unique_port_range_for_tests(1).start;
289 bind_to_async(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port).await
290}
291
292pub fn bind_to_with_config(
293 ip_addr: IpAddr,
294 port: u16,
295 config: SocketConfiguration,
296) -> io::Result<UdpSocket> {
297 let sock = udp_socket_with_config(config)?;
298
299 let addr = SocketAddr::new(ip_addr, port);
300
301 sock.bind(&SockAddr::from(addr)).map(|_| sock.into())
302}
303
304pub fn bind_common_with_config(
306 ip_addr: IpAddr,
307 port: u16,
308 config: SocketConfiguration,
309) -> io::Result<(UdpSocket, TcpListener)> {
310 let sock = udp_socket_with_config(config)?;
311
312 let addr = SocketAddr::new(ip_addr, port);
313 let sock_addr = SockAddr::from(addr);
314 sock.bind(&sock_addr)
315 .and_then(|_| TcpListener::bind(addr).map(|listener| (sock.into(), listener)))
316}
317
318pub fn bind_two_in_range_with_offset_and_config(
319 ip_addr: IpAddr,
320 range: PortRange,
321 offset: u16,
322 sock1_config: SocketConfiguration,
323 sock2_config: SocketConfiguration,
324) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> {
325 if range.1.saturating_sub(range.0) < offset {
326 return Err(io::Error::other(
327 "range too small to find two ports with the correct offset".to_string(),
328 ));
329 }
330
331 let max_start_port = range.1.saturating_sub(offset);
332 for port in range.0..=max_start_port {
333 let first_bind_result = bind_to_with_config(ip_addr, port, sock1_config);
334 if let Ok(first_bind) = first_bind_result {
335 let second_port = port.saturating_add(offset);
336 let second_bind_result = bind_to_with_config(ip_addr, second_port, sock2_config);
337 if let Ok(second_bind) = second_bind_result {
338 return Ok((
339 (first_bind.local_addr().unwrap().port(), first_bind),
340 (second_bind.local_addr().unwrap().port(), second_bind),
341 ));
342 }
343 }
344 }
345 Err(io::Error::other(
346 "couldn't find two ports with the correct offset in range".to_string(),
347 ))
348}
349
350pub fn bind_more_with_config(
351 socket: UdpSocket,
352 num: usize,
353 mut config: SocketConfiguration,
354) -> io::Result<Vec<UdpSocket>> {
355 if !PLATFORM_SUPPORTS_SOCKET_CONFIGS {
356 if num > 1 {
357 warn!(
358 "bind_more_with_config() only supports 1 socket on this platform ({num} requested)"
359 );
360 }
361 Ok(vec![socket])
362 } else {
363 set_reuse_port(&socket)?;
364 config.reuseport = true;
365 let addr = socket.local_addr().unwrap();
366 let ip = addr.ip();
367 let port = addr.port();
368 std::iter::once(Ok(socket))
369 .chain((1..num).map(|_| bind_to_with_config(ip, port, config)))
370 .collect()
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use {
377 super::*,
378 crate::{
379 bind_in_range, get_cluster_shred_version, get_public_ip_addr_with_binding,
380 ip_echo_client, ip_echo_server, parse_host,
381 sockets::{localhost_port_range_for_tests, unique_port_range_for_tests},
382 verify_all_reachable_tcp, verify_all_reachable_udp, DEFAULT_IP_ECHO_SERVER_THREADS,
383 MAX_PORT_VERIFY_THREADS,
384 },
385 itertools::Itertools,
386 std::{net::Ipv4Addr, time::Duration},
387 tokio::runtime::Runtime,
388 };
389
390 fn runtime() -> Runtime {
391 tokio::runtime::Builder::new_current_thread()
392 .enable_all()
393 .build()
394 .expect("Can not create a runtime")
395 }
396
397 #[test]
398 fn test_bind() {
399 let (pr_s, pr_e) = localhost_port_range_for_tests();
400 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
401 let config = SocketConfiguration::default();
402 let s = bind_in_range(ip_addr, (pr_s, pr_e)).unwrap();
403 assert_eq!(s.0, pr_s, "bind_in_range should use first available port");
404 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
405 let x = bind_to_with_config(ip_addr, pr_s + 1, config).unwrap();
406 let y = bind_more_with_config(x, 2, config).unwrap();
407 assert_eq!(
408 y[0].local_addr().unwrap().port(),
409 y[1].local_addr().unwrap().port()
410 );
411 bind_to_with_config(ip_addr, pr_s, SocketConfiguration::default()).unwrap_err();
412 bind_in_range(ip_addr, (pr_s, pr_s + 2)).unwrap_err();
413
414 let (port, v) =
415 multi_bind_in_range_with_config(ip_addr, (pr_s + 5, pr_e), config, 10).unwrap();
416 for sock in &v {
417 assert_eq!(port, sock.local_addr().unwrap().port());
418 }
419 }
420
421 #[test]
422 fn test_bind_with_any_port() {
423 let x = bind_to_localhost_unique().unwrap();
424 let y = bind_to_localhost_unique().unwrap();
425 assert_ne!(
426 x.local_addr().unwrap().port(),
427 y.local_addr().unwrap().port()
428 );
429 }
430
431 #[test]
432 fn test_bind_in_range_nil() {
433 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
434 let range = unique_port_range_for_tests(2);
435 bind_in_range(ip_addr, (range.end, range.end)).unwrap_err();
436 bind_in_range(ip_addr, (range.end, range.start)).unwrap_err();
437 }
438
439 #[test]
440 fn test_bind_on_top() {
441 let config = SocketConfiguration::default();
442 let localhost = IpAddr::V4(Ipv4Addr::LOCALHOST);
443 let port_range = localhost_port_range_for_tests();
444 let (_p, s) = bind_in_range_with_config(localhost, port_range, config).unwrap();
445 let _socks = bind_more_with_config(s, 8, config).unwrap();
446
447 let _socks2 = multi_bind_in_range_with_config(localhost, port_range, config, 8).unwrap();
448 }
449
450 #[test]
451 fn test_bind_common_in_range() {
452 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
453 let range = unique_port_range_for_tests(5);
454 let config = SocketConfiguration::default();
455 let (port, _sockets) =
456 bind_common_in_range_with_config(ip_addr, (range.start, range.end), config).unwrap();
457 assert!(range.contains(&port));
458 bind_common_in_range_with_config(ip_addr, (port, port + 1), config).unwrap_err();
459 }
460
461 #[test]
462 fn test_bind_two_in_range_with_offset() {
463 agave_logger::setup();
464 let config = SocketConfiguration::default();
465 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
466 let offset = 6;
467 let port_range = unique_port_range_for_tests(10);
468 if let Ok(((port1, _), (port2, _))) = bind_two_in_range_with_offset_and_config(
469 ip_addr,
470 (port_range.start, port_range.end),
471 offset,
472 config,
473 config,
474 ) {
475 assert!(port2 == port1 + offset);
476 }
477 let offset = 42;
478 if let Ok(((port1, _), (port2, _))) = bind_two_in_range_with_offset_and_config(
479 ip_addr,
480 (port_range.start, port_range.end),
481 offset,
482 config,
483 config,
484 ) {
485 assert!(port2 == port1 + offset);
486 }
487 assert!(bind_two_in_range_with_offset_and_config(
488 ip_addr,
489 (port_range.start, port_range.start + 5),
490 offset,
491 config,
492 config
493 )
494 .is_err());
495 }
496
497 #[test]
498 fn test_get_public_ip_addr_none() {
499 agave_logger::setup();
500 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
501 let (pr_s, pr_e) = localhost_port_range_for_tests();
502 let config = SocketConfiguration::default();
503 let (_server_port, (server_udp_socket, server_tcp_listener)) =
504 bind_common_in_range_with_config(ip_addr, (pr_s, pr_e), config).unwrap();
505
506 let _runtime = ip_echo_server(
507 server_tcp_listener,
508 DEFAULT_IP_ECHO_SERVER_THREADS,
509 Some(42),
510 );
511
512 let server_ip_echo_addr = server_udp_socket.local_addr().unwrap();
513 assert_eq!(
514 get_public_ip_addr_with_binding(
515 &server_ip_echo_addr,
516 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
517 )
518 .unwrap(),
519 parse_host("127.0.0.1").unwrap(),
520 );
521 assert_eq!(get_cluster_shred_version(&server_ip_echo_addr).unwrap(), 42);
522 assert!(verify_all_reachable_tcp(&server_ip_echo_addr, vec![],));
523 assert!(verify_all_reachable_udp(&server_ip_echo_addr, &[],));
524 }
525
526 #[test]
527 fn test_get_public_ip_addr_reachable() {
528 agave_logger::setup();
529 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
530 let port_range = localhost_port_range_for_tests();
531 let config = SocketConfiguration::default();
532 let (_server_port, (server_udp_socket, server_tcp_listener)) =
533 bind_common_in_range_with_config(ip_addr, port_range, config).unwrap();
534 let (_client_port, (client_udp_socket, client_tcp_listener)) =
535 bind_common_in_range_with_config(ip_addr, port_range, config).unwrap();
536
537 let _runtime = ip_echo_server(
538 server_tcp_listener,
539 DEFAULT_IP_ECHO_SERVER_THREADS,
540 Some(65535),
541 );
542
543 let ip_echo_server_addr = server_udp_socket.local_addr().unwrap();
544 assert_eq!(
545 get_public_ip_addr_with_binding(
546 &ip_echo_server_addr,
547 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
548 )
549 .unwrap(),
550 parse_host("127.0.0.1").unwrap(),
551 );
552 assert_eq!(
553 get_cluster_shred_version(&ip_echo_server_addr).unwrap(),
554 65535
555 );
556 assert!(verify_all_reachable_tcp(
557 &ip_echo_server_addr,
558 vec![client_tcp_listener],
559 ));
560 assert!(verify_all_reachable_udp(
561 &ip_echo_server_addr,
562 &[&client_udp_socket],
563 ));
564 }
565
566 #[test]
567 fn test_verify_ports_tcp_unreachable() {
568 agave_logger::setup();
569 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
570 let port_range = localhost_port_range_for_tests();
571 let config = SocketConfiguration::default();
572 let (_server_port, (server_udp_socket, _server_tcp_listener)) =
573 bind_common_in_range_with_config(ip_addr, port_range, config).unwrap();
574
575 let server_ip_echo_addr = server_udp_socket.local_addr().unwrap();
577
578 let (_, (_client_udp_socket, client_tcp_listener)) =
579 bind_common_in_range_with_config(ip_addr, port_range, config).unwrap();
580
581 let rt = runtime();
582 assert!(!rt.block_on(ip_echo_client::verify_all_reachable_tcp(
583 server_ip_echo_addr,
584 vec![client_tcp_listener],
585 Duration::from_secs(2),
586 )));
587 }
588
589 #[test]
590 fn test_verify_ports_udp_unreachable() {
591 agave_logger::setup();
592 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
593 let port_range = unique_port_range_for_tests(2);
594 let config = SocketConfiguration::default();
595 let (_server_port, (server_udp_socket, _server_tcp_listener)) =
596 bind_common_in_range_with_config(ip_addr, (port_range.start, port_range.end), config)
597 .unwrap();
598
599 let server_ip_echo_addr = server_udp_socket.local_addr().unwrap();
601
602 let (_correct_client_port, (client_udp_socket, _client_tcp_listener)) =
603 bind_common_in_range_with_config(ip_addr, (port_range.start, port_range.end), config)
604 .unwrap();
605
606 let rt = runtime();
607 assert!(!rt.block_on(ip_echo_client::verify_all_reachable_udp(
608 server_ip_echo_addr,
609 &[&client_udp_socket],
610 Duration::from_secs(2),
611 3,
612 )));
613 }
614
615 #[test]
616 fn test_verify_many_ports_reachable() {
617 agave_logger::setup();
618 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
619 let config = SocketConfiguration::default();
620 let mut tcp_listeners = vec![];
621 let mut udp_sockets = vec![];
622
623 let port_range = unique_port_range_for_tests(1);
624 let (_server_port, (_, server_tcp_listener)) =
625 bind_common_in_range_with_config(ip_addr, (port_range.start, port_range.end), config)
626 .unwrap();
627 for _ in 0..MAX_PORT_VERIFY_THREADS * 2 {
628 let port_range = unique_port_range_for_tests(1);
629 let (_client_port, (client_udp_socket, client_tcp_listener)) =
630 bind_common_in_range_with_config(
631 ip_addr,
632 (port_range.start, port_range.end),
633 config,
634 )
635 .unwrap();
636 tcp_listeners.push(client_tcp_listener);
637 udp_sockets.push(client_udp_socket);
638 }
639
640 let ip_echo_server_addr = server_tcp_listener.local_addr().unwrap();
641
642 let _runtime = ip_echo_server(
643 server_tcp_listener,
644 DEFAULT_IP_ECHO_SERVER_THREADS,
645 Some(65535),
646 );
647
648 assert_eq!(
649 get_public_ip_addr_with_binding(
650 &ip_echo_server_addr,
651 IpAddr::V4(Ipv4Addr::UNSPECIFIED)
652 )
653 .unwrap(),
654 parse_host("127.0.0.1").unwrap(),
655 );
656
657 let socket_refs = udp_sockets.iter().collect_vec();
658 assert!(verify_all_reachable_tcp(
659 &ip_echo_server_addr,
660 tcp_listeners,
661 ));
662 assert!(verify_all_reachable_udp(&ip_echo_server_addr, &socket_refs));
663 }
664
665 #[cfg(not(target_os = "macos"))]
668 #[test]
669 fn test_verify_udp_multiple_ips_reachable() {
670 agave_logger::setup();
671 let config = SocketConfiguration::default();
672 let ip_a = IpAddr::V4(Ipv4Addr::LOCALHOST);
673 let ip_b = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2));
674
675 let port_range = localhost_port_range_for_tests();
676
677 let (_srv_udp_port, (srv_udp_sock, srv_tcp_listener)) =
678 bind_common_in_range_with_config(ip_a, port_range, config).unwrap();
679
680 let ip_echo_server_addr = srv_udp_sock.local_addr().unwrap();
681 let _runtime = ip_echo_server(
682 srv_tcp_listener,
683 DEFAULT_IP_ECHO_SERVER_THREADS,
684 Some(42),
685 );
686
687 let mut udp_sockets = Vec::new();
688 let (_p1, (sock_a, _tl_a)) =
689 bind_common_in_range_with_config(ip_a, port_range, config).unwrap();
690 let (_p2, (sock_b, _tl_b)) =
691 bind_common_in_range_with_config(ip_b, port_range, config).unwrap();
692
693 udp_sockets.push(sock_a);
694 udp_sockets.push(sock_b);
695
696 let socket_refs: Vec<&UdpSocket> = udp_sockets.iter().collect();
697
698 assert!(
699 verify_all_reachable_udp(&ip_echo_server_addr, &socket_refs),
700 "all UDP ports on both 127.0.0.1 and 127.0.0.2 should be reachable"
701 );
702 }
703}