Skip to content

Commit

Permalink
Refactor networking code into network module
Browse files Browse the repository at this point in the history
  • Loading branch information
keesverruijt committed Feb 1, 2025
1 parent 1351c23 commit 559d427
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 368 deletions.
151 changes: 95 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ netlink-sys = { version = "0.8.4", features = ["tokio", "tokio_socket"] }
rtnetlink = "0.14.1"

[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.59.0", features = ["Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_WiFi", "Win32_Networking_WinSock", "Win32_System", "Win32_System_Threading", "Win32_Security", "Win32_System_Diagnostics", "Win32_System_Diagnostics_Debug", "Win32_System_IO"] }
w32-error = "1.0.0"
windows = { version = "0.59.0", features = ["Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_WiFi", "Win32_Networking_WinSock", "Win32_System", "Win32_System_Threading", "Win32_Security", "Win32_System_Diagnostics", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_System_SystemServices" ] }

[build-dependencies]
protobuf-codegen = "3.5.1"
Expand Down
3 changes: 2 additions & 1 deletion src/furuno/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ use tokio_graceful_shutdown::SubsystemHandle;
use trail::TrailBuffer;

use crate::locator::{Locator, LocatorId};
use crate::network::create_udp_multicast_listen;
use crate::protos::RadarMessage::radar_message::Spoke;
use crate::protos::RadarMessage::RadarMessage;
use crate::radar::*;
use crate::util::{create_udp_multicast_listen, PrintableSpoke};
use crate::util::PrintableSpoke;

use super::{FURUNO_SPOKES, FURUNO_SPOKE_LEN};

Expand Down
23 changes: 15 additions & 8 deletions src/locator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use tokio::time::sleep;
use tokio_graceful_shutdown::SubsystemHandle;

use crate::radar::{RadarError, SharedRadars};
use crate::{furuno, navico, raymarine, util, Cli};
use crate::{furuno, navico, network, raymarine, Cli};

const LOCATOR_PACKET_BUFFER_LEN: usize = 300; // Long enough for any location packet

Expand Down Expand Up @@ -197,9 +197,16 @@ impl Locator {
Err(RadarError::Shutdown)
});
set.spawn(async move {
if let Err(e) = util::wait_for_ip_addr_change(child_token).await {
log::error!("Failed to wait for IP change: {e}");
sleep(Duration::from_secs(30)).await;
if let Err(e) = network::wait_for_ip_addr_change(child_token).await {
match e {
RadarError::Shutdown => {
return Err(RadarError::Shutdown);
}
_ => {
log::error!("Failed to wait for IP change: {e}");
sleep(Duration::from_secs(30)).await;
}
}
}
Err(RadarError::Timeout)
});
Expand Down Expand Up @@ -315,7 +322,7 @@ fn create_listen_sockets(
let mut active: bool = false;

if only_interface.is_none() || only_interface.as_ref() == Some(&itf.name) {
if avoid_wifi && util::is_wireless_interface(&itf.name) {
if avoid_wifi && network::is_wireless_interface(&itf.name) {
trace!("Ignoring wireless interface '{}'", itf.name);
continue;
}
Expand Down Expand Up @@ -352,7 +359,7 @@ fn create_listen_sockets(
radar_listen_address.address
{
let socket = if !listen_addr.ip().is_multicast()
&& !util::match_ipv4(
&& !network::match_ipv4(
&nic_ip,
listen_addr.ip(),
&nic_netmask,
Expand All @@ -366,9 +373,9 @@ fn create_listen_sockets(
);
continue;
}
util::create_udp_listen(&listen_addr, &nic_ip, true)
network::create_udp_listen(&listen_addr, &nic_ip, true)
} else {
util::create_udp_listen(&listen_addr, &nic_ip, false)
network::create_udp_listen(&listen_addr, &nic_ip, false)
};

match socket {
Expand Down
2 changes: 1 addition & 1 deletion src/navico/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use std::cmp::{max, min};
use log::{debug, trace};
use tokio::net::UdpSocket;

use crate::network::create_multicast_send;
use crate::radar::{RadarError, RadarInfo, SharedRadars};
use crate::settings::{ControlType, ControlValue, Controls};
use crate::util::create_multicast_send;

use super::Model;

Expand Down
3 changes: 2 additions & 1 deletion src/navico/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ use trail::TrailBuffer;

use crate::locator::LocatorId;
use crate::navico::NAVICO_SPOKE_LEN;
use crate::network::create_udp_multicast_listen;
use crate::protos::RadarMessage::radar_message::Spoke;
use crate::protos::RadarMessage::RadarMessage;
use crate::radar::*;
use crate::settings::ControlType;
use crate::util::{create_udp_multicast_listen, PrintableSpoke};
use crate::util::PrintableSpoke;

use super::{
DataUpdate, NAVICO_SPOKES, NAVICO_SPOKES_RAW, RADAR_LINE_DATA_LENGTH, SPOKES_PER_FRAME,
Expand Down
3 changes: 2 additions & 1 deletion src/navico/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ use tokio::sync::mpsc::Sender;
use tokio::time::{sleep, sleep_until, Instant};
use tokio_graceful_shutdown::SubsystemHandle;

use crate::network::create_udp_multicast_listen;
use crate::radar::{DopplerMode, RadarError, RadarInfo, RangeDetection, SharedRadars};
use crate::settings::{ControlMessage, ControlType, ControlValue};
use crate::util::{c_string, c_wide_string, create_udp_multicast_listen};
use crate::util::{c_string, c_wide_string};
use crate::Cli;

use super::command::Command;
Expand Down
35 changes: 35 additions & 0 deletions src/network/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,38 @@ pub async fn wait_for_ip_addr_change(cancel_token: CancellationToken) -> Result<
}
}
}

pub fn is_wireless_interface(interface_name: &str) -> bool {
use libc::{c_int, c_void, ifreq, ioctl, strncpy, AF_INET};
use std::ffi::CString;

const SIOCGIWNAME: c_int = 0x8B01; // Wireless Extensions request to get interface name

// Open a socket for ioctl operations
let socket_fd = unsafe { libc::socket(AF_INET, libc::SOCK_DGRAM, 0) };
if socket_fd < 0 {
return false;
}

// Prepare the interface request structure
let mut ifr = unsafe { std::mem::zeroed::<ifreq>() };
let iface_cstring = CString::new(interface_name).expect("Invalid interface name");
unsafe {
strncpy(
ifr.ifr_name.as_mut_ptr(),
iface_cstring.as_ptr(),
ifr.ifr_name.len(),
);
}

// Perform the ioctl call
let res = unsafe { ioctl(socket_fd, SIOCGIWNAME, &mut ifr as *mut _ as *mut c_void) };

// Close the socket
unsafe { libc::close(socket_fd) };

match res {
0 => true, // The interface supports wireless extensions
_ => false,
}
}
12 changes: 12 additions & 0 deletions src/network/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,15 @@ pub(crate) async fn wait_for_ip_addr_change(

Ok(())
}

pub fn is_wireless_interface(interface_name: &str) -> bool {
use system_configuration::dynamic_store::*;

let store = SCDynamicStoreBuilder::new("networkInterfaceInfo").build();

let key = format!("State:/Network/Interface/{}/AirPort", interface_name);
if let Some(_) = store.get(key.as_str()) {
return true;
}
false
}
179 changes: 179 additions & 0 deletions src/network/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,186 @@
use socket2::{Domain, Protocol, Type};
use std::net::SocketAddrV4;
use std::{
io,
net::{IpAddr, Ipv4Addr, SocketAddr},
};
use tokio::net::UdpSocket;

#[cfg(target_os = "linux")]
pub(crate) mod linux;
#[cfg(target_os = "macos")]
pub(crate) mod macos;

#[cfg(target_os = "windows")]
pub(crate) mod windows;

// this will be common for all our sockets
pub fn new_socket() -> io::Result<socket2::Socket> {
let socket = socket2::Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP))?;

// we're going to use read timeouts so that we don't hang waiting for packets
socket.set_nonblocking(true)?;
socket.set_reuse_address(true)?;

Ok(socket)
}

/// On Windows, unlike all Unix variants, it is improper to bind to the multicast address
///
/// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550(v=vs.85).aspx
#[cfg(windows)]
fn bind_to_multicast(
socket: &socket2::Socket,
addr: &SocketAddrV4,
nic_addr: &Ipv4Addr,
) -> io::Result<()> {
socket.join_multicast_v4(addr.ip(), nic_addr)?;

let socketaddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), addr.port());
socket.bind(&socket2::SockAddr::from(socketaddr))?;
log::trace!("Binding multicast socket to {}", socketaddr);

Ok(())
}

/// On unixes we bind to the multicast address, which causes multicast packets to be filtered
#[cfg(unix)]
fn bind_to_multicast(
socket: &socket2::Socket,
addr: &SocketAddrV4,
nic_addr: &Ipv4Addr,
) -> io::Result<()> {
// Linux is special, if we don't disable IP_MULTICAST_ALL the kernel forgets on
// which device the multicast packet arrived and sends it to all sockets.
#[cfg(target_os = "linux")]
{
use std::{io, mem, os::unix::io::AsRawFd};

unsafe {
let optval: libc::c_int = 0;
let ret = libc::setsockopt(
socket.as_raw_fd(),
libc::SOL_IP,
libc::IP_MULTICAST_ALL,
&optval as *const _ as *const libc::c_void,
mem::size_of_val(&optval) as libc::socklen_t,
);
if ret != 0 {
return Err(io::Error::last_os_error());
}
}
}

socket.join_multicast_v4(addr.ip(), nic_addr)?;

let socketaddr = SocketAddr::new(IpAddr::V4(*addr.ip()), addr.port());
socket.bind(&socket2::SockAddr::from(socketaddr))?;
log::trace!(
"Binding multicast socket to {} nic {}",
socketaddr,
nic_addr
);

Ok(())
}

/// On Windows, unlike all Unix variants, it is improper to bind to the multicast address
///
/// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550(v=vs.85).aspx
#[cfg(windows)]
fn bind_to_broadcast(
socket: &socket2::Socket,
addr: &SocketAddrV4,
nic_addr: &Ipv4Addr,
) -> io::Result<()> {
let _ = socket.set_broadcast(true);
let _ = addr; // Not used on Windows

let socketaddr = SocketAddr::new(IpAddr::V4(*nic_addr), addr.port());

socket.bind(&socket2::SockAddr::from(socketaddr))?;
log::trace!("Binding broadcast socket to {}", socketaddr);
Ok(())
}

/// On unixes we bind to the multicast address, which causes multicast packets to be filtered
#[cfg(unix)]
fn bind_to_broadcast(
socket: &socket2::Socket,
addr: &SocketAddrV4,
nic_addr: &Ipv4Addr,
) -> io::Result<()> {
let _ = socket.set_broadcast(true);
let _ = nic_addr; // Not used on Linux

socket.bind(&socket2::SockAddr::from(*addr))?;
log::trace!("Binding broadcast socket to {}", *addr);
Ok(())
}

pub fn create_udp_multicast_listen(
addr: &SocketAddrV4,
nic_addr: &Ipv4Addr,
) -> io::Result<UdpSocket> {
let socket: socket2::Socket = new_socket()?;

bind_to_multicast(&socket, addr, nic_addr)?;

let socket = UdpSocket::from_std(socket.into())?;
Ok(socket)
}

pub fn create_udp_listen(
addr: &SocketAddrV4,
nic_addr: &Ipv4Addr,
no_broadcast: bool,
) -> io::Result<UdpSocket> {
let socket: socket2::Socket = new_socket()?;

if addr.ip().is_multicast() {
bind_to_multicast(&socket, addr, nic_addr)?;
} else if !no_broadcast {
bind_to_broadcast(&socket, addr, nic_addr)?;
} else {
let socketaddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), addr.port());

socket.bind(&socket2::SockAddr::from(socketaddr))?;
log::trace!("Binding socket to {}", socketaddr);
}

let socket = UdpSocket::from_std(socket.into())?;
Ok(socket)
}

pub fn create_multicast_send(addr: &SocketAddrV4, nic_addr: &Ipv4Addr) -> io::Result<UdpSocket> {
let socket: socket2::Socket = new_socket()?;

let socketaddr = SocketAddr::new(IpAddr::V4(*addr.ip()), addr.port());
let socketaddr_nic = SocketAddr::new(IpAddr::V4(*nic_addr), addr.port());
socket.bind(&socket2::SockAddr::from(socketaddr_nic))?;
socket.connect(&socket2::SockAddr::from(socketaddr))?;

let socket = UdpSocket::from_std(socket.into())?;
Ok(socket)
}

pub fn match_ipv4(addr: &Ipv4Addr, bcast: &Ipv4Addr, netmask: &Ipv4Addr) -> bool {
let r = addr & netmask;
let b = bcast & netmask;
r == b
}

#[cfg(target_os = "macos")]
pub(crate) use macos::is_wireless_interface;
#[cfg(target_os = "macos")]
pub(crate) use macos::wait_for_ip_addr_change;

#[cfg(target_os = "linux")]
pub(crate) use linux::is_wireless_interface;
#[cfg(target_os = "linux")]
pub(crate) use linux::wait_for_ip_addr_change;

#[cfg(target_os = "windows")]
pub(crate) use windows::is_wireless_interface;
#[cfg(target_os = "windows")]
pub(crate) use windows::wait_for_ip_addr_change;
Loading

0 comments on commit 559d427

Please sign in to comment.