Skip to main content

tiny_dlna/
net.rs

1//! Picking the local IP address other devices should use to reach us.
2
3use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
4
5/// The local IPv4 address used to route packets to `target`.
6/// No packet is sent: connecting a UDP socket only selects a route.
7pub fn local_ip_towards(target: IpAddr) -> Option<Ipv4Addr> {
8    let sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).ok()?;
9    sock.connect(SocketAddr::new(target, 9)).ok()?;
10    match sock.local_addr().ok()?.ip() {
11        IpAddr::V4(ip) if !ip.is_unspecified() && !ip.is_loopback() => Some(ip),
12        _ => None,
13    }
14}
15
16/// A best-guess LAN address of this host, used when no route-based answer
17/// is available. Prefers 192.168.x.x, then other private ranges, then any
18/// non-loopback address; within a class the smallest address wins, which
19/// usually skips VM bridges like 192.168.65.1.
20pub fn host_ip() -> Option<Ipv4Addr> {
21    let mut ips: Vec<Ipv4Addr> = if_addrs::get_if_addrs()
22        .ok()?
23        .into_iter()
24        .filter_map(|iface| match iface.ip() {
25            IpAddr::V4(ip) if !ip.is_loopback() && !ip.is_link_local() => Some(ip),
26            _ => None,
27        })
28        .collect();
29    let rank = |ip: &Ipv4Addr| match ip.octets() {
30        [192, 168, ..] => 0,
31        _ if ip.is_private() => 1,
32        _ => 2,
33    };
34    ips.sort_by_key(|ip| (rank(ip), *ip));
35    ips.first().copied()
36}
37
38/// The address `peer` should use to reach this host.
39pub fn local_ip_for(peer: IpAddr) -> Option<Ipv4Addr> {
40    local_ip_towards(peer).or_else(host_ip)
41}