1#![cfg_attr(
2 not(feature = "agave-unstable-api"),
3 deprecated(
4 since = "3.1.0",
5 note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6 v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7 acknowledge use of an interface that may break without warning."
8 )
9)]
10#![warn(if_let_rescope)]
14#![warn(keyword_idents_2024)]
15#![warn(missing_unsafe_on_extern)]
16#![warn(rust_2024_guarded_string_incompatible_syntax)]
17#![warn(rust_2024_incompatible_pat)]
18#![warn(tail_expr_drop_order)]
19#![warn(unsafe_attr_outside_unsafe)]
20#![warn(unsafe_op_in_unsafe_fn)]
21
22mod ip_echo_client;
23mod ip_echo_server;
24pub mod multihomed_sockets;
25pub mod sockets;
26pub mod token_bucket;
27
28#[cfg(feature = "dev-context-only-utils")]
29pub mod tooling_for_tests;
30
31pub use ip_echo_server::{
32 ip_echo_server, IpEchoServer, DEFAULT_IP_ECHO_SERVER_THREADS, MAX_PORT_COUNT_PER_MESSAGE,
33 MINIMUM_IP_ECHO_SERVER_THREADS,
34};
35use {
36 crate::sockets::{udp_socket_with_config, PLATFORM_SUPPORTS_SOCKET_CONFIGS},
37 ip_echo_client::{ip_echo_server_request, ip_echo_server_request_with_binding},
38 ip_echo_server::IpEchoServerMessage,
39 log::*,
40 rand::{thread_rng, Rng},
41 socket2::SockAddr,
42 std::{
43 io::{self},
44 net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, ToSocketAddrs, UdpSocket},
45 },
46 url::Url,
47};
48
49pub struct UdpSocketPair {
51 pub addr: SocketAddr, pub receiver: UdpSocket, pub sender: UdpSocket, }
55
56pub type PortRange = (u16, u16);
57
58#[cfg(not(debug_assertions))]
59pub const VALIDATOR_PORT_RANGE: PortRange = (8000, 10_000);
61
62#[cfg(debug_assertions)]
65pub const VALIDATOR_PORT_RANGE: PortRange = (
66 crate::sockets::UNIQUE_ALLOC_BASE_PORT - 512,
67 crate::sockets::UNIQUE_ALLOC_BASE_PORT,
68);
69
70pub const MINIMUM_VALIDATOR_PORT_RANGE_WIDTH: u16 = 25; pub(crate) const HEADER_LENGTH: usize = 4;
73pub(crate) const IP_ECHO_SERVER_RESPONSE_LENGTH: usize = HEADER_LENGTH + 23;
74
75#[deprecated(
78 since = "3.0.0",
79 note = "Use `get_public_ip_addr_with_binding` instead"
80)]
81pub fn get_public_ip_addr(ip_echo_server_addr: &SocketAddr) -> Result<IpAddr, String> {
82 #[allow(deprecated)]
83 {
84 let fut = ip_echo_server_request(*ip_echo_server_addr, IpEchoServerMessage::default());
85 let rt = tokio::runtime::Builder::new_current_thread()
86 .enable_all()
87 .build()
88 .map_err(|e| e.to_string())?;
89 let resp = rt.block_on(fut).map_err(|e| e.to_string())?;
90 Ok(resp.address)
91 }
92}
93
94pub fn get_public_ip_addr_with_binding(
97 ip_echo_server_addr: &SocketAddr,
98 bind_address: IpAddr,
99) -> anyhow::Result<IpAddr> {
100 let fut = ip_echo_server_request_with_binding(
101 *ip_echo_server_addr,
102 IpEchoServerMessage::default(),
103 bind_address,
104 );
105 let rt = tokio::runtime::Builder::new_current_thread()
106 .enable_all()
107 .build()?;
108 let resp = rt.block_on(fut)?;
109 Ok(resp.address)
110}
111
112pub fn get_cluster_shred_version(ip_echo_server_addr: &SocketAddr) -> Result<u16, String> {
114 let fut = ip_echo_server_request(*ip_echo_server_addr, IpEchoServerMessage::default());
115 let rt = tokio::runtime::Builder::new_current_thread()
116 .enable_all()
117 .build()
118 .map_err(|e| e.to_string())?;
119 let resp = rt.block_on(fut).map_err(|e| e.to_string())?;
120 resp.shred_version
121 .ok_or_else(|| "IP echo server does not return a shred-version".to_owned())
122}
123
124pub fn get_cluster_shred_version_with_binding(
127 ip_echo_server_addr: &SocketAddr,
128 bind_address: IpAddr,
129) -> anyhow::Result<u16> {
130 let fut = ip_echo_server_request_with_binding(
131 *ip_echo_server_addr,
132 IpEchoServerMessage::default(),
133 bind_address,
134 );
135 let rt = tokio::runtime::Builder::new_current_thread()
136 .enable_all()
137 .build()?;
138 let resp = rt.block_on(fut)?;
139 resp.shred_version
140 .ok_or_else(|| anyhow::anyhow!("IP echo server does not return a shred-version"))
141}
142
143const MAX_PORT_VERIFY_THREADS: usize = 64;
146
147pub fn verify_all_reachable_udp(
152 ip_echo_server_addr: &SocketAddr,
153 udp_sockets: &[&UdpSocket],
154) -> bool {
155 let rt = tokio::runtime::Builder::new_current_thread()
156 .enable_all()
157 .max_blocking_threads(MAX_PORT_VERIFY_THREADS)
158 .build()
159 .expect("Tokio builder should be able to reliably create a current thread runtime");
160 let fut = ip_echo_client::verify_all_reachable_udp(
161 *ip_echo_server_addr,
162 udp_sockets,
163 ip_echo_client::TIMEOUT,
164 ip_echo_client::DEFAULT_RETRY_COUNT,
165 );
166 rt.block_on(fut)
167}
168
169pub fn verify_all_reachable_tcp(
174 ip_echo_server_addr: &SocketAddr,
175 tcp_listeners: Vec<TcpListener>,
176) -> bool {
177 let rt = tokio::runtime::Builder::new_current_thread()
178 .enable_all()
179 .max_blocking_threads(MAX_PORT_VERIFY_THREADS)
180 .build()
181 .expect("Tokio builder should be able to reliably create a current thread runtime");
182 let fut = ip_echo_client::verify_all_reachable_tcp(
183 *ip_echo_server_addr,
184 tcp_listeners,
185 ip_echo_client::TIMEOUT,
186 );
187 rt.block_on(fut)
188}
189
190pub fn parse_port_or_addr(optstr: Option<&str>, default_addr: SocketAddr) -> SocketAddr {
191 if let Some(addrstr) = optstr {
192 if let Ok(port) = addrstr.parse() {
193 let mut addr = default_addr;
194 addr.set_port(port);
195 addr
196 } else if let Ok(addr) = addrstr.parse() {
197 addr
198 } else {
199 default_addr
200 }
201 } else {
202 default_addr
203 }
204}
205
206pub fn parse_port_range(port_range: &str) -> Option<PortRange> {
207 let ports: Vec<&str> = port_range.split('-').collect();
208 if ports.len() != 2 {
209 return None;
210 }
211
212 let start_port = ports[0].parse();
213 let end_port = ports[1].parse();
214
215 if start_port.is_err() || end_port.is_err() {
216 return None;
217 }
218 let start_port = start_port.unwrap();
219 let end_port = end_port.unwrap();
220 if end_port < start_port {
221 return None;
222 }
223 Some((start_port, end_port))
224}
225
226pub fn parse_host(host: &str) -> Result<IpAddr, String> {
227 let parsed_url = Url::parse(&format!("http://{host}")).map_err(|e| e.to_string())?;
230 if parsed_url.port().is_some() {
231 return Err(format!("Expected port in URL: {host}"));
232 }
233
234 let ips: Vec<_> = (host, 0)
236 .to_socket_addrs()
237 .map_err(|err| err.to_string())?
238 .map(|socket_address| socket_address.ip())
239 .collect();
240 if ips.is_empty() {
241 Err(format!("Unable to resolve host: {host}"))
242 } else {
243 Ok(ips[0])
244 }
245}
246
247pub fn is_host(string: String) -> Result<(), String> {
248 parse_host(&string).map(|_| ())
249}
250
251pub fn parse_host_port(host_port: &str) -> Result<SocketAddr, String> {
252 let addrs: Vec<_> = host_port
253 .to_socket_addrs()
254 .map_err(|err| format!("Unable to resolve host {host_port}: {err}"))?
255 .collect();
256 if addrs.is_empty() {
257 Err(format!("Unable to resolve host: {host_port}"))
258 } else {
259 Ok(addrs[0])
260 }
261}
262
263pub fn is_host_port(string: String) -> Result<(), String> {
264 parse_host_port(&string).map(|_| ())
265}
266
267#[deprecated(
268 since = "3.0.0",
269 note = "Please use the equivalent struct from solana-net-utils::sockets"
270)]
271#[derive(Clone, Copy, Debug, Default)]
272pub struct SocketConfig {
273 reuseport: bool,
274 recv_buffer_size: Option<usize>,
275 send_buffer_size: Option<usize>,
276}
277
278#[allow(deprecated)]
279impl SocketConfig {
280 pub fn reuseport(mut self, reuseport: bool) -> Self {
281 self.reuseport = reuseport;
282 self
283 }
284
285 pub fn recv_buffer_size(mut self, size: usize) -> Self {
292 self.recv_buffer_size = Some(size);
293 self
294 }
295
296 pub fn send_buffer_size(mut self, size: usize) -> Self {
303 self.send_buffer_size = Some(size);
304 self
305 }
306}
307
308#[deprecated(
309 since = "3.0.0",
310 note = "Please use the equivalent from solana-net-utils::sockets"
311)]
312#[allow(deprecated)]
313pub fn bind_common_in_range_with_config(
315 ip_addr: IpAddr,
316 range: PortRange,
317 config: SocketConfig,
318) -> io::Result<(u16, (UdpSocket, TcpListener))> {
319 for port in range.0..range.1 {
320 if let Ok((sock, listener)) = bind_common_with_config(ip_addr, port, config) {
321 return Result::Ok((sock.local_addr().unwrap().port(), (sock, listener)));
322 }
323 }
324
325 Err(io::Error::other(format!(
326 "No available TCP/UDP ports in {range:?}"
327 )))
328}
329
330pub fn bind_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<(u16, UdpSocket)> {
331 let config = sockets::SocketConfiguration::default();
332 sockets::bind_in_range_with_config(ip_addr, range, config)
333}
334
335#[deprecated(
336 since = "3.0.0",
337 note = "Please use the equivalent from solana-net-utils::sockets"
338)]
339#[allow(deprecated)]
340pub fn bind_in_range_with_config(
341 ip_addr: IpAddr,
342 range: PortRange,
343 config: SocketConfig,
344) -> io::Result<(u16, UdpSocket)> {
345 let socket = udp_socket_with_config(config.into())?;
346
347 for port in range.0..range.1 {
348 let addr = SocketAddr::new(ip_addr, port);
349
350 if socket.bind(&SockAddr::from(addr)).is_ok() {
351 let udp_socket: UdpSocket = socket.into();
352 return Result::Ok((udp_socket.local_addr().unwrap().port(), udp_socket));
353 }
354 }
355
356 Err(io::Error::other(format!(
357 "No available UDP ports in {range:?}"
358 )))
359}
360
361#[deprecated(
362 since = "3.0.0",
363 note = "Please use the equivalent from solana-net-utils::sockets"
364)]
365#[allow(deprecated)]
366pub fn bind_with_any_port_with_config(
367 ip_addr: IpAddr,
368 config: SocketConfig,
369) -> io::Result<UdpSocket> {
370 let sock = udp_socket_with_config(config.into())?;
371 let addr = SocketAddr::new(ip_addr, 0);
372 let bind = sock.bind(&SockAddr::from(addr));
373 match bind {
374 Ok(_) => Result::Ok(sock.into()),
375 Err(err) => Err(io::Error::other(format!("No available UDP port: {err}"))),
376 }
377}
378
379#[deprecated(
380 since = "3.0.0",
381 note = "Please use the equivalent from solana-net-utils::sockets"
382)]
383#[allow(deprecated)]
384pub fn multi_bind_in_range_with_config(
386 ip_addr: IpAddr,
387 range: PortRange,
388 config: SocketConfig,
389 mut num: usize,
390) -> io::Result<(u16, Vec<UdpSocket>)> {
391 if !PLATFORM_SUPPORTS_SOCKET_CONFIGS && num != 1 {
392 warn!(
394 "multi_bind_in_range_with_config() only supports 1 socket on this platform ({num} \
395 requested)"
396 );
397 num = 1;
398 }
399 let (port, socket) = bind_in_range_with_config(ip_addr, range, config)?;
400 let sockets = bind_more_with_config(socket, num, config)?;
401 Ok((port, sockets))
402}
403
404#[deprecated(
405 since = "3.0.0",
406 note = "Please use the eqiuvalent from solana-net-utils::sockets"
407)]
408#[allow(deprecated)]
409pub fn bind_to(ip_addr: IpAddr, port: u16, reuseport: bool) -> io::Result<UdpSocket> {
410 let config = SocketConfig {
411 reuseport,
412 ..Default::default()
413 };
414 bind_to_with_config(ip_addr, port, config)
415}
416
417pub fn bind_to_localhost() -> io::Result<UdpSocket> {
418 let config = sockets::SocketConfiguration::default();
419 sockets::bind_to_with_config(IpAddr::V4(Ipv4Addr::LOCALHOST), 0, config)
420}
421
422pub fn bind_to_unspecified() -> io::Result<UdpSocket> {
423 let config = sockets::SocketConfiguration::default();
424 sockets::bind_to_with_config(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0, config)
425}
426
427#[deprecated(
428 since = "3.0.0",
429 note = "Please avoid this function in favor of sockets::bind_to_with_config"
430)]
431#[allow(deprecated)]
432pub fn bind_to_with_config(
433 ip_addr: IpAddr,
434 port: u16,
435 config: SocketConfig,
436) -> io::Result<UdpSocket> {
437 let sock = udp_socket_with_config(config.into())?;
438 let addr = SocketAddr::new(ip_addr, port);
439 sock.bind(&SockAddr::from(addr)).map(|_| sock.into())
440}
441
442#[deprecated(
443 since = "3.0.0",
444 note = "Please avoid this function, it is easy to misuse"
445)]
446#[allow(deprecated)]
447pub fn bind_to_with_config_non_blocking(
448 ip_addr: IpAddr,
449 port: u16,
450 config: SocketConfig,
451) -> io::Result<UdpSocket> {
452 let sock = udp_socket_with_config(config.into())?;
453
454 let addr = SocketAddr::new(ip_addr, port);
455
456 sock.bind(&SockAddr::from(addr))?;
457 sock.set_nonblocking(true)?;
458 Ok(sock.into())
459}
460
461#[deprecated(
462 since = "3.0.0",
463 note = "Please avoid this function in favor of sockets::bind_common_with_config"
464)]
465pub fn bind_common(ip_addr: IpAddr, port: u16) -> io::Result<(UdpSocket, TcpListener)> {
467 #[allow(deprecated)]
468 {
469 let config = sockets::SocketConfiguration::default();
470 sockets::bind_common_with_config(ip_addr, port, config)
471 }
472}
473
474#[deprecated(
475 since = "3.0.0",
476 note = "Please avoid this function in favor of sockets::bind_common_with_config"
477)]
478#[allow(deprecated)]
479pub fn bind_common_with_config(
481 ip_addr: IpAddr,
482 port: u16,
483 config: SocketConfig,
484) -> io::Result<(UdpSocket, TcpListener)> {
485 let sock = udp_socket_with_config(config.into())?;
486
487 let addr = SocketAddr::new(ip_addr, port);
488 let sock_addr = SockAddr::from(addr);
489 sock.bind(&sock_addr)
490 .and_then(|_| TcpListener::bind(addr).map(|listener| (sock.into(), listener)))
491}
492
493#[deprecated(
494 since = "3.0.0",
495 note = "Please avoid this function, in favor of \
496 sockets::bind_two_in_range_with_offset_and_config"
497)]
498#[allow(deprecated)]
499pub fn bind_two_in_range_with_offset(
500 ip_addr: IpAddr,
501 range: PortRange,
502 offset: u16,
503) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> {
504 let sock_config = sockets::SocketConfiguration::default();
505 sockets::bind_two_in_range_with_offset_and_config(
506 ip_addr,
507 range,
508 offset,
509 sock_config,
510 sock_config,
511 )
512}
513
514#[deprecated(
515 since = "3.0.0",
516 note = "Please avoid this function, in favor of \
517 sockets::bind_two_in_range_with_offset_and_config"
518)]
519#[allow(deprecated)]
520pub fn bind_two_in_range_with_offset_and_config(
521 ip_addr: IpAddr,
522 range: PortRange,
523 offset: u16,
524 sock1_config: SocketConfig,
525 sock2_config: SocketConfig,
526) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> {
527 if range.1.saturating_sub(range.0) < offset {
528 return Err(io::Error::other(
529 "range too small to find two ports with the correct offset".to_string(),
530 ));
531 }
532
533 for port in range.0..range.1 {
534 let first_bind = bind_to_with_config(ip_addr, port, sock1_config);
535 if let Ok(first_bind) = first_bind {
536 if range.1.saturating_sub(port) >= offset {
537 let second_bind =
538 bind_to_with_config(ip_addr, port.saturating_add(offset), sock2_config);
539 if let Ok(second_bind) = second_bind {
540 return Ok((
541 (first_bind.local_addr().unwrap().port(), first_bind),
542 (second_bind.local_addr().unwrap().port(), second_bind),
543 ));
544 }
545 } else {
546 break;
547 }
548 }
549 }
550 Err(io::Error::other(
551 "couldn't find two ports with the correct offset in range".to_string(),
552 ))
553}
554
555pub fn find_available_port_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<u16> {
562 let [port] = find_available_ports_in_range(ip_addr, range)?;
563 Ok(port)
564}
565
566pub fn find_available_ports_in_range<const N: usize>(
571 ip_addr: IpAddr,
572 range: PortRange,
573) -> io::Result<[u16; N]> {
574 let mut result = [0u16; N];
575 let range = range.0..range.1;
576 let mut next_port_to_try = range
577 .clone()
578 .cycle() .skip(thread_rng().gen_range(range.clone()) as usize) .take(range.len()) .peekable();
582 let mut num = 0;
583 let config = sockets::SocketConfiguration::default();
584 while num < N {
585 let port_to_try = next_port_to_try.next().unwrap(); let bind = sockets::bind_common_with_config(ip_addr, port_to_try, config);
587 match bind {
588 Ok(_) => {
589 result[num] = port_to_try;
590 num = num.saturating_add(1);
591 }
592 Err(err) => {
593 if next_port_to_try.peek().is_none() {
594 return Err(err);
595 }
596 }
597 }
598 }
599 Ok(result)
600}
601
602#[deprecated(
603 since = "3.0.0",
604 note = "Please avoid this function, in favor of sockets::bind_more_with_config"
605)]
606#[allow(deprecated)]
607pub fn bind_more_with_config(
608 socket: UdpSocket,
609 num: usize,
610 config: SocketConfig,
611) -> io::Result<Vec<UdpSocket>> {
612 if !PLATFORM_SUPPORTS_SOCKET_CONFIGS {
613 if num > 1 {
614 warn!(
615 "bind_more_with_config() only supports 1 socket on this platform ({num} requested)"
616 );
617 }
618 Ok(vec![socket])
619 } else {
620 let addr = socket.local_addr().unwrap();
621 let ip = addr.ip();
622 let port = addr.port();
623 std::iter::once(Ok(socket))
624 .chain((1..num).map(|_| bind_to_with_config(ip, port, config)))
625 .collect()
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use {
632 super::*, crate::sockets::unique_port_range_for_tests,
633 ip_echo_server::IpEchoServerResponse, itertools::Itertools, std::net::Ipv4Addr,
634 };
635
636 #[test]
637 fn test_response_length() {
638 let resp = IpEchoServerResponse {
639 address: IpAddr::from([u16::MAX; 8]), shred_version: Some(u16::MAX),
641 };
642 let resp_size = bincode::serialized_size(&resp).unwrap();
643 assert_eq!(
644 IP_ECHO_SERVER_RESPONSE_LENGTH,
645 HEADER_LENGTH + resp_size as usize
646 );
647 }
648
649 #[test]
651 fn test_backward_compat() {
652 let address = IpAddr::from([
653 525u16, 524u16, 523u16, 522u16, 521u16, 520u16, 519u16, 518u16,
654 ]);
655 let response = IpEchoServerResponse {
656 address,
657 shred_version: Some(42),
658 };
659 let mut data = vec![0u8; IP_ECHO_SERVER_RESPONSE_LENGTH];
660 bincode::serialize_into(&mut data[HEADER_LENGTH..], &response).unwrap();
661 data.truncate(HEADER_LENGTH + 20);
662 assert_eq!(
663 bincode::deserialize::<IpAddr>(&data[HEADER_LENGTH..]).unwrap(),
664 address
665 );
666 }
667
668 #[test]
670 fn test_forward_compat() {
671 let address = IpAddr::from([
672 525u16, 524u16, 523u16, 522u16, 521u16, 520u16, 519u16, 518u16,
673 ]);
674 let mut data = [0u8; IP_ECHO_SERVER_RESPONSE_LENGTH];
675 bincode::serialize_into(&mut data[HEADER_LENGTH..], &address).unwrap();
676 let response: Result<IpEchoServerResponse, _> =
677 bincode::deserialize(&data[HEADER_LENGTH..]);
678 assert_eq!(
679 response.unwrap(),
680 IpEchoServerResponse {
681 address,
682 shred_version: None,
683 }
684 );
685 }
686
687 #[test]
688 fn test_parse_port_or_addr() {
689 let p1 = parse_port_or_addr(Some("9000"), SocketAddr::from(([1, 2, 3, 4], 1)));
690 assert_eq!(p1.port(), 9000);
691 let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), SocketAddr::from(([1, 2, 3, 4], 1)));
692 assert_eq!(p2.port(), 7000);
693 let p2 = parse_port_or_addr(Some("hi there"), SocketAddr::from(([1, 2, 3, 4], 1)));
694 assert_eq!(p2.port(), 1);
695 let p3 = parse_port_or_addr(None, SocketAddr::from(([1, 2, 3, 4], 1)));
696 assert_eq!(p3.port(), 1);
697 }
698
699 #[test]
700 fn test_parse_port_range() {
701 assert_eq!(parse_port_range("garbage"), None);
702 assert_eq!(parse_port_range("1-"), None);
703 assert_eq!(parse_port_range("1-2"), Some((1, 2)));
704 assert_eq!(parse_port_range("1-2-3"), None);
705 assert_eq!(parse_port_range("2-1"), None);
706 }
707
708 #[test]
709 fn test_parse_host() {
710 parse_host("localhost:1234").unwrap_err();
711 parse_host("localhost").unwrap();
712 parse_host("127.0.0.0:1234").unwrap_err();
713 parse_host("127.0.0.0").unwrap();
714 }
715
716 #[test]
717 fn test_parse_host_port() {
718 parse_host_port("localhost:1234").unwrap();
719 parse_host_port("localhost").unwrap_err();
720 parse_host_port("127.0.0.0:1234").unwrap();
721 parse_host_port("127.0.0.0").unwrap_err();
722 }
723
724 #[test]
725 fn test_is_host_port() {
726 assert!(is_host_port("localhost:1234".to_string()).is_ok());
727 assert!(is_host_port("localhost".to_string()).is_err());
728 }
729
730 #[test]
731 fn test_find_available_port_in_range() {
732 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
733 let range = sockets::unique_port_range_for_tests(4);
734 let (pr_s, pr_e) = (range.start, range.end);
735 assert_eq!(
736 find_available_port_in_range(ip_addr, (pr_s, pr_s + 1)).unwrap(),
737 pr_s
738 );
739 let port = find_available_port_in_range(ip_addr, (pr_s, pr_e)).unwrap();
740 assert!((pr_s..pr_e).contains(&port));
741
742 let _socket = sockets::bind_to(ip_addr, port).unwrap();
743 find_available_port_in_range(ip_addr, (port, port + 1)).unwrap_err();
744 }
745
746 #[test]
747 fn test_find_available_ports_in_range() {
748 let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
749 let port_range = sockets::localhost_port_range_for_tests();
750 assert!(port_range.1 - port_range.0 > 16);
751 let sock = sockets::bind_to_with_config(
753 ip_addr,
754 port_range.0 + 2,
755 sockets::SocketConfiguration::default(),
756 )
757 .unwrap();
758 let ports: [u16; 15] = find_available_ports_in_range(ip_addr, port_range).unwrap();
759 let mut ports_vec = Vec::from(ports);
760 ports_vec.push(sock.local_addr().unwrap().port());
761 let res: Vec<_> = ports_vec.into_iter().unique().collect();
762 assert_eq!(res.len(), 16, "Should reserve 16 unique ports");
763 }
764
765 #[allow(deprecated)]
766 #[test]
767 fn test_multi_bind_in_range_with_config_reuseport_disabled() {
768 let ip_addr: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
769 let config = SocketConfig::default(); let port_range = unique_port_range_for_tests(3);
772 let result =
773 multi_bind_in_range_with_config(ip_addr, (port_range.start, port_range.end), config, 2);
774
775 assert!(
776 result.is_err(),
777 "Expected an error when reuseport is not set to true"
778 );
779 }
780}