diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index bcd31f458d1acd..2f6149848cc1e0 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -72,6 +72,22 @@ config DUMMY To compile this driver as a module, choose M here: the module will be called dummy. +config DUMMY_RS + tristate "Dummy net driver support" + depends on HAS_RUST + help + This is essentially a bit-bucket device (i.e. traffic you send to + this device is consigned into oblivion) with a configurable IP + address. It is most commonly used in order to make your currently + inactive SLIP address seem like a real address for local programs. + If you use SLIP or PPP, you might want to say Y here. It won't + enlarge your kernel. What a deal. Read about it in the Network + Administrator's Guide, available from + . + + To compile this driver as a module, choose M here: the module + will be called dummy_rs. + config WIREGUARD tristate "WireGuard secure network tunnel" depends on NET && INET diff --git a/drivers/net/Makefile b/drivers/net/Makefile index f4990ff32fa4cc..8c2f8e10e49617 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_BONDING) += bonding/ obj-$(CONFIG_IPVLAN) += ipvlan/ obj-$(CONFIG_IPVTAP) += ipvlan/ obj-$(CONFIG_DUMMY) += dummy.o +obj-$(CONFIG_DUMMY_RS) += dummy_rs.o obj-$(CONFIG_WIREGUARD) += wireguard/ obj-$(CONFIG_EQUALIZER) += eql.o obj-$(CONFIG_IFB) += ifb.o diff --git a/drivers/net/dummy_rs.rs b/drivers/net/dummy_rs.rs new file mode 100644 index 00000000000000..736a9e790e8284 --- /dev/null +++ b/drivers/net/dummy_rs.rs @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rust dummy network driver +//! +//! This is a demonstration of what a small driver looks like in Rust, based on drivers/net/dummy.c. +//! This code is provided as a demonstration only, not as a proposal to mass-rewrite existing drivers in Rust +//! +//! The purpose of this driver is to provide a device to point a +//! route through, but not to actually transmit packets. +//! +//! Why? If you have a machine whose only connection is an occasional +//! PPP/SLIP/PLIP link, you can only connect to your own hostname +//! when the link is up. Otherwise you have to use localhost. +//! This isn't very consistent. +//! +//! One solution is to set up a dummy link using PPP/SLIP/PLIP, +//! but this seems (to me) too much overhead for too little gain. +//! This driver provides a small alternative. Thus you can do +//! +//! [when not running slip] +//! ifconfig dummy slip.addr.ess.here up +//! [to go to slip] +//! ifconfig dummy down +//! dip whatever +//! +//! This was written by looking at the dummy network driver from Nick +//! Holloway, which was written by looking at Donald Becker's skeleton driver +//! and the loopback driver. +//! +//! Finn Behrens, 30th April 2021 +//! +//! rust rewrite of the C version from Nick Holloway, 27th May 1994 +//! see [dummy.c](./dummy.c) + +#![no_std] +#![feature(allocator_api, global_asm)] + +use kernel::net::device; +use kernel::net::prelude::*; +use kernel::net::rtnl; +use kernel::Error; +use kernel::{ + net::netlink::{NlAttrVec, NlExtAck}, + prelude::*, +}; + +module! { + type: RustNetDummy, + name: b"dummy_rs", + author: b"Rust for Linux Contributors", + description: b"Rust dummy network driver", + license: b"GPL v2", + alias_rtnl_link: b"dummy_rs", + params: { + numdummies: usize { + default: 0, + permissions: 0, + description: b"Number of dummy_rs pseudo devices", + }, + }, +} + +fn setup(dev: &mut NetDevice) { + dev.ether_setup(); + + dev.set_ops(); + + // Fill in device structure with ethernet-generic values. + dev.add_flag(device::Iff::NOARP); + dev.remove_flag(device::Iff::MULTICAST); + + dev.add_private_flag(device::IffPriv::LIVE_ADDR_CHANGE); + dev.add_private_flag(device::IffPriv::NO_QUEUE); + + let mut feature = device::feature::NetIF::new(); + + feature += device::feature::NETIF_F_SG; + feature += device::feature::NETIF_F_FRAGLIST; + feature += device::feature::NETIF_F_GSO_SOFTWARE; + feature += device::feature::NETIF_F_HW_CSUM; + feature += device::feature::NETIF_F_HIGHDMA; + feature += device::feature::NETIF_F_LLTX; + feature += device::feature::NETIF_F_GSO_ENCAP_ALL; + + dev.set_features(feature); + dev.set_hw_features(feature); + dev.set_hw_enc_features(feature); + + dev.hw_addr_random(); + dev.set_mtu(0, 0); +} + +fn validate(tb: &NlAttrVec, _data: &NlAttrVec, _ext_ack: &NlExtAck) -> Result { + if let Some(addr) = tb.get(kernel::bindings::IFLA_ADDRESS) { + if Some(kernel::net::netlink::ETH_ALEN) != addr.nla_len() { + return Err(Error::EINVAL); + } + if !addr.is_valid_ether_addr() { + return Err(Error::EADDRNOTAVAIL); + } + } + Ok(()) +} + +rtnl_link_ops! { + kind: b"dummy_rs", + type: DummyRsDev, + setup: setup, + validate: validate, +} + +struct RustNetDummy { + //dev: NetDevice, +} + +impl KernelModule for RustNetDummy { + fn init() -> Result { + let num = *numdummies.read(); + + unsafe { dummy_rs_link_ops.register() }?; + + for _ in 0..(num) { + let dev = NetDevice::new( + DummyRsDev, + kernel::cstr!("dummyrs%d"), + kernel::net::device::NetNameAssingType::Enum, + 1, + 1, + )?; + dev.set_rtnl_ops(unsafe { &dummy_rs_link_ops }); + + if let Err(e) = dev.register() { + pr_warn!("could not register: {}", e.to_kernel_errno()); + return Err(e); + } + } + + Ok(RustNetDummy { + //dev, + }) + } +} + +impl Drop for RustNetDummy { + fn drop(&mut self) { + // TODO: remove unsafe somehow + unsafe { dummy_rs_link_ops.unregister() }; + } +} + +struct DummyRsDev; + +impl NetDeviceOps for DummyRsDev { + kernel::declare_net_device_ops!( + get_stats64, + change_carrier, + validate_addr, + set_mac_addr, + set_rx_mode + ); + + fn init(dev: &mut NetDevice) -> Result { + dev.set_new_pcpu_lstats()?; + Ok(()) + } + + fn uninit(dev: &mut NetDevice) { + unsafe { dev.free_lstats() }; + } + + fn start_xmit(skb: SkBuff, dev: &mut NetDevice) -> kernel::net::device::NetdevTX { + let mut skb = skb; + + dev.lstats_add(skb.len()); + + skb.tx_timestamp(); + drop(skb); + + kernel::net::device::NetdevTX::TX_OK + } + + fn get_stats64(dev: &NetDevice, stats: &mut rtnl::RtnlLinkStats64) { + stats.dev_read(dev); + } + + fn change_carrier(dev: &mut NetDevice, new_carrier: bool) -> Result { + dev.carrier_set(new_carrier); + + Ok(()) + } + + fn validate_addr(dev: &NetDevice) -> Result { + device::helpers::eth_validate_addr(dev) + } + + fn set_mac_addr(dev: &mut NetDevice, p: *mut kernel::c_types::c_void) -> Result { + device::helpers::eth_mac_addr(dev, p) + } + + // [Someting about faking multicast](https://elixir.bootlin.com/linux/v5.12-rc4/source/drivers/net/dummy.c#L48). + fn set_rx_mode(_dev: &mut NetDevice) {} +} + +impl NetDeviceAdapter for DummyRsDev { + type Inner = Self; + + type Ops = Self; + + type EthOps = Self; + + fn setup(dev: &mut NetDevice) { + setup(dev); + } +} + +impl EthToolOps for DummyRsDev { + kernel::declare_eth_tool_ops!(get_drvinfo, get_ts_info); + + fn get_drvinfo(_dev: &NetDevice, info: &mut ethtool::EthtoolDrvinfo) { + // TODO: how to do this more efficient without unsafe? + // FIXME: !! + let info: &kernel::bindings::ethtool_drvinfo = info.get_internal(); + unsafe { + kernel::bindings::strlcpy( + &(info.driver) as *const _ as *mut i8, + b"dummy_rs\0" as *const _ as *mut i8, + 32, + ); + } + } + + fn get_ts_info(dev: &NetDevice, info: &mut ethtool::EthToolTsInfo) -> Result { + kernel::net::ethtool::helpers::ethtool_op_get_ts_info(dev, info) + } +} diff --git a/rust/helpers.c b/rust/helpers.c index f38ed02438ae9b..a3ea572c416dd1 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include void rust_helper_BUG(void) { @@ -105,8 +108,36 @@ size_t rust_helper_copy_to_iter(const void *addr, size_t bytes, struct iov_iter } EXPORT_SYMBOL_GPL(rust_helper_copy_to_iter); -#if !defined(CONFIG_ARM) +void *rust_helper_netdev_priv(struct net_device *dev) +{ + return netdev_priv(dev); +} +EXPORT_SYMBOL_GPL(rust_helper_netdev_priv); + +void rust_helper_eth_hw_addr_random(struct net_device *dev) +{ + eth_hw_addr_random(dev); +} +EXPORT_SYMBOL_GPL(rust_helper_eth_hw_addr_random); + +int rust_helper_net_device_set_new_lstats(struct net_device *dev) +{ + dev->lstats = netdev_alloc_pcpu_stats(struct pcpu_lstats); + if (!dev->lstats) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL_GPL(rust_helper_net_device_set_new_lstats); + +void rust_helper_dev_lstats_add(struct net_device *dev, unsigned int len) +{ + dev_lstats_add(dev, len); +} +EXPORT_SYMBOL_GPL(rust_helper_dev_lstats_add); + // See https://github.com/rust-lang/rust-bindgen/issues/1671 +#if !defined(CONFIG_ARM) static_assert(__builtin_types_compatible_p(size_t, uintptr_t), "size_t must match uintptr_t, what architecture is this??"); #endif diff --git a/rust/kernel/bindings_helper.h b/rust/kernel/bindings_helper.h index ec052e54350fd9..af3a190471e1ed 100644 --- a/rust/kernel/bindings_helper.h +++ b/rust/kernel/bindings_helper.h @@ -2,6 +2,12 @@ #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -17,3 +23,5 @@ // `bindgen` gets confused at certain things const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO; + +const int BINDINGS_NLA_HDRLEN = NLA_HDRLEN; diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 27043dc8778d75..d1b19e9ba6d9da 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -6,7 +6,7 @@ use crate::{bindings, c_types}; use alloc::{alloc::AllocError, collections::TryReserveError}; -use core::{num::TryFromIntError, str::Utf8Error}; +use core::{convert::TryFrom, num::TryFromIntError, str::Utf8Error}; /// Generic integer kernel error. /// @@ -48,6 +48,9 @@ impl Error { /// Interrupted system call. pub const EINTR: Self = Error(-(bindings::EINTR as i32)); + /// Cannot assign requested address + pub const EADDRNOTAVAIL: Self = Error(-(bindings::EADDRNOTAVAIL as i32)); + /// Creates an [`Error`] from a kernel error code. pub fn from_kernel_errno(errno: c_types::c_int) -> Error { Error(errno) @@ -104,3 +107,27 @@ impl From for Error { Error::ENOMEM } } + +#[doc(hidden)] +/// Used by the rtnl_link_ops macro to interface with C +pub fn c_from_kernel_result(r: Result) -> T +where + T: TryFrom, + T::Error: core::fmt::Debug, +{ + match r { + Ok(v) => v, + Err(e) => T::try_from(e.to_kernel_errno()).unwrap(), + } +} + +#[doc(hidden)] +#[macro_export] +/// Used by the rtnl_link_ops macro to interface with C +macro_rules! c_from_kernel_result { + ($($tt:tt)*) => {{ + $crate::c_from_kernel_result((|| { + $($tt)* + })()) + }}; +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index da488d67776ae2..6b8655122a5a4e 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -43,6 +43,7 @@ mod error; pub mod file; pub mod file_operations; pub mod miscdev; +pub mod net; pub mod pages; pub mod linked_list; @@ -65,7 +66,7 @@ pub mod iov_iter; mod types; pub mod user_ptr; -pub use crate::error::{Error, Result}; +pub use crate::error::{c_from_kernel_result, Error, Result}; pub use crate::types::{CStr, Mode}; /// Page size defined in terms of the `PAGE_SHIFT` macro from C. diff --git a/rust/kernel/net/device.rs b/rust/kernel/net/device.rs new file mode 100644 index 00000000000000..227c349589bc51 --- /dev/null +++ b/rust/kernel/net/device.rs @@ -0,0 +1,1037 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Net Device Operations. +//! +//! C header: [`include/linux/netdevice.h`](../../../../include/linux/netdevice.h) + +use core::{marker, mem, ptr}; + +use crate::bindings; +use crate::c_from_kernel_result; +use crate::error::{Error, Result}; +use crate::types::{SavedAsPointer, SavedAsPointerMut}; +use crate::{c_types, CStr}; + +use super::ethtool::EthToolOps; +use super::rtnl::{RtnlLinkStats64, RtnlLock}; +use super::skbuff::SkBuff; + +extern "C" { + #[allow(improper_ctypes)] + fn rust_helper_netdev_priv(dev: *const bindings::net_device) -> *mut c_types::c_void; + + #[allow(improper_ctypes)] + fn rust_helper_eth_hw_addr_random(dev: *const bindings::net_device); + + #[allow(improper_ctypes)] + fn rust_helper_net_device_set_new_lstats(dev: *mut bindings::net_device) -> c_types::c_int; + + #[allow(improper_ctypes)] + fn rust_helper_dev_lstats_add(dev: *mut bindings::net_device, len: u32); +} + +/// interface name assignment types (sysfs name_assign_type attribute). +#[repr(u8)] +pub enum NetNameAssingType { + /// Unknown network name assing type. + Unknown = bindings::NET_NAME_UNKNOWN as u8, + /// Enum network name assing type. + Enum = bindings::NET_NAME_ENUM as u8, +} + +unsafe extern "C" fn setup_netdev_callback(dev: *mut bindings::net_device) { + let mut dev = NetDevice::::from_pointer(dev); + + T::setup(&mut dev); +} + +/// Wraps the kernel's `struct net_device`. +/// +/// # Invariants +/// +/// The pointer [`NetDevice::ptr`] is non-null and valid. +#[repr(transparent)] +pub struct NetDevice { + ptr: *const bindings::net_device, + priv_data: marker::PhantomData, +} + +unsafe impl Sync for NetDevice {} + +impl NetDevice { + /// Allocate and create a new NetDevice with private data T. + /// This function locks [`RtnlLock`]. + pub fn new( + priv_data: T, + format_name: CStr<'static>, + name_assign_type: NetNameAssingType, + txqs: u32, + rxqs: u32, + ) -> Result { + let _lock = RtnlLock::lock(); + // SAFETY: Lock is hold. + let dev = unsafe { Self::new_locked(priv_data, format_name, name_assign_type, txqs, rxqs) }; + dev + } + + /// Allocate and create a new NetDevice with private data T. + /// No lock is accuired by this function, therefor this function is unsafe. + /// + /// # Safety + /// + /// The caller has to hold the [`RtnlLock`]. + pub unsafe fn new_locked( + priv_data: T, + format_name: CStr<'static>, + name_assign_type: NetNameAssingType, + txqs: u32, + rxqs: u32, + ) -> Result { + if txqs < 1 || rxqs < 1 { + return Err(Error::EINVAL); + } + let size = mem::size_of::() as i32; + + let ptr = bindings::alloc_netdev_mqs( + size, + format_name.as_ptr() as _, + name_assign_type as u8, + Some(setup_netdev_callback::), + txqs, + rxqs, + ); + if ptr.is_null() { + return Err(Error::ENOMEM); + } + + if size != 0 { + let dest = rust_helper_netdev_priv(ptr) as *mut T; + ptr::write(dest, priv_data); + } + + Ok(Self { + ptr, + priv_data: marker::PhantomData::, + }) + } + + /// Return a refference to the private data of the [`NetDevice`]. + pub fn get_priv_data(&self) -> &T { + // SAFETY: self.ptr is valid if self is valid + let priv_ptr = unsafe { rust_helper_netdev_priv(self.ptr) } as *mut T; + + // SAFETY: ptr is valid and of type T + unsafe { priv_ptr.as_ref() }.unwrap() + } + + /// Return a mutable refference of the private data of the [`NetDevice`] + pub fn get_priv_data_mut(&mut self) -> &mut T { + // SAFETY: self.ptr is valid if self is valid + let priv_ptr = unsafe { rust_helper_netdev_priv(self.ptr) as *mut T }; + + // SAFETY: ptr is valid and of type T + unsafe { priv_ptr.as_mut().unwrap() } + } + + /// Setup Ethernet network device. + /// + /// Fill in the fields of the device structure with Ethernet-generic values. + pub fn ether_setup(&mut self) { + // SAFETY: self.ptr is valid + unsafe { bindings::ether_setup(self.ptr as *mut bindings::net_device) } + } + + /// Generate software assigned random Ethernet and set device flag. + /// + /// Generate a random Ethernet address (MAC) to be used by a net device + /// and set addr_assign_type so the state can be read by sysfs and be + /// used by userspace. + pub fn hw_addr_random(&mut self) { + // SAFETY: self.ptr is valid + unsafe { rust_helper_eth_hw_addr_random(self.ptr) }; + } + + /// Register a network device. + /// + /// Take a completed network device structure and add it to the kernel + /// interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier + /// chain. + /// + /// This is a wrapper around register_netdevice that takes the rtnl semaphore + /// and expands the device name if you passed a format string to + /// alloc_netdev. + pub fn register(&self) -> Result { + // SAFETY: self.ptr is valid + let err = unsafe { bindings::register_netdev(self.ptr as *mut bindings::net_device) }; + + if err != 0 { + Err(Error::from_kernel_errno(err)) + } else { + Ok(()) + } + } + + /// Register a network device if the RtnlLock is already hold. + /// + /// Take a completed network device structure and add it to the kernel + /// interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier + /// chain. 0 is returned on success. A negative errno code is returned + /// on a failure to set up the device, or if the name is a duplicate. + /// + /// Callers must hold the rtnl semaphore. You may want + /// [`register`] instead of this. + /// + /// BUGS: + /// The locking appears insufficient to guarantee two parallel registers + /// will not get the same name. + /// + /// # Safety + /// + /// caller must hold the RtnlLock and semaphore + pub unsafe fn register_locked(&self) -> Result { + let err = bindings::register_netdevice(self.ptr as *mut bindings::net_device); + + if err != 0 { + Err(Error::from_kernel_errno(err)) + } else { + Ok(()) + } + } + + /// Set the rtnl_link_ops to a network interface. + /// + /// Takes a static mut created with [`crate::net::rtnl_link_ops!`] and assing it to [`self`]. + pub fn set_rtnl_ops(&self, ops: &'static super::rtnl::RtnlLinkOps) { + // get rtnl_lock + let _lock = RtnlLock::lock(); + + // SAFETY: lock is hold + unsafe { self.set_rtnl_ops_locked(ops) } + } + + /// Set the rtnl_link_ops to a network interface, while the caller holds the rtnl_lock. + /// + /// Takes a static mut created with [`crate::net::rtnl_link_ops!`] and assing it to self. + /// + /// # Safety + /// + /// The caller has to hold the [`RtnlLock`]. + pub unsafe fn set_rtnl_ops_locked(&self, ops: &'static super::rtnl::RtnlLinkOps) { + // SAFETY: prt is valid if self is valid + let mut dev = (self.ptr as *mut bindings::net_device).as_mut().unwrap(); + + dev.rtnl_link_ops = ops.get_pointer() as *mut bindings::rtnl_link_ops; + } + + /// Add a [`Iff`] flag to the [`NetDevice`]. + pub fn add_flag(&mut self, flag: Iff) { + // SAFETY: prt is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut().unwrap() }; + + dev.flags |= flag as u32; + } + + /// Remove a [`Iff`] flag from the [`NetDevice`]. + pub fn remove_flag(&mut self, flag: Iff) { + // SAFETY: prt is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut().unwrap() }; + + dev.flags &= !(flag as u32); + } + + /// Add a [`IffPriv`] private_flag to the [`NetDevice`]. + pub fn add_private_flag(&mut self, flag: IffPriv) { + // SAFETY: prt is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut().unwrap() }; + + dev.priv_flags |= flag as u32; + } + + /// Remove a [`IffPriv`] private_flag from the [`NetDevice`]. + pub fn remove_private_flag(&mut self, flag: IffPriv) { + // SAFETY: prt is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut().unwrap() }; + + dev.priv_flags &= !(flag as u32); + } + + /// Set a [`feature::NetIF`] `feature` set to the [`NetDevice`]. + pub fn set_features(&mut self, features: feature::NetIF) { + // SAFETY: ptr is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut() }.unwrap(); + + dev.features = features.into(); + } + + /// Get the [`feature::NetIF`] `feature` set from the [`NetDevice`]. + pub fn get_features(&self) -> feature::NetIF { + // SAFETY: ptr is valid if self is valid + let dev = unsafe { self.ptr.as_ref() }.unwrap(); + + feature::NetIF::from(dev.features) + } + + /// Set a [`feature::NetIF`] `hw_feature` set to the [`NetDevice`]. + pub fn set_hw_features(&mut self, features: feature::NetIF) { + // SAFETY: ptr is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut() }.unwrap(); + + dev.hw_features = features.into(); + } + + /// Get the [`feature::NetIF`] `hw_feature` set from the [`NetDevice`]. + pub fn get_hw_features(&self) -> feature::NetIF { + // SAFETY: ptr is valid if self is valid + let dev = unsafe { self.ptr.as_ref() }.unwrap(); + + feature::NetIF::from(dev.hw_features) + } + + /// Set a [`feature::NetIF`] `hw_enc_feature` set to the [`NetDevice`]. + pub fn set_hw_enc_features(&mut self, features: feature::NetIF) { + // SAFETY: ptr is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut() }.unwrap(); + + dev.hw_enc_features = features.into(); + } + + /// Get the [`feature::NetIF`] `hw_enc_feature` set from the [`NetDevice`]. + pub fn get_hw_enc_features(&self) -> feature::NetIF { + // SAFETY: ptr is valid if self is valid + let dev = unsafe { self.ptr.as_ref() }.unwrap(); + + feature::NetIF::from(dev.hw_enc_features) + } + + /// Set mut for the [`NetDevice`]. + pub fn set_mtu(&mut self, min: u32, max: u32) { + // SAFETY: prt is valid if self is valid + let mut dev = unsafe { (self.ptr as *mut bindings::net_device).as_mut().unwrap() }; + + dev.min_mtu = min; + dev.max_mtu = max; + } + + /// Create a new `pcpu_lstats` struct and assing it to the [`NetDevice`]. + // This is more or less a workaround, as I did not find a way to create a pcpu marco + // and assing some value to the anonymous union. + pub fn set_new_pcpu_lstats(&mut self) -> Result { + // SAFETY: calling c function + let ret = + unsafe { rust_helper_net_device_set_new_lstats(self.ptr as *mut bindings::net_device) }; + + if ret != 0 { + Err(Error::from_kernel_errno(ret)) + } else { + Ok(()) + } + } + + /// Free the lstats field. + /// # Safety + /// + /// Only call when the same device had set_new_pcpu_lstats called + pub unsafe fn free_lstats(&mut self) { + // SAFETY: self.ptr->lstats is valid if self is valid + let net_device: &bindings::net_device = self.get_internal(); + + let lstats = net_device.__bindgen_anon_1.lstats; + if !lstats.is_null() { + bindings::free_percpu(lstats as *mut _) + } + } + + /// Add a value the the internal lstats. + pub fn lstats_add(&mut self, len: u32) { + // SAFETD: calling c function + unsafe { + rust_helper_dev_lstats_add(self.ptr as *mut bindings::net_device, len); + } + } + + /// Set carrier. + pub fn carrier_set(&mut self, status: bool) { + // SAFETY: self.ptr is valid if self is valid + if status { + unsafe { bindings::netif_carrier_on(self.ptr as *mut bindings::net_device) } + } else { + unsafe { bindings::netif_carrier_off(self.ptr as *mut bindings::net_device) } + } + } + + /// Set `netdev_ops` and `ethtool_ops` from the [`NetDeviceAdapter`] T to the [`NetDevice`]. + /// This also sets `needs_free_netdev` to true. + pub fn set_ops(&mut self) { + let internal = self.get_internal_mut(); + internal.needs_free_netdev = true; + // SAFETY: T is valid for this netdevice, so build is valid. + unsafe { + internal.netdev_ops = NetDeviceOperationsVtable::::build(); + internal.ethtool_ops = super::ethtool::EthToolOperationsVtable::::build(); + } + } +} + +impl SavedAsPointer for NetDevice { + type InternalType = bindings::net_device; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { + ptr, + priv_data: marker::PhantomData::, + } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} + +impl SavedAsPointerMut for NetDevice {} + +/// Trait holding the type of the NetDevice, and implementing the setup function. +pub trait NetDeviceAdapter: Sized { + /// Type of the Inner Private data field + type Inner: Sized; // = Self + + /// Type ipmlementing all functions used for [`NetDeviceOps`]. + type Ops: NetDeviceOps; + + /// Type implementing all functions used for [`EthToolOps`]. + type EthOps: EthToolOps; + + /// Callback to initialize the device + /// Function tables have to be assinged via [`NetDevice::set_ops`] + fn setup(dev: &mut NetDevice); +} + +#[repr(i32)] +#[allow(non_camel_case_types)] +/// Maps to [`bindings::netdev_tx`] from the kernel. +pub enum NetdevTX { + /// TX_OK + TX_OK = bindings::netdev_tx_NETDEV_TX_OK, + /// TX_BUSY + TX_BUSY = bindings::netdev_tx_NETDEV_TX_BUSY, +} + +unsafe extern "C" fn ndo_init_callback( + dev: *mut bindings::net_device, +) -> c_types::c_int { + c_from_kernel_result! { + T::Ops::init(&mut NetDevice::::from_pointer(dev))?; + Ok(0) + } +} + +unsafe extern "C" fn ndo_uninit_callback(dev: *mut bindings::net_device) { + // SAFETY: pointer is valid as it comes form C + T::Ops::uninit(&mut NetDevice::::from_pointer(dev)); +} + +unsafe extern "C" fn ndo_start_xmit_callback( + skb: *mut bindings::sk_buff, + dev: *mut bindings::net_device, +) -> bindings::netdev_tx_t { + T::Ops::start_xmit(SkBuff::from_pointer(skb), &mut NetDevice::from_pointer(dev)) + as bindings::netdev_tx_t +} + +unsafe extern "C" fn ndo_get_stats64_callback( + dev: *mut bindings::net_device, + stats: *mut bindings::rtnl_link_stats64, +) { + T::Ops::get_stats64( + &NetDevice::::from_pointer(dev), + &mut RtnlLinkStats64::from_pointer(stats), + ); +} + +unsafe extern "C" fn ndo_change_carrier_callback( + dev: *mut bindings::net_device, + change_carrier: bool, +) -> c_types::c_int { + c_from_kernel_result! { + T::Ops::change_carrier(&mut NetDevice::::from_pointer(dev), change_carrier)?; + Ok(0) + } +} + +unsafe extern "C" fn ndo_validate_addr_callback( + dev: *mut bindings::net_device, +) -> c_types::c_int { + c_from_kernel_result! { + T::Ops::validate_addr(&NetDevice::::from_pointer(dev))?; + Ok(0) + } +} + +unsafe extern "C" fn ndo_set_mac_address_callback( + dev: *mut bindings::net_device, + p: *mut c_types::c_void, +) -> c_types::c_int { + c_from_kernel_result! { + T::Ops::set_mac_addr(&mut NetDevice::::from_pointer(dev), p)?; + Ok(0) + } +} + +unsafe extern "C" fn ndo_set_rx_mode_callback(dev: *mut bindings::net_device) { + T::Ops::set_rx_mode(&mut NetDevice::::from_pointer(dev)) +} + +pub(crate) struct NetDeviceOperationsVtable(marker::PhantomData); + +impl NetDeviceOperationsVtable { + const VTABLE: bindings::net_device_ops = bindings::net_device_ops { + ndo_init: Some(ndo_init_callback::), + ndo_uninit: Some(ndo_uninit_callback::), + ndo_open: None, + ndo_stop: None, + ndo_start_xmit: Some(ndo_start_xmit_callback::), + ndo_features_check: None, + ndo_select_queue: None, + ndo_change_rx_flags: None, + ndo_set_rx_mode: if T::Ops::TO_USE.set_rx_mode { + Some(ndo_set_rx_mode_callback::) + } else { + None + }, + ndo_set_mac_address: if T::Ops::TO_USE.set_mac_addr { + Some(ndo_set_mac_address_callback::) + } else { + None + }, + ndo_validate_addr: if T::Ops::TO_USE.validate_addr { + Some(ndo_validate_addr_callback::) + } else { + None + }, + ndo_do_ioctl: None, + ndo_set_config: None, + ndo_change_mtu: None, + ndo_neigh_setup: None, + ndo_tx_timeout: None, + ndo_get_stats64: if T::Ops::TO_USE.get_stats64 { + Some(ndo_get_stats64_callback::) + } else { + None + }, + ndo_has_offload_stats: None, + ndo_get_offload_stats: None, + ndo_get_stats: None, + ndo_vlan_rx_add_vid: None, + ndo_vlan_rx_kill_vid: None, + + #[cfg(CONFIG_NET_POLL_CONTROLLER)] + ndo_poll_controller: None, + #[cfg(CONFIG_NET_POLL_CONTROLLER)] + ndo_netpoll_setup: None, + #[cfg(CONFIG_NET_POLL_CONTROLLER)] + ndo_netpoll_cleanup: None, + + ndo_set_vf_mac: None, + ndo_set_vf_vlan: None, + ndo_set_vf_rate: None, + ndo_set_vf_spoofchk: None, + ndo_set_vf_trust: None, + ndo_get_vf_config: None, + ndo_set_vf_link_state: None, + ndo_get_vf_stats: None, + ndo_set_vf_port: None, + ndo_get_vf_port: None, + ndo_get_vf_guid: None, + ndo_set_vf_guid: None, + ndo_set_vf_rss_query_en: None, + ndo_setup_tc: None, + + #[cfg(any(CONFIG_FCOE = "y", CONFIG_FCOE = "m"))] + ndo_fcoe_enable: None, + #[cfg(any(CONFIG_FCOE = "y", CONFIG_FCOE = "m"))] + ndo_fcoe_disable: None, + #[cfg(any(CONFIG_FCOE = "y", CONFIG_FCOE = "m"))] + ndo_fcoe_ddp_setup: None, + #[cfg(any(CONFIG_FCOE = "y", CONFIG_FCOE = "m"))] + ndo_fcoe_ddp_done: None, + #[cfg(any(CONFIG_FCOE = "y", CONFIG_FCOE = "m"))] + ndo_fcoe_ddp_target: None, + #[cfg(any(CONFIG_FCOE = "y", CONFIG_FCOE = "m"))] + ndo_fcoe_get_hbainfo: None, + + #[cfg(any(CONFIG_LIBFCOE = "y", CONFIG_LIBFCOE = "m"))] + ndo_fcoe_get_wwn: None, + + #[cfg(CONFIG_RFS_ACCEL)] + ndo_rx_flow_steer: None, + + ndo_add_slave: None, + ndo_del_slave: None, + ndo_get_xmit_slave: None, + ndo_sk_get_lower_dev: None, + ndo_fix_features: None, + ndo_set_features: None, + ndo_neigh_construct: None, + ndo_neigh_destroy: None, + ndo_fdb_add: None, + ndo_fdb_del: None, + ndo_fdb_dump: None, + ndo_fdb_get: None, + ndo_bridge_setlink: None, + ndo_bridge_getlink: None, + ndo_bridge_dellink: None, + ndo_change_carrier: if T::Ops::TO_USE.change_carrier { + Some(ndo_change_carrier_callback::) + } else { + None + }, + ndo_get_phys_port_id: None, + ndo_get_port_parent_id: None, + ndo_get_phys_port_name: None, + ndo_dfwd_add_station: None, + ndo_dfwd_del_station: None, + ndo_set_tx_maxrate: None, + ndo_get_iflink: None, + ndo_change_proto_down: None, + ndo_fill_metadata_dst: None, + ndo_set_rx_headroom: None, + ndo_bpf: None, + ndo_xdp_xmit: None, + ndo_xsk_wakeup: None, + ndo_get_devlink_port: None, + ndo_tunnel_ctl: None, + ndo_get_peer_dev: None, + }; + + /// Builds an instance of [`struct net_device_ops`]. + /// + /// # Safety + /// + /// The caller must ensure that the adapter is compatible with the way the device is registered. + pub(crate) const unsafe fn build() -> &'static bindings::net_device_ops { + &Self::VTABLE + } +} + +/// Represents which fields of [`struct net_device_ops`] should pe populated with pointers for the trait [`NetDeviceOps`]. +pub struct ToUse { + /// Trait defines a `ndo_change_carrier` function. + pub change_carrier: bool, + + /// Trait defines a `ndo_get_stats64` function. + pub get_stats64: bool, + + /// Trait defines a `ndo_validate_addr` function. + pub validate_addr: bool, + + /// Trait defines a `ndo_set_mac_addr` function. + pub set_mac_addr: bool, + + /// Trait defines a `ndo_set_rx_mode` function. + pub set_rx_mode: bool, +} + +/// This trait does not include any functions exept [`init`] and [`uninit`]. +#[doc(hidden)] +pub const USE_NONE: ToUse = ToUse { + change_carrier: false, + get_stats64: false, + validate_addr: false, + set_mac_addr: false, + set_rx_mode: false, +}; + +/// Defines the [`NetDeviceOps::TO_USE`] field based on a list of fields to be populated. +#[macro_export] +macro_rules! declare_net_device_ops { + () => { + const TO_USE: $crate::net::device::ToUse = $crate::net::device::USE_NONE; + }; + ($($i:ident),+) => { + const TO_USE: kernel::net::device::ToUse = + $crate::net::device::ToUse { + $($i: true),+ , + ..$crate::net::device::USE_NONE + }; + }; +} + +/// Corresponds to the kernel's `struct net_device_ops`. +/// +/// You Implement this trait whenever you would create a `struct net_device_ops`. +pub trait NetDeviceOps: Send + Sync + Sized { + /// The methods to use to populate [`struct net_device_ops`]. + const TO_USE: ToUse; + + /// This function is called once when a network device is registered. + /// The network device can use this for any late stage initialization + /// or semantic validation. It can fail with an error code which will + /// be propagated back to register_netdev. + fn init(dev: &mut NetDevice) -> Result; + + /// This function is called when device is unregistered or when registration + /// fails. It is not called if init fails. + fn uninit(dev: &mut NetDevice); + + /// Called when a packet needs to be transmitted. + /// `Ok(())` returns NETDEV_TX_OK, Error maps to `NETDEV_TX_BUSY` + /// Returns NETDEV_TX_OK. Can return NETDEV_TX_BUSY, but you should stop + /// the queue before that can happen; it's for obsolete devices and weird + /// corner cases, but the stack really does a non-trivial amount + /// of useless work if you return NETDEV_TX_BUSY. + fn start_xmit(_skb: SkBuff, _dev: &mut NetDevice) -> NetdevTX { + NetdevTX::TX_OK + } + + /// Called when a user wants to get the network device usage + /// statistics. + /// + /// Must fill in a zero-initialised [`RtnlLinkStats64`] structure + /// passed by the caller. + fn get_stats64(_dev: &NetDevice, _stats: &mut RtnlLinkStats64) {} + + /// Called to change device carrier. Soft-devices (like dummy, team, etc) + /// which do not represent real hardware may define this to allow their + /// userspace components to manage their virtual carrier state. Devices + /// that determine carrier state from physical hardware properties (eg + /// network cables) or protocol-dependent mechanisms (eg + /// USB_CDC_NOTIFY_NETWORK_CONNECTION) should NOT implement this function, and + /// therefor NOT set [`TO_USE.change_carrier`]. + fn change_carrier(_dev: &mut NetDevice, _new_carrier: bool) -> Result { + Err(Error::EINVAL) + } + + /// Test if Media Access Control address is valid for the device. + fn validate_addr(_dev: &NetDevice) -> Result { + Err(Error::EINVAL) + } + + /// This function is called when the Media Access Control address + /// needs to be changed. If this interface is not defined, the + /// MAC address can not be changed. + fn set_mac_addr(_dev: &mut NetDevice, _p: *mut c_types::c_void) -> Result { + Err(Error::EINVAL) + } + + /// This function is called device changes address list filtering. + /// If driver handles unicast address filtering, it should set + /// IFF_UNICAST_FLT in its priv_flags. + fn set_rx_mode(_dev: &mut NetDevice) {} +} + +/// Iff flags +#[repr(u32)] +#[allow(non_camel_case_types)] +pub enum Iff { + /// UP + UP = bindings::net_device_flags_IFF_UP, + /// BROADCAST + BROADCAST = bindings::net_device_flags_IFF_BROADCAST, + /// DEBUG + DEBUG = bindings::net_device_flags_IFF_DEBUG, + /// LOOPBACK + LOOPBACK = bindings::net_device_flags_IFF_LOOPBACK, + /// POINTOPOINT + POINTOPOINT = bindings::net_device_flags_IFF_POINTOPOINT, + /// NOTRAILERS + NOTRAILERS = bindings::net_device_flags_IFF_NOTRAILERS, + /// RUNNING + RUNNING = bindings::net_device_flags_IFF_RUNNING, + /// NOARP + NOARP = bindings::net_device_flags_IFF_NOARP, + /// PROMISC + PROMISC = bindings::net_device_flags_IFF_PROMISC, + /// ALLMULTI + ALLMULTI = bindings::net_device_flags_IFF_ALLMULTI, + /// MASTER + MASTER = bindings::net_device_flags_IFF_MASTER, + /// SLAVE + SLAVE = bindings::net_device_flags_IFF_SLAVE, + /// MULTICAST + MULTICAST = bindings::net_device_flags_IFF_MULTICAST, + /// PORTSEL + PORTSEL = bindings::net_device_flags_IFF_PORTSEL, + /// AUTOMEDIA + AUTOMEDIA = bindings::net_device_flags_IFF_AUTOMEDIA, + /// DYNAMIC + DYNAMIC = bindings::net_device_flags_IFF_DYNAMIC, + + // #if __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO // TODO: is this needed? + /// LOWER + LOWER = bindings::net_device_flags_IFF_LOWER_UP, + /// DORMANT + DORMANT = bindings::net_device_flags_IFF_DORMANT, + /// ECHO + ECHO = bindings::net_device_flags_IFF_ECHO, +} + +/// Iff private flags +#[repr(u32)] +#[allow(non_camel_case_types)] +pub enum IffPriv { + /// 802.1Q VLAN device. + IFF_802_1Q_VLAN = bindings::netdev_priv_flags_IFF_802_1Q_VLAN, /* TODO: find a good name without leading 8 */ + /// Ethernet bridging device. + EBRIDGE = bindings::netdev_priv_flags_IFF_EBRIDGE, + /// Bonding master or slave. + BONDING = bindings::netdev_priv_flags_IFF_BONDING, + /// ISATAP interface (RFC4214). + ISATAP = bindings::netdev_priv_flags_IFF_ISATAP, + /// WAN HDLC device. + WAN_HDLC = bindings::netdev_priv_flags_IFF_WAN_HDLC, + /// dev_hard_start_xmit() is allowed to release skb->dst + XMIT_DST_RELEASE = bindings::netdev_priv_flags_IFF_XMIT_DST_RELEASE, + /// Disallow bridging this ether dev. + DONT_BRIDGE = bindings::netdev_priv_flags_IFF_DONT_BRIDGE, + /// Disable netpoll at run-time. + DISABLE_NETPOLL = bindings::netdev_priv_flags_IFF_DISABLE_NETPOLL, + /// Device used as macvlan port. + MACVLAN_PORT = bindings::netdev_priv_flags_IFF_MACVLAN_PORT, + /// Device used as bridge port. + BRIDGE_PORT = bindings::netdev_priv_flags_IFF_BRIDGE_PORT, + /// Device used as Open vSwitch datapath port. + OVS_DATAPATH = bindings::netdev_priv_flags_IFF_OVS_DATAPATH, + /// The interface supports sharing skbs on transmit. + TX_SKB_SHARING = bindings::netdev_priv_flags_IFF_TX_SKB_SHARING, + /// Supports unicast filtering. + UNICAST_FLT = bindings::netdev_priv_flags_IFF_UNICAST_FLT, + /// Device used as team port. + TEAM_PORT = bindings::netdev_priv_flags_IFF_TEAM_PORT, + /// Device supports sending custom FCS. + SUPP_NOFCS = bindings::netdev_priv_flags_IFF_SUPP_NOFCS, + /// Device supports hardware address change when it's running. + LIVE_ADDR_CHANGE = bindings::netdev_priv_flags_IFF_LIVE_ADDR_CHANGE, + /// Macvlan device. + MACVLAN = bindings::netdev_priv_flags_IFF_MACVLAN, + /// IFF_XMIT_DST_RELEASE not taking into account underlying stacked devices. + XMIT_DST_RELEASE_PERM = bindings::netdev_priv_flags_IFF_XMIT_DST_RELEASE_PERM, + /// Device is an L3 master device. + L3MDEV_MASTER = bindings::netdev_priv_flags_IFF_L3MDEV_MASTER, + /// Device can run without qdisc attached. + NO_QUEUE = bindings::netdev_priv_flags_IFF_NO_QUEUE, + /// Device is a Open vSwitch master. + OPENVSWITCH = bindings::netdev_priv_flags_IFF_OPENVSWITCH, + /// Device is enslaved to an L3 master device. + L3MDEV_SLAVE = bindings::netdev_priv_flags_IFF_L3MDEV_SLAVE, + /// Device is a team device. + TEAM = bindings::netdev_priv_flags_IFF_TEAM, + /// Device has had Rx Flow indirection table configured. + RXFH_CONFIGURED = bindings::netdev_priv_flags_IFF_RXFH_CONFIGURED, + /// The headroom value is controlled by an external entity (i.e. the master device for bridged veth). + PHONY_HEADROOM = bindings::netdev_priv_flags_IFF_PHONY_HEADROOM, + /// Device is a MACsec device. + MACSEC = bindings::netdev_priv_flags_IFF_MACSEC, + /// Device doesn't support the rx_handler hook. + NO_RX_HANDLER = bindings::netdev_priv_flags_IFF_NO_RX_HANDLER, + /// Device is a failover master device. + FAILOVER = bindings::netdev_priv_flags_IFF_FAILOVER, + /// Device is lower dev of a failover master device. + FAILOVER_SLAVE = bindings::netdev_priv_flags_IFF_FAILOVER_SLAVE, + /// Only invoke the rx handler of L3 master device. + L3MDEV_RX_HANDLER = bindings::netdev_priv_flags_IFF_L3MDEV_RX_HANDLER, + /// Rename is allowed while device is up and running. + LIVE_RENAME_OK = bindings::netdev_priv_flags_IFF_LIVE_RENAME_OK, +} + +/// Feature flags. +pub mod feature { + use core::convert::{From, Into}; + use core::ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}; + + /// Holds multiple flags to give to an interface via [`NetDevice::set_flag`]. + #[repr(transparent)] + #[derive(Debug, Clone, Copy)] + pub struct NetIF(u64); + + impl NetIF { + /// Create new Flag with value `0`. + pub const fn new() -> Self { + Self(0) + } + + /// Add flag to Self. + pub fn add_flag(&mut self, flag: u64) { + self.0 |= flag; + } + + /// Remove the given flag from Self. + pub fn remove_flag(&mut self, flag: u64) { + self.0 &= !(flag); + } + } + + impl Deref for NetIF { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl DerefMut for NetIF { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl Add for NetIF { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self::from(self.0 | rhs.0) + } + } + + impl Add for NetIF { + type Output = Self; + + fn add(self, rhs: u64) -> Self::Output { + Self::from(self.0 | rhs) + } + } + + impl Sub for NetIF { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self::from(self.0 & !rhs.0) + } + } + + impl Sub for NetIF { + type Output = Self; + + fn sub(self, rhs: u64) -> Self::Output { + Self::from(self.0 & !rhs) + } + } + + impl AddAssign for NetIF { + fn add_assign(&mut self, rhs: Self) { + self.0 |= rhs.0 + } + } + + impl AddAssign for NetIF { + fn add_assign(&mut self, rhs: u64) { + self.0 |= rhs + } + } + + impl SubAssign for NetIF { + fn sub_assign(&mut self, rhs: Self) { + self.0 &= !rhs.0 + } + } + + impl SubAssign for NetIF { + fn sub_assign(&mut self, rhs: u64) { + self.0 &= !rhs + } + } + + impl From for NetIF { + fn from(flags: u64) -> Self { + Self(flags) + } + } + + impl Into for NetIF { + fn into(self) -> u64 { + self.0 + } + } + + macro_rules! _netif_f { + ($name:ident, $binding:ident) => { + /// [`NetIF`] flag for $binding + pub const $name: u64 = 1u64 << $crate::bindings::$binding; + }; + } + + macro_rules! _netif_f_sum { + ($name:ident, $($f:ident),+) => { + /// [`NetIF`] flag for $name + pub const $name: u64 = $($crate::net::device::feature::$f |)* 0; + }; + } + + _netif_f!(NETIF_F_SG, NETIF_F_SG_BIT); + _netif_f!(NETIF_F_FRAGLIST, NETIF_F_FRAGLIST_BIT); + _netif_f!(NETIF_F_TSO, NETIF_F_TSO_BIT); + _netif_f!(NETIF_F_TSO6, NETIF_F_TSO6_BIT); + _netif_f!(NETIF_F_TSO_ECN, NETIF_F_TSO_ECN_BIT); + _netif_f!(NETIF_F_TSO_MANGLEID, NETIF_F_TSO_MANGLEID_BIT); + _netif_f!(NETIF_F_GSO_SCTP, NETIF_F_GSO_SCTP_BIT); + _netif_f!(NETIF_F_GSO_UDP_L4, NETIF_F_GSO_UDP_L4_BIT); + _netif_f!(NETIF_F_GSO_FRAGLIST, NETIF_F_GSO_FRAGLIST_BIT); + _netif_f!(NETIF_F_HW_CSUM, NETIF_F_HW_CSUM_BIT); + _netif_f!(NETIF_F_HIGHDMA, NETIF_F_HIGHDMA_BIT); + _netif_f!(NETIF_F_LLTX, NETIF_F_LLTX_BIT); + _netif_f!(NETIF_F_GSO_GRE, NETIF_F_GSO_GRE_BIT); + _netif_f!(NETIF_F_GSO_GRE_CSUM, NETIF_F_GSO_GRE_CSUM_BIT); + _netif_f!(NETIF_F_GSO_IPXIP4, NETIF_F_GSO_IPXIP4_BIT); + _netif_f!(NETIF_F_GSO_IPXIP6, NETIF_F_GSO_IPXIP6_BIT); + _netif_f!(NETIF_F_GSO_UDP_TUNNEL, NETIF_F_GSO_UDP_TUNNEL_BIT); + _netif_f!(NETIF_F_GSO_UDP_TUNNEL_CSUM, NETIF_F_GSO_UDP_TUNNEL_CSUM_BIT); + + _netif_f_sum!( + NETIF_F_ALL_TSO, + NETIF_F_TSO, + NETIF_F_TSO6, + NETIF_F_TSO_ECN, + NETIF_F_TSO_MANGLEID + ); + _netif_f_sum!( + NETIF_F_GSO_SOFTWARE, + NETIF_F_ALL_TSO, + NETIF_F_GSO_SCTP, + NETIF_F_GSO_UDP_L4, + NETIF_F_GSO_FRAGLIST + ); + _netif_f_sum!( + NETIF_F_GSO_ENCAP_ALL, + NETIF_F_GSO_GRE, + NETIF_F_GSO_GRE_CSUM, + NETIF_F_GSO_IPXIP4, + NETIF_F_GSO_IPXIP6, + NETIF_F_GSO_UDP_TUNNEL, + NETIF_F_GSO_UDP_TUNNEL_CSUM + ); +} + +/// Helper functions for NetDevices. +pub mod helpers { + use super::*; + + /// Validate the eth addres for the [`NetDevice`] `dev`. + pub fn eth_validate_addr(dev: &NetDevice) -> Result { + // SAFETY: dev.ptr is valid if dev is valid + let ret = + unsafe { bindings::eth_validate_addr(dev.get_pointer() as *mut bindings::net_device) }; + if ret != 0 { + Err(Error::from_kernel_errno(ret)) + } else { + Ok(()) + } + } + + /// Set new Ethernet hardware address. + /// + /// This doesn't change hardware matching, so needs to be overridden + /// for most real devices. + pub fn eth_mac_addr( + dev: &mut NetDevice, + socket_addr: *mut c_types::c_void, + ) -> Result { + // SAFETY: dev.ptr is valid if dev is valid + let ret = unsafe { + bindings::eth_mac_addr(dev.get_pointer() as *mut bindings::net_device, socket_addr) + }; + + if ret != 0 { + Err(Error::from_kernel_errno(ret)) + } else { + Ok(()) + } + } +} diff --git a/rust/kernel/net/ethtool.rs b/rust/kernel/net/ethtool.rs new file mode 100644 index 00000000000000..369f861e5bc678 --- /dev/null +++ b/rust/kernel/net/ethtool.rs @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Ethtool Operations. +//! +//! C header: [`include/linux/ethtool.h`](../../../../include/linux/ethtool.h) + +use core::marker; + +use crate::bindings; +use crate::c_from_kernel_result; +use crate::c_types; +use crate::error::{Error, Result}; +use crate::types::{SavedAsPointer, SavedAsPointerMut}; + +use super::device::{NetDevice, NetDeviceAdapter}; + +unsafe extern "C" fn get_drvinfo_callback( + dev: *mut bindings::net_device, + info: *mut bindings::ethtool_drvinfo, +) { + T::EthOps::get_drvinfo( + &NetDevice::::from_pointer(dev), + &mut EthtoolDrvinfo::from_pointer(info), + ); +} + +unsafe extern "C" fn get_ts_info_callback( + dev: *mut bindings::net_device, + info: *mut bindings::ethtool_ts_info, +) -> c_types::c_int { + c_from_kernel_result! { + T::EthOps::get_ts_info( + &NetDevice::::from_pointer(dev), + &mut EthToolTsInfo::from_pointer(info) + )?; + Ok(0) + } +} + +pub(crate) struct EthToolOperationsVtable(marker::PhantomData); + +impl EthToolOperationsVtable { + const VTABLE: bindings::ethtool_ops = bindings::ethtool_ops { + _bitfield_align_1: [], + _bitfield_1: bindings::__BindgenBitfieldUnit::<[u8; 1usize]>::new([0u8; 1usize]), + supported_coalesce_params: 0, + get_drvinfo: if T::EthOps::TO_USE.get_drvinfo { + Some(get_drvinfo_callback::) + } else { + None + }, + get_regs_len: None, + get_regs: None, + get_wol: None, + set_wol: None, + get_msglevel: None, + set_msglevel: None, + nway_reset: None, + get_link: None, + get_link_ext_state: None, + get_eeprom_len: None, + get_eeprom: None, + set_eeprom: None, + get_coalesce: None, + set_coalesce: None, + get_ringparam: None, + set_ringparam: None, + get_pause_stats: None, + get_pauseparam: None, + set_pauseparam: None, + self_test: None, + get_strings: None, + set_phys_id: None, + get_ethtool_stats: None, + begin: None, + complete: None, + get_priv_flags: None, + set_priv_flags: None, + get_sset_count: None, + get_rxnfc: None, + set_rxnfc: None, + flash_device: None, + reset: None, + get_rxfh_key_size: None, + get_rxfh_indir_size: None, + get_rxfh: None, + set_rxfh: None, + get_rxfh_context: None, + set_rxfh_context: None, + get_channels: None, + set_channels: None, + get_dump_flag: None, + get_dump_data: None, + set_dump: None, + get_ts_info: if T::EthOps::TO_USE.get_ts_info { + Some(get_ts_info_callback::) + } else { + None + }, + get_module_info: None, + get_module_eeprom: None, + get_eee: None, + set_eee: None, + get_tunable: None, + set_tunable: None, + get_per_queue_coalesce: None, + set_per_queue_coalesce: None, + get_link_ksettings: None, + set_link_ksettings: None, + get_fecparam: None, + set_fecparam: None, + get_ethtool_phy_stats: None, + get_phy_tunable: None, + set_phy_tunable: None, + }; + + /// Builds an instance of [`struct ethtool_ops`]. + /// + /// # Safety + /// + /// The caller must ensure that the adapter is compatible with the way the device is registered. + pub(crate) const unsafe fn build() -> &'static bindings::ethtool_ops { + &Self::VTABLE + } +} + +/// Represents which fields of [`struct ethtool_ops`] should pe populated with pointers for the trait [`EthToolOps`] +pub struct EthToolToUse { + /// Trait defines a `get_drvinfo` function. + pub get_drvinfo: bool, + + /// Trait defines a `get_ts_info` function. + pub get_ts_info: bool, +} + +/// This trait does not include any functions. +#[doc(hidden)] +pub const ETH_TOOL_USE_NONE: EthToolToUse = EthToolToUse { + get_drvinfo: false, + get_ts_info: false, +}; + +/// Defines the [`EthToolOps::TO_USE`] field based on a list of fields to be populated. +#[macro_export] +macro_rules! declare_eth_tool_ops { + () => { + const TO_USE: $crate::net::ethtool::EthToolToUse = $crate::net::ethtool::ETH_TOOL_USE_NONE; + }; + ($($i:ident),+) => { + const TO_USE: kernel::net::ethtool::EthToolToUse = + $crate::net::ethtool::EthToolToUse { + $($i: true),+ , + ..$crate::net::ethtool::ETH_TOOL_USE_NONE + }; + }; +} + +/// Operations table needed for ethtool. +/// [`Self::TO_USE`] defines which functions are implemented by this type. +pub trait EthToolOps: Send + Sync + Sized { + /// Struct [`EthToolToUse`] which signals which function are ipmlemeted by this type. + const TO_USE: EthToolToUse; + + /// Report driver/device information. Should only set the + /// @driver, @version, @fw_version and @bus_info fields. If not + /// implemented, the @driver and @bus_info fields will be filled in + /// according to the netdev's parent device. + fn get_drvinfo(_dev: &NetDevice, _info: &mut EthtoolDrvinfo) {} + + /// Get the time stamping and PTP hardware clock capabilities. + /// Drivers supporting transmit time stamps in software should set this to + /// [`helpers::ethtool_op_get_ts_info`]. + fn get_ts_info(_dev: &NetDevice, _info: &mut EthToolTsInfo) -> Result { + Err(Error::EINVAL) + } +} + +/// Wrappes the [`bindings::ethtool_ts_info`] struct. +#[repr(transparent)] +pub struct EthToolTsInfo { + ptr: *const bindings::ethtool_ts_info, +} + +impl SavedAsPointer for EthToolTsInfo { + type InternalType = bindings::ethtool_ts_info; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} + +impl SavedAsPointerMut for EthToolTsInfo {} + +/// Wrappes the [`bindings::ethtool_drvinfo`] struct. +pub struct EthtoolDrvinfo { + ptr: *const bindings::ethtool_drvinfo, +} + +impl SavedAsPointer for EthtoolDrvinfo { + type InternalType = bindings::ethtool_drvinfo; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} + +impl SavedAsPointerMut for EthtoolDrvinfo {} + +/// Helper functions for ethtool. +pub mod helpers { + use super::*; + + /// Get ts info for the device `dev`. + pub fn ethtool_op_get_ts_info( + dev: &NetDevice, + info: &mut EthToolTsInfo, + ) -> Result { + // SAFETY: dev.ptr is valid if dev is valid + // SAFETY: info.ptr is valid if info is valid + unsafe { bindings::ethtool_op_get_ts_info(dev.get_pointer_mut(), info.get_pointer_mut()) }; + Ok(()) + } +} diff --git a/rust/kernel/net/mod.rs b/rust/kernel/net/mod.rs new file mode 100644 index 00000000000000..9ea262adb380eb --- /dev/null +++ b/rust/kernel/net/mod.rs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Network subsystem in rust for the linux kernel. + +use core::mem; + +pub mod device; +pub mod ethtool; +pub mod netlink; +pub mod rtnl; +pub mod skbuff; + +#[doc(inline)] +pub use module::rtnl_link_ops; + +/// Determine if the Ethernet address is a multicast. +/// +/// Return true if the address is a multicast address. +/// By definition the broadcast address is also a multicast address. +/// +/// # Parameters +/// - `addr`: Pointer to a six-byte array containing the Ethernet address +#[cfg(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)] +pub unsafe fn is_multicast_ether_addr(addr: *const u8) -> bool { + let a: u32 = *(addr as *const u32); + + if cfg!(target_endian = "big") { + (0x01 & (a >> (((mem::size_of::() as u32) * 8) - 8))) != 0 + } else { + (0x01 & a) != 0 + } +} + +/// Determine if the Ethernet address is a multicast. +/// +/// Return true if the address is a multicast address. +/// By definition the broadcast address is also a multicast address. +/// +/// # Parameters +/// - `addr`: Pointer to a six-byte array containing the Ethernet address +#[cfg(not(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS))] +pub unsafe fn is_multicast_ether_addr(addr: *const u8) -> bool { + let a: u16 = *(addr as *const u16); + + if cfg!(target_endian = "big") { + (0x01 & (a >> (((mem::size_of::() as u16) * 8) - 8))) != 0 + } else { + (0x01 & a) != 0 + } +} + +/// Determine if give Ethernet address is all zeros. +/// Return true if the address is all zeroes. +/// +/// # Parameters +/// - `addr`: Pointer to a six-byte array containing the Ethernet address +/// +/// # Safety +/// Please note: addr must be aligned to u16. +#[cfg(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)] +pub unsafe fn is_zero_ether_addr(addr: *const u8) -> bool { + *(addr as *const u32) | (*((addr as usize + 4) as *const u16) as u32) == 0 +} + +/// Determine if give Ethernet address is all zeros. +/// Return true if the address is all zeroes. +/// +/// # Parameters +/// - `addr`: Pointer to a six-byte array containing the Ethernet address +/// +/// # Safety +/// Please note: addr must be aligned to u16. +#[cfg(not(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS))] +pub unsafe fn is_zero_ether_addr(addr: *const u8) -> bool { + *(addr as *const u16) + | *((addr as usize + 2) as *const u16) + | *((addr as usize + 4) as *const u16) + == 0 +} + +/// is_valid_ether_addr - Determine if the given Ethernet address is valid. +/// Check that the Ethernet address (MAC) is not 00:00:00:00:00:00, is not +/// a multicast address, and is not FF:FF:FF:FF:FF:FF. +/// +/// Return true if the address is valid. +/// +/// # Parameters +/// - `addr`: Pointer to a six-byte array containing the Ethernet address +/// +/// # Safety +/// Please note: addr must be aligned to u16. +pub unsafe fn is_valid_ether_addr(addr: *const u8) -> bool { + !is_multicast_ether_addr(addr) && !is_zero_ether_addr(addr) +} + +/// Prelude for all net related imports. +pub mod prelude { + pub use super::rtnl_link_ops; + pub use super::{ + device::{NetDevice, NetDeviceAdapter, NetDeviceOps}, + ethtool::{self, EthToolOps}, + rtnl::{RtnlLinkOps, RtnlLock}, + skbuff::SkBuff, + }; +} diff --git a/rust/kernel/net/netlink.rs b/rust/kernel/net/netlink.rs new file mode 100644 index 00000000000000..8867c9a3705991 --- /dev/null +++ b/rust/kernel/net/netlink.rs @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Netlink helpers. +//! +//! C header: [`include/linux/rtnetlink.h`](../../../../include/linux/rtnetlink.h) + +use crate::bindings; +use crate::types::SavedAsPointer; + +/// Octets in one ethernet addr +pub const ETH_ALEN: u16 = bindings::ETH_ALEN as u16; + +const NLA_HDRLEN: i32 = bindings::BINDINGS_NLA_HDRLEN; +/// Max Position for NlAttrVec. +pub const __IFLA_MAX: usize = bindings::__IFLA_MAX as usize; + +/// Wrapper for the kernels [`bindings::nlattr`] type. +#[repr(transparent)] +pub struct NlAttr { + ptr: *const bindings::nlattr, +} + +impl NlAttr { + /// Check If the netlink is 0, This can happen in [`NlAttrVec`] structs. + pub fn is_null(&self) -> bool { + self.ptr.is_null() + } + + /// Return the length of the nla data. + /// Returns None if [`Self::is_null`] is true. + pub fn nla_len(&self) -> Option { + if self.is_null() { + return None; + } + + // NO-PANIC: self is valid and not null + // SAFETY: ptr is valid if self is valid + Some(self.get_internal().nla_len - NLA_HDRLEN as u16) + } + + /// Get a pointer to the data inside the nla package. + pub unsafe fn data(&self) -> *const i8 { + ((self.ptr as usize) + NLA_HDRLEN as usize) as *const i8 + } + + /// Check if address inside the data is valid. + pub fn is_valid_ether_addr(&self) -> bool { + // SAFETY: self.o is valid if self is valid + unsafe { + let data = self.data() as *const u8; + super::is_valid_ether_addr(data) + } + } + + /// Return a onwed version of Self. + /// The pointer inside of [`Self::get_pointer`] still shows onto the same data. + unsafe fn clone(&self) -> Self { + Self { + ptr: self.ptr.clone(), + } + } +} + +impl SavedAsPointer for NlAttr { + type InternalType = bindings::nlattr; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} + +/// Wrapper for the kernels [`bindings::netlink_ext_ack`] stuct. +#[repr(transparent)] +pub struct NlExtAck { + ptr: *const bindings::netlink_ext_ack, +} + +impl SavedAsPointer for NlExtAck { + type InternalType = bindings::netlink_ext_ack; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} + +/// Wrapper for a list of [`NlAttr`] with size [`__IFLA_MAX`]. +#[repr(transparent)] +pub struct NlAttrVec { + ptr: *const *const bindings::nlattr, +} + +impl NlAttrVec { + /// Get The [`NlAttr`] from position `offset`. + /// + /// Returns None if `offset` is bigger than [`__IFLA_MAX`]. + pub fn get(&self, offset: u32) -> Option { + if offset > __IFLA_MAX as u32 { + return None; + } + + let vec = unsafe { &*(self.ptr as *const [NlAttr; __IFLA_MAX]) }; + let nlattr = &vec[offset as usize]; + if nlattr.is_null() { + None + } else { + Some(unsafe { nlattr.clone() }) + } + } +} + +impl SavedAsPointer for NlAttrVec { + type InternalType = *const bindings::nlattr; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} diff --git a/rust/kernel/net/rtnl.rs b/rust/kernel/net/rtnl.rs new file mode 100644 index 00000000000000..998796e7af0101 --- /dev/null +++ b/rust/kernel/net/rtnl.rs @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rtnl Link Operations. +//! +//! C header: [`include/linux/rtnetlink.h`](../../../../include/linux/rtnetlink.h) + +use core::ptr; + +use crate::bindings; +use crate::error::{Error, Result}; + +use super::device::{NetDevice, NetDeviceAdapter}; + +use crate::types::{SavedAsPointer, SavedAsPointerMut}; + +// TODO: inner bool, to allow other unlock mechanism? +/// Lock, acquired via [`bindings::rtnl_lock`]. +#[must_use = "the rtnl unlocks immediately when the guard is unused"] +pub struct RtnlLock { + _private: (), +} + +impl RtnlLock { + /// Creat a new lock via [`bindings::rtnl_lock`]. + pub fn lock() -> Self { + // SAFETY: C function without parameters + unsafe { bindings::rtnl_lock() }; + + Self { _private: () } + } +} + +impl Drop for RtnlLock { + fn drop(&mut self) { + // SAFETY: C function without parameters + unsafe { bindings::rtnl_unlock() }; + } +} + +#[doc(hidden)] +/// Empty [`bindings::rtnl_link_ops`] table. +pub const RTNL_LINK_OPS_EMPTY: bindings::rtnl_link_ops = bindings::rtnl_link_ops { + list: bindings::list_head { + next: ptr::null::() as *mut bindings::list_head, + prev: ptr::null::() as *mut bindings::list_head, + }, + kind: ptr::null::(), + priv_size: 0, + setup: None, + maxtype: 0, + policy: ptr::null::(), + validate: None, + newlink: None, + changelink: None, + dellink: None, + get_size: None, + fill_info: None, + get_xstats_size: None, + fill_xstats: None, + get_num_tx_queues: None, + get_num_rx_queues: None, + slave_maxtype: 0, + slave_policy: ptr::null::(), + slave_changelink: None, + get_slave_size: None, + fill_slave_info: None, + get_link_net: None, + get_linkxstats_size: None, + fill_linkxstats: None, +}; + +/// Transparent wrapper for [`bindings::rtnl_link_ops`] for the macro [`kernel::rtnl_link_ops`]. +#[repr(transparent)] +pub struct RtnlLinkOps(pub bindings::rtnl_link_ops); + +unsafe impl Sync for RtnlLinkOps {} + +impl RtnlLinkOps { + /// Register this op table with kernel. + pub fn register(&self) -> Result { + // SAFETY: ptr of self is valid if self is valid + let ret = unsafe { + let ptr = self.get_pointer(); + + bindings::rtnl_link_register(ptr as *mut bindings::rtnl_link_ops) + }; + + if ret != 0 { + Err(Error::from_kernel_errno(ret)) + } else { + Ok(()) + } + } + + /// Get a pointer to this struct. + pub unsafe fn get_pointer(&self) -> *const bindings::rtnl_link_ops { + &self.0 as *const _ as *const bindings::rtnl_link_ops + } + + /// Deregister the op table from the kernel. + pub fn unregister(&self) { + let ptr = self as *const _ as *mut bindings::rtnl_link_ops; + + // SAFETY: ptr is valid if self is valid + unsafe { bindings::rtnl_link_unregister(ptr) }; + } +} + +impl Drop for RtnlLinkOps { + fn drop(&mut self) { + crate::pr_info!("dropping rtnl_link_ops"); + } +} + +/// Wrapper for a kernel [`bindings::rtnl_link_stats64`]. +#[repr(transparent)] +pub struct RtnlLinkStats64 { + ptr: *const bindings::rtnl_link_stats64, +} + +impl RtnlLinkStats64 { + /// Read the lstats from the [`NetDevice`] `dev` to [`Self`]. + pub fn dev_read(&mut self, dev: &NetDevice) { + let stats = self.get_internal_mut(); + // SAFETY: call to C function + unsafe { + bindings::dev_lstats_read( + dev.get_pointer() as *mut bindings::net_device, + &stats.tx_packets as *const u64 as *mut u64, + &stats.tx_bytes as *const u64 as *mut u64, + ); + } + } +} + +impl SavedAsPointer for RtnlLinkStats64 { + type InternalType = bindings::rtnl_link_stats64; + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } +} + +impl SavedAsPointerMut for RtnlLinkStats64 {} diff --git a/rust/kernel/net/skbuff.rs b/rust/kernel/net/skbuff.rs new file mode 100644 index 00000000000000..423444ff87f77f --- /dev/null +++ b/rust/kernel/net/skbuff.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Definitors for the struct [`kernel::bindings::sk_buff`] memory handlers. +//! +//! C header: [`include/linux/skbuff.h`](../../../../include/linux/skbuff.h) + +use core::{ops::Drop, ptr}; + +use crate::bindings; +use crate::types::SavedAsPointer; + +/// Wraps the kernel's `struct sk_buff`. +/// +/// # Invariants +/// +/// The pointer [`SkBuff::ptr`] is non-null and valid. +#[repr(transparent)] +pub struct SkBuff { + ptr: *const bindings::sk_buff, +} + +impl SkBuff { + /// Binding to [`bindings::skb_clone_tx_timestamp`]. + #[cfg(CONFIG_NETWORK_PHY_TIMESTAMPING)] + pub fn clone_tx_timestamp(&mut self) { + // SAFETY: self.ptr is valid if self is valid + unsafe { + bindings::skb_clone_tx_timestamp(self.ptr as *mut bindings::sk_buff); + } + } + + /// Binding to [`bindings::skb_clone_tx_timestamp`]. + #[cfg(not(CONFIG_NETWORK_PHY_TIMESTAMPING))] + pub fn clone_tx_timestamp(&mut self) { + // NOOP + } + + /// Driver hook for transmit timestamping. + /// + /// Ethernet MAC Drivers should call this function in their hard_xmit() + /// function immediately before giving the sk_buff to the MAC hardware. + /// + /// Specifically, one should make absolutely sure that this function is + /// called before TX completion of this packet can trigger. Otherwise + /// the packet could potentially already be freed. + pub fn tx_timestamp(&mut self) { + self.clone_tx_timestamp(); + if self.shinfo().tx_flags() as u32 & bindings::SKBTX_SW_TSTAMP != 0 { + unsafe { + bindings::skb_tstamp_tx(self.ptr as *mut bindings::sk_buff, ptr::null_mut()); + } + // skb_tstamp_tx(skb, NULL); + } + } + + /// Length of the [`SkBuff`]. + pub fn len(&self) -> u32 { + self.get_internal().len + } + + /// Get the Shared info for this SKBuffer. + pub fn shinfo(&self) -> SkbSharedInfo { + // SAFETY: self.ptr is valid if self is valid + unsafe { + let info = self.shinfo_int(); + SkbSharedInfo::from_pointer(info) + } + } + + unsafe fn shinfo_int(&self) -> *mut bindings::skb_shared_info { + self.end_pointer() as *mut bindings::skb_shared_info + } + + // NET_SKBUFF_DATA_USES_OFFSET + #[cfg(target_pointer_width = "64")] + fn end_pointer(&self) -> *mut u8 { + let sk_reff = self.get_internal(); + (sk_reff.head as usize + sk_reff.end as usize) as *mut u8 + } + + // !NET_SKBUFF_DATA_USES_OFFSET + #[cfg(not(target_pointer_width = "64"))] + fn end_pointer(&self) -> *mut u8 { + let sk_reff = self.get_internal(); + (sk_reff.end) as *mut u8 + } +} + +impl SavedAsPointer for SkBuff { + type InternalType = bindings::sk_buff; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} + +impl Drop for SkBuff { + #[cfg(CONFIG_TRACEPOINTS)] + fn drop(&mut self) { + // SAFETY: self.ptr is valid if self is valid + unsafe { + bindings::consume_skb(self.ptr as *mut bindings::sk_buff); + } + } + + #[cfg(not(CONFIG_TRACEPOINTS))] + fn drop(&mut self) { + // SAFETY: self.ptr is valid if self is valid + unsafe { + bindings::kfree_skb(self.ptr as *mut bindings::sk_buff); + } + } +} + +/// Wraps the kernel's `struct skb_shared_info`. +/// +/// # Invariants +/// +/// The pointer [`SkbSharedInfo::ptr`] is non-null and valid. +#[repr(transparent)] +pub struct SkbSharedInfo { + ptr: *const bindings::skb_shared_info, +} + +impl SkbSharedInfo { + /// The `tx_flag` field of the wrapped [`bindings::skb_shared_info`] + pub fn tx_flags(&self) -> u8 { + self.get_internal().tx_flags + } +} + +impl SavedAsPointer for SkbSharedInfo { + type InternalType = bindings::skb_shared_info; + + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self { + Self { ptr } + } + + fn get_pointer(&self) -> *const Self::InternalType { + self.ptr + } +} diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index 956639c4a3548d..f795b1c290bb0b 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -20,3 +20,5 @@ pub use super::{pr_alert, pr_cont, pr_crit, pr_emerg, pr_err, pr_info, pr_notice pub use super::static_assert; pub use super::{KernelModule, Result}; + +pub use crate::types::{SavedAsPointer, SavedAsPointerMut}; diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index 5af7cf8cf80664..eb5112a6ec4fd9 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -136,3 +136,41 @@ impl PointerWrapper for Pin { Pin::new_unchecked(T::from_pointer(p)) } } + +/// Trait for wrappers which are holding a direct pointer that they are getting from a C side interface. +pub trait SavedAsPointer { + /// The underlying C type from [`crate::bindings`] which this Wrapper is containing. + type InternalType; + + /// Returns the raw pointer stored in the wrapper. + fn get_pointer(&self) -> *const Self::InternalType; + + /// Returns the instance back which contains the raw pointer `ptr`. + /// + /// # Safety + /// + /// The passed pointer must be valid and of type `*const [`Self::InternalType`]`. + unsafe fn from_pointer(ptr: *const Self::InternalType) -> Self; + + /// Returns a reference to to the internal struct + fn get_internal(&self) -> &Self::InternalType { + let ptr = self.get_pointer(); + + unsafe { ptr.as_ref() }.unwrap() + } +} + +/// Trait for [`SavedAsPointer`] types which also can be accesed in a mutable way. +pub trait SavedAsPointerMut: SavedAsPointer { + /// Return a mutable pointer to the underlying struct. + fn get_pointer_mut(&self) -> *mut Self::InternalType { + self.get_pointer() as *mut Self::InternalType + } + + /// Returns a mutable referecne to the internal struct. + fn get_internal_mut(&mut self) -> &mut Self::InternalType { + let ptr = self.get_pointer() as *mut Self::InternalType; + + unsafe { ptr.as_mut() }.unwrap() + } +} diff --git a/rust/module.rs b/rust/module.rs index dfd2d2cfce0277..f4c25ab4ffcff0 100644 --- a/rust/module.rs +++ b/rust/module.rs @@ -103,6 +103,14 @@ fn expect_end(it: &mut token_stream::IntoIter) { } } +fn get_ident(it: &mut token_stream::IntoIter, expected_name: &str) -> String { + assert_eq!(expect_ident(it), expected_name); + assert_eq!(expect_punct(it), ':'); + let ident = expect_ident(it); + assert_eq!(expect_punct(it), ','); + ident +} + fn get_literal(it: &mut token_stream::IntoIter, expected_name: &str) -> String { assert_eq!(expect_ident(it), expected_name); assert_eq!(expect_punct(it), ':'); @@ -858,3 +866,201 @@ pub fn module_misc_device(ts: TokenStream) -> TokenStream { .parse() .expect("Error parsing formatted string into token stream.") } + +/// Declares a rtnl link operation table. +/// +/// The `type` argument should match the type used for `T` in `NetDevice`. +/// +/// # Examples +/// +/// ```rust,no_run +/// use kernel::prelude::*; +/// use kernel::net::prelude::*; +/// +/// fn setup(dev: &mut NetDevice) { +/// dev.ether_setup(); +/// +/// dev.set_ops(); +/// // ... +/// } +/// +/// rtnl_link_ops! { +/// kind: b"dummy_rs", +/// type: DummyRsDev, +/// setup: setup, +/// maxtype: 20, +/// } +/// +/// struct DummyRsDev; +/// +/// impl NetDeviceOps for DummyRsDev { +/// kernel::declare_net_device_ops!(); +/// +/// fn init(dev: &NetDevice) -> KernelResult<()> { +/// Ok(()) +/// } +/// +/// fn uninit(dev: &NetDevice) { +/// +/// } +/// } +/// +/// impl EthToolOps for DummyRsDev { +/// kernel::declare_eth_tool_ops!(); +/// } +/// +/// fn call_from_module_init() -> KernelResult<()> { +/// let mut dev = NetDevice::new(DummyRsDev, kernel::cstr!("dummyrs%d"), kernel::net::device::NetNameAssingType::Enum, 1, 1)?; +/// +/// dev.register(); +/// dev.set_rtnl_ops(dummy_rs_rtnl_link_ops); +/// +/// Ok(()) +/// } +/// ``` +#[proc_macro] +pub fn rtnl_link_ops(ts: TokenStream) -> TokenStream { + let mut it = ts.into_iter(); + let literals = vec!["maxtype", "policy", "slave_maxtype", "slave_policy"]; + + let mut found_idents = Vec::new(); + + let kind = get_byte_string(&mut it, "kind"); + let netdevice = get_ident(&mut it, "type"); + + let mut callbacks = String::new(); + let mut fields = String::new(); + + loop { + let name = match it.next() { + Some(TokenTree::Ident(ident)) => ident.to_string(), + Some(_) => panic!("Expected Ident or End"), + None => break, + }; + + assert_eq!(expect_punct(&mut it), ':'); + + if literals.contains(&name.as_str()) { + let literal = expect_literal(&mut it); + fields.push_str(&format!( + "{name}: {literal},\n", + name = name, + literal = literal + )); + } else { + let func = expect_ident(&mut it); + callbacks.push_str(&build_rtnl_links_callback(&name, &netdevice, &func, &kind)); + found_idents.push(name); + } + + assert_eq!(expect_punct(&mut it), ','); + } + + expect_end(&mut it); + + let callback_fields = found_idents + .iter() + .map(|name| format!("{}: Some(__rtnl_link_{}_callback_{}),", name, name, kind)) + .collect::>() + .join("\n"); + + let ops_struct = format!( + r#" + #[doc(hidden)] + #[used] + #[no_mangle] + pub static mut {kind}_link_ops: kernel::net::rtnl::RtnlLinkOps = kernel::net::rtnl::RtnlLinkOps(kernel::bindings::rtnl_link_ops {{ + priv_size: core::mem::size_of::<<{netdevice} as kernel::net::device::NetDeviceAdapter>::Inner>(), + kind: b"{kind}\0".as_ptr() as *const i8, + {callback_fields} + {fields} + ..kernel::net::rtnl::RTNL_LINK_OPS_EMPTY + }}); + "#, + kind = kind, + netdevice = netdevice, + callback_fields = callback_fields, + fields = fields, + ); + + format!( + r#" + {} + + // TODO: add #[link_section = ".data.read_mosty"] if x86 + #[cfg(any(CONFIG_X86, CONFIG_SPARC64))] + #[link_section = ".data.read_mostly"] + {} + + #[cfg(not(any(CONFIG_X86, CONFIG_SPARC64)))] + {} + "#, + callbacks, ops_struct, ops_struct, + ) + .parse() + .expect("Error parsing formatted string into token stream.") +} + +struct RtnlLinkValues { + callback_params: String, + return_type: String, + wrapper_before: String, + wrapper_after: String, + params: String, +} + +impl RtnlLinkValues { + fn new(callback_params: &str, wrapper_before: &str, params: &str) -> Self { + Self { + callback_params: callback_params.to_string(), + return_type: "()".to_string(), + wrapper_before: wrapper_before.to_string(), + wrapper_after: "".to_string(), + params: params.to_string(), + } + } +} + +fn get_rtnl_links_values(name: &str, netdevice: &str) -> RtnlLinkValues { + let setup_dev = format!( + "let mut dev = kernel::net::device::NetDevice::<{}>::from_pointer(dev);", + netdevice + ); + match name { + "setup" => RtnlLinkValues::new("dev: *mut kernel::bindings::net_device", &setup_dev, "&mut dev"), + "validate" => RtnlLinkValues { + callback_params: "tb: *mut *mut kernel::bindings::nlattr, data: *mut *mut kernel::bindings::nlattr, extack: *mut kernel::bindings::netlink_ext_ack".to_string(), + return_type: "kernel::c_types::c_int".to_string(), + wrapper_before: r#"kernel::c_from_kernel_result! { + let tb = kernel::net::netlink::NlAttrVec::from_pointer(tb as *const *const kernel::bindings::nlattr); + let data = kernel::net::netlink::NlAttrVec::from_pointer(data as *const *const kernel::bindings::nlattr); + let extack = kernel::net::netlink::NlExtAck::from_pointer(extack); + "#.to_string(), + wrapper_after: "?; Ok(0) }".to_string(), + params: "&tb, &data, &extack".to_string(), + }, + _ => panic!("invalid rtnl_link_ops function '{}'", name), + } +} + +fn build_rtnl_links_callback(name: &str, netdevice: &str, func: &str, kind: &str) -> String { + let values = get_rtnl_links_values(name, netdevice); + format!( + r#" + #[doc(hidden)] + pub unsafe extern "C" fn __rtnl_link_{name}_callback_{kind}({cb_params}) -> {cb_return} {{ + {cb_before} + {cb_func}({cb_r_params}) + {cb_after} + }} + "#, + name = name, + kind = kind, + cb_params = values.callback_params, + cb_return = values.return_type, + cb_before = values.wrapper_before, + cb_func = func, + cb_r_params = values.params, + cb_after = values.wrapper_after, + ) +}