#![cfg(feature = "agave-unstable-api")]
use std::{
net::{IpAddr, Ipv4Addr, UdpSocket},
ops::Deref,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
pub enum CurrentSocket<'a> {
Same(&'a UdpSocket),
Changed(&'a UdpSocket),
}
pub trait SocketProvider {
fn current_socket(&self) -> CurrentSocket<'_>;
#[inline]
fn current_socket_ref(&self) -> &UdpSocket {
match self.current_socket() {
CurrentSocket::Same(sock) | CurrentSocket::Changed(sock) => sock,
}
}
}
pub struct FixedSocketProvider {
socket: Arc<UdpSocket>,
}
impl FixedSocketProvider {
pub fn new(socket: Arc<UdpSocket>) -> Self {
Self { socket }
}
}
impl SocketProvider for FixedSocketProvider {
#[inline]
fn current_socket(&self) -> CurrentSocket<'_> {
CurrentSocket::Same(self.socket.as_ref())
}
}
pub struct MultihomedSocketProvider {
sockets: Arc<[UdpSocket]>,
bind_ip_addrs: Arc<BindIpAddrs>,
last_index: AtomicUsize,
}
impl MultihomedSocketProvider {
pub fn new(sockets: Arc<[UdpSocket]>, bind_ip_addrs: Arc<BindIpAddrs>) -> Self {
Self {
sockets,
bind_ip_addrs,
last_index: AtomicUsize::new(usize::MAX),
}
}
}
impl SocketProvider for MultihomedSocketProvider {
#[inline]
fn current_socket(&self) -> CurrentSocket<'_> {
let idx = self.bind_ip_addrs.active_index();
let last = self.last_index.swap(idx, Ordering::AcqRel);
let sock = &self.sockets[idx];
if last == idx {
CurrentSocket::Same(sock)
} else {
CurrentSocket::Changed(sock)
}
}
}
#[derive(Debug, Clone)]
pub struct BindIpAddrs {
addrs: Vec<IpAddr>,
active_index: Arc<AtomicUsize>,
}
impl Default for BindIpAddrs {
fn default() -> Self {
Self::new(vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]).unwrap()
}
}
impl BindIpAddrs {
pub fn new(addrs: Vec<IpAddr>) -> Result<Self, String> {
if addrs.is_empty() {
return Err(
"BindIpAddrs requires at least one IP address (--bind-address)".to_string(),
);
}
if addrs.len() > 1 {
for ip in &addrs {
if ip.is_loopback() || ip.is_unspecified() || ip.is_multicast() {
return Err(format!(
"Invalid configuration: {ip:?} is not allowed with multiple \
--bind-address values (loopback, unspecified, or multicast)"
));
}
}
}
Ok(Self {
addrs,
active_index: Arc::new(AtomicUsize::new(0)),
})
}
#[inline]
pub fn active(&self) -> IpAddr {
self.addrs[self.active_index.load(Ordering::Acquire)]
}
pub fn set_active(&self, index: usize) -> Result<IpAddr, String> {
if index >= self.addrs.len() {
return Err(format!(
"Index {index} out of range, only {} IPs available",
self.addrs.len()
));
}
self.active_index.store(index, Ordering::Release);
Ok(self.addrs[index])
}
#[inline]
pub fn active_index(&self) -> usize {
self.active_index.load(Ordering::Acquire)
}
#[inline]
pub fn multihoming_enabled(&self) -> bool {
self.addrs.len() > 1
}
}
impl Deref for BindIpAddrs {
type Target = [IpAddr];
fn deref(&self) -> &Self::Target {
&self.addrs
}
}
impl AsRef<[IpAddr]> for BindIpAddrs {
fn as_ref(&self) -> &[IpAddr] {
&self.addrs
}
}