Skip to content

Commit

Permalink
feat(net): implement local address detection (#822)
Browse files Browse the repository at this point in the history
Based on the work done in `x-hp`.
  • Loading branch information
dignifiedquire committed Mar 9, 2023
1 parent 55a4c3a commit 9323e10
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 10 deletions.
103 changes: 93 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ bytes = "1"
clap = { version = "4", features = ["derive"], optional = true }
console = { version = "0.15.5", optional = true }
data-encoding = { version = "2.3.3", optional = true }
default-net = "0.12.0"
der = { version = "0.6", features = ["alloc", "derive"] }
derive_more = "0.99.17"
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
Expand Down
6 changes: 6 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ expression = "MIT AND ISC AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 },
]

[advisories]
ignore = [
# Waiting on https://github.com/shellrow/default-net/issues/19
"RUSTSEC-2022-0008",
]
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub mod protocol;
pub mod provider;
pub mod rpc_protocol;

pub mod net;

mod tls;
mod util;

Expand Down
160 changes: 160 additions & 0 deletions src/net.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Networking related utilities

use std::net::{IpAddr, Ipv6Addr};

const IFF_UP: u32 = 0x1;
const IFF_LOOPBACK: u32 = 0x8;

/// List of machine's IP addresses.
#[derive(Debug, Clone, PartialEq)]
pub struct LocalAddresses {
/// Loopback addresses.
pub loopback: Vec<IpAddr>,
/// Regular addresses.
pub regular: Vec<IpAddr>,
}

impl Default for LocalAddresses {
fn default() -> Self {
Self::new()
}
}

impl LocalAddresses {
/// Returns the machine's IP addresses.
/// If there are no regular addresses it will return any IPv4 linklocal or IPv6 unique local
/// addresses because we know of environments where these are used with NAT to provide connectivity.
pub fn new() -> Self {
let ifaces = default_net::interface::get_interfaces();

let mut loopback = Vec::new();
let mut regular4 = Vec::new();
let mut regular6 = Vec::new();
let mut linklocal4 = Vec::new();
let mut ula6 = Vec::new();

for iface in ifaces {
if !is_up(&iface) {
// Skip down interfaces
continue;
}
let ifc_is_loopback = is_loopback(&iface);
let addrs = iface
.ipv4
.iter()
.map(|a| IpAddr::V4(a.addr))
.chain(iface.ipv6.iter().map(|a| IpAddr::V6(a.addr)));

for ip in addrs {
let ip = to_canonical(ip);

if ip.is_loopback() || ifc_is_loopback {
loopback.push(ip);
} else if is_link_local(ip) {
if ip.is_ipv4() {
linklocal4.push(ip);
}

// We know of no cases where the IPv6 fe80:: addresses
// are used to provide WAN connectivity. It is also very
// common for users to have no IPv6 WAN connectivity,
// but their OS supports IPv6 so they have an fe80::
// address. We don't want to report all of those
// IPv6 LL to Control.
} else if ip.is_ipv6() && is_private(&ip) {
// Google Cloud Run uses NAT with IPv6 Unique
// Local Addresses to provide IPv6 connectivity.
ula6.push(ip);
} else if ip.is_ipv4() {
regular4.push(ip);
} else {
regular6.push(ip);
}
}
}

if regular4.is_empty() && regular6.is_empty() {
// if we have no usable IP addresses then be willing to accept
// addresses we otherwise wouldn't, like:
// + 169.254.x.x (AWS Lambda uses NAT with these)
// + IPv6 ULA (Google Cloud Run uses these with address translation)
regular4 = linklocal4;
regular6 = ula6;
}
let mut regular = regular4;
regular.extend(regular6);

regular.sort();
loopback.sort();

LocalAddresses { loopback, regular }
}
}

const fn is_up(interface: &default_net::Interface) -> bool {
interface.flags & IFF_UP != 0
}

const fn is_loopback(interface: &default_net::Interface) -> bool {
interface.flags & IFF_LOOPBACK != 0
}

/// Reports whether ip is a private address, according to RFC 1918
/// (IPv4 addresses) and RFC 4193 (IPv6 addresses). That is, it reports whether
/// ip is in 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, or fc00::/7.
fn is_private(ip: &IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => {
// RFC 1918 allocates 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 as
// private IPv4 address subnets.
let octets = ip.octets();
octets[0] == 10
|| (octets[0] == 172 && octets[1] & 0xf0 == 16)
|| (octets[0] == 192 && octets[1] == 168)
}
IpAddr::V6(ip) => is_private_v6(ip),
}
}

fn is_private_v6(ip: &Ipv6Addr) -> bool {
// RFC 4193 allocates fc00::/7 as the unique local unicast IPv6 address subnet.
ip.octets()[0] & 0xfe == 0xfc
}

fn is_link_local(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => ip.is_link_local(),
IpAddr::V6(ip) => is_unicast_link_local(ip),
}
}

// TODO: replace with IpAddr::to_canoncial once stabilized.
fn to_canonical(ip: IpAddr) -> IpAddr {
match ip {
ip @ IpAddr::V4(_) => ip,
IpAddr::V6(ip) => {
if let Some(ip) = ip.to_ipv4_mapped() {
IpAddr::V4(ip)
} else {
IpAddr::V6(ip)
}
}
}
}
// Copied from std lib, not stable yet
const fn is_unicast_link_local(addr: Ipv6Addr) -> bool {
(addr.segments()[0] & 0xffc0) == 0xfe80
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_local_addresses() {
let addrs = LocalAddresses::new();
dbg!(&addrs);
assert!(!addrs.loopback.is_empty());
assert!(!addrs.regular.is_empty());
}
}

0 comments on commit 9323e10

Please sign in to comment.