erbium-net-1.0.8/.cargo_vcs_info.json0000644000000001571046102023000131360ustar { "git": { "sha1": "b749e02790238af1568a8c8a4da7f46a8a9fd4b7" }, "path_in_vcs": "crates/erbium-net" }erbium-net-1.0.8/Cargo.lock0000644000000265721046102023000111220ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "erbium-net" version = "1.0.8" dependencies = [ "bytes", "futures", "log", "mio", "netlink-packet-core", "netlink-packet-route", "netlink-sys", "nix", "tokio", ] [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys", ] [[package]] name = "futures" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "slab", ] [[package]] name = "libc" version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "mio" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", "wasi", "windows-sys", ] [[package]] name = "netlink-packet-core" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ "anyhow", "byteorder", "netlink-packet-utils", ] [[package]] name = "netlink-packet-route" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0800eae8638a299eaa67476e1c6b6692922273e0f7939fd188fc861c837b9cd2" dependencies = [ "anyhow", "bitflags", "byteorder", "libc", "log", "netlink-packet-core", "netlink-packet-utils", ] [[package]] name = "netlink-packet-utils" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" dependencies = [ "anyhow", "byteorder", "paste", "thiserror", ] [[package]] name = "netlink-sys" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" dependencies = [ "bytes", "futures-util", "libc", "log", "tokio", ] [[package]] name = "nix" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", "memoffset", ] [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "signal-hook-registry" version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ "errno", "libc", ] [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", "windows-sys", ] [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio" version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-macros" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] erbium-net-1.0.8/Cargo.toml0000644000000026521046102023000111360ustar # 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 = "2024" name = "erbium-net" version = "1.0.8" authors = ["Perry Lorier "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Network services for small/home networks - Low level networking abstractions" readme = false license = "Apache-2.0" resolver = "2" [lib] name = "erbium_net" path = "src/lib.rs" [dependencies.bytes] version = ">=1.2" [dependencies.futures] version = "0.3.8" [dependencies.log] version = "0.4" [dependencies.mio] version = ">=0.8,<=1.1" features = [ "net", "os-poll", ] [dependencies.netlink-packet-core] version = ">=0.4, <=0.7" [dependencies.netlink-packet-route] version = ">=0.20, <=0.23" [dependencies.netlink-sys] version = "0.8" features = ["tokio_socket"] [dependencies.nix] version = ">=0.29,<=0.31" features = [ "net", "socket", "uio", "fs", ] [dependencies.tokio] version = "1.8.4" features = ["full"] erbium-net-1.0.8/Cargo.toml.orig000064400000000000000000000012061046102023000145670ustar 00000000000000[package] name = "erbium-net" authors = ["Perry Lorier "] edition = "2024" 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,<=1.1", features=["net", "os-poll"] } netlink-packet-core = ">=0.4, <=0.7" netlink-packet-route = ">=0.20, <=0.23" netlink-sys = { version="0.8", features=["tokio_socket"] } nix = { version = ">=0.29,<=0.31", features=["net", "socket", "uio", "fs"] } tokio = { version = "1.8.4", features = ["full"] } erbium-net-1.0.8/src/addr/link.rs000064400000000000000000000072501046102023000147110ustar 00000000000000/* Copyright 2024 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.8/src/addr/mod.rs000064400000000000000000000102251046102023000145270ustar 00000000000000/* Copyright 2024 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.len())).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 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 } } fn ip(&self) -> Option { if let Some(&v4) = self.as_sockaddr_in() { Some(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.8/src/lib.rs000064400000000000000000000100411046102023000136000ustar 00000000000000/* Copyright 2024 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!( Ipv4Subnet::new("192.168.0.128".parse().unwrap(), 25) .unwrap() .contains("192.168.0.200".parse().unwrap()) ); assert!( !Ipv4Subnet::new("192.168.0.128".parse().unwrap(), 25) .unwrap() .contains("192.168.0.100".parse().unwrap()) ); } erbium-net-1.0.8/src/netinfo.rs000064400000000000000000000633111046102023000145040ustar 00000000000000/* Copyright 2024 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::NetlinkPayload::InnerMessage; use netlink_packet_core::constants::*; use netlink_packet_core::*; use netlink_packet_route::RouteNetlinkMessage::*; use netlink_packet_route::{ AddressFamily, RouteNetlinkMessage, address::{AddressAttribute, AddressMessage}, link::{LinkAttribute, LinkFlags, LinkLayerType, LinkMessage}, route::{RouteAddress, RouteAttribute, RouteMessage}, }; use netlink_sys::TokioSocket as Socket; use netlink_sys::{AsyncSocket as _, AsyncSocketExt as _, SocketAddr, protocols}; // These were removed in https://github.com/rust-netlink/netlink-packet-route/commit/88b1348cc0a257c55e520cae3bde3c66d5bc65a3 with no obvious replacement. // I imagine that .add_membership() will eventually be cleaned up to require some new type and // these will be redundant then. const RTNLGRP_LINK: u32 = 1; const RTNLGRP_IPV4_IFADDR: u32 = 5; const RTNLGRP_IPV4_ROUTE: u32 = 7; const RTNLGRP_IPV6_IFADDR: u32 = 9; const RTNLGRP_IPV6_ROUTE: u32 = 11; #[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)] pub struct IfFlags(LinkFlags); impl IfFlags { pub fn has_multicast(&self) -> bool { self.0.contains(LinkFlags::Multicast) } } #[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>); struct NetLinkNetInfo {} impl NetLinkNetInfo { fn decode_linklayer(linktype: LinkLayerType, addr: &[u8]) -> LinkLayer { match linktype { LinkLayerType::Ether => { LinkLayer::Ethernet([addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]]) } LinkLayerType::Loopback => LinkLayer::None, LinkLayerType::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) { let mut ifaddr = None; let ifprefixlen = addr.header.prefix_len; for i in &addr.attributes { if let AddressAttribute::Address(a) = i { ifaddr = Some(*a) } } (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) { let mut ifname: Option = None; let mut ifmtu: Option = None; let mut ifaddr = None; let ifflags = link.header.flags.clone(); let ifidx = link.header.index; for i in &link.attributes { match i { LinkAttribute::IfName(name) => ifname = Some(name.clone()), LinkAttribute::Mtu(mtu) => ifmtu = Some(*mtu), LinkAttribute::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 { let mut destination = None; let mut oifidx = None; let mut gateway = None; for nla in &route.attributes { match nla { RouteAttribute::Destination(dest) => { destination = match dest { RouteAddress::Inet(addr) => Some((*addr).into()), RouteAddress::Inet6(addr) => Some((*addr).into()), f => panic!("Unexpected family {:?}", f), } } RouteAttribute::Gateway(via) => { gateway = match via { RouteAddress::Inet(addr) => Some((*addr).into()), RouteAddress::Inet6(addr) => Some((*addr).into()), f => panic!("Unexpected family {:?}", f), } } RouteAttribute::Oif(oif) => oifidx = Some(oif), RouteAttribute::Table(254) => (), RouteAttribute::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 { AddressFamily::Inet => "0.0.0.0".parse().unwrap(), AddressFamily::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::InnerMessage(RouteNetlinkMessage::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 = netlink_packet_route::AddressFamily::Other(address_family); let mut packet = NetlinkMessage::new( hdr, NetlinkPayload::InnerMessage(RouteNetlinkMessage::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.into() { AddressFamily::Inet => socket .socket_mut() .add_membership(RTNLGRP_IPV4_ROUTE) .unwrap(), AddressFamily::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 = AddressFamily::Packet; let mut packet = NetlinkMessage::new( hdr, NetlinkPayload::InnerMessage(RouteNetlinkMessage::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 mut offset = 0; while offset < pkt.len() { let rx_packet = >::deserialize(&pkt[offset..]).unwrap(); offset += rx_packet.header.length as usize; 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, AddressFamily::Inet.into(), ) .await; state = State::ReadingRoute4 } State::ReadingRoute4 => { trace!("Finished Route4"); NetLinkNetInfo::send_routedump( &mut socket, &mut seq, AddressFamily::Inet6.into(), ) .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(LinkFlags::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(LinkFlags::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 .values() .flat_map(|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.clone()) } 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() { 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 = LinkLayerType::Ether; lmsg.attributes = vec![ LinkAttribute::IfName("test1".into()), LinkAttribute::Mtu(1500), LinkAttribute::Address(vec![0x00, 0x53, 0x00, 0x00, 0x00, 0x00]), LinkAttribute::Broadcast(vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]), ]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new( hdr, NetlinkPayload::from(RouteNetlinkMessage::NewLink(lmsg)), ), ) .await; hdr.sequence_number = 2; let mut amsg = AddressMessage::default(); amsg.header.index = IFIDX; amsg.header.family = AddressFamily::Inet; amsg.header.prefix_len = 24; amsg.attributes = vec![AddressAttribute::Address("192.0.2.1".parse().unwrap())]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new( hdr, NetlinkPayload::from(RouteNetlinkMessage::NewAddress(amsg)), ), ) .await; let mut a6msg = AddressMessage::default(); a6msg.header.index = IFIDX; a6msg.header.family = AddressFamily::Inet6; a6msg.header.prefix_len = 24; a6msg.attributes = vec![AddressAttribute::Address("2001:db8::".parse().unwrap())]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new( hdr, NetlinkPayload::from(RouteNetlinkMessage::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 = LinkLayerType::Ether; lmsg.attributes = vec![ LinkAttribute::IfName("test1".into()), LinkAttribute::Mtu(1501), LinkAttribute::Address(vec![0x00, 0x53, 0x00, 0x00, 0x00, 0x01]), LinkAttribute::Broadcast(vec![0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE]), ]; NetLinkNetInfo::process_message( &ni, &NetlinkMessage::new( hdr, NetlinkPayload::from(RouteNetlinkMessage::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.8/src/packet.rs000064400000000000000000000133711046102023000143120ustar 00000000000000/* Copyright 2024 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(&src.ip().octets()); pseudohdr.push_bytes(&dst.ip().octets()); 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(), srcmac, &dst.ip(), 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.8/src/raw.rs000064400000000000000000000164151046102023000136360ustar 00000000000000/* Copyright 2024 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 std::os::fd::AsFd for RawSocket { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { self.fd.as_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, 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.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 std::os::fd::AsFd for Raw6Socket { fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> { self.fd.as_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, 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: std::os::fd::BorrowedFd, opt: O, val: &O::Val, ) -> Result<()> { nix::sys::socket::setsockopt(&fd, opt, val).map_err(|e| e.into()) } } erbium-net-1.0.8/src/socket.rs000064400000000000000000000274021046102023000143330ustar 00000000000000/* Copyright 2024 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, }; match m.cmsgs() { Ok(cmsgs) => { for cmsg in 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), } } } Err(err) => { log::warn!("Failed to decode control message: {:?}", err); } } 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) => { if let Some(buf) = rm.iovs().next() { ev.retain_ready(); Ok(RecvMsg::new(rm, buf.into())) } else { // Zero length datagram ev.retain_ready(); Ok(RecvMsg::new(rm, vec![])) } } 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.8/src/udp.rs000064400000000000000000000071701046102023000136330ustar 00000000000000/* Copyright 2024 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 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, 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, 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, nix::sys::socket::sockopt::ReusePort, &b) .map_err(|e| e.into()) } }