erbium-net-1.0.5/.cargo_vcs_info.json0000644000000001570000000000100131110ustar { "git": { "sha1": "52c81e9577d5ab4bb78bc9d7eb90d19fe9ad7f4b" }, "path_in_vcs": "crates/erbium-net" }erbium-net-1.0.5/Cargo.toml0000644000000022700000000000100111050ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "erbium-net" version = "1.0.5" authors = ["Perry Lorier "] description = "Network services for small/home networks - Low level networking abstractions" license = "Apache-2.0" [dependencies.bytes] version = ">=1.2" [dependencies.futures] version = "0.3.8" [dependencies.log] version = "0.4" [dependencies.mio] version = "0.8" features = [ "net", "os-poll", ] [dependencies.netlink-packet-core] version = ">=0.4, <=0.5" [dependencies.netlink-packet-route] version = ">=0.12, <=0.15" [dependencies.netlink-sys] version = "0.8" features = ["tokio_socket"] [dependencies.nix] version = "0.26" features = ["net"] [dependencies.tokio] version = "1.8.4" features = ["full"] erbium-net-1.0.5/Cargo.toml.orig000064400000000000000000000011361046102023000145660ustar 00000000000000[package] name = "erbium-net" authors = ["Perry Lorier "] edition = "2021" description = "Network services for small/home networks - Low level networking abstractions" version.workspace = true license.workspace = true [dependencies] bytes = { version = ">=1.2" } futures = "0.3.8" log = "0.4" mio = { version = "0.8", features=["net", "os-poll"] } netlink-packet-core = ">=0.4, <=0.5" netlink-packet-route = ">=0.12, <=0.15" netlink-sys = { version="0.8", features=["tokio_socket"] } nix = { version = "0.26", features=["net"] } tokio = { version = "1.8.4", features = ["full"] } erbium-net-1.0.5/src/addr/link.rs000064400000000000000000000072501046102023000147060ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * * We try and use the nix types here, but they're somewhat frustrating to use, for instance not * providing useful safe constructors. So we do that here. */ pub use nix::sys::socket::LinkAddr; pub type IfIndex = usize; /// physical layer protocol (Ethertype) #[derive(Debug, PartialEq, Eq, Hash)] pub struct EtherType(pub u16); impl EtherType { pub const UNSPECIFIED: EtherType = EtherType(0); pub const IP: EtherType = EtherType(0x0800); pub const ARP: EtherType = EtherType(0x0806); pub const IPV6: EtherType = EtherType(0x86DD); pub const LLDP: EtherType = EtherType(0x88CC); } impl From for u16 { fn from(e: EtherType) -> u16 { e.0 } } impl From for EtherType { fn from(e: u16) -> EtherType { EtherType(e) } } /// Arp Hardware Type (ArpHrd) #[derive(Debug, Hash, Eq, PartialEq)] pub struct ArpHrd(pub u16); #[allow(dead_code)] impl ArpHrd { const UNSPECIFIED: ArpHrd = ArpHrd(0); const ETHERNET: ArpHrd = ArpHrd(1); } impl From for u16 { fn from(a: ArpHrd) -> u16 { a.0 } } impl From for ArpHrd { fn from(u: u16) -> Self { Self(u) } } #[derive(Debug, Hash, Eq, PartialEq)] pub struct PacketType(pub u8); #[allow(dead_code)] impl PacketType { const HOST: PacketType = PacketType(0); const BROADCAST: PacketType = PacketType(1); const MULTICAST: PacketType = PacketType(2); const OTHER_HOST: PacketType = PacketType(3); const OUTGOING: PacketType = PacketType(4); const LOOPBACK: PacketType = PacketType(5); const USER: PacketType = PacketType(6); const KERNEL: PacketType = PacketType(7); const FAST_ROUTE: PacketType = PacketType(8); } impl From for u8 { fn from(p: PacketType) -> u8 { p.0 } } impl From for PacketType { fn from(p: u8) -> PacketType { Self(p) } } /// nix doesn't provide a safe way to construct LinkAddrs, so we provide our own. pub fn new_linkaddr( protocol: EtherType, ifindex: IfIndex, hatype: ArpHrd, pkttype: PacketType, addr: &[u8], ) -> LinkAddr { let mut sll_addr = [0_u8; 8]; sll_addr[..addr.len()].copy_from_slice(addr); // We are careful to use the same version of libc here as nix does to avoid type confusion. let ll = nix::libc::sockaddr_ll { sll_family: nix::libc::AF_PACKET as u16, sll_protocol: u16::to_be(protocol.into()), sll_ifindex: ifindex as i32, sll_hatype: hatype.into(), sll_pkttype: pkttype.into(), sll_halen: addr.len() as u8, sll_addr, }; unsafe { use nix::sys::socket::SockaddrLike as _; LinkAddr::from_raw( &ll as *const _ as *const _, Some( std::mem::size_of_val(&ll) as u32, /* why doesn't from_raw take a usize? */ ), ) .unwrap() } } pub fn linkaddr_for_ifindex(ifindex: IfIndex) -> LinkAddr { new_linkaddr( EtherType::UNSPECIFIED, ifindex, ArpHrd::UNSPECIFIED, PacketType::HOST, &[0; 8], ) } erbium-net-1.0.5/src/addr/mod.rs000064400000000000000000000112111046102023000145200ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * * Unfortunately, the std library address types are often woefully lacking, causing everyone to * create their own types, often missing conversions. This leads to leaking internal details * everywhere. Instead, we alias the types here, so we have one consistent set of types. */ mod link; pub use link::*; pub use nix::sys::socket::{ SockaddrIn as Inet4Addr, SockaddrIn6 as Inet6Addr, SockaddrStorage as NetAddr, UnixAddr, }; pub use std::net::{Ipv4Addr, Ipv6Addr}; pub const UNSPECIFIED6: Ipv6Addr = Ipv6Addr::UNSPECIFIED; pub const UNSPECIFIED4: Ipv4Addr = Ipv4Addr::UNSPECIFIED; pub const ALL_NODES: Ipv6Addr = Ipv6Addr::new( 0xff02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, ); pub const ALL_ROUTERS: Ipv6Addr = Ipv6Addr::new( 0xff02, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0002, ); /// Converts the socket address to a NetAddr. pub trait ToNetAddr { fn to_net_addr(&self) -> NetAddr; } impl ToNetAddr for X { fn to_net_addr(&self) -> NetAddr { use nix::sys::socket::SockaddrLike; unsafe { NetAddr::from_raw(::as_ptr(self), Some(Self::size())).unwrap() } } } pub fn tokio_to_unixaddr(src: &tokio::net::unix::SocketAddr) -> UnixAddr { if let Some(path) = src.as_pathname() { UnixAddr::new(path).unwrap() } else { unimplemented!() } } // convenience function for .map() pub fn to_net_addr(x: X) -> NetAddr { x.to_net_addr() } /// Takes an address, gives it a port, and makes a NetAddr. pub trait WithPort { fn with_port(&self, port: u16) -> NetAddr; } impl WithPort for std::net::Ipv4Addr { fn with_port(&self, port: u16) -> NetAddr { Inet4Addr::from(std::net::SocketAddrV4::new(*self, port)).to_net_addr() } } impl WithPort for std::net::Ipv6Addr { fn with_port(&self, port: u16) -> NetAddr { Inet6Addr::from(std::net::SocketAddrV6::new(*self, port, 0, 0)).to_net_addr() } } impl WithPort for std::net::IpAddr { fn with_port(&self, port: u16) -> NetAddr { match self { Self::V4(ip) => ip.with_port(port), Self::V6(ip) => ip.with_port(port), } } } // I can't implement ToSocketAddrs on nix's types directly, but nix doesn't implement them either. // (https://github.com/nix-rust/nix/issues/1799) // // I'm so very much over everyone having their own socket types. sigh. // // So this trait will be used on exactly one type - NetAddr. pub trait NetAddrExt { fn to_std_socket_addr(&self) -> Option; fn to_unix_addr(&self) -> Option; fn ip(&self) -> Option; fn port(&self) -> Option; } impl NetAddrExt for NetAddr { fn to_std_socket_addr(&self) -> Option { if let Some(&v4) = self.as_sockaddr_in() { Some(std::net::SocketAddrV4::from(v4).into()) } else if let Some(&v6) = self.as_sockaddr_in6() { Some(std::net::SocketAddrV6::from(v6).into()) } else { None } } // unixaddr is difficult to create from just a sockaddr. (https://github.com/nix-rust/nix/issues/1800) fn to_unix_addr(&self) -> Option { use nix::sys::socket::SockaddrLike; if self.family() == Some(nix::sys::socket::AddressFamily::Unix) { unsafe { UnixAddr::from_raw(::as_ptr(self), Some(Self::size())) } } else { None } } fn ip(&self) -> Option { if let Some(&v4) = self.as_sockaddr_in() { Some(std::net::Ipv4Addr::from(v4.ip()).into()) } else if let Some(&v6) = self.as_sockaddr_in6() { Some(v6.ip().into()) } else { None } } fn port(&self) -> Option { if let Some(&v4) = self.as_sockaddr_in() { Some(v4.port()) } else if let Some(&v6) = self.as_sockaddr_in6() { Some(v6.port()) } else { None } } } erbium-net-1.0.5/src/lib.rs000064400000000000000000000101031046102023000135740ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 */ pub mod addr; pub mod netinfo; pub mod packet; pub mod raw; pub mod socket; pub mod udp; /* TODO: only erbium-net should use nix and not expose this as an external API, but we've not got * there yet, so export the version we use here so that everything is always consistent. */ pub use nix; pub use nix::sys::socket::sockopt::*; // TODO: Write better Debug or to_string() method. #[derive(Clone, Copy, Debug)] pub struct Ipv4Subnet { pub addr: std::net::Ipv4Addr, pub prefixlen: u8, } #[derive(Debug)] pub enum Error { InvalidSubnet, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidSubnet => write!(f, "Invalid Subnet"), } } } impl std::fmt::Display for Ipv4Subnet { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}/{}", self.addr, self.prefixlen) } } impl Ipv4Subnet { pub fn new(addr: std::net::Ipv4Addr, prefixlen: u8) -> Result { let ret = Self { addr, prefixlen }; /* If the prefix is too short, then return an error */ if u32::from(ret.addr) & !u32::from(ret.netmask()) != 0 { Err(Error::InvalidSubnet) } else { Ok(ret) } } pub fn network(&self) -> std::net::Ipv4Addr { (u32::from(self.addr) & u32::from(self.netmask())).into() } pub fn netmask(&self) -> std::net::Ipv4Addr { (!(0xffff_ffff_u64 >> self.prefixlen) as u32).into() } pub fn contains(&self, ip: std::net::Ipv4Addr) -> bool { u32::from(ip) & u32::from(self.netmask()) == u32::from(self.addr) } pub fn broadcast(&self) -> std::net::Ipv4Addr { (u32::from(self.network()) | !u32::from(self.netmask())).into() } } #[test] fn test_netmask() -> Result<(), Error> { assert_eq!( Ipv4Subnet::new("0.0.0.0".parse().unwrap(), 0)?.netmask(), "0.0.0.0".parse::().unwrap() ); assert_eq!( Ipv4Subnet::new("0.0.0.0".parse().unwrap(), 8)?.netmask(), "255.0.0.0".parse::().unwrap() ); assert_eq!( Ipv4Subnet::new("0.0.0.0".parse().unwrap(), 16)?.netmask(), "255.255.0.0".parse::().unwrap() ); assert_eq!( Ipv4Subnet::new("0.0.0.0".parse().unwrap(), 24)?.netmask(), "255.255.255.0".parse::().unwrap() ); assert_eq!( Ipv4Subnet::new("0.0.0.0".parse().unwrap(), 25)?.netmask(), "255.255.255.128".parse::().unwrap() ); assert_eq!( Ipv4Subnet::new("0.0.0.0".parse().unwrap(), 32)?.netmask(), "255.255.255.255".parse::().unwrap() ); Ok(()) } #[test] fn test_prefix() { let subnet = Ipv4Subnet::new("192.0.2.112".parse().unwrap(), 28).unwrap(); assert_eq!( subnet.broadcast(), "192.0.2.127".parse::().unwrap() ); assert_eq!( subnet.netmask(), "255.255.255.240".parse::().unwrap() ); } #[test] fn test_contains() { assert_eq!( Ipv4Subnet::new("192.168.0.128".parse().unwrap(), 25) .unwrap() .contains("192.168.0.200".parse().unwrap()), true ); assert_eq!( Ipv4Subnet::new("192.168.0.128".parse().unwrap(), 25) .unwrap() .contains("192.168.0.100".parse().unwrap()), false ); } erbium-net-1.0.5/src/netinfo.rs000064400000000000000000000624141046102023000145040ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * * API for finding out information about interfaces. * Currently uses netlink, but ideally should eventually be generalised for other platforms. */ use netlink_packet_core::constants::*; use netlink_packet_core::NetlinkPayload::InnerMessage; use netlink_packet_core::*; use netlink_packet_route::RtnlMessage::*; use netlink_packet_route::{constants::*, AddressMessage, LinkMessage, RouteMessage, RtnlMessage}; use netlink_sys::TokioSocket as Socket; use netlink_sys::{protocols, AsyncSocket as _, AsyncSocketExt as _, SocketAddr}; #[cfg(not(test))] use log::{trace, warn}; #[cfg(test)] use {println as trace, println as warn}; #[derive(Clone, PartialEq, Eq)] pub enum LinkLayer { Ethernet([u8; 6]), None, } impl std::fmt::Debug for LinkLayer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { LinkLayer::Ethernet(e) => write!( f, "Ethernet({})", e.iter() .map(|b| format!("{:0>2x}", b)) .collect::>() .join(":") ), LinkLayer::None => write!(f, "None"), } } } #[derive(Debug, Clone, Copy)] pub struct IfFlags(u32); impl IfFlags { pub const fn has_multicast(&self) -> bool { self.0 & IFF_MULTICAST != 0 } } #[derive(Debug)] struct IfInfo { name: String, addresses: Vec<(std::net::IpAddr, u8)>, lladdr: LinkLayer, mtu: u32, //operstate: netlink_packet_route::rtnl::link::nlas::link_state::State, // Is private flags: IfFlags, } #[derive(Debug, Eq, PartialEq)] pub struct RouteInfo { pub addr: std::net::IpAddr, pub prefixlen: u8, pub oifidx: Option, pub nexthop: Option, } impl std::fmt::Display for RouteInfo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}/{}", self.addr, self.prefixlen)?; if let Some(nexthop) = self.nexthop { write!(f, " via {}", nexthop)?; } if let Some(oifidx) = self.oifidx { write!(f, " dev if#{}", oifidx)?; } Ok(()) } } #[derive(Debug)] struct NetInfo { name2idx: std::collections::HashMap, intf: std::collections::HashMap, routeinfo: Vec, } impl NetInfo { fn new() -> Self { NetInfo { name2idx: std::collections::HashMap::new(), intf: std::collections::HashMap::new(), routeinfo: vec![], } } fn add_interface(&mut self, ifidx: u32, ifinfo: IfInfo) { self.name2idx.insert(ifinfo.name.clone(), ifidx); self.intf.insert(ifidx, ifinfo); } } #[derive(Clone)] pub struct SharedNetInfo(std::sync::Arc>); fn convert_address(addr: &[u8], family: u16) -> std::net::IpAddr { match family { AF_INET => { std::net::IpAddr::V4(std::net::Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3])) } AF_INET6 => std::net::IpAddr::V6( [ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15], ] .into(), ), x => panic!("Unknown address family {:?}", x), } } struct NetLinkNetInfo {} impl NetLinkNetInfo { fn decode_linklayer(linktype: u16, addr: &[u8]) -> LinkLayer { match linktype { ARPHRD_ETHER => { LinkLayer::Ethernet([addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]]) } ARPHRD_LOOPBACK => LinkLayer::None, ARPHRD_SIT => LinkLayer::None, // Actually this is a IpAddr, but we don't do DHCP over it, so... l => { warn!("Unknown Linklayer: {:?}", l); LinkLayer::None } } } fn parse_addr(addr: &AddressMessage) -> (std::net::IpAddr, u8) { use netlink_packet_route::address::nlas::Nla::*; let mut ifaddr = None; let iffamily = addr.header.family; let ifprefixlen = addr.header.prefix_len; for i in &addr.nlas { if let Address(a) = i { ifaddr = Some(convert_address(a, iffamily.into())); } } (ifaddr.unwrap(), ifprefixlen) } async fn process_newaddr(ni: &SharedNetInfo, addr: &AddressMessage) { let ifindex = addr.header.index; let ifaddr = NetLinkNetInfo::parse_addr(addr); let mut ni = ni.0.write().await; let ii = ni.intf.get_mut(&ifindex).unwrap(); // TODO: Error? if !ii.addresses.contains(&ifaddr) { /* It's common to renew IPv6 addresses, don't treat them as new if * the address already exists. */ ii.addresses.push(ifaddr); let (ip, prefixlen) = ifaddr; trace!( "Found addr {}/{} for {}(#{}), now {:?}", ip, prefixlen, ii.name, ifindex, ii.addresses ); } } async fn process_deladdr(sni: &SharedNetInfo, addr: &AddressMessage) { let ifindex = addr.header.index; let ifaddr = NetLinkNetInfo::parse_addr(addr); let mut ni = sni.0.write().await; let ii = ni.intf.get_mut(&ifindex).unwrap(); // TODO: Error? ii.addresses.retain(|&x| x != ifaddr); let (ip, prefixlen) = ifaddr; trace!( "Lost addr {}/{} for {}(#{}), now {:?}", ip, prefixlen, ii.name, ifindex, ii.addresses ); } async fn process_newlink(sni: &SharedNetInfo, link: &LinkMessage) { use netlink_packet_route::link::nlas::Nla::*; let mut ifname: Option = None; let mut ifmtu: Option = None; let mut ifaddr = None; let ifflags = link.header.flags; let ifidx = link.header.index; for i in &link.nlas { match i { IfName(name) => ifname = Some(name.clone()), Mtu(mtu) => ifmtu = Some(*mtu), Address(addr) => ifaddr = Some(addr.clone()), _ => (), } } let ifaddr = ifaddr.map_or(LinkLayer::None, |x| { NetLinkNetInfo::decode_linklayer(link.header.link_layer_type, &x) }); let mut netinfo = sni.0.write().await; /* This might be an update to an existing interface. * (eg the interface might be changing it's oper state from down/up etc. * So preserve some information. */ let old_ifinfo = netinfo.intf.remove(&ifidx); let (old_name, old_addresses, old_mtu) = old_ifinfo .map(|x| (Some(x.name), Some(x.addresses), Some(x.mtu))) .unwrap_or((None, None, None)); let ifinfo = IfInfo { name: ifname.or(old_name).expect("Interface with unknown name"), mtu: ifmtu.or(old_mtu).expect("Interface missing MTU"), addresses: old_addresses.unwrap_or_default(), lladdr: ifaddr, flags: IfFlags(ifflags), }; trace!( "Found new interface {}(#{}) {:?} ({:?})", ifinfo.name, ifidx, ifinfo, link ); netinfo.add_interface(ifidx, ifinfo); } fn decode_route(route: &RouteMessage) -> Option { use netlink_packet_route::rtnl::nlas::route::Nla::*; use std::convert::TryFrom as _; let mut destination = None; let mut oifidx = None; let mut gateway = None; for nla in &route.nlas { match nla { Destination(dest) => { destination = match route.header.address_family as u16 { AF_INET => <[u8; 4]>::try_from(&dest[..]) .map(std::net::IpAddr::from) .ok(), AF_INET6 => <[u8; 16]>::try_from(&dest[..]) .map(std::net::IpAddr::from) .ok(), f => panic!("Unexpected family {}", f), } } Gateway(via) => { gateway = match route.header.address_family as u16 { AF_INET => <[u8; 4]>::try_from(&via[..]) .map(std::net::IpAddr::from) .ok(), AF_INET6 => <[u8; 16]>::try_from(&via[..]) .map(std::net::IpAddr::from) .ok(), f => panic!("Unexpected family {}", f), } } Oif(oif) => oifidx = Some(oif), Table(254) => (), Table(_) => return None, /* Skip routes that are not in the "main" table */ _ => (), /* Ignore unknown nlas */ } } Some(RouteInfo { addr: destination.unwrap_or_else(|| match route.header.address_family as u16 { AF_INET => "0.0.0.0".parse().unwrap(), AF_INET6 => "::".parse().unwrap(), _ => unreachable!(), }), prefixlen: route.header.destination_prefix_length, oifidx: oifidx.copied(), nexthop: gateway, }) } async fn process_newroute(sni: &SharedNetInfo, route: &RouteMessage) { if let Some(ri) = NetLinkNetInfo::decode_route(route) { trace!("New Route: {}", ri); sni.0.write().await.routeinfo.push(ri); } } async fn process_delroute(sni: &SharedNetInfo, route: &RouteMessage) { if let Some(ri) = NetLinkNetInfo::decode_route(route) { trace!("Del Route: {}", ri); /* We basically assume there will only ever be one route for each prefix. * We'd have to be a lot more careful if we were to support multiple routes to a * particular prefix */ sni.0.write().await.routeinfo.retain(|r| *r != ri); } } async fn send_linkdump(socket: &mut Socket, seq: &mut u32) { let mut hdr = NetlinkHeader::default(); hdr.flags = NLM_F_REQUEST | NLM_F_DUMP; hdr.sequence_number = *seq; let mut packet = NetlinkMessage::new( hdr, NetlinkPayload::from(RtnlMessage::GetLink(LinkMessage::default())), ); *seq += 1; packet.finalize(); let mut buf = vec![0; packet.header.length as usize]; // Before calling serialize, it is important to check that the buffer in which we're emitting is big // enough for the packet, other `serialize()` panics. assert!(buf.len() == packet.buffer_len()); packet.serialize(&mut buf[..]); socket.socket_mut().add_membership(RTNLGRP_LINK).unwrap(); if let Err(e) = socket.send(&buf[..]).await { warn!("SEND ERROR {}", e); } } async fn send_routedump(socket: &mut Socket, seq: &mut u32, address_family: u8) { let mut hdr = NetlinkHeader::default(); hdr.flags = NLM_F_REQUEST | NLM_F_DUMP; hdr.sequence_number = *seq; let mut rmsg = RouteMessage::default(); rmsg.header.address_family = address_family; let mut packet = NetlinkMessage::new(hdr, NetlinkPayload::from(RtnlMessage::GetRoute(rmsg))); *seq += 1; packet.finalize(); let mut buf = vec![0; packet.header.length as usize]; // Before calling serialize, it is important to check that the buffer in which we're emitting is big // enough for the packet, other `serialize()` panics. assert!(buf.len() == packet.buffer_len()); packet.serialize(&mut buf[..]); match address_family as u16 { AF_INET => socket .socket_mut() .add_membership(RTNLGRP_IPV4_ROUTE) .unwrap(), AF_INET6 => socket .socket_mut() .add_membership(RTNLGRP_IPV6_ROUTE) .unwrap(), _ => unreachable!(), } if let Err(e) = socket.send(&buf[..]).await { warn!("SEND ERROR {}", e); } } async fn send_addrdump(socket: &mut Socket, seq: &mut u32) { let mut hdr = NetlinkHeader::default(); hdr.flags = NLM_F_REQUEST | NLM_F_DUMP; hdr.sequence_number = *seq; let mut amsg = AddressMessage::default(); amsg.header.family = AF_PACKET as u8; let mut packet = NetlinkMessage::new(hdr, NetlinkPayload::from(RtnlMessage::GetAddress(amsg))); *seq += 1; packet.finalize(); let mut buf = vec![0; packet.header.length as usize]; // Before calling serialize, it is important to check that the buffer in which we're emitting is big // enough for the packet, other `serialize()` panics. assert!(buf.len() == packet.buffer_len()); packet.serialize(&mut buf[..]); socket .socket_mut() .add_membership(RTNLGRP_IPV4_IFADDR) .unwrap(); socket .socket_mut() .add_membership(RTNLGRP_IPV6_IFADDR) .unwrap(); if let Err(e) = socket.send(&buf[..]).await { warn!("SEND ERROR {}", e); } } async fn process_message(sni: &SharedNetInfo, rx_packet: &NetlinkMessage) -> bool { match &rx_packet.payload { InnerMessage(NewLink(link)) => { NetLinkNetInfo::process_newlink(sni, link).await; false } InnerMessage(NewAddress(addr)) => { NetLinkNetInfo::process_newaddr(sni, addr).await; false } InnerMessage(DelAddress(addr)) => { NetLinkNetInfo::process_deladdr(sni, addr).await; false } InnerMessage(NewRoute(route)) => { NetLinkNetInfo::process_newroute(sni, route).await; false } InnerMessage(DelRoute(route)) => { NetLinkNetInfo::process_delroute(sni, route).await; false } NetlinkPayload::Done => true, e => { warn!("Unknown: {:?}", e); false } } } async fn run(sni: SharedNetInfo, chan: tokio::sync::mpsc::Sender<()>) { let mut socket = Socket::new(protocols::NETLINK_ROUTE).unwrap(); let mut seq = 1; socket.socket_mut().connect(&SocketAddr::new(0, 0)).unwrap(); NetLinkNetInfo::send_linkdump(&mut socket, &mut seq).await; enum State { ReadingLink, ReadingAddr, ReadingRoute4, ReadingRoute6, Done, } let mut state = State::ReadingLink; // we set the NLM_F_DUMP flag so we expect a multipart rx_packet in response. while let Ok((pkt, _)) = socket.recv_from_full().await { let rx_packet = >::deserialize(&pkt).unwrap(); if NetLinkNetInfo::process_message(&sni, &rx_packet).await { match state { State::ReadingLink => { trace!("Finished Link"); NetLinkNetInfo::send_addrdump(&mut socket, &mut seq).await; state = State::ReadingAddr } State::ReadingAddr => { trace!("Finished Addr"); NetLinkNetInfo::send_routedump(&mut socket, &mut seq, AF_INET as u8).await; state = State::ReadingRoute4 } State::ReadingRoute4 => { trace!("Finished Route4"); NetLinkNetInfo::send_routedump(&mut socket, &mut seq, AF_INET6 as u8).await; state = State::ReadingRoute6 } State::ReadingRoute6 => { // Try and inform anyone listening that we have completed. // But if it fails, don't worry, we'll send another one soonish. trace!("Finished Route6"); let _ = chan.try_send(()); state = State::Done } State::Done => {} } } } } } impl SharedNetInfo { pub async fn new() -> Self { let (s, mut c) = tokio::sync::mpsc::channel::<()>(1); let shared = SharedNetInfo(std::sync::Arc::new( tokio::sync::RwLock::new(NetInfo::new()), )); tokio::spawn(NetLinkNetInfo::run(shared.clone(), s)); // We want to block and wait until all the data is loaded, otherwise we'll cause confusion. c.recv().await; shared } #[cfg(test)] pub fn new_for_test() -> Self { let mut ni = NetInfo::new(); ni.add_interface( 0, IfInfo { name: "lo".into(), addresses: vec![("127.0.0.1".parse().unwrap(), 8)], lladdr: LinkLayer::None, mtu: 65536, flags: IfFlags(IFF_MULTICAST), }, ); ni.add_interface( 1, IfInfo { name: "eth0".into(), addresses: vec![("192.0.2.254".parse().unwrap(), 24)], lladdr: LinkLayer::Ethernet([0x00, 0x00, 0x5E, 0x00, 0x53, 0xFF]), mtu: 1500, flags: IfFlags(IFF_MULTICAST), }, ); SharedNetInfo(std::sync::Arc::new(tokio::sync::RwLock::new(ni))) } #[allow(dead_code)] pub async fn get_interfaces(&self) -> Vec { self.0 .read() .await .intf .values() .map(|x| x.name.clone()) .collect() } pub async fn get_ifindexes(&self) -> Vec { self.0.read().await.intf.keys().copied().collect() } pub async fn get_linkaddr_by_ifidx(&self, ifidx: u32) -> Option { self.0 .read() .await .intf .get(&ifidx) .map(|x| x.lladdr.clone()) } pub async fn get_if_prefixes(&self) -> Vec<(std::net::IpAddr, u8)> { self.0 .read() .await .intf .iter() .flat_map(|(_ifidx, x)| x.addresses.clone()) .collect() } pub async fn get_prefixes_by_ifidx(&self, ifidx: u32) -> Option> { self.0 .read() .await .intf .get(&ifidx) .map(|x| x.addresses.clone()) } pub async fn get_ipv4_by_ifidx(&self, ifidx: u32) -> Option { self.get_prefixes_by_ifidx(ifidx) .await .and_then(|prefixes| { prefixes .iter() .filter_map(|(prefix, _prefixlen)| { if let std::net::IpAddr::V4(addr) = prefix { Some(addr) } else { None } }) .copied() .next() }) } pub async fn get_mtu_by_ifidx(&self, ifidx: u32) -> Option { self.0.read().await.intf.get(&ifidx).map(|x| x.mtu) } pub async fn get_name_by_ifidx(&self, ifidx: u32) -> Option { self.0.read().await.intf.get(&ifidx).map(|x| x.name.clone()) } pub async fn get_safe_name_by_ifidx(&self, ifidx: u32) -> String { match self.get_name_by_ifidx(ifidx).await { Some(ifname) => ifname, None => format!("if#{}", ifidx), } } pub async fn get_flags_by_ifidx(&self, ifidx: u32) -> Option { self.0.read().await.intf.get(&ifidx).map(|x| x.flags) } pub async fn get_ipv4_default_route( &self, ) -> Option<(Option, Option)> { self.0.read().await.routeinfo.iter().find_map(|ri| { if ri.prefixlen == 0 && ri.addr.is_ipv4() { Some(( if let Some(std::net::IpAddr::V4(nexthop)) = ri.nexthop { Some(nexthop) } else { None }, ri.oifidx, )) } else { None } }) } pub async fn get_ipv6_default_route( &self, ) -> Option<(Option, Option)> { self.0.read().await.routeinfo.iter().find_map(|ri| { if ri.prefixlen == 0 && ri.addr.is_ipv6() { Some(( if let Some(std::net::IpAddr::V6(nexthop)) = ri.nexthop { Some(nexthop) } else { None }, ri.oifidx, )) } else { None } }) } } #[tokio::test] async fn test_interface() { use netlink_packet_route::rtnl; const IFIDX: u32 = 10; let ni = SharedNetInfo::new_for_test(); let mut hdr = NetlinkHeader::default(); hdr.sequence_number = 1; let mut lmsg = LinkMessage::default(); lmsg.header.index = IFIDX; lmsg.header.link_layer_type = ARPHRD_ETHER; lmsg.nlas = vec![ rtnl::link::nlas::Nla::IfName("test1".into()), rtnl::link::nlas::Nla::Mtu(1500), rtnl::link::nlas::Nla::Address(vec![0x00, 0x53, 0x00, 0x00, 0x00, 0x00]), rtnl::link::nlas::Nla::Broadcast(vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]), ]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new(hdr, NetlinkPayload::from(RtnlMessage::NewLink(lmsg))), ) .await; hdr.sequence_number = 2; let mut amsg = AddressMessage::default(); amsg.header.index = IFIDX; amsg.header.family = AF_INET as u8; amsg.header.prefix_len = 24; amsg.nlas = vec![rtnl::address::nlas::Nla::Address(vec![192, 0, 2, 1])]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new(hdr, NetlinkPayload::from(RtnlMessage::NewAddress(amsg))), ) .await; let mut a6msg = AddressMessage::default(); a6msg.header.index = IFIDX; a6msg.header.family = AF_INET as u8; a6msg.header.prefix_len = 24; a6msg.nlas = vec![rtnl::address::nlas::Nla::Address(vec![ 0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ])]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new(hdr, NetlinkPayload::from(RtnlMessage::NewAddress(a6msg))), ) .await; assert!(ni.get_interfaces().await.contains(&"test1".into())); assert_eq!( ni.get_ipv4_by_ifidx(IFIDX).await, Some(std::net::Ipv4Addr::new(192, 0, 2, 1)) ); assert_eq!(ni.get_mtu_by_ifidx(IFIDX).await, Some(1500)); assert_eq!(ni.get_name_by_ifidx(IFIDX).await, Some("test1".to_string())); assert_eq!( ni.get_linkaddr_by_ifidx(IFIDX).await, Some(LinkLayer::Ethernet([0x00, 0x53, 0x00, 0x00, 0x00, 0x00])) ); /* It's common to get a second NewLink, make sure we preserve the addresses */ hdr.sequence_number = 1; let mut lmsg = LinkMessage::default(); lmsg.header.index = IFIDX; lmsg.header.link_layer_type = ARPHRD_ETHER; lmsg.nlas = vec![ rtnl::link::nlas::Nla::IfName("test1".into()), rtnl::link::nlas::Nla::Mtu(1501), rtnl::link::nlas::Nla::Address(vec![0x00, 0x53, 0x00, 0x00, 0x00, 0x01]), rtnl::link::nlas::Nla::Broadcast(vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE]), ]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new(hdr, NetlinkPayload::from(RtnlMessage::NewLink(lmsg))), ) .await; /* Did this disturb the data that was already there? */ assert_eq!( { let mut v = ni.get_interfaces().await; v.sort(); v }, vec!["eth0".to_string(), "lo".to_string(), "test1".to_string()] ); assert_eq!( ni.get_ipv4_by_ifidx(IFIDX).await, Some(std::net::Ipv4Addr::new(192, 0, 2, 1)) ); assert_eq!(ni.get_mtu_by_ifidx(IFIDX).await, Some(1501)); assert_eq!(ni.get_name_by_ifidx(IFIDX).await, Some("test1".to_string()),); assert_eq!( ni.get_linkaddr_by_ifidx(IFIDX).await, Some(LinkLayer::Ethernet([0x00, 0x53, 0x00, 0x00, 0x00, 0x01])) ); } #[tokio::test] async fn test_netinfo_startup() { // Initialise netinfo and make sure it doesn't block indefinately on startup. println!("about to start"); let _ = SharedNetInfo::new().await; println!("new complete"); } erbium-net-1.0.5/src/packet.rs000064400000000000000000000135541046102023000143120ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * * Functions to create raw packets as a [u8] */ use crate::addr::Inet4Addr; use std::net; const fn partial_netsum(current: u32, buffer: &[u8]) -> u32 { let mut i = 0; let mut sum = current; let mut count = buffer.len(); while count > 1 { let v = ((buffer[i] as u32) << 8) | (buffer[i + 1] as u32); sum += v; i += 2; count -= 2; } if count > 0 { let v = (buffer[i] as u32) << 8; sum += v; } sum } const fn finish_netsum(sum: u32) -> u16 { let mut sum = sum; while sum > 0xffff { sum = (sum >> 16) + (sum & 0xFFFF); } !(sum as u16) } #[derive(Clone, Debug)] pub enum Tail<'a> { Payload(&'a [u8]), Fragment(Box>), #[allow(dead_code)] None, } impl<'a> Tail<'a> { fn len(&self) -> usize { match self { Tail::Payload(x) => x.len(), Tail::Fragment(x) => x.len(), Tail::None => 0, } } fn partial_netsum(&self, current: u32) -> u32 { match self { Tail::Payload(x) => partial_netsum(current, x), Tail::Fragment(x) => x.partial_netsum(current), Tail::None => current, } } } #[derive(Clone, Debug)] pub struct Fragment<'a> { buffer: Vec, tail: Tail<'a>, } impl<'a> Fragment<'a> { fn len(&self) -> usize { self.buffer.len() + self.tail.len() } fn partial_netsum(&self, current: u32) -> u32 { self.tail .partial_netsum(partial_netsum(current, &self.buffer)) } fn netsum(&self) -> u16 { finish_netsum(self.partial_netsum(0)) } pub fn flatten(&self) -> Vec { let mut x = self; let mut ret = vec![]; loop { ret.extend_from_slice(&x.buffer); match &x.tail { Tail::None => break, Tail::Payload(x) => { ret.extend_from_slice(x); break; } Tail::Fragment(f) => { x = f.as_ref(); } } } ret } const fn from_tail(tail: Tail) -> Fragment { Fragment { buffer: vec![], tail, } } fn push_u8(&mut self, b: u8) { self.buffer.push(b); } fn push_bytes(&mut self, b: &[u8]) { self.buffer.extend_from_slice(b); } fn push_be16(&mut self, b: u16) { self.push_bytes(&b.to_be_bytes()); } fn new_ethernet<'l>( dst: &[u8; 6], src: &[u8; 6], ethertype: u16, payload: Tail<'l>, ) -> Fragment<'l> { let mut f = Fragment::from_tail(payload); f.push_bytes(dst); f.push_bytes(src); f.push_be16(ethertype); f } fn new_ipv4<'l>( src: &net::Ipv4Addr, srcmac: &[u8; 6], dst: &net::Ipv4Addr, dstmac: &[u8; 6], protocol: u8, payload: Tail<'l>, ) -> Fragment<'l> { let mut f = Fragment::from_tail(payload); f.push_u8(0x45); /* version 4, length 5*4 bytes */ f.push_u8(0x00); /* ToS */ f.push_be16(20_u16 + f.tail.len() as u16); /* Total Length */ f.push_be16(0x0000); /* Identification */ f.push_be16(0x0000); /* Flags + Frag Offset */ f.push_u8(0x01); /* TTL */ f.push_u8(protocol); f.push_be16(0x0000); /* Checksum - filled in below*/ f.push_bytes(&src.octets()); f.push_bytes(&dst.octets()); let netsum = finish_netsum(partial_netsum(0, &f.buffer)); f.buffer[10] = (netsum >> 8) as u8; f.buffer[11] = (netsum & 0xFF) as u8; Self::new_ethernet(dstmac, srcmac, 0x0800_u16, Tail::Fragment(Box::new(f))) } pub fn new_udp4<'l>( src: Inet4Addr, srcmac: &[u8; 6], dst: Inet4Addr, dstmac: &[u8; 6], payload: Tail<'l>, ) -> Fragment<'l> { let mut f = Self::from_tail(payload); f.push_be16(src.port()); f.push_be16(dst.port()); f.push_be16(8_u16 + f.tail.len() as u16); /* Length */ f.push_be16(0x0000); /* TODO: Checksum */ let l = f.len(); let mut pseudohdr = Self::from_tail(Tail::Fragment(Box::new(f.clone()))); let udp_protocol: u8 = 17; pseudohdr.push_bytes(&u32::to_be_bytes(src.ip())); pseudohdr.push_bytes(&u32::to_be_bytes(dst.ip())); pseudohdr.push_u8(0x00_u8); pseudohdr.push_u8(udp_protocol); pseudohdr.push_be16(l as u16); let netsum = pseudohdr.netsum(); f.buffer[6] = (netsum >> 8) as u8; f.buffer[7] = (netsum & 0xFF) as u8; let t = Tail::Fragment(Box::new(f.clone())); Self::new_ipv4( &src.ip().into(), srcmac, &dst.ip().into(), dstmac, udp_protocol, t, ) } } #[test] fn test_udp_packet() { let u = Fragment::new_udp4( "192.0.2.1:1".parse().unwrap(), &[2, 0, 0, 0, 0, 0], "192.0.2.2:2".parse().unwrap(), &[2, 0, 0, 0, 0, 1], Tail::Payload(&[1, 2, 3, 4]), ); println!("u={:?}", u); } #[test] fn test_checksum() { let data = vec![8, 0, 0, 0, 0x12, 0x34, 0x00, 0x01]; assert_eq!(finish_netsum(partial_netsum(0, &data)), 0xE5CA); } erbium-net-1.0.5/src/raw.rs000064400000000000000000000160371046102023000136330ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * * Low level functions to create/use an async raw socket. */ use nix::sys::socket; use std::io; use std::os::unix::io::AsRawFd; use std::os::unix::io::RawFd; use tokio::io::unix::AsyncFd; use crate::{addr::NetAddr, udp}; use nix::libc; pub type Error = std::io::Error; pub type Result = std::result::Result; pub type MsgFlags = socket::MsgFlags; pub use std::io::{IoSlice, IoSliceMut}; /* These should be refactored out somewhere */ pub type ControlMessage = udp::ControlMessage; #[derive(Copy, Clone)] pub struct IpProto(u8); impl IpProto { pub const ICMP: IpProto = IpProto(1); pub const TCP: IpProto = IpProto(6); pub const UDP: IpProto = IpProto(17); pub const ICMP6: IpProto = IpProto(58); } impl From for u8 { fn from(ipp: IpProto) -> Self { ipp.0 } } impl From for u16 { fn from(ipp: IpProto) -> Self { ipp.0 as u16 } } #[derive(Copy, Clone)] pub struct EthProto(u16); impl EthProto { pub const IP4: EthProto = EthProto(0x0800); pub const ALL: EthProto = EthProto(0x0003); pub const LLDP: EthProto = EthProto(0x88cc); } #[derive(Debug)] pub struct RawSocket { fd: AsyncFd, } impl AsRawFd for RawSocket { fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } } impl RawSocket { pub fn new(protocol: EthProto) -> Result { Ok(Self { fd: AsyncFd::new(crate::socket::new_socket( libc::AF_PACKET, libc::SOCK_RAW, protocol.0.to_be() as libc::c_int, )?)?, }) } #[allow(dead_code)] pub fn send(&self, buf: &[u8], flags: MsgFlags) -> Result { socket::send(self.as_raw_fd(), buf, flags).map_err(|e| e.into()) } pub async fn recv_msg( &self, bufsize: usize, flags: MsgFlags, ) -> io::Result { crate::socket::recv_msg(&self.fd, bufsize, flags).await } pub async fn send_msg( &self, buffer: &[u8], cmsg: &ControlMessage, flags: MsgFlags, addr: Option<&NetAddr>, ) -> io::Result<()> { crate::socket::send_msg(&self.fd, buffer, cmsg, flags, addr).await } pub fn set_socket_option( &self, opt: O, val: &O::Val, ) -> Result<()> { nix::sys::socket::setsockopt(self.as_raw_fd(), opt, val).map_err(|e| e.into()) } } #[derive(Debug)] pub struct CookedRawSocket { fd: AsyncFd, } impl AsRawFd for CookedRawSocket { fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } } impl CookedRawSocket { pub fn new(protocol: EthProto) -> Result { Ok(Self { fd: AsyncFd::new(crate::socket::new_socket( libc::AF_PACKET, libc::SOCK_RAW, protocol.0 as libc::c_int, )?)?, }) } #[allow(dead_code)] pub fn send(&self, buf: &[u8], flags: MsgFlags) -> Result { socket::send(self.as_raw_fd(), buf, flags).map_err(|e| e.into()) } pub async fn recv_msg( &self, bufsize: usize, flags: MsgFlags, ) -> io::Result { crate::socket::recv_msg(&self.fd, bufsize, flags).await } pub async fn send_msg( &self, buffer: &[u8], cmsg: &ControlMessage, flags: MsgFlags, addr: Option<&NetAddr>, ) -> io::Result<()> { crate::socket::send_msg(&self.fd, buffer, cmsg, flags, addr).await } pub fn set_socket_option( &self, opt: O, val: &O::Val, ) -> Result<()> { nix::sys::socket::setsockopt(self.as_raw_fd(), opt, val).map_err(|e| e.into()) } } #[derive(Debug)] pub struct Raw6Socket { fd: AsyncFd, } impl AsRawFd for Raw6Socket { fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } } impl Raw6Socket { pub fn new(protocol: IpProto) -> Result { Ok(Self { fd: AsyncFd::new(crate::socket::new_socket( libc::AF_INET6, libc::SOCK_RAW, protocol.0 as libc::c_int, )?)?, }) } #[allow(dead_code)] pub fn send(&self, buf: &[u8], flags: MsgFlags) -> Result { socket::send(self.as_raw_fd(), buf, flags).map_err(|e| e.into()) } pub async fn recv_msg( &self, bufsize: usize, flags: MsgFlags, ) -> io::Result { crate::socket::recv_msg(&self.fd, bufsize, flags).await } pub async fn send_msg( &self, buffer: &[u8], cmsg: &ControlMessage, flags: MsgFlags, addr: Option<&NetAddr>, ) -> io::Result<()> { crate::socket::send_msg(&self.fd, buffer, cmsg, flags, addr).await } pub fn set_socket_option( &self, opt: O, val: &O::Val, ) -> Result<()> { nix::sys::socket::setsockopt(self.as_raw_fd(), opt, val).map_err(|e| e.into()) } } #[derive(Debug)] pub struct Raw4Socket { fd: AsyncFd, } impl AsRawFd for Raw4Socket { fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } } impl Raw4Socket { pub fn new(protocol: IpProto) -> Result { Ok(Self { fd: AsyncFd::new(crate::socket::new_socket( libc::AF_INET, libc::SOCK_RAW, protocol.0 as libc::c_int, )?)?, }) } #[allow(dead_code)] pub fn send(&self, buf: &[u8], flags: MsgFlags) -> Result { socket::send(self.as_raw_fd(), buf, flags).map_err(|e| e.into()) } pub async fn recv_msg( &self, bufsize: usize, flags: MsgFlags, ) -> io::Result { crate::socket::recv_msg(&self.fd, bufsize, flags).await } pub async fn send_msg( &self, buffer: &[u8], cmsg: &ControlMessage, flags: MsgFlags, addr: Option<&NetAddr>, ) -> io::Result<()> { crate::socket::send_msg(&self.fd, buffer, cmsg, flags, addr).await } pub fn set_socket_option( fd: RawFd, opt: O, val: &O::Val, ) -> Result<()> { nix::sys::socket::setsockopt(fd, opt, val).map_err(|e| e.into()) } } erbium-net-1.0.5/src/socket.rs000064400000000000000000000265201046102023000143300ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * * Common Socket Traits */ use crate::addr::NetAddr; use std::os::unix::io::{OwnedFd, RawFd}; pub fn std_to_libc_in_addr(addr: std::net::Ipv4Addr) -> libc::in_addr { libc::in_addr { s_addr: addr .octets() .iter() .fold(0, |acc, x| ((acc << 8) | (*x as u32))), } } pub const fn std_to_libc_in6_addr(addr: std::net::Ipv6Addr) -> libc::in6_addr { libc::in6_addr { s6_addr: addr.octets(), } } pub type MsgFlags = nix::sys::socket::MsgFlags; pub use std::io::{IoSlice, IoSliceMut}; use nix::libc; #[derive(Debug)] pub struct ControlMessage { pub send_from: Option, /* private, used to hold memory after conversions */ pktinfo4: libc::in_pktinfo, pktinfo6: libc::in6_pktinfo, } impl ControlMessage { pub fn new() -> Self { Self { send_from: None, pktinfo4: libc::in_pktinfo { ipi_ifindex: 0, /* Unspecified interface */ ipi_addr: std_to_libc_in_addr(std::net::Ipv4Addr::UNSPECIFIED), ipi_spec_dst: std_to_libc_in_addr(std::net::Ipv4Addr::UNSPECIFIED), }, pktinfo6: libc::in6_pktinfo { ipi6_ifindex: 0, /* Unspecified interface */ ipi6_addr: std_to_libc_in6_addr(std::net::Ipv6Addr::UNSPECIFIED), }, } } #[must_use] pub const fn set_send_from(mut self, send_from: Option) -> Self { self.send_from = send_from; self } #[must_use] pub const fn set_src4_intf(mut self, intf: u32) -> Self { self.pktinfo4.ipi_ifindex = intf as i32; self } #[must_use] pub const fn set_src6_intf(mut self, intf: u32) -> Self { self.pktinfo6.ipi6_ifindex = intf; self } pub fn convert_to_cmsg(&mut self) -> Vec { let mut cmsgs: Vec = vec![]; if let Some(addr) = self.send_from { match addr { std::net::IpAddr::V4(ip) => { self.pktinfo4.ipi_spec_dst = std_to_libc_in_addr(ip); cmsgs.push(nix::sys::socket::ControlMessage::Ipv4PacketInfo( &self.pktinfo4, )) } std::net::IpAddr::V6(ip) => { self.pktinfo6.ipi6_addr = std_to_libc_in6_addr(ip); cmsgs.push(nix::sys::socket::ControlMessage::Ipv6PacketInfo( &self.pktinfo6, )) } } } cmsgs } } impl Default for ControlMessage { fn default() -> Self { Self::new() } } #[derive(Debug)] pub struct RecvMsg { pub buffer: Vec, pub address: Option, /* TODO: These should probably return std types */ /* Or possibly have accessors that convert them for you */ /* either way, we shouldn't be exporting nix types here */ timestamp: Option, ipv4pktinfo: Option, ipv6pktinfo: Option, } impl RecvMsg { fn new(m: nix::sys::socket::RecvMsg, buffer: Vec) -> RecvMsg { let mut r = RecvMsg { buffer, address: m.address, timestamp: None, ipv4pktinfo: None, ipv6pktinfo: None, }; for cmsg in m.cmsgs() { use nix::sys::socket::ControlMessageOwned; match cmsg { ControlMessageOwned::ScmTimestamp(rtime) => { r.timestamp = Some(rtime); } ControlMessageOwned::Ipv4PacketInfo(pi) => { r.ipv4pktinfo = Some(pi); } ControlMessageOwned::Ipv6PacketInfo(pi) => { r.ipv6pktinfo = Some(pi); } x => log::warn!("Unknown control message {:?}", x), } } r } /// Returns the local address of the packet. /// /// This is primarily used by UDP sockets to tell you which address a packet arrived on when /// the UDP socket is bound to INADDR_ANY or IN6ADDR_ANY. pub const fn local_ip(&self) -> Option { // This function can be overridden to provide different implementations for different // platforms. // if let Some(pi) = self.ipv6pktinfo { // Oh come on, this conversion is even more ridiculous than the last one! Some(std::net::IpAddr::V6(std::net::Ipv6Addr::new( (pi.ipi6_addr.s6_addr[0] as u16) << 8 | (pi.ipi6_addr.s6_addr[1] as u16), (pi.ipi6_addr.s6_addr[2] as u16) << 8 | (pi.ipi6_addr.s6_addr[3] as u16), (pi.ipi6_addr.s6_addr[4] as u16) << 8 | (pi.ipi6_addr.s6_addr[5] as u16), (pi.ipi6_addr.s6_addr[6] as u16) << 8 | (pi.ipi6_addr.s6_addr[7] as u16), (pi.ipi6_addr.s6_addr[8] as u16) << 8 | (pi.ipi6_addr.s6_addr[9] as u16), (pi.ipi6_addr.s6_addr[10] as u16) << 8 | (pi.ipi6_addr.s6_addr[11] as u16), (pi.ipi6_addr.s6_addr[12] as u16) << 8 | (pi.ipi6_addr.s6_addr[13] as u16), (pi.ipi6_addr.s6_addr[14] as u16) << 8 | (pi.ipi6_addr.s6_addr[15] as u16), ))) } else if let Some(pi) = self.ipv4pktinfo { let ip = pi.ipi_addr.s_addr.to_ne_bytes(); // This is already in big endian form, don't try and perform a conversion. // It is a pity I haven't found a nicer way to do this conversion. Some(std::net::IpAddr::V4(std::net::Ipv4Addr::new( ip[0], ip[1], ip[2], ip[3], ))) } else { None } } pub fn local_intf(&self) -> Option { if let Some(pi) = self.ipv6pktinfo { Some(pi.ipi6_ifindex as i32) } else { self.ipv4pktinfo.map(|pi| pi.ipi_ifindex) } } } #[derive(Debug)] pub struct SocketFd { fd: OwnedFd, } impl std::os::unix::io::AsRawFd for SocketFd { fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } } pub fn new_socket( domain: libc::c_int, ty: libc::c_int, protocol: libc::c_int, ) -> Result { // I would love to use the nix socket() wrapper, except, uh, it has a closed enum. // See https://github.com/nix-rust/nix/issues/854 // // So I have to use the libc version directly. unsafe { use std::os::unix::io::FromRawFd as _; let fd = libc::socket( domain, ty | libc::SOCK_CLOEXEC | libc::SOCK_NONBLOCK, protocol, ); if fd == -1 { return Err(std::io::Error::last_os_error()); } Ok(SocketFd { fd: OwnedFd::from_raw_fd(fd as RawFd), }) } } pub async fn recv_msg( sock: &tokio::io::unix::AsyncFd, bufsize: usize, flags: MsgFlags, ) -> Result { let mut ev = sock.readable().await?; let mut buf = Vec::new(); buf.resize_with(bufsize, Default::default); let iov = &mut [IoSliceMut::new(buf.as_mut_slice())]; let mut cmsg = Vec::new(); cmsg.resize_with(65536, Default::default); /* TODO: Calculate a more reasonable size */ let mut flags = flags; flags.set(MsgFlags::MSG_DONTWAIT, true); match nix::sys::socket::recvmsg(sock.get_ref().as_raw_fd(), iov, Some(&mut cmsg), flags) { Ok(rm) => { let buf = rm.iovs().next().unwrap(); ev.retain_ready(); Ok(RecvMsg::new(rm, buf.into())) } Err(e) if e == nix::errno::Errno::EAGAIN => { ev.clear_ready(); Err(e.into()) } Err(e) => { ev.retain_ready(); Err(e.into()) } } } /// This function makes an async stream out of calling recv_msg repeatedly. pub fn recv_msg_stream( sock: &tokio::io::unix::AsyncFd, bufsize: usize, flags: MsgFlags, ) -> impl futures::stream::Stream> + '_ where F: std::os::unix::io::AsRawFd, { futures::stream::unfold((), move |()| async move { Some((recv_msg::(sock, bufsize, flags).await, ())) }) } pub async fn send_msg( sock: &tokio::io::unix::AsyncFd, buffer: &[u8], cmsg: &ControlMessage, flags: MsgFlags, from: Option<&NetAddr>, ) -> std::io::Result<()> { let mut ev = sock.writable().await?; let iov = &[IoSlice::new(buffer)]; let mut cmsgs: Vec = vec![]; let mut in_pktinfo = cmsg.pktinfo4; let mut in6_pktinfo = cmsg.pktinfo6; if let Some(addr) = cmsg.send_from { match addr { std::net::IpAddr::V4(ip) => { in_pktinfo.ipi_spec_dst = std_to_libc_in_addr(ip); cmsgs.push(nix::sys::socket::ControlMessage::Ipv4PacketInfo( &in_pktinfo, )) } std::net::IpAddr::V6(ip) => { in6_pktinfo.ipi6_addr = std_to_libc_in6_addr(ip); cmsgs.push(nix::sys::socket::ControlMessage::Ipv6PacketInfo( &in6_pktinfo, )) } } } else if in6_pktinfo.ipi6_ifindex != 0 { cmsgs.push(nix::sys::socket::ControlMessage::Ipv6PacketInfo( &in6_pktinfo, )); } else if in_pktinfo.ipi_ifindex != 0 { cmsgs.push(nix::sys::socket::ControlMessage::Ipv4PacketInfo( &in_pktinfo, )); } match nix::sys::socket::sendmsg(sock.get_ref().as_raw_fd(), iov, &cmsgs, flags, from) { Ok(_) => { ev.retain_ready(); Ok(()) } Err(nix::errno::Errno::EAGAIN) => { ev.clear_ready(); Err(nix::errno::Errno::EAGAIN.into()) } Err(e) => { ev.retain_ready(); Err(e.into()) } } } pub fn set_ipv6_unicast_hoplimit(fd: RawFd, val: i32) -> Result<(), nix::Error> { unsafe { let res = libc::setsockopt( fd, libc::IPPROTO_IPV6, libc::IPV6_UNICAST_HOPS, &val as *const i32 as *const libc::c_void, std::mem::size_of::() as libc::socklen_t, ); nix::errno::Errno::result(res).map(drop) } } pub fn set_ipv6_multicast_hoplimit(fd: RawFd, val: i32) -> Result<(), nix::Error> { unsafe { let res = libc::setsockopt( fd, libc::IPPROTO_IPV6, libc::IPV6_MULTICAST_HOPS, &val as *const i32 as *const libc::c_void, std::mem::size_of::() as libc::socklen_t, ); nix::errno::Errno::result(res).map(drop) } } erbium-net-1.0.5/src/udp.rs000064400000000000000000000075371046102023000136370ustar 00000000000000/* Copyright 2023 Perry Lorier * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 * * An async UdpSocket type with recvmsg / sendmsg support. */ // We desperately need recvmsg / sendmsg support, and rust doesn't support it, so we need *yet // another* udp socket type. // // This should not export libc::, nix:: or tokio:: types, only std:: and it's own types to insulate // the rest of the program of the horrors of portability. use crate::addr::NetAddr; use std::convert::TryFrom; use std::io; use std::net; use std::os::unix::io::AsRawFd as _; use tokio::io::unix::AsyncFd; use nix::libc; pub struct UdpSocket { fd: AsyncFd, } pub type MsgFlags = crate::socket::MsgFlags; pub type ControlMessage = crate::socket::ControlMessage; pub type RecvMsg = crate::socket::RecvMsg; pub fn std_to_libc_in_addr(addr: net::Ipv4Addr) -> libc::in_addr { libc::in_addr { s_addr: addr .octets() .iter() .fold(0, |acc, x| ((acc << 8) | (*x as u32))), } } pub const fn std_to_libc_in6_addr(addr: net::Ipv6Addr) -> libc::in6_addr { libc::in6_addr { s6_addr: addr.octets(), } } impl TryFrom for UdpSocket { type Error = io::Error; fn try_from(s: mio::net::UdpSocket) -> Result { Ok(UdpSocket { fd: AsyncFd::new(s)?, }) } } impl UdpSocket { pub async fn bind(addrs: &[NetAddr]) -> Result { use crate::addr::NetAddrExt as _; let mut last_err = None; for addr in addrs { match mio::net::UdpSocket::bind(addr.to_std_socket_addr().unwrap()) { Ok(socket) => return Self::try_from(socket), Err(e) => last_err = Some(e), } } Err(last_err.unwrap_or_else(|| { io::Error::new( io::ErrorKind::InvalidInput, "could not resolve to any address", ) })) } pub async fn recv_msg(&self, bufsize: usize, flags: MsgFlags) -> io::Result { crate::socket::recv_msg(&self.fd, bufsize, flags).await } pub async fn send_msg( &self, buffer: &[u8], cmsg: &ControlMessage, flags: MsgFlags, addr: Option<&NetAddr>, ) -> io::Result<()> { crate::socket::send_msg(&self.fd, buffer, cmsg, flags, addr).await } pub fn local_addr(&self) -> Result { self.fd.get_ref().local_addr().map(|x| x.into()) } pub fn set_opt_ipv4_packet_info(&self, b: bool) -> Result<(), io::Error> { nix::sys::socket::setsockopt( self.fd.get_ref().as_raw_fd(), nix::sys::socket::sockopt::Ipv4PacketInfo, &b, ) .map_err(|e| e.into()) } pub fn set_opt_ipv6_packet_info(&self, b: bool) -> Result<(), io::Error> { nix::sys::socket::setsockopt( self.fd.get_ref().as_raw_fd(), nix::sys::socket::sockopt::Ipv6RecvPacketInfo, &b, ) .map_err(|e| e.into()) } pub fn set_opt_reuse_port(&self, b: bool) -> Result<(), io::Error> { nix::sys::socket::setsockopt( self.fd.get_ref().as_raw_fd(), nix::sys::socket::sockopt::ReusePort, &b, ) .map_err(|e| e.into()) } }