solana_net_utils/
lib.rs

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//! The `net_utils` module assists with networking
11
12// Activate some of the Rust 2024 lints to make the future migration easier.
13#![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
49/// A data type representing a public Udp socket
50pub struct UdpSocketPair {
51    pub addr: SocketAddr,    // Public address of the socket
52    pub receiver: UdpSocket, // Locally bound socket that can receive from the public address
53    pub sender: UdpSocket,   // Locally bound socket to send via public address
54}
55
56pub type PortRange = (u16, u16);
57
58#[cfg(not(debug_assertions))]
59/// Port range available to validator by default
60pub const VALIDATOR_PORT_RANGE: PortRange = (8000, 10_000);
61
62// Sets the port range outside of the region used by other tests to avoid interference
63// This arrangement is not ideal, but can be removed once ConnectionCache is deprecated
64#[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; // VALIDATOR_PORT_RANGE must be at least this wide
71
72pub(crate) const HEADER_LENGTH: usize = 4;
73pub(crate) const IP_ECHO_SERVER_RESPONSE_LENGTH: usize = HEADER_LENGTH + 23;
74
75/// Determine the public IP address of this machine by asking an ip_echo_server at the given
76/// address.
77#[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
94/// Determine the public IP address of this machine by asking an ip_echo_server at the given
95/// address. This function will bind to the provided bind_addreess.
96pub 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
112/// Retrieves cluster shred version from Entrypoint address provided.
113pub 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
124/// Retrieves cluster shred version from Entrypoint address provided,
125/// binds client-side socket to the IP provided.
126pub 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
143// Limit the maximum number of port verify threads to something reasonable
144// in case the port ranges provided are very large.
145const MAX_PORT_VERIFY_THREADS: usize = 64;
146
147/// Checks if all of the provided UDP ports are reachable by the machine at
148/// `ip_echo_server_addr`. Tests must complete within timeout provided.
149/// Tests will run concurrently when possible, using up to 64 threads for IO.
150/// This function assumes that all sockets are bound to the same IP, and will panic otherwise
151pub 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
169/// Checks if all of the provided TCP ports are reachable by the machine at
170/// `ip_echo_server_addr`. Tests must complete within timeout provided.
171/// Tests will run concurrently when possible, using up to 64 threads for IO.
172/// This function assumes that all sockets are bound to the same IP, and will panic otherwise.
173pub 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    // First, check if the host syntax is valid. This check is needed because addresses
228    // such as `("localhost:1234", 0)` will resolve to IPs on some networks.
229    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    // Next, check to see if it resolves to an IP address
235    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    /// Sets the receive buffer size for the socket (no effect on windows/ios).
286    ///
287    /// **Note:** On Linux the kernel will double the value you specify.
288    /// For example, if you specify `16MB`, the kernel will configure the
289    /// socket to use `32MB`.
290    /// See: https://man7.org/linux/man-pages/man7/socket.7.html: SO_RCVBUF
291    pub fn recv_buffer_size(mut self, size: usize) -> Self {
292        self.recv_buffer_size = Some(size);
293        self
294    }
295
296    /// Sets the send buffer size for the socket (no effect on windows/ios)
297    ///
298    /// **Note:** On Linux the kernel will double the value you specify.
299    /// For example, if you specify `16MB`, the kernel will configure the
300    /// socket to use `32MB`.
301    /// See: https://man7.org/linux/man-pages/man7/socket.7.html: SO_SNDBUF
302    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)]
313/// Find a port in the given range with a socket config that is available for both TCP and UDP
314pub 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)]
384/// binds num sockets to the same port in a range with config
385pub 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        // See https://github.com/solana-labs/solana/issues/4607
393        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)]
465/// binds both a UdpSocket and a TcpListener
466pub 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)]
479/// binds both a UdpSocket and a TcpListener on the same port
480pub 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
555/// Searches for an open port on a given binding ip_addr in the provided range.
556///
557/// This will start at a random point in the range provided, and search sequenctially.
558/// If it can not find anything, an Error is returned.
559///
560/// Keep in mind this will not reserve the port for you, only find one that is empty.
561pub 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
566/// Searches for several ports on a given binding ip_addr in the provided range.
567///
568/// This will start at a random point in the range provided, and search sequentially.
569/// If it can not find anything, an Error is returned.
570pub 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() // loop over the end of the range
579        .skip(thread_rng().gen_range(range.clone()) as usize) // skip to random position
580        .take(range.len()) // never take the same value twice
581        .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(); // this unwrap never fails since we exit earlier
586        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]), // IPv6 variant
640            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    // Asserts that an old client can parse the response from a new server.
650    #[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    // Asserts that a new client can parse the response from an old server.
669    #[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        // reserve 1 port to make it non-trivial
752        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(); //reuseport is false by default
770
771        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}