Browse Source

net-utils: Deprecate explicit reuseport (#6639)

* deprecate reuseport flag

* fix tests and common usage of reuseport functions

* clean up external users of .reuseport()
Alex Pyattaev 5 months ago
parent
commit
0cdebbcff8

+ 6 - 3
bench-streamer/src/main.rs

@@ -3,7 +3,10 @@
 use {
 use {
     clap::{crate_description, crate_name, value_t_or_exit, Arg, Command},
     clap::{crate_description, crate_name, value_t_or_exit, Arg, Command},
     crossbeam_channel::unbounded,
     crossbeam_channel::unbounded,
-    solana_net_utils::{bind_to_unspecified, SocketConfig},
+    solana_net_utils::{
+        bind_to_unspecified,
+        sockets::{multi_bind_in_range_with_config, SocketConfiguration},
+    },
     solana_streamer::{
     solana_streamer::{
         packet::{Packet, PacketBatchRecycler, PinnedPacketBatch, PACKET_DATA_SIZE},
         packet::{Packet, PacketBatchRecycler, PinnedPacketBatch, PACKET_DATA_SIZE},
         sendmmsg::batch_send,
         sendmmsg::batch_send,
@@ -104,10 +107,10 @@ fn main() -> Result<()> {
 
 
     let port = 0;
     let port = 0;
     let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
     let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
-    let (_port, read_sockets) = solana_net_utils::multi_bind_in_range_with_config(
+    let (_port, read_sockets) = multi_bind_in_range_with_config(
         ip_addr,
         ip_addr,
         (port, port + num_sockets as u16),
         (port, port + num_sockets as u16),
-        SocketConfig::default().reuseport(true),
+        SocketConfiguration::default(),
         num_sockets,
         num_sockets,
     )
     )
     .unwrap();
     .unwrap();

+ 6 - 3
bench-vote/src/main.rs

@@ -9,7 +9,10 @@ use {
     solana_hash::Hash,
     solana_hash::Hash,
     solana_keypair::Keypair,
     solana_keypair::Keypair,
     solana_message::Message,
     solana_message::Message,
-    solana_net_utils::{bind_to_unspecified, SocketConfig},
+    solana_net_utils::{
+        bind_to_unspecified,
+        sockets::{multi_bind_in_range_with_config, SocketConfiguration as SocketConfig},
+    },
     solana_pubkey::Pubkey,
     solana_pubkey::Pubkey,
     solana_signer::Signer,
     solana_signer::Signer,
     solana_streamer::{
     solana_streamer::{
@@ -187,8 +190,8 @@ fn main() -> Result<()> {
         let mut read_channels = Vec::new();
         let mut read_channels = Vec::new();
         let mut read_threads = Vec::new();
         let mut read_threads = Vec::new();
         let recycler = PacketBatchRecycler::default();
         let recycler = PacketBatchRecycler::default();
-        let config = SocketConfig::default().reuseport(true);
-        let (port, read_sockets) = solana_net_utils::multi_bind_in_range_with_config(
+        let config = SocketConfig::default();
+        let (port, read_sockets) = multi_bind_in_range_with_config(
             ip_addr,
             ip_addr,
             (port, port + num_sockets as u16),
             (port, port + num_sockets as u16),
             config,
             config,

+ 5 - 3
connection-cache/src/connection_cache.rs

@@ -514,7 +514,9 @@ mod tests {
         async_trait::async_trait,
         async_trait::async_trait,
         rand::{Rng, SeedableRng},
         rand::{Rng, SeedableRng},
         rand_chacha::ChaChaRng,
         rand_chacha::ChaChaRng,
-        solana_net_utils::SocketConfig,
+        solana_net_utils::sockets::{
+            bind_with_any_port_with_config, SocketConfiguration as SocketConfig,
+        },
         solana_transaction_error::TransportResult,
         solana_transaction_error::TransportResult,
         std::{
         std::{
             net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
             net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
@@ -572,7 +574,7 @@ mod tests {
         fn default() -> Self {
         fn default() -> Self {
             Self {
             Self {
                 udp_socket: Arc::new(
                 udp_socket: Arc::new(
-                    solana_net_utils::bind_with_any_port_with_config(
+                    bind_with_any_port_with_config(
                         IpAddr::V4(Ipv4Addr::UNSPECIFIED),
                         IpAddr::V4(Ipv4Addr::UNSPECIFIED),
                         SocketConfig::default(),
                         SocketConfig::default(),
                     )
                     )
@@ -586,7 +588,7 @@ mod tests {
         fn new() -> Result<Self, ClientError> {
         fn new() -> Result<Self, ClientError> {
             Ok(Self {
             Ok(Self {
                 udp_socket: Arc::new(
                 udp_socket: Arc::new(
-                    solana_net_utils::bind_with_any_port_with_config(
+                    bind_with_any_port_with_config(
                         IpAddr::V4(Ipv4Addr::UNSPECIFIED),
                         IpAddr::V4(Ipv4Addr::UNSPECIFIED),
                         SocketConfig::default(),
                         SocketConfig::default(),
                     )
                     )

+ 2 - 2
docs/src/validator/tvu.md

@@ -23,13 +23,13 @@ Internally, TVU is actually bound with multiple sockets to improve kernel's hand
 
 
 > **NOTE:** TPU sockets use similar logic
 > **NOTE:** TPU sockets use similar logic
 
 
-A node advertises one external ip/port for TVU while binding multiple sockets to that same port using SO_REUSEPORT:
+A node advertises one external ip/port for TVU while binding multiple sockets to that same port:
 
 
 ```rust
 ```rust
 let (tvu_port, tvu_sockets) = multi_bind_in_range_with_config(
 let (tvu_port, tvu_sockets) = multi_bind_in_range_with_config(
     bind_ip_addr,
     bind_ip_addr,
     port_range,
     port_range,
-    socket_config_reuseport,
+    socket_config,
     num_tvu_sockets.get(),
     num_tvu_sockets.get(),
 )
 )
 .expect("tvu multi_bind");
 .expect("tvu multi_bind");

+ 31 - 47
gossip/src/cluster_info.rs

@@ -52,12 +52,14 @@ use {
     solana_keypair::{signable::Signable, Keypair},
     solana_keypair::{signable::Signable, Keypair},
     solana_ledger::shred::Shred,
     solana_ledger::shred::Shred,
     solana_net_utils::{
     solana_net_utils::{
-        bind_common_in_range_with_config, bind_in_range, bind_in_range_with_config,
-        bind_more_with_config, bind_to_localhost, bind_to_unspecified, bind_to_with_config,
-        bind_two_in_range_with_offset_and_config, find_available_ports_in_range,
-        multi_bind_in_range_with_config,
-        sockets::{bind_gossip_port_in_range, localhost_port_range_for_tests},
-        PortRange, SocketConfig, VALIDATOR_PORT_RANGE,
+        bind_in_range, bind_to_localhost, bind_to_unspecified, find_available_ports_in_range,
+        sockets::{
+            bind_common_in_range_with_config, bind_gossip_port_in_range, bind_in_range_with_config,
+            bind_more_with_config, bind_to_with_config, bind_two_in_range_with_offset_and_config,
+            localhost_port_range_for_tests, multi_bind_in_range_with_config,
+            SocketConfiguration as SocketConfig,
+        },
+        PortRange, VALIDATOR_PORT_RANGE,
     },
     },
     solana_perf::{
     solana_perf::{
         data_budget::DataBudget,
         data_budget::DataBudget,
@@ -2372,7 +2374,7 @@ impl Node {
         let localhost_ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
         let localhost_ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
         let port_range = localhost_port_range_for_tests();
         let port_range = localhost_port_range_for_tests();
         let udp_config = SocketConfig::default();
         let udp_config = SocketConfig::default();
-        let quic_config = SocketConfig::default().reuseport(true);
+        let quic_config = SocketConfig::default();
         let ((_tpu_port, tpu), (_tpu_quic_port, tpu_quic)) =
         let ((_tpu_port, tpu), (_tpu_quic_port, tpu_quic)) =
             bind_two_in_range_with_offset_and_config(
             bind_two_in_range_with_offset_and_config(
                 localhost_ip_addr,
                 localhost_ip_addr,
@@ -2524,7 +2526,6 @@ impl Node {
             bind_gossip_port_in_range(gossip_addr, port_range, bind_ip_addr);
             bind_gossip_port_in_range(gossip_addr, port_range, bind_ip_addr);
 
 
         let socket_config = SocketConfig::default();
         let socket_config = SocketConfig::default();
-        let socket_config_reuseport = SocketConfig::default().reuseport(true);
         let (tvu_port, tvu) = Self::bind_with_config(bind_ip_addr, port_range, socket_config);
         let (tvu_port, tvu) = Self::bind_with_config(bind_ip_addr, port_range, socket_config);
         let (tvu_quic_port, tvu_quic) =
         let (tvu_quic_port, tvu_quic) =
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
@@ -2534,12 +2535,11 @@ impl Node {
                 port_range,
                 port_range,
                 QUIC_PORT_OFFSET,
                 QUIC_PORT_OFFSET,
                 socket_config,
                 socket_config,
-                socket_config_reuseport,
+                socket_config,
             )
             )
             .unwrap();
             .unwrap();
         let tpu_quic: Vec<UdpSocket> =
         let tpu_quic: Vec<UdpSocket> =
-            bind_more_with_config(tpu_quic, DEFAULT_QUIC_ENDPOINTS, socket_config_reuseport)
-                .unwrap();
+            bind_more_with_config(tpu_quic, DEFAULT_QUIC_ENDPOINTS, socket_config).unwrap();
 
 
         let ((tpu_forwards_port, tpu_forwards), (_tpu_forwards_quic_port, tpu_forwards_quic)) =
         let ((tpu_forwards_port, tpu_forwards), (_tpu_forwards_quic_port, tpu_forwards_quic)) =
             bind_two_in_range_with_offset_and_config(
             bind_two_in_range_with_offset_and_config(
@@ -2547,26 +2547,19 @@ impl Node {
                 port_range,
                 port_range,
                 QUIC_PORT_OFFSET,
                 QUIC_PORT_OFFSET,
                 socket_config,
                 socket_config,
-                socket_config_reuseport,
+                socket_config,
             )
             )
             .unwrap();
             .unwrap();
-        let tpu_forwards_quic = bind_more_with_config(
-            tpu_forwards_quic,
-            DEFAULT_QUIC_ENDPOINTS,
-            socket_config_reuseport,
-        )
-        .unwrap();
+        let tpu_forwards_quic =
+            bind_more_with_config(tpu_forwards_quic, DEFAULT_QUIC_ENDPOINTS, socket_config)
+                .unwrap();
 
 
         let (tpu_vote_port, tpu_vote) =
         let (tpu_vote_port, tpu_vote) =
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
         let (tpu_vote_quic_port, tpu_vote_quic) =
         let (tpu_vote_quic_port, tpu_vote_quic) =
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
-        let tpu_vote_quic: Vec<UdpSocket> = bind_more_with_config(
-            tpu_vote_quic,
-            DEFAULT_QUIC_ENDPOINTS,
-            socket_config_reuseport,
-        )
-        .unwrap();
+        let tpu_vote_quic: Vec<UdpSocket> =
+            bind_more_with_config(tpu_vote_quic, DEFAULT_QUIC_ENDPOINTS, socket_config).unwrap();
 
 
         let (_, retransmit_socket) =
         let (_, retransmit_socket) =
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
@@ -2680,12 +2673,11 @@ impl Node {
             bind_gossip_port_in_range(&gossip_addr, port_range, bind_ip_addr);
             bind_gossip_port_in_range(&gossip_addr, port_range, bind_ip_addr);
 
 
         let socket_config = SocketConfig::default();
         let socket_config = SocketConfig::default();
-        let socket_config_reuseport = SocketConfig::default().reuseport(true);
 
 
         let (tvu_port, tvu_sockets) = multi_bind_in_range_with_config(
         let (tvu_port, tvu_sockets) = multi_bind_in_range_with_config(
             bind_ip_addr,
             bind_ip_addr,
             port_range,
             port_range,
-            socket_config_reuseport,
+            socket_config,
             num_tvu_receive_sockets.get(),
             num_tvu_receive_sockets.get(),
         )
         )
         .expect("tvu multi_bind");
         .expect("tvu multi_bind");
@@ -2694,20 +2686,19 @@ impl Node {
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
 
 
         let (tpu_port, tpu_sockets) =
         let (tpu_port, tpu_sockets) =
-            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config_reuseport, 32)
+            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config, 32)
                 .expect("tpu multi_bind");
                 .expect("tpu multi_bind");
 
 
         let (_tpu_port_quic, tpu_quic) = Self::bind_with_config(
         let (_tpu_port_quic, tpu_quic) = Self::bind_with_config(
             bind_ip_addr,
             bind_ip_addr,
             (tpu_port + QUIC_PORT_OFFSET, tpu_port + QUIC_PORT_OFFSET + 1),
             (tpu_port + QUIC_PORT_OFFSET, tpu_port + QUIC_PORT_OFFSET + 1),
-            socket_config_reuseport,
+            socket_config,
         );
         );
         let tpu_quic =
         let tpu_quic =
-            bind_more_with_config(tpu_quic, num_quic_endpoints.get(), socket_config_reuseport)
-                .unwrap();
+            bind_more_with_config(tpu_quic, num_quic_endpoints.get(), socket_config).unwrap();
 
 
         let (tpu_forwards_port, tpu_forwards_sockets) =
         let (tpu_forwards_port, tpu_forwards_sockets) =
-            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config_reuseport, 8)
+            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config, 8)
                 .expect("tpu_forwards multi_bind");
                 .expect("tpu_forwards multi_bind");
 
 
         let (_tpu_forwards_port_quic, tpu_forwards_quic) = Self::bind_with_config(
         let (_tpu_forwards_port_quic, tpu_forwards_quic) = Self::bind_with_config(
@@ -2716,33 +2707,26 @@ impl Node {
                 tpu_forwards_port + QUIC_PORT_OFFSET,
                 tpu_forwards_port + QUIC_PORT_OFFSET,
                 tpu_forwards_port + QUIC_PORT_OFFSET + 1,
                 tpu_forwards_port + QUIC_PORT_OFFSET + 1,
             ),
             ),
-            socket_config_reuseport,
+            socket_config,
         );
         );
-        let tpu_forwards_quic = bind_more_with_config(
-            tpu_forwards_quic,
-            num_quic_endpoints.get(),
-            socket_config_reuseport,
-        )
-        .unwrap();
+        let tpu_forwards_quic =
+            bind_more_with_config(tpu_forwards_quic, num_quic_endpoints.get(), socket_config)
+                .unwrap();
 
 
         let (tpu_vote_port, tpu_vote_sockets) =
         let (tpu_vote_port, tpu_vote_sockets) =
-            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config_reuseport, 1)
+            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config, 1)
                 .expect("tpu_vote multi_bind");
                 .expect("tpu_vote multi_bind");
 
 
         let (tpu_vote_quic_port, tpu_vote_quic) =
         let (tpu_vote_quic_port, tpu_vote_quic) =
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
 
 
-        let tpu_vote_quic = bind_more_with_config(
-            tpu_vote_quic,
-            num_quic_endpoints.get(),
-            socket_config_reuseport,
-        )
-        .unwrap();
+        let tpu_vote_quic =
+            bind_more_with_config(tpu_vote_quic, num_quic_endpoints.get(), socket_config).unwrap();
 
 
         let (_, retransmit_sockets) = multi_bind_in_range_with_config(
         let (_, retransmit_sockets) = multi_bind_in_range_with_config(
             bind_ip_addr,
             bind_ip_addr,
             port_range,
             port_range,
-            socket_config_reuseport,
+            socket_config,
             num_tvu_retransmit_sockets.get(),
             num_tvu_retransmit_sockets.get(),
         )
         )
         .expect("retransmit multi_bind");
         .expect("retransmit multi_bind");
@@ -2756,7 +2740,7 @@ impl Node {
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
             Self::bind_with_config(bind_ip_addr, port_range, socket_config);
 
 
         let (_, broadcast) =
         let (_, broadcast) =
-            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config_reuseport, 4)
+            multi_bind_in_range_with_config(bind_ip_addr, port_range, socket_config, 4)
                 .expect("broadcast multi_bind");
                 .expect("broadcast multi_bind");
 
 
         let (_, ancestor_hashes_requests) =
         let (_, ancestor_hashes_requests) =

+ 119 - 140
net-utils/src/lib.rs

@@ -10,14 +10,13 @@ pub use ip_echo_server::{
     ip_echo_server, IpEchoServer, DEFAULT_IP_ECHO_SERVER_THREADS, MAX_PORT_COUNT_PER_MESSAGE,
     ip_echo_server, IpEchoServer, DEFAULT_IP_ECHO_SERVER_THREADS, MAX_PORT_COUNT_PER_MESSAGE,
     MINIMUM_IP_ECHO_SERVER_THREADS,
     MINIMUM_IP_ECHO_SERVER_THREADS,
 };
 };
-#[cfg(feature = "dev-context-only-utils")]
-use tokio::net::UdpSocket as TokioUdpSocket;
 use {
 use {
+    crate::sockets::{udp_socket_with_config, PLATFORM_SUPPORTS_SOCKET_CONFIGS},
     ip_echo_client::{ip_echo_server_request, ip_echo_server_request_with_binding},
     ip_echo_client::{ip_echo_server_request, ip_echo_server_request_with_binding},
     ip_echo_server::IpEchoServerMessage,
     ip_echo_server::IpEchoServerMessage,
     log::*,
     log::*,
     rand::{thread_rng, Rng},
     rand::{thread_rng, Rng},
-    socket2::{Domain, SockAddr, Socket, Type},
+    socket2::SockAddr,
     std::{
     std::{
         io::{self},
         io::{self},
         net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, ToSocketAddrs, UdpSocket},
         net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener, ToSocketAddrs, UdpSocket},
@@ -225,6 +224,10 @@ pub fn is_host_port(string: String) -> Result<(), String> {
     parse_host_port(&string).map(|_| ())
     parse_host_port(&string).map(|_| ())
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please use the equivalent struct from solana-net-utils::sockets"
+)]
 #[derive(Clone, Copy, Debug, Default)]
 #[derive(Clone, Copy, Debug, Default)]
 pub struct SocketConfig {
 pub struct SocketConfig {
     reuseport: bool,
     reuseport: bool,
@@ -232,6 +235,7 @@ pub struct SocketConfig {
     send_buffer_size: Option<usize>,
     send_buffer_size: Option<usize>,
 }
 }
 
 
+#[allow(deprecated)]
 impl SocketConfig {
 impl SocketConfig {
     pub fn reuseport(mut self, reuseport: bool) -> Self {
     pub fn reuseport(mut self, reuseport: bool) -> Self {
         self.reuseport = reuseport;
         self.reuseport = reuseport;
@@ -261,40 +265,12 @@ impl SocketConfig {
     }
     }
 }
 }
 
 
-#[cfg(any(windows, target_os = "ios"))]
-fn udp_socket_with_config(_config: SocketConfig) -> io::Result<Socket> {
-    let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
-    Ok(sock)
-}
-
-#[cfg(not(any(windows, target_os = "ios")))]
-fn udp_socket_with_config(config: SocketConfig) -> io::Result<Socket> {
-    use nix::sys::socket::{setsockopt, sockopt::ReusePort};
-    let SocketConfig {
-        reuseport,
-        recv_buffer_size,
-        send_buffer_size,
-    } = config;
-
-    let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
-
-    // Set buffer sizes
-    if let Some(recv_buffer_size) = recv_buffer_size {
-        sock.set_recv_buffer_size(recv_buffer_size)?;
-    }
-
-    if let Some(send_buffer_size) = send_buffer_size {
-        sock.set_send_buffer_size(send_buffer_size)?;
-    }
-
-    if reuseport {
-        setsockopt(&sock, ReusePort, &true).ok();
-    }
-
-    Ok(sock)
-}
-
-// Find a port in the given range with a socket config that is available for both TCP and UDP
+#[deprecated(
+    since = "2.3.2",
+    note = "Please use the equivalent from solana-net-utils::sockets"
+)]
+#[allow(deprecated)]
+/// Find a port in the given range with a socket config that is available for both TCP and UDP
 pub fn bind_common_in_range_with_config(
 pub fn bind_common_in_range_with_config(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     range: PortRange,
     range: PortRange,
@@ -312,23 +288,28 @@ pub fn bind_common_in_range_with_config(
 }
 }
 
 
 pub fn bind_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<(u16, UdpSocket)> {
 pub fn bind_in_range(ip_addr: IpAddr, range: PortRange) -> io::Result<(u16, UdpSocket)> {
-    let config = SocketConfig::default();
-    bind_in_range_with_config(ip_addr, range, config)
+    let config = sockets::SocketConfiguration::default();
+    sockets::bind_in_range_with_config(ip_addr, range, config)
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please use the equivalent from solana-net-utils::sockets"
+)]
+#[allow(deprecated)]
 pub fn bind_in_range_with_config(
 pub fn bind_in_range_with_config(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     range: PortRange,
     range: PortRange,
     config: SocketConfig,
     config: SocketConfig,
 ) -> io::Result<(u16, UdpSocket)> {
 ) -> io::Result<(u16, UdpSocket)> {
-    let sock = udp_socket_with_config(config)?;
+    let socket = udp_socket_with_config(config.into())?;
 
 
     for port in range.0..range.1 {
     for port in range.0..range.1 {
         let addr = SocketAddr::new(ip_addr, port);
         let addr = SocketAddr::new(ip_addr, port);
 
 
-        if sock.bind(&SockAddr::from(addr)).is_ok() {
-            let sock: UdpSocket = sock.into();
-            return Result::Ok((sock.local_addr().unwrap().port(), sock));
+        if socket.bind(&SockAddr::from(addr)).is_ok() {
+            let udp_socket: UdpSocket = socket.into();
+            return Result::Ok((udp_socket.local_addr().unwrap().port(), udp_socket));
         }
         }
     }
     }
 
 
@@ -337,11 +318,16 @@ pub fn bind_in_range_with_config(
     )))
     )))
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please use the equivalent from solana-net-utils::sockets"
+)]
+#[allow(deprecated)]
 pub fn bind_with_any_port_with_config(
 pub fn bind_with_any_port_with_config(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     config: SocketConfig,
     config: SocketConfig,
 ) -> io::Result<UdpSocket> {
 ) -> io::Result<UdpSocket> {
-    let sock = udp_socket_with_config(config)?;
+    let sock = udp_socket_with_config(config.into())?;
     let addr = SocketAddr::new(ip_addr, 0);
     let addr = SocketAddr::new(ip_addr, 0);
     match sock.bind(&SockAddr::from(addr)) {
     match sock.bind(&SockAddr::from(addr)) {
         Ok(_) => Result::Ok(sock.into()),
         Ok(_) => Result::Ok(sock.into()),
@@ -349,6 +335,11 @@ pub fn bind_with_any_port_with_config(
     }
     }
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please use the equivalent from solana-net-utils::sockets"
+)]
+#[allow(deprecated)]
 /// binds num sockets to the same port in a range with config
 /// binds num sockets to the same port in a range with config
 pub fn multi_bind_in_range_with_config(
 pub fn multi_bind_in_range_with_config(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
@@ -356,122 +347,68 @@ pub fn multi_bind_in_range_with_config(
     config: SocketConfig,
     config: SocketConfig,
     mut num: usize,
     mut num: usize,
 ) -> io::Result<(u16, Vec<UdpSocket>)> {
 ) -> io::Result<(u16, Vec<UdpSocket>)> {
-    if !config.reuseport {
-        return Err(io::Error::new(
-            io::ErrorKind::InvalidInput,
-            "SocketConfig.reuseport must be true for multi_bind_in_range_with_config",
-        ));
-    }
-    if cfg!(windows) && num != 1 {
+    if !PLATFORM_SUPPORTS_SOCKET_CONFIGS && num != 1 {
         // See https://github.com/solana-labs/solana/issues/4607
         // See https://github.com/solana-labs/solana/issues/4607
         warn!(
         warn!(
-            "multi_bind_in_range_with_config() only supports 1 socket in windows ({} requested)",
+            "multi_bind_in_range_with_config() only supports 1 socket on this platform ({} requested)",
             num
             num
         );
         );
         num = 1;
         num = 1;
     }
     }
-    let mut sockets = Vec::with_capacity(num);
-
-    const NUM_TRIES: usize = 100;
-    let mut port = 0;
-    let mut error = None;
-    for _ in 0..NUM_TRIES {
-        port = {
-            let (port, _) = bind_in_range(ip_addr, range)?;
-            port
-        }; // drop the probe, port should be available... briefly.
-
-        for _ in 0..num {
-            let sock = bind_to_with_config(ip_addr, port, config);
-            if let Ok(sock) = sock {
-                sockets.push(sock);
-            } else {
-                error = Some(sock);
-                break;
-            }
-        }
-        if sockets.len() == num {
-            break;
-        } else {
-            sockets.clear();
-        }
-    }
-    if sockets.len() != num {
-        error.unwrap()?;
-    }
+    let (port, socket) = bind_in_range_with_config(ip_addr, range, config)?;
+    let sockets = bind_more_with_config(socket, num, config)?;
     Ok((port, sockets))
     Ok((port, sockets))
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please use the eqiuvalent from solana-net-utils::sockets"
+)]
+#[allow(deprecated)]
 pub fn bind_to(ip_addr: IpAddr, port: u16, reuseport: bool) -> io::Result<UdpSocket> {
 pub fn bind_to(ip_addr: IpAddr, port: u16, reuseport: bool) -> io::Result<UdpSocket> {
-    let config = SocketConfig::default().reuseport(reuseport);
+    let config = SocketConfig {
+        reuseport,
+        ..Default::default()
+    };
     bind_to_with_config(ip_addr, port, config)
     bind_to_with_config(ip_addr, port, config)
 }
 }
 
 
-#[cfg(feature = "dev-context-only-utils")]
-pub async fn bind_to_async(
-    ip_addr: IpAddr,
-    port: u16,
-    reuseport: bool,
-) -> io::Result<TokioUdpSocket> {
-    let config = SocketConfig::default().reuseport(reuseport);
-    let socket = bind_to_with_config_non_blocking(ip_addr, port, config)?;
-    TokioUdpSocket::from_std(socket)
-}
-
 pub fn bind_to_localhost() -> io::Result<UdpSocket> {
 pub fn bind_to_localhost() -> io::Result<UdpSocket> {
-    bind_to(
-        IpAddr::V4(Ipv4Addr::LOCALHOST),
-        /*port:*/ 0,
-        /*reuseport:*/ false,
-    )
-}
-
-#[cfg(feature = "dev-context-only-utils")]
-pub async fn bind_to_localhost_async() -> io::Result<TokioUdpSocket> {
-    bind_to_async(
-        IpAddr::V4(Ipv4Addr::LOCALHOST),
-        /*port:*/ 0,
-        /*reuseport:*/ false,
-    )
-    .await
+    let config = sockets::SocketConfiguration::default();
+    sockets::bind_to_with_config(IpAddr::V4(Ipv4Addr::LOCALHOST), 0, config)
 }
 }
 
 
 pub fn bind_to_unspecified() -> io::Result<UdpSocket> {
 pub fn bind_to_unspecified() -> io::Result<UdpSocket> {
-    bind_to(
-        IpAddr::V4(Ipv4Addr::UNSPECIFIED),
-        /*port:*/ 0,
-        /*reuseport:*/ false,
-    )
-}
-
-#[cfg(feature = "dev-context-only-utils")]
-pub async fn bind_to_unspecified_async() -> io::Result<TokioUdpSocket> {
-    bind_to_async(
-        IpAddr::V4(Ipv4Addr::UNSPECIFIED),
-        /*port:*/ 0,
-        /*reuseport:*/ false,
-    )
-    .await
+    let config = sockets::SocketConfiguration::default();
+    sockets::bind_to_with_config(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0, config)
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please avoid this function in favor of sockets::bind_to_with_config"
+)]
+#[allow(deprecated)]
 pub fn bind_to_with_config(
 pub fn bind_to_with_config(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     port: u16,
     port: u16,
     config: SocketConfig,
     config: SocketConfig,
 ) -> io::Result<UdpSocket> {
 ) -> io::Result<UdpSocket> {
-    let sock = udp_socket_with_config(config)?;
-
+    let sock = udp_socket_with_config(config.into())?;
     let addr = SocketAddr::new(ip_addr, port);
     let addr = SocketAddr::new(ip_addr, port);
-
     sock.bind(&SockAddr::from(addr)).map(|_| sock.into())
     sock.bind(&SockAddr::from(addr)).map(|_| sock.into())
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please avoid this function, it is easy to misuse"
+)]
+#[allow(deprecated)]
 pub fn bind_to_with_config_non_blocking(
 pub fn bind_to_with_config_non_blocking(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     port: u16,
     port: u16,
     config: SocketConfig,
     config: SocketConfig,
 ) -> io::Result<UdpSocket> {
 ) -> io::Result<UdpSocket> {
-    let sock = udp_socket_with_config(config)?;
+    let sock = udp_socket_with_config(config.into())?;
 
 
     let addr = SocketAddr::new(ip_addr, port);
     let addr = SocketAddr::new(ip_addr, port);
 
 
@@ -480,19 +417,28 @@ pub fn bind_to_with_config_non_blocking(
     Ok(sock.into())
     Ok(sock.into())
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please avoid this function in favor of sockets::bind_common_with_config"
+)]
 /// binds both a UdpSocket and a TcpListener
 /// binds both a UdpSocket and a TcpListener
 pub fn bind_common(ip_addr: IpAddr, port: u16) -> io::Result<(UdpSocket, TcpListener)> {
 pub fn bind_common(ip_addr: IpAddr, port: u16) -> io::Result<(UdpSocket, TcpListener)> {
-    let config = SocketConfig::default();
-    bind_common_with_config(ip_addr, port, config)
+    let config = sockets::SocketConfiguration::default();
+    sockets::bind_common_with_config(ip_addr, port, config)
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please avoid this function in favor of sockets::bind_common_with_config"
+)]
+#[allow(deprecated)]
 /// binds both a UdpSocket and a TcpListener on the same port
 /// binds both a UdpSocket and a TcpListener on the same port
 pub fn bind_common_with_config(
 pub fn bind_common_with_config(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     port: u16,
     port: u16,
     config: SocketConfig,
     config: SocketConfig,
 ) -> io::Result<(UdpSocket, TcpListener)> {
 ) -> io::Result<(UdpSocket, TcpListener)> {
-    let sock = udp_socket_with_config(config)?;
+    let sock = udp_socket_with_config(config.into())?;
 
 
     let addr = SocketAddr::new(ip_addr, port);
     let addr = SocketAddr::new(ip_addr, port);
     let sock_addr = SockAddr::from(addr);
     let sock_addr = SockAddr::from(addr);
@@ -500,16 +446,31 @@ pub fn bind_common_with_config(
         .and_then(|_| TcpListener::bind(addr).map(|listener| (sock.into(), listener)))
         .and_then(|_| TcpListener::bind(addr).map(|listener| (sock.into(), listener)))
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please avoid this function, in favor of sockets::bind_two_in_range_with_offset_and_config"
+)]
+#[allow(deprecated)]
 pub fn bind_two_in_range_with_offset(
 pub fn bind_two_in_range_with_offset(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     range: PortRange,
     range: PortRange,
     offset: u16,
     offset: u16,
 ) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> {
 ) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> {
-    let sock1_config = SocketConfig::default();
-    let sock2_config = SocketConfig::default();
-    bind_two_in_range_with_offset_and_config(ip_addr, range, offset, sock1_config, sock2_config)
+    let sock_config = sockets::SocketConfiguration::default();
+    sockets::bind_two_in_range_with_offset_and_config(
+        ip_addr,
+        range,
+        offset,
+        sock_config,
+        sock_config,
+    )
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please avoid this function, in favor of sockets::bind_two_in_range_with_offset_and_config"
+)]
+#[allow(deprecated)]
 pub fn bind_two_in_range_with_offset_and_config(
 pub fn bind_two_in_range_with_offset_and_config(
     ip_addr: IpAddr,
     ip_addr: IpAddr,
     range: PortRange,
     range: PortRange,
@@ -522,6 +483,7 @@ pub fn bind_two_in_range_with_offset_and_config(
             "range too small to find two ports with the correct offset".to_string(),
             "range too small to find two ports with the correct offset".to_string(),
         ));
         ));
     }
     }
+
     for port in range.0..range.1 {
     for port in range.0..range.1 {
         if let Ok(first_bind) = bind_to_with_config(ip_addr, port, sock1_config) {
         if let Ok(first_bind) = bind_to_with_config(ip_addr, port, sock1_config) {
             if range.1.saturating_sub(port) >= offset {
             if range.1.saturating_sub(port) >= offset {
@@ -571,9 +533,10 @@ pub fn find_available_ports_in_range<const N: usize>(
         .take(range.len()) // never take the same value twice
         .take(range.len()) // never take the same value twice
         .peekable();
         .peekable();
     let mut num = 0;
     let mut num = 0;
+    let config = sockets::SocketConfiguration::default();
     while num < N {
     while num < N {
         let port_to_try = next_port_to_try.next().unwrap(); // this unwrap never fails since we exit earlier
         let port_to_try = next_port_to_try.next().unwrap(); // this unwrap never fails since we exit earlier
-        match bind_common(ip_addr, port_to_try) {
+        match sockets::bind_common_with_config(ip_addr, port_to_try, config) {
             Ok(_) => {
             Ok(_) => {
                 result[num] = port_to_try;
                 result[num] = port_to_try;
                 num = num.saturating_add(1);
                 num = num.saturating_add(1);
@@ -588,20 +551,36 @@ pub fn find_available_ports_in_range<const N: usize>(
     Ok(result)
     Ok(result)
 }
 }
 
 
+#[deprecated(
+    since = "2.3.2",
+    note = "Please avoid this function, in favor of sockets::bind_more_with_config"
+)]
+#[allow(deprecated)]
 pub fn bind_more_with_config(
 pub fn bind_more_with_config(
     socket: UdpSocket,
     socket: UdpSocket,
     num: usize,
     num: usize,
     config: SocketConfig,
     config: SocketConfig,
 ) -> io::Result<Vec<UdpSocket>> {
 ) -> io::Result<Vec<UdpSocket>> {
-    let addr = socket.local_addr().unwrap();
-    let ip = addr.ip();
-    let port = addr.port();
-    std::iter::once(Ok(socket))
-        .chain((1..num).map(|_| bind_to_with_config(ip, port, config)))
-        .collect()
+    if !PLATFORM_SUPPORTS_SOCKET_CONFIGS {
+        if num > 1 {
+            warn!(
+                "bind_more_with_config() only supports 1 socket on this platform ({} requested)",
+                num
+            );
+        }
+        Ok(vec![socket])
+    } else {
+        let addr = socket.local_addr().unwrap();
+        let ip = addr.ip();
+        let port = addr.port();
+        std::iter::once(Ok(socket))
+            .chain((1..num).map(|_| bind_to_with_config(ip, port, config)))
+            .collect()
+    }
 }
 }
 
 
 #[cfg(test)]
 #[cfg(test)]
+#[allow(deprecated)]
 mod tests {
 mod tests {
     use {
     use {
         super::*,
         super::*,

+ 386 - 2
net-utils/src/sockets.rs

@@ -1,10 +1,15 @@
 use {
 use {
-    crate::{bind_common_in_range_with_config, bind_common_with_config, PortRange, SocketConfig},
+    crate::PortRange,
+    log::warn,
+    socket2::{Domain, SockAddr, Socket, Type},
     std::{
     std::{
+        io,
         net::{IpAddr, SocketAddr, TcpListener, UdpSocket},
         net::{IpAddr, SocketAddr, TcpListener, UdpSocket},
         sync::atomic::{AtomicU16, Ordering},
         sync::atomic::{AtomicU16, Ordering},
     },
     },
 };
 };
+#[cfg(feature = "dev-context-only-utils")]
+use {std::net::Ipv4Addr, tokio::net::UdpSocket as TokioUdpSocket};
 // base port for deconflicted allocations
 // base port for deconflicted allocations
 const BASE_PORT: u16 = 5000;
 const BASE_PORT: u16 = 5000;
 // how much to allocate per individual process.
 // how much to allocate per individual process.
@@ -44,7 +49,7 @@ pub fn bind_gossip_port_in_range(
     port_range: PortRange,
     port_range: PortRange,
     bind_ip_addr: IpAddr,
     bind_ip_addr: IpAddr,
 ) -> (u16, (UdpSocket, TcpListener)) {
 ) -> (u16, (UdpSocket, TcpListener)) {
-    let config = SocketConfig::default();
+    let config = SocketConfiguration::default();
     if gossip_addr.port() != 0 {
     if gossip_addr.port() != 0 {
         (
         (
             gossip_addr.port(),
             gossip_addr.port(),
@@ -56,3 +61,382 @@ pub fn bind_gossip_port_in_range(
         bind_common_in_range_with_config(bind_ip_addr, port_range, config).expect("Failed to bind")
         bind_common_in_range_with_config(bind_ip_addr, port_range, config).expect("Failed to bind")
     }
     }
 }
 }
+
+/// True on platforms that support advanced socket configuration
+pub(crate) const PLATFORM_SUPPORTS_SOCKET_CONFIGS: bool =
+    cfg!(not(any(windows, target_os = "ios")));
+
+#[derive(Clone, Copy, Debug, Default)]
+pub struct SocketConfiguration {
+    reuseport: bool, // controls SO_REUSEPORT, this is not intended to be set explicitly
+    recv_buffer_size: Option<usize>,
+    send_buffer_size: Option<usize>,
+    non_blocking: bool,
+}
+
+impl SocketConfiguration {
+    /// Sets the receive buffer size for the socket (no effect on windows/ios).
+    ///
+    /// **Note:** On Linux the kernel will double the value you specify.
+    /// For example, if you specify `16MB`, the kernel will configure the
+    /// socket to use `32MB`.
+    /// See: https://man7.org/linux/man-pages/man7/socket.7.html: SO_RCVBUF
+    pub fn recv_buffer_size(mut self, size: usize) -> Self {
+        self.recv_buffer_size = Some(size);
+        self
+    }
+
+    /// Sets the send buffer size for the socket (no effect on windows/ios)
+    ///
+    /// **Note:** On Linux the kernel will double the value you specify.
+    /// For example, if you specify `16MB`, the kernel will configure the
+    /// socket to use `32MB`.
+    /// See: https://man7.org/linux/man-pages/man7/socket.7.html: SO_SNDBUF
+    pub fn send_buffer_size(mut self, size: usize) -> Self {
+        self.send_buffer_size = Some(size);
+        self
+    }
+
+    /// Configure the socket for non-blocking IO
+    pub fn set_non_blocking(mut self, non_blocking: bool) -> Self {
+        self.non_blocking = non_blocking;
+        self
+    }
+}
+
+#[allow(deprecated)]
+impl From<crate::SocketConfig> for SocketConfiguration {
+    fn from(value: crate::SocketConfig) -> Self {
+        Self {
+            reuseport: value.reuseport,
+            recv_buffer_size: value.recv_buffer_size,
+            send_buffer_size: value.send_buffer_size,
+            non_blocking: false,
+        }
+    }
+}
+
+#[cfg(any(windows, target_os = "ios"))]
+fn set_reuse_port<T>(_socket: &T) -> io::Result<()> {
+    Ok(())
+}
+
+/// Sets SO_REUSEPORT on platforms that support it.
+#[cfg(not(any(windows, target_os = "ios")))]
+fn set_reuse_port<T>(socket: &T) -> io::Result<()>
+where
+    T: std::os::fd::AsFd,
+{
+    use nix::sys::socket::{setsockopt, sockopt::ReusePort};
+    setsockopt(socket, ReusePort, &true).map_err(io::Error::from)
+}
+
+pub(crate) fn udp_socket_with_config(config: SocketConfiguration) -> io::Result<Socket> {
+    let SocketConfiguration {
+        reuseport,
+        recv_buffer_size,
+        send_buffer_size,
+        non_blocking,
+    } = config;
+    let sock = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
+    if PLATFORM_SUPPORTS_SOCKET_CONFIGS {
+        // Set buffer sizes
+        if let Some(recv_buffer_size) = recv_buffer_size {
+            sock.set_recv_buffer_size(recv_buffer_size)?;
+        }
+        if let Some(send_buffer_size) = send_buffer_size {
+            sock.set_send_buffer_size(send_buffer_size)?;
+        }
+
+        if reuseport {
+            set_reuse_port(&sock)?;
+        }
+    }
+    sock.set_nonblocking(non_blocking)?;
+    Ok(sock)
+}
+
+/// Find a port in the given range with a socket config that is available for both TCP and UDP
+pub fn bind_common_in_range_with_config(
+    ip_addr: IpAddr,
+    range: PortRange,
+    config: SocketConfiguration,
+) -> io::Result<(u16, (UdpSocket, TcpListener))> {
+    for port in range.0..range.1 {
+        if let Ok((sock, listener)) = bind_common_with_config(ip_addr, port, config) {
+            return Result::Ok((sock.local_addr().unwrap().port(), (sock, listener)));
+        }
+    }
+
+    Err(io::Error::other(format!(
+        "No available TCP/UDP ports in {range:?}"
+    )))
+}
+
+pub fn bind_in_range_with_config(
+    ip_addr: IpAddr,
+    range: PortRange,
+    config: SocketConfiguration,
+) -> io::Result<(u16, UdpSocket)> {
+    let socket = udp_socket_with_config(config)?;
+
+    for port in range.0..range.1 {
+        let addr = SocketAddr::new(ip_addr, port);
+
+        if socket.bind(&SockAddr::from(addr)).is_ok() {
+            let udp_socket: UdpSocket = socket.into();
+            return Result::Ok((udp_socket.local_addr().unwrap().port(), udp_socket));
+        }
+    }
+
+    Err(io::Error::other(format!(
+        "No available UDP ports in {range:?}"
+    )))
+}
+
+pub fn bind_with_any_port_with_config(
+    ip_addr: IpAddr,
+    config: SocketConfiguration,
+) -> io::Result<UdpSocket> {
+    let sock = udp_socket_with_config(config)?;
+    let addr = SocketAddr::new(ip_addr, 0);
+    match sock.bind(&SockAddr::from(addr)) {
+        Ok(_) => Result::Ok(sock.into()),
+        Err(err) => Err(io::Error::other(format!("No available UDP port: {err}"))),
+    }
+}
+
+/// binds num sockets to the same port in a range with config
+pub fn multi_bind_in_range_with_config(
+    ip_addr: IpAddr,
+    range: PortRange,
+    config: SocketConfiguration,
+    mut num: usize,
+) -> io::Result<(u16, Vec<UdpSocket>)> {
+    if !PLATFORM_SUPPORTS_SOCKET_CONFIGS && num != 1 {
+        // See https://github.com/solana-labs/solana/issues/4607
+        warn!(
+            "multi_bind_in_range_with_config() only supports 1 socket on this platform ({} requested)",
+            num
+        );
+        num = 1;
+    }
+    let (port, socket) = bind_in_range_with_config(ip_addr, range, config)?;
+    let sockets = bind_more_with_config(socket, num, config)?;
+    Ok((port, sockets))
+}
+
+pub fn bind_to(ip_addr: IpAddr, port: u16) -> io::Result<UdpSocket> {
+    let config = SocketConfiguration {
+        ..Default::default()
+    };
+    bind_to_with_config(ip_addr, port, config)
+}
+
+#[cfg(feature = "dev-context-only-utils")]
+pub async fn bind_to_async(ip_addr: IpAddr, port: u16) -> io::Result<TokioUdpSocket> {
+    let config = SocketConfiguration {
+        non_blocking: true,
+        ..Default::default()
+    };
+    let socket = bind_to_with_config(ip_addr, port, config)?;
+    TokioUdpSocket::from_std(socket)
+}
+
+#[cfg(feature = "dev-context-only-utils")]
+pub async fn bind_to_localhost_async() -> io::Result<TokioUdpSocket> {
+    bind_to_async(IpAddr::V4(Ipv4Addr::LOCALHOST), 0).await
+}
+
+#[cfg(feature = "dev-context-only-utils")]
+pub async fn bind_to_unspecified_async() -> io::Result<TokioUdpSocket> {
+    bind_to_async(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0).await
+}
+
+pub fn bind_to_with_config(
+    ip_addr: IpAddr,
+    port: u16,
+    config: SocketConfiguration,
+) -> io::Result<UdpSocket> {
+    let sock = udp_socket_with_config(config)?;
+
+    let addr = SocketAddr::new(ip_addr, port);
+
+    sock.bind(&SockAddr::from(addr)).map(|_| sock.into())
+}
+
+/// binds both a UdpSocket and a TcpListener on the same port
+pub fn bind_common_with_config(
+    ip_addr: IpAddr,
+    port: u16,
+    config: SocketConfiguration,
+) -> io::Result<(UdpSocket, TcpListener)> {
+    let sock = udp_socket_with_config(config)?;
+
+    let addr = SocketAddr::new(ip_addr, port);
+    let sock_addr = SockAddr::from(addr);
+    sock.bind(&sock_addr)
+        .and_then(|_| TcpListener::bind(addr).map(|listener| (sock.into(), listener)))
+}
+
+pub fn bind_two_in_range_with_offset_and_config(
+    ip_addr: IpAddr,
+    range: PortRange,
+    offset: u16,
+    sock1_config: SocketConfiguration,
+    sock2_config: SocketConfiguration,
+) -> io::Result<((u16, UdpSocket), (u16, UdpSocket))> {
+    if range.1.saturating_sub(range.0) < offset {
+        return Err(io::Error::other(
+            "range too small to find two ports with the correct offset".to_string(),
+        ));
+    }
+
+    for port in range.0..range.1 {
+        if let Ok(first_bind) = bind_to_with_config(ip_addr, port, sock1_config) {
+            if range.1.saturating_sub(port) >= offset {
+                if let Ok(second_bind) =
+                    bind_to_with_config(ip_addr, port.saturating_add(offset), sock2_config)
+                {
+                    return Ok((
+                        (first_bind.local_addr().unwrap().port(), first_bind),
+                        (second_bind.local_addr().unwrap().port(), second_bind),
+                    ));
+                }
+            } else {
+                break;
+            }
+        }
+    }
+    Err(io::Error::other(
+        "couldn't find two ports with the correct offset in range".to_string(),
+    ))
+}
+
+pub fn bind_more_with_config(
+    socket: UdpSocket,
+    num: usize,
+    mut config: SocketConfiguration,
+) -> io::Result<Vec<UdpSocket>> {
+    if !PLATFORM_SUPPORTS_SOCKET_CONFIGS {
+        if num > 1 {
+            warn!(
+                "bind_more_with_config() only supports 1 socket on this platform ({} requested)",
+                num
+            );
+        }
+        Ok(vec![socket])
+    } else {
+        set_reuse_port(&socket)?;
+        config.reuseport = true;
+        let addr = socket.local_addr().unwrap();
+        let ip = addr.ip();
+        let port = addr.port();
+        std::iter::once(Ok(socket))
+            .chain((1..num).map(|_| bind_to_with_config(ip, port, config)))
+            .collect()
+    }
+}
+
+#[cfg(test)]
+#[allow(deprecated)]
+mod tests {
+    use {
+        super::*,
+        crate::{bind_in_range, sockets::localhost_port_range_for_tests},
+        std::net::Ipv4Addr,
+    };
+
+    #[test]
+    fn test_bind() {
+        let (pr_s, pr_e) = localhost_port_range_for_tests();
+        let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
+        let config = SocketConfiguration::default();
+        let s = bind_in_range(ip_addr, (pr_s, pr_e)).unwrap();
+        assert_eq!(s.0, pr_s, "bind_in_range should use first available port");
+        let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
+        let x = bind_to_with_config(ip_addr, pr_s + 1, config).unwrap();
+        let y = bind_more_with_config(x, 2, config).unwrap();
+        assert_eq!(
+            y[0].local_addr().unwrap().port(),
+            y[1].local_addr().unwrap().port()
+        );
+        bind_to_with_config(ip_addr, pr_s, SocketConfiguration::default()).unwrap_err();
+        bind_in_range(ip_addr, (pr_s, pr_s + 2)).unwrap_err();
+
+        let (port, v) =
+            multi_bind_in_range_with_config(ip_addr, (pr_s + 5, pr_e), config, 10).unwrap();
+        for sock in &v {
+            assert_eq!(port, sock.local_addr().unwrap().port());
+        }
+    }
+
+    #[test]
+    fn test_bind_with_any_port() {
+        let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
+        let config = SocketConfiguration::default();
+        let x = bind_with_any_port_with_config(ip_addr, config).unwrap();
+        let y = bind_with_any_port_with_config(ip_addr, config).unwrap();
+        assert_ne!(
+            x.local_addr().unwrap().port(),
+            y.local_addr().unwrap().port()
+        );
+    }
+
+    #[test]
+    fn test_bind_in_range_nil() {
+        let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
+        bind_in_range(ip_addr, (2000, 2000)).unwrap_err();
+        bind_in_range(ip_addr, (2000, 1999)).unwrap_err();
+    }
+
+    #[test]
+    fn test_bind_on_top() {
+        let config = SocketConfiguration::default();
+        let localhost = IpAddr::V4(Ipv4Addr::LOCALHOST);
+        let port_range = localhost_port_range_for_tests();
+        let (_p, s) = bind_in_range_with_config(localhost, port_range, config).unwrap();
+        let _socks = bind_more_with_config(s, 8, config).unwrap();
+
+        let _socks2 = multi_bind_in_range_with_config(localhost, port_range, config, 8).unwrap();
+    }
+
+    #[test]
+    fn test_bind_common_in_range() {
+        let ip_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
+        let (pr_s, pr_e) = localhost_port_range_for_tests();
+        let config = SocketConfiguration::default();
+        let (port, _sockets) =
+            bind_common_in_range_with_config(ip_addr, (pr_s, pr_e), config).unwrap();
+        assert!((pr_s..pr_e).contains(&port));
+
+        bind_common_in_range_with_config(ip_addr, (port, port + 1), config).unwrap_err();
+    }
+
+    #[test]
+    fn test_bind_two_in_range_with_offset() {
+        solana_logger::setup();
+        let config = SocketConfiguration::default();
+        let ip_addr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
+        let offset = 6;
+        if let Ok(((port1, _), (port2, _))) =
+            bind_two_in_range_with_offset_and_config(ip_addr, (1024, 65535), offset, config, config)
+        {
+            assert!(port2 == port1 + offset);
+        }
+        let offset = 42;
+        if let Ok(((port1, _), (port2, _))) =
+            bind_two_in_range_with_offset_and_config(ip_addr, (1024, 65535), offset, config, config)
+        {
+            assert!(port2 == port1 + offset);
+        }
+        assert!(bind_two_in_range_with_offset_and_config(
+            ip_addr,
+            (1024, 1044),
+            offset,
+            config,
+            config
+        )
+        .is_err());
+    }
+}

+ 5 - 2
quic-client/src/nonblocking/quic_client.rs

@@ -17,7 +17,10 @@ use {
     },
     },
     solana_keypair::Keypair,
     solana_keypair::Keypair,
     solana_measure::measure::Measure,
     solana_measure::measure::Measure,
-    solana_net_utils::{SocketConfig, VALIDATOR_PORT_RANGE},
+    solana_net_utils::{
+        sockets::{bind_in_range_with_config, SocketConfiguration as SocketConfig},
+        VALIDATOR_PORT_RANGE,
+    },
     solana_quic_definitions::{
     solana_quic_definitions::{
         QUIC_CONNECTION_HANDSHAKE_TIMEOUT, QUIC_KEEP_ALIVE, QUIC_MAX_TIMEOUT, QUIC_SEND_FAIRNESS,
         QUIC_CONNECTION_HANDSHAKE_TIMEOUT, QUIC_KEEP_ALIVE, QUIC_MAX_TIMEOUT, QUIC_SEND_FAIRNESS,
     },
     },
@@ -78,7 +81,7 @@ impl QuicLazyInitializedEndpoint {
             endpoint.clone()
             endpoint.clone()
         } else {
         } else {
             let config = SocketConfig::default();
             let config = SocketConfig::default();
-            let client_socket = solana_net_utils::bind_in_range_with_config(
+            let client_socket = bind_in_range_with_config(
                 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
                 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
                 VALIDATOR_PORT_RANGE,
                 VALIDATOR_PORT_RANGE,
                 config,
                 config,

+ 3 - 3
streamer/src/nonblocking/recvmmsg.rs

@@ -57,7 +57,7 @@ pub async fn recv_mmsg_exact(
 mod tests {
 mod tests {
     use {
     use {
         crate::{nonblocking::recvmmsg::*, packet::PACKET_DATA_SIZE},
         crate::{nonblocking::recvmmsg::*, packet::PACKET_DATA_SIZE},
-        solana_net_utils::{bind_to_async, bind_to_localhost_async},
+        solana_net_utils::sockets::{bind_to_async, bind_to_localhost_async},
         std::{net::SocketAddr, time::Instant},
         std::{net::SocketAddr, time::Instant},
         tokio::net::UdpSocket,
         tokio::net::UdpSocket,
     };
     };
@@ -68,9 +68,9 @@ mod tests {
         let sock_addr: SocketAddr = ip_str
         let sock_addr: SocketAddr = ip_str
             .parse()
             .parse()
             .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
             .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
-        let reader = bind_to_async(sock_addr.ip(), sock_addr.port(), /*reuseport:*/ false).await?;
+        let reader = bind_to_async(sock_addr.ip(), sock_addr.port()).await?;
         let addr = reader.local_addr()?;
         let addr = reader.local_addr()?;
-        let sender = bind_to_async(sock_addr.ip(), sock_addr.port(), /*reuseport:*/ false).await?;
+        let sender = bind_to_async(sock_addr.ip(), sock_addr.port()).await?;
         let saddr = sender.local_addr()?;
         let saddr = sender.local_addr()?;
         Ok((reader, addr, sender, saddr))
         Ok((reader, addr, sender, saddr))
     }
     }

+ 1 - 1
streamer/src/nonblocking/sendmmsg.rs

@@ -61,7 +61,7 @@ mod tests {
             sendmmsg::SendPktsError,
             sendmmsg::SendPktsError,
         },
         },
         assert_matches::assert_matches,
         assert_matches::assert_matches,
-        solana_net_utils::{bind_to_localhost_async, bind_to_unspecified_async},
+        solana_net_utils::sockets::{bind_to_localhost_async, bind_to_unspecified_async},
         solana_packet::PACKET_DATA_SIZE,
         solana_packet::PACKET_DATA_SIZE,
         std::{
         std::{
             io::ErrorKind,
             io::ErrorKind,

+ 6 - 3
streamer/src/nonblocking/testing_utilities.rs

@@ -12,8 +12,11 @@ use {
     },
     },
     solana_keypair::Keypair,
     solana_keypair::Keypair,
     solana_net_utils::{
     solana_net_utils::{
-        bind_to_localhost, multi_bind_in_range_with_config,
-        sockets::localhost_port_range_for_tests, SocketConfig,
+        bind_to_localhost,
+        sockets::{
+            localhost_port_range_for_tests, multi_bind_in_range_with_config,
+            SocketConfiguration as SocketConfig,
+        },
     },
     },
     solana_perf::packet::PacketBatch,
     solana_perf::packet::PacketBatch,
     solana_quic_definitions::{QUIC_KEEP_ALIVE, QUIC_MAX_TIMEOUT, QUIC_SEND_FAIRNESS},
     solana_quic_definitions::{QUIC_KEEP_ALIVE, QUIC_MAX_TIMEOUT, QUIC_SEND_FAIRNESS},
@@ -66,7 +69,7 @@ pub fn create_quic_server_sockets() -> Vec<UdpSocket> {
     multi_bind_in_range_with_config(
     multi_bind_in_range_with_config(
         IpAddr::V4(Ipv4Addr::LOCALHOST),
         IpAddr::V4(Ipv4Addr::LOCALHOST),
         port_range,
         port_range,
-        SocketConfig::default().reuseport(true),
+        SocketConfig::default(),
         num,
         num,
     )
     )
     .expect("bind operation for quic server sockets should succeed")
     .expect("bind operation for quic server sockets should succeed")

+ 3 - 2
streamer/src/recvmmsg.rs

@@ -182,8 +182,9 @@ pub fn recv_mmsg(sock: &UdpSocket, packets: &mut [Packet]) -> io::Result</*num p
 mod tests {
 mod tests {
     use {
     use {
         crate::{packet::PACKET_DATA_SIZE, recvmmsg::*},
         crate::{packet::PACKET_DATA_SIZE, recvmmsg::*},
-        solana_net_utils::{
-            bind_in_range_with_config, sockets::localhost_port_range_for_tests, SocketConfig,
+        solana_net_utils::sockets::{
+            bind_in_range_with_config, localhost_port_range_for_tests,
+            SocketConfiguration as SocketConfig,
         },
         },
         std::{
         std::{
             net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket},
             net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket},

+ 4 - 2
udp-client/src/lib.rs

@@ -16,7 +16,9 @@ use {
         connection_cache_stats::ConnectionCacheStats,
         connection_cache_stats::ConnectionCacheStats,
     },
     },
     solana_keypair::Keypair,
     solana_keypair::Keypair,
-    solana_net_utils::SocketConfig,
+    solana_net_utils::sockets::{
+        bind_with_any_port_with_config, SocketConfiguration as SocketConfig,
+    },
     std::{
     std::{
         net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
         net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
         sync::Arc,
         sync::Arc,
@@ -63,7 +65,7 @@ pub struct UdpConfig {
 
 
 impl NewConnectionConfig for UdpConfig {
 impl NewConnectionConfig for UdpConfig {
     fn new() -> Result<Self, ClientError> {
     fn new() -> Result<Self, ClientError> {
-        let socket = solana_net_utils::bind_with_any_port_with_config(
+        let socket = bind_with_any_port_with_config(
             IpAddr::V4(Ipv4Addr::UNSPECIFIED),
             IpAddr::V4(Ipv4Addr::UNSPECIFIED),
             SocketConfig::default(),
             SocketConfig::default(),
         )
         )

+ 5 - 9
udp-client/src/nonblocking/udp_client.rs

@@ -46,7 +46,9 @@ impl ClientConnection for UdpClientConnection {
 mod tests {
 mod tests {
     use {
     use {
         super::*,
         super::*,
-        solana_net_utils::{bind_to_async, SocketConfig},
+        solana_net_utils::sockets::{
+            bind_to_async, bind_with_any_port_with_config, SocketConfiguration as SocketConfig,
+        },
         solana_packet::{Packet, PACKET_DATA_SIZE},
         solana_packet::{Packet, PACKET_DATA_SIZE},
         solana_streamer::nonblocking::recvmmsg::recv_mmsg,
         solana_streamer::nonblocking::recvmmsg::recv_mmsg,
         std::net::{IpAddr, Ipv4Addr},
         std::net::{IpAddr, Ipv4Addr},
@@ -73,19 +75,13 @@ mod tests {
     async fn test_send_from_addr() {
     async fn test_send_from_addr() {
         let addr_str = "0.0.0.0:50100";
         let addr_str = "0.0.0.0:50100";
         let addr = addr_str.parse().unwrap();
         let addr = addr_str.parse().unwrap();
-        let socket = solana_net_utils::bind_with_any_port_with_config(
+        let socket = bind_with_any_port_with_config(
             IpAddr::V4(Ipv4Addr::UNSPECIFIED),
             IpAddr::V4(Ipv4Addr::UNSPECIFIED),
             SocketConfig::default(),
             SocketConfig::default(),
         )
         )
         .unwrap();
         .unwrap();
         let connection = UdpClientConnection::new_from_addr(socket, addr);
         let connection = UdpClientConnection::new_from_addr(socket, addr);
-        let reader = bind_to_async(
-            addr.ip(),
-            /*port*/ addr.port(),
-            /*reuseport:*/ false,
-        )
-        .await
-        .expect("bind");
+        let reader = bind_to_async(addr.ip(), addr.port()).await.expect("bind");
         check_send_one(&connection, &reader).await;
         check_send_one(&connection, &reader).await;
         check_send_batch(&connection, &reader).await;
         check_send_batch(&connection, &reader).await;
     }
     }

+ 2 - 2
vortexor/src/main.rs

@@ -5,7 +5,7 @@ use {
     solana_core::banking_trace::BankingTracer,
     solana_core::banking_trace::BankingTracer,
     solana_keypair::read_keypair_file,
     solana_keypair::read_keypair_file,
     solana_logger::redirect_stderr_to_file,
     solana_logger::redirect_stderr_to_file,
-    solana_net_utils::{bind_in_range_with_config, SocketConfig},
+    solana_net_utils::sockets::{bind_in_range_with_config, SocketConfiguration as SocketConfig},
     solana_quic_definitions::QUIC_PORT_OFFSET,
     solana_quic_definitions::QUIC_PORT_OFFSET,
     solana_signer::Signer,
     solana_signer::Signer,
     solana_streamer::streamer::StakedNodes,
     solana_streamer::streamer::StakedNodes,
@@ -100,7 +100,7 @@ pub fn main() {
     )
     )
     .unwrap();
     .unwrap();
 
 
-    let config = SocketConfig::default().reuseport(false);
+    let config = SocketConfig::default();
 
 
     let sender_socket =
     let sender_socket =
         bind_in_range_with_config(*bind_address, dynamic_port_range, config).unwrap();
         bind_in_range_with_config(*bind_address, dynamic_port_range, config).unwrap();

+ 4 - 2
vortexor/src/vortexor.rs

@@ -5,7 +5,9 @@ use {
         sigverify_stage::SigVerifyStage,
         sigverify_stage::SigVerifyStage,
     },
     },
     solana_keypair::Keypair,
     solana_keypair::Keypair,
-    solana_net_utils::{multi_bind_in_range_with_config, SocketConfig},
+    solana_net_utils::sockets::{
+        multi_bind_in_range_with_config, SocketConfiguration as SocketConfig,
+    },
     solana_perf::packet::PacketBatch,
     solana_perf::packet::PacketBatch,
     solana_quic_definitions::NotifyKeyUpdate,
     solana_quic_definitions::NotifyKeyUpdate,
     solana_streamer::{
     solana_streamer::{
@@ -61,7 +63,7 @@ impl Vortexor {
         tpu_forward_address: Option<SocketAddr>,
         tpu_forward_address: Option<SocketAddr>,
         num_quic_endpoints: usize,
         num_quic_endpoints: usize,
     ) -> TpuSockets {
     ) -> TpuSockets {
-        let quic_config = SocketConfig::default().reuseport(true);
+        let quic_config = SocketConfig::default();
 
 
         let tpu_quic = bind_sockets(
         let tpu_quic = bind_sockets(
             bind_address,
             bind_address,