Skip to main content

solana_net_utils/
lib.rs

1#![cfg(feature = "agave-unstable-api")]
2//! The `net_utils` module assists with networking
3
4pub mod banlist;
5mod ip_echo_client;
6mod ip_echo_server;
7pub mod multihomed_sockets;
8pub mod pinned_xdp_sender;
9pub mod quic_socket;
10pub mod socket_addr_space;
11pub mod sockets;
12#[cfg(any(target_os = "android", target_os = "windows"))]
13#[path = "test_port_allocator_legacy.rs"]
14pub(crate) mod test_port_allocator;
15#[cfg(not(any(target_os = "android", target_os = "windows")))]
16pub(crate) mod test_port_allocator;
17pub mod token_bucket;
18
19#[cfg(feature = "dev-context-only-utils")]
20pub mod tooling_for_tests;
21
22pub use {
23    agave_xdp::transmitter::TrySendError,
24    ip_echo_client::IpEchoClientError,
25    ip_echo_server::{
26        DEFAULT_IP_ECHO_SERVER_THREADS, IpEchoServer, MAX_PORT_COUNT_PER_MESSAGE, ip_echo_server,
27    },
28    pinned_xdp_sender::PinnedXdpSender,
29    socket_addr_space::SocketAddrSpace,
30};
31use {
32    ip_echo_client::{ip_echo_server_request, ip_echo_server_request_with_binding},
33    ip_echo_server::IpEchoServerMessage,
34    rand::{Rng, rng},
35    std::{
36        io::{self},
37        net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, ToSocketAddrs, UdpSocket},
38    },
39    url::Url,
40};
41
42/// A data type representing a public Udp socket
43pub struct UdpSocketPair {
44    pub addr: SocketAddr,    // Public address of the socket
45    pub receiver: UdpSocket, // Locally bound socket that can receive from the public address
46    pub sender: UdpSocket,   // Locally bound socket to send via public address
47}
48
49pub type PortRange = (u16, u16);
50
51#[cfg(not(debug_assertions))]
52/// Port range available to validator by default
53pub const VALIDATOR_PORT_RANGE: PortRange = (8000, 10_000);
54
55// Sets the port range outside of the region used by other tests to avoid interference
56// This arrangement is not ideal, but can be removed once ConnectionCache is deprecated
57#[cfg(debug_assertions)]
58pub const VALIDATOR_PORT_RANGE: PortRange = (
59    crate::sockets::UNIQUE_ALLOC_BASE_PORT - 512,
60    crate::sockets::UNIQUE_ALLOC_BASE_PORT,
61);
62
63pub const MINIMUM_VALIDATOR_PORT_RANGE_WIDTH: u16 = 26; // VALIDATOR_PORT_RANGE must be at least this wide
64
65/// Transport protocol used to reach a peer socket.
66#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
67pub enum Protocol {
68    UDP,
69    QUIC,
70}
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. This function will bind to the provided bind_addreess.
77pub fn get_public_ip_addr_with_binding(
78    ip_echo_server_addr: &SocketAddr,
79    bind_address: IpAddr,
80) -> Result<IpAddr, IpEchoClientError> {
81    let fut = ip_echo_server_request_with_binding(
82        *ip_echo_server_addr,
83        IpEchoServerMessage::default(),
84        bind_address,
85    );
86    let rt = tokio::runtime::Builder::new_current_thread()
87        .enable_all()
88        .build()?;
89    let resp = rt.block_on(fut)?;
90    Ok(resp.address)
91}
92
93/// Retrieves cluster shred version from Entrypoint address provided.
94pub fn get_cluster_shred_version(ip_echo_server_addr: &SocketAddr) -> Result<u16, String> {
95    let fut = ip_echo_server_request(*ip_echo_server_addr, IpEchoServerMessage::default());
96    let rt = tokio::runtime::Builder::new_current_thread()
97        .enable_all()
98        .build()
99        .map_err(|e| e.to_string())?;
100    let resp = rt.block_on(fut).map_err(|e| e.to_string())?;
101    resp.shred_version
102        .ok_or_else(|| "IP echo server does not return a shred-version".to_owned())
103}
104
105/// Retrieves cluster shred version from Entrypoint address provided,
106/// binds client-side socket to the IP provided.
107pub fn get_cluster_shred_version_with_binding(
108    ip_echo_server_addr: &SocketAddr,
109    bind_address: IpAddr,
110) -> Result<u16, IpEchoClientError> {
111    let fut = ip_echo_server_request_with_binding(
112        *ip_echo_server_addr,
113        IpEchoServerMessage::default(),
114        bind_address,
115    );
116    let rt = tokio::runtime::Builder::new_current_thread()
117        .enable_all()
118        .build()?;
119    let resp = rt.block_on(fut)?;
120    resp.shred_version.ok_or_else(|| {
121        IpEchoClientError::InvalidResponse(
122            "IP echo server does not return a shred-version".to_owned(),
123        )
124    })
125}
126
127// Limit the maximum number of port verify threads to something reasonable
128// in case the port ranges provided are very large.
129const MAX_PORT_VERIFY_THREADS: usize = 64;
130
131/// Checks if all of the provided UDP ports are reachable by the machine at
132/// `ip_echo_server_addr`. Tests must complete within timeout provided.
133/// Tests will run concurrently when possible, using up to 64 threads for IO.
134/// This function assumes that all sockets are bound to the same IP, and will panic otherwise
135pub fn verify_all_reachable_udp(
136    ip_echo_server_addr: &SocketAddr,
137    udp_sockets: &[&UdpSocket],
138) -> bool {
139    let rt = tokio::runtime::Builder::new_current_thread()
140        .enable_all()
141        .max_blocking_threads(MAX_PORT_VERIFY_THREADS)
142        .build()
143        .expect("Tokio builder should be able to reliably create a current thread runtime");
144    let fut = ip_echo_client::verify_all_reachable_udp(
145        *ip_echo_server_addr,
146        udp_sockets,
147        ip_echo_client::TIMEOUT,
148        ip_echo_client::DEFAULT_RETRY_COUNT,
149    );
150    rt.block_on(fut)
151}
152
153/// Checks if all of the provided TCP ports are reachable by the machine at
154/// `ip_echo_server_addr`. Tests must complete within timeout provided.
155/// Tests will run concurrently when possible, using up to 64 threads for IO.
156/// This function assumes that all sockets are bound to the same IP, and will panic otherwise.
157pub fn verify_all_reachable_tcp(
158    ip_echo_server_addr: &SocketAddr,
159    tcp_listeners: Vec<TcpListener>,
160) -> bool {
161    let rt = tokio::runtime::Builder::new_current_thread()
162        .enable_all()
163        .max_blocking_threads(MAX_PORT_VERIFY_THREADS)
164        .build()
165        .expect("Tokio builder should be able to reliably create a current thread runtime");
166    let fut = ip_echo_client::verify_all_reachable_tcp(
167        *ip_echo_server_addr,
168        tcp_listeners,
169        ip_echo_client::TIMEOUT,
170    );
171    rt.block_on(fut)
172}
173
174pub fn parse_port_or_addr(optstr: Option<&str>, default_addr: SocketAddr) -> SocketAddr {
175    if let Some(addrstr) = optstr {
176        if let Ok(port) = addrstr.parse() {
177            let mut addr = default_addr;
178            addr.set_port(port);
179            addr
180        } else if let Ok(addr) = addrstr.parse() {
181            addr
182        } else {
183            default_addr
184        }
185    } else {
186        default_addr
187    }
188}
189
190pub fn parse_port_range(port_range: &str) -> Option<PortRange> {
191    let ports: Vec<&str> = port_range.split('-').collect();
192    if ports.len() != 2 {
193        return None;
194    }
195
196    let start_port = ports[0].parse();
197    let end_port = ports[1].parse();
198
199    if start_port.is_err() || end_port.is_err() {
200        return None;
201    }
202    let start_port = start_port.unwrap();
203    let end_port = end_port.unwrap();
204    if end_port < start_port {
205        return None;
206    }
207    Some((start_port, end_port))
208}
209
210fn select_ipv4<T>(
211    host: &str,
212    mut values: impl Iterator<Item = T>,
213    mut ip_addr: impl FnMut(&T) -> IpAddr,
214) -> Result<T, String> {
215    let Some(first_value) = values.next() else {
216        return Err(format!("Unable to resolve host: {host}"));
217    };
218
219    if ip_addr(&first_value).is_ipv4() {
220        return Ok(first_value);
221    }
222
223    values
224        .find(|value| ip_addr(value).is_ipv4())
225        .ok_or_else(|| format!("IPv6 addresses are not supported: {host}"))
226}
227
228pub fn parse_host(host: &str) -> Result<IpAddr, String> {
229    if let Ok(IpAddr::V6(_)) = host.parse::<IpAddr>() {
230        return Err(format!("IPv6 addresses are not supported: {host}"));
231    }
232
233    // First, check if the host syntax is valid. This check is needed because addresses
234    // such as `("localhost:1234", 0)` will resolve to IPs on some networks.
235    let parsed_url = Url::parse(&format!("http://{host}")).map_err(|e| e.to_string())?;
236    if parsed_url.port().is_some() {
237        return Err(format!("Expected port in URL: {host}"));
238    }
239
240    // Next, check to see if it resolves to an IPv4 address
241    let ips = (host, 0)
242        .to_socket_addrs()
243        .map_err(|err| err.to_string())?
244        .map(|socket_address| socket_address.ip());
245
246    select_ipv4(host, ips, |ip| *ip)
247}
248
249pub fn is_host(string: String) -> Result<(), String> {
250    parse_host(&string).map(|_| ())
251}
252
253pub fn parse_host_port(host_port: &str) -> Result<SocketAddr, String> {
254    let addrs = host_port
255        .to_socket_addrs()
256        .map_err(|err| format!("Unable to resolve host {host_port}: {err}"))?;
257    select_ipv4(host_port, addrs, SocketAddr::ip)
258}
259
260pub fn is_host_port(string: String) -> Result<(), String> {
261    parse_host_port(&string).map(|_| ())
262}
263
264pub fn bind_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<(u16, UdpSocket)> {
265    let config = sockets::SocketConfiguration::default();
266    sockets::bind_in_range_with_config(ip_addr, range, config)
267}
268
269pub fn bind_to_unspecified() -> io::Result<UdpSocket> {
270    let config = sockets::SocketConfiguration::default();
271    sockets::bind_to_with_config(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0, config)
272}
273
274/// Searches for an open port on a given binding ip_addr in the provided range.
275///
276/// This will start at a random point in the range provided, and search sequenctially.
277/// If it can not find anything, an Error is returned.
278///
279/// Keep in mind this will not reserve the port for you, only find one that is empty.
280pub fn find_available_port_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<u16> {
281    let [port] = find_available_ports_in_range(ip_addr, range)?;
282    Ok(port)
283}
284
285/// Searches for several ports on a given binding ip_addr in the provided range.
286///
287/// This will start at a random point in the range provided, and search sequentially.
288/// If it can not find anything, an Error is returned.
289pub fn find_available_ports_in_range<const N: usize>(
290    ip_addr: IpAddr,
291    range: PortRange,
292) -> io::Result<[u16; N]> {
293    let mut result = [0u16; N];
294    let range = range.0..range.1;
295    let mut next_port_to_try = range
296        .clone()
297        .cycle() // loop over the end of the range
298        .skip(rng().random_range(range.clone()) as usize) // skip to random position
299        .take(range.len()) // never take the same value twice
300        .peekable();
301    let mut num = 0;
302    let config = sockets::SocketConfiguration::default();
303    while num < N {
304        let port_to_try = next_port_to_try.next().unwrap(); // this unwrap never fails since we exit earlier
305        let bind = sockets::bind_common_with_config(ip_addr, port_to_try, config);
306        match bind {
307            Ok(_) => {
308                result[num] = port_to_try;
309                num = num.saturating_add(1);
310            }
311            Err(err) => {
312                if next_port_to_try.peek().is_none() {
313                    return Err(err);
314                }
315            }
316        }
317    }
318    Ok(result)
319}
320
321#[cfg(test)]
322mod tests {
323    use {
324        super::*, ip_echo_server::IpEchoServerResponse, itertools::Itertools, std::net::Ipv4Addr,
325    };
326
327    #[test]
328    fn test_response_length() {
329        let resp = IpEchoServerResponse {
330            address: IpAddr::from([u16::MAX; 8]), // IPv6 variant
331            shred_version: Some(u16::MAX),
332        };
333        let resp_size = bincode::serialized_size(&resp).unwrap();
334        assert_eq!(
335            IP_ECHO_SERVER_RESPONSE_LENGTH,
336            HEADER_LENGTH + resp_size as usize
337        );
338    }
339
340    // Asserts that an old client can parse the response from a new server.
341    #[test]
342    fn test_backward_compat() {
343        let address = IpAddr::from([
344            525u16, 524u16, 523u16, 522u16, 521u16, 520u16, 519u16, 518u16,
345        ]);
346        let response = IpEchoServerResponse {
347            address,
348            shred_version: Some(42),
349        };
350        let mut data = vec![0u8; IP_ECHO_SERVER_RESPONSE_LENGTH];
351        bincode::serialize_into(&mut data[HEADER_LENGTH..], &response).unwrap();
352        data.truncate(HEADER_LENGTH + 20);
353        assert_eq!(
354            bincode::deserialize::<IpAddr>(&data[HEADER_LENGTH..]).unwrap(),
355            address
356        );
357    }
358
359    // Asserts that a new client can parse the response from an old server.
360    #[test]
361    fn test_forward_compat() {
362        let address = IpAddr::from([
363            525u16, 524u16, 523u16, 522u16, 521u16, 520u16, 519u16, 518u16,
364        ]);
365        let mut data = [0u8; IP_ECHO_SERVER_RESPONSE_LENGTH];
366        bincode::serialize_into(&mut data[HEADER_LENGTH..], &address).unwrap();
367        let response: Result<IpEchoServerResponse, _> =
368            bincode::deserialize(&data[HEADER_LENGTH..]);
369        assert_eq!(
370            response.unwrap(),
371            IpEchoServerResponse {
372                address,
373                shred_version: None,
374            }
375        );
376    }
377
378    #[test]
379    fn test_parse_port_or_addr() {
380        let p1 = parse_port_or_addr(Some("9000"), SocketAddr::from(([1, 2, 3, 4], 1)));
381        assert_eq!(p1.port(), 9000);
382        let p2 = parse_port_or_addr(Some("127.0.0.1:7000"), SocketAddr::from(([1, 2, 3, 4], 1)));
383        assert_eq!(p2.port(), 7000);
384        let p2 = parse_port_or_addr(Some("hi there"), SocketAddr::from(([1, 2, 3, 4], 1)));
385        assert_eq!(p2.port(), 1);
386        let p3 = parse_port_or_addr(None, SocketAddr::from(([1, 2, 3, 4], 1)));
387        assert_eq!(p3.port(), 1);
388    }
389
390    #[test]
391    fn test_parse_port_range() {
392        assert_eq!(parse_port_range("garbage"), None);
393        assert_eq!(parse_port_range("1-"), None);
394        assert_eq!(parse_port_range("1-2"), Some((1, 2)));
395        assert_eq!(parse_port_range("1-2-3"), None);
396        assert_eq!(parse_port_range("2-1"), None);
397    }
398
399    #[test]
400    fn test_parse_host() {
401        parse_host("localhost:1234").unwrap_err();
402        parse_host("localhost").unwrap();
403        parse_host("127.0.0.0:1234").unwrap_err();
404        parse_host("127.0.0.0").unwrap();
405        parse_host("2001:db8:abcd:42::dead:beef").unwrap_err();
406
407        assert_eq!(
408            select_ipv4(
409                "ipv6-only.test",
410                [IpAddr::V6(std::net::Ipv6Addr::LOCALHOST)].into_iter(),
411                |ip| *ip,
412            )
413            .unwrap_err(),
414            "IPv6 addresses are not supported: ipv6-only.test",
415        );
416        assert_eq!(
417            select_ipv4(
418                "dual-stack.test",
419                [
420                    IpAddr::V6(std::net::Ipv6Addr::LOCALHOST),
421                    IpAddr::V4(Ipv4Addr::LOCALHOST),
422                ]
423                .into_iter(),
424                |ip| *ip,
425            )
426            .unwrap(),
427            IpAddr::V4(Ipv4Addr::LOCALHOST),
428        );
429    }
430
431    #[test]
432    fn test_parse_host_port() {
433        parse_host_port("localhost:1234").unwrap();
434        parse_host_port("localhost").unwrap_err();
435        parse_host_port("127.0.0.0:1234").unwrap();
436        parse_host_port("127.0.0.0").unwrap_err();
437        assert_eq!(
438            parse_host_port("[2001:db8:abcd:42::dead:beef]:1234").unwrap_err(),
439            "IPv6 addresses are not supported: [2001:db8:abcd:42::dead:beef]:1234",
440        );
441    }
442
443    #[test]
444    fn test_is_host_port() {
445        assert!(is_host_port("localhost:1234".to_string()).is_ok());
446        assert!(is_host_port("localhost".to_string()).is_err());
447    }
448
449    #[test]
450    fn test_find_available_port_in_range() {
451        let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
452        let range = sockets::unique_port_range_for_tests(4);
453        let (pr_s, pr_e) = (range.start, range.end);
454        assert_eq!(
455            find_available_port_in_range(ip_addr, (pr_s, pr_s + 1)).unwrap(),
456            pr_s
457        );
458        let port = find_available_port_in_range(ip_addr, (pr_s, pr_e)).unwrap();
459        assert!((pr_s..pr_e).contains(&port));
460
461        let _socket = sockets::bind_to(ip_addr, port).unwrap();
462        find_available_port_in_range(ip_addr, (port, port + 1)).unwrap_err();
463    }
464
465    #[test]
466    fn test_find_available_ports_in_range() {
467        let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
468        let port_range = sockets::localhost_port_range_for_tests();
469        assert!(port_range.1 - port_range.0 > 16);
470        // reserve 1 port to make it non-trivial
471        let sock = sockets::bind_to_with_config(
472            ip_addr,
473            port_range.0 + 2,
474            sockets::SocketConfiguration::default(),
475        )
476        .unwrap();
477        let ports: [u16; 15] = find_available_ports_in_range(ip_addr, port_range).unwrap();
478        let mut ports_vec = Vec::from(ports);
479        ports_vec.push(sock.local_addr().unwrap().port());
480        let res: Vec<_> = ports_vec.into_iter().unique().collect();
481        assert_eq!(res.len(), 16, "Should reserve 16 unique ports");
482    }
483}