Skip to content

Basic networking support #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dt-rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
- "nordic,nrf52-flash-controller"
- "nordic,nrf51-flash-controller"
- "raspberrypi,pico-flash-controller"
- "st,stm32-flash-controller"
- "zephyr,sim-flash"
level: 0
actions:
Expand Down
8 changes: 8 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,12 @@ void rust_panic_wrap(void)
k_panic();
}

/* `errno` is usually defined as a macro rather than a variable, which makes it difficult to
* access in Rust. So create a simple getter function in C.
*/
int get_errno(void)
{
return errno;
}

#endif
7 changes: 7 additions & 0 deletions samples/net/echo_server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(echo_server)

rust_cargo_application()
21 changes: 21 additions & 0 deletions samples/net/echo_server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (c) 2025 Witekio
# SPDX-License-Identifier: Apache-2.0

[package]
# This must be rustapp for now.
name = "rustapp"
version = "0.1.0"
edition = "2021"
description = "Echo's back any data received from a UDP network socket to the sender"
license = "Apache-2.0 OR MIT"

[lib]
crate-type = ["staticlib"]

[dependencies]
zephyr = "0.1.0"
log = "0.4.22"
static_cell = "2.1.0"

[build-dependencies]
zephyr-build = "0.1.0"
3 changes: 3 additions & 0 deletions samples/net/echo_server/boards/qemu_cortex_m3.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_NET_QEMU_ETHERNET=y
CONFIG_NET_L2_ETHERNET=y
9 changes: 9 additions & 0 deletions samples/net/echo_server/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fn main() {
// This call will make make config entries available in the code for every device tree node, to
// allow conditional compilation based on whether it is present in the device tree.
// For example, it will be possible to have:
// ```rust
// #[cfg(dt = "aliases::led0")]
// ```
zephyr_build::dt_cfgs();
}
23 changes: 23 additions & 0 deletions samples/net/echo_server/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CONFIG_RUST=y
CONFIG_RUST_ALLOC=y # For threads
CONFIG_MAIN_STACK_SIZE=2048

# Networking config
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=y
CONFIG_NET_UDP=y
CONFIG_NET_TCP=y
CONFIG_NET_SOCKETS=y

# Net config settings for sample
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV6=y
CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1"
CONFIG_NET_CONFIG_PEER_IPV6_ADDR="2001:db8::2"
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1"
CONFIG_NET_CONFIG_PEER_IPV4_ADDR="192.0.2.2"

CONFIG_SHELL=y
CONFIG_NET_SHELL=y
15 changes: 15 additions & 0 deletions samples/net/echo_server/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# See doc/develop/test/twister.rst for what is here.
sample:
name: Echo Server Sample
common:
filter: CONFIG_RUST_SUPPORTED
platform_allow:
- qemu_cortex_m3
- qemu_riscv32
- qemu_riscv64
tests:
sample.rust.net.echo_server:
tags:
- net
depends_on: netif
harness: net
108 changes: 108 additions & 0 deletions samples/net/echo_server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2025 Witekio
// SPDX-License-Identifier: Apache-2.0

#![no_std]
#![allow(unexpected_cfgs)]

use log::{error, info, warn};
use static_cell::ConstStaticCell;
use zephyr::error::Result;
use zephyr::kobj_define;
use zephyr::net::{TcpListener, TcpStream, UdpSocket};

#[no_mangle]
extern "C" fn rust_main() {
unsafe {
zephyr::set_logger().unwrap();
}

// Don't allocate the large RX buffers on the stack
static UDP_RX_BUF: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
static TCP_RX_BUF: ConstStaticCell<[u8; 2048]> = ConstStaticCell::new([0; 2048]);
let udp_rx_buf = UDP_RX_BUF.take();
let tcp_rx_buf = TCP_RX_BUF.take();

// Create the sockets
let sockaddr = "0.0.0.0:4242".parse().unwrap();
let udp_sock = UdpSocket::bind(&sockaddr).expect("Failed to bind UdpSocket");
let mut tcp_listener = TcpListener::bind(&sockaddr).expect("Failed to bind TcpListener");

info!("Echo server bound to address {:?}", sockaddr);

// Spawn the UDP echo server in a thread
let udp_thread = UDP_THREAD
.init_once(UDP_STACK.init_once(()).unwrap())
.unwrap();

udp_thread.spawn(move || {
if let Err(e) = run_udp_echo(udp_sock, udp_rx_buf) {
error!("UDP echo thread failed with error {:?}", e);
}
});

// Run the TCP echo server in the main thread
loop {
match tcp_listener.accept() {
Ok((sock, peer)) => {
info!("Accepted connection from peer address {:?}", peer);
let _ = run_tcp_echo(sock, tcp_rx_buf);
}
Err(e) => {
error!("Failed to accept TCP connection with error {:?}", e);
break;
}
}
}

info!("TCP echo server exited");
}

fn run_udp_echo(sock: UdpSocket, buf: &mut [u8]) -> Result<()> {
loop {
let (n, peer) = sock.recv_from(buf)?;

// Note that being able to set the MSG_TRUNC sockopt is not implemented yet so it should
// not be possible to get n > rx_buf.len(), but it's probably still worth including the
// check for when this sockopt is implemented.
let n_trunc = n.min(buf.len());
if n != n_trunc {
warn!("Data truncated, got {} / {} bytes", n_trunc, n);
}

info!(
"Echoing {} bytes to peer address {:?} via UDP",
n_trunc, peer
);
let _ = sock.send_to(&buf[0..n_trunc], &peer)?;
}
}

fn run_tcp_echo(mut sock: TcpStream, buf: &mut [u8]) -> Result<()> {
loop {
let n = sock.recv(buf)?;

if 0 == n {
info!("TCP client disconected");
return Ok(());
}

info!("Echoing {} bytes via TCP", n);
let mut remaining = &buf[0..n];
while !remaining.is_empty() {
match sock.send(remaining)? {
0 => {
info!("TCP client disconnected");
return Ok(());
}
n => remaining = &remaining[n..],
};
}
}
}

const STACK_SIZE: usize = zephyr::kconfig::CONFIG_MAIN_STACK_SIZE as usize;

kobj_define! {
static UDP_THREAD: StaticThread;
static UDP_STACK: ThreadStack<STACK_SIZE>;
}
8 changes: 8 additions & 0 deletions tests/net/tcp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(tcp_rust)

rust_cargo_application()
16 changes: 16 additions & 0 deletions tests/net/tcp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright (c) 2025 Witekio
# SPDX-License-Identifier: Apache-2.0

[package]
# This must be rustapp for now.
name = "rustapp"
version = "0.1.0"
edition = "2021"
description = "Tests of TCP sockets"
license = "Apache-2.0 or MIT"

[lib]
crate-type = ["staticlib"]

[dependencies]
zephyr = "0.1.0"
22 changes: 22 additions & 0 deletions tests/net/tcp/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (c) 2025 Witekio
# SPDX-License-Identifier: Apache-2.0

CONFIG_RUST=y
CONFIG_RUST_ALLOC=y # For threads
CONFIG_MAIN_STACK_SIZE=2048

# Networking config
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=y
CONFIG_NET_TCP=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_PKT_RX_COUNT=16
CONFIG_NET_PKT_TX_COUNT=16

# Networking drivers
CONFIG_NET_DRIVERS=y
CONFIG_NET_TEST=y
CONFIG_NET_LOOPBACK=y
CONFIG_TEST_RANDOM_GENERATOR=y

61 changes: 61 additions & 0 deletions tests/net/tcp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2025 Witekio
// SPDX-License-Identifier: Apache-2.0

#![no_std]

use zephyr::net::{TcpListener, TcpStream};
use zephyr::printkln;

#[no_mangle]
extern "C" fn rust_main() {
let tx_data: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
let mut server_recv_buf: [u8; 64] = [0; 64];
let mut client_recv_buf: [u8; 64] = [0; 64];

// The test creates client and server TcpStreams, and echo's some data between them to verify
// that the TCP connection is functioning
printkln!("Starting tests");

let server_sockaddr = "0.0.0.0:4242"
.parse()
.expect("Failed to parse server sockaddr");

let connect_sockaddr = "127.0.0.1:4242"
.parse()
.expect("Failed to parse connect sockaddr");

let mut listener = TcpListener::bind(&server_sockaddr).expect("Failed to create TcpListener");

let mut client_stream =
TcpStream::connect(&connect_sockaddr).expect("Failed to create client TcpStream");

let sent = client_stream
.send(&tx_data)
.expect("Client failed to send data");

assert_eq!(sent, tx_data.len());

let (mut server_stream, _) = listener.accept().expect("Failed to accept connection");

let recvd = server_stream
.recv(&mut server_recv_buf)
.expect("Server failed to receive data");

assert_eq!(recvd, tx_data.len());
assert_eq!(server_recv_buf[0..recvd], tx_data);

let sent = server_stream
.send(&server_recv_buf[0..recvd])
.expect("Failed to send data");

assert_eq!(sent, recvd);

let recvd = client_stream
.recv(&mut client_recv_buf)
.expect("Client failed to receive data");

assert_eq!(recvd, sent);
assert_eq!(client_recv_buf[0..recvd], tx_data);

printkln!("All tests passed");
}
15 changes: 15 additions & 0 deletions tests/net/tcp/testcase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
common:
filter: CONFIG_RUST_SUPPORTED
platform_allow:
- qemu_cortex_m0
- qemu_cortex_m3
- qemu_riscv32
- qemu_riscv64
- nrf52840dk/nrf52840
tests:
test.rust.tcp:
harness: console
harness_config:
type: one_line
regex:
- "All tests passed"
8 changes: 8 additions & 0 deletions tests/net/udp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(udp_rust)

rust_cargo_application()
16 changes: 16 additions & 0 deletions tests/net/udp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright (c) 2025 Witekio
# SPDX-License-Identifier: Apache-2.0

[package]
# This must be rustapp for now.
name = "rustapp"
version = "0.1.0"
edition = "2021"
description = "Tests of UDP sockets"
license = "Apache-2.0 or MIT"

[lib]
crate-type = ["staticlib"]

[dependencies]
zephyr = "0.1.0"
19 changes: 19 additions & 0 deletions tests/net/udp/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2025 Witekio
# SPDX-License-Identifier: Apache-2.0

CONFIG_RUST=y
CONFIG_MAIN_STACK_SIZE=2048

# Networking config
CONFIG_NETWORKING=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=y
CONFIG_NET_UDP=y
CONFIG_NET_SOCKETS=y

# Networking drivers
CONFIG_NET_DRIVERS=y
CONFIG_NET_TEST=y
CONFIG_NET_LOOPBACK=y
CONFIG_TEST_RANDOM_GENERATOR=y

Loading
Loading