Skip to content

Commit

Permalink
Rust NetDevice and NetDeviceOperationsVtable struct
Browse files Browse the repository at this point in the history
also adds drivers/net/dummy_rs.rs

Signed-off-by: Finn Behrens <[email protected]>
  • Loading branch information
kloenk committed May 7, 2021
1 parent 99bec9d commit 07fc327
Show file tree
Hide file tree
Showing 14 changed files with 2,203 additions and 3 deletions.
16 changes: 16 additions & 0 deletions drivers/net/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
<http://www.tldp.org/docs.html#guide>.

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
Expand Down
1 change: 1 addition & 0 deletions drivers/net/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
241 changes: 241 additions & 0 deletions drivers/net/dummy_rs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// 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 Donald Becker's skeleton driver
//! and the loopback driver. I then threw away anything that didn't
//! apply! Thanks to Alan Cox for the key clue on what to do with
//! misguided packets.
//!
//! Finn Behrens, 30th April 20201
//!
//! 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 core::ops::Deref;

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<DummyRsDev>) {
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) -> KernelResult<()> {
if let Some(addr) = tb.get(kernel::bindings::IFLA_ADDRESS) {
if addr.nla_len() != kernel::net::netlink::ETH_ALEN {
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<DummyRsDev>,
}

impl KernelModule for RustNetDummy {
fn init() -> KernelResult<Self> {
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<Self> for DummyRsDev {
kernel::declare_net_device_ops!(
get_stats64,
change_carrier,
validate_addr,
set_mac_addr,
set_rx_mode
);

fn init(dev: &mut NetDevice<Self>) -> KernelResult<()> {
dev.set_new_pcpu_lstats()?;
Ok(())
}

fn uninit(dev: &mut NetDevice<Self>) {
unsafe { dev.free_lstats() };
}

fn start_xmit(skb: SkBuff, dev: &mut NetDevice<Self>) -> 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<Self>, stats: &mut rtnl::RtnlLinkStats64) {
stats.dev_read(dev);
}

fn change_carrier(dev: &mut NetDevice<Self>, new_carrier: bool) -> KernelResult<()> {
dev.carrier_set(new_carrier);

Ok(())
}

fn validate_addr(dev: &NetDevice<Self>) -> KernelResult<()> {
device::helpers::eth_validate_addr(dev)
}

fn set_mac_addr(
dev: &mut NetDevice<Self>,
p: *mut kernel::c_types::c_void,
) -> KernelResult<()> {
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<Self>) {}
}

impl NetDeviceAdapter for DummyRsDev {
type Inner = Self;

type Ops = Self;

type EthOps = Self;

fn setup(dev: &mut NetDevice<Self>) {
setup(dev);
}
}

impl EthToolOps<Self> for DummyRsDev {
kernel::declare_eth_tool_ops!(get_drvinfo, get_ts_info);

fn get_drvinfo(_dev: &NetDevice<Self>, info: &mut ethtool::EthtoolDrvinfo) {
// TODO: how to do this more efficient without unsafe?
// FIXME: !!
let info: &kernel::bindings::ethtool_drvinfo = info.deref();
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<Self>, info: &mut ethtool::EthToolTsInfo) -> KernelResult<()> {
kernel::net::ethtool::helpers::ethtool_op_get_ts_info(dev, info)
}
}
33 changes: 32 additions & 1 deletion rust/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include <linux/gfp.h>
#include <linux/highmem.h>
#include <linux/uio.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/rtnetlink.h>

void rust_helper_BUG(void)
{
Expand Down Expand Up @@ -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
8 changes: 8 additions & 0 deletions rust/kernel/bindings_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/etherdevice.h>
#include <linux/netdev_features.h>
#include <linux/rtnetlink.h>
#include <net/rtnetlink.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/slab.h>
Expand All @@ -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;
26 changes: 25 additions & 1 deletion rust/kernel/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -104,3 +107,24 @@ impl From<AllocError> for Error {
Error::ENOMEM
}
}

/// Used by the rtnl_link_ops macro to interface with C
pub fn c_from_kernel_result<T>(r: KernelResult<T>) -> T
where
T: TryFrom<c_types::c_int>,
T::Error: core::fmt::Debug,
{
match r {
Ok(v) => v,
Err(e) => T::try_from(e.to_kernel_errno()).unwrap(),
}
}

#[macro_export]
macro_rules! c_from_kernel_result {
($($tt:tt)*) => {{
$crate::c_from_kernel_result((|| {
$($tt)*
})())
}};
}
Loading

0 comments on commit 07fc327

Please sign in to comment.