Skip to content

Commit 7c69776

Browse files
zephyr: net: add initial support for UDP sockets
Initial implementation of a Rust wrapper around the Zephyr networking API for UDP sockets, loosely based on the UdpSocket in the Rust std library. It's possible to create/bind a socket and sendto/recvfrom data, but not much else. There is also some supporting functionality for converting IP addresses and socket addresses between the C representation and Rust's core::net types. Signed-off-by: Matt Rodgers <[email protected]>
1 parent 269703d commit 7c69776

File tree

9 files changed

+456
-0
lines changed

9 files changed

+456
-0
lines changed

tests/net/udp/CMakeLists.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(udp_rust)
7+
8+
rust_cargo_application()

tests/net/udp/Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) 2025 Witekio
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
[package]
5+
# This must be rustapp for now.
6+
name = "rustapp"
7+
version = "0.1.0"
8+
edition = "2021"
9+
description = "Tests of UDP sockets"
10+
license = "Apache-2.0 or MIT"
11+
12+
[lib]
13+
crate-type = ["staticlib"]
14+
15+
[dependencies]
16+
zephyr = "0.1.0"

tests/net/udp/prj.conf

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright (c) 2025 Witekio
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
CONFIG_RUST=y
5+
CONFIG_MAIN_STACK_SIZE=2048
6+
7+
# Networking config
8+
CONFIG_NETWORKING=y
9+
CONFIG_NET_IPV4=y
10+
CONFIG_NET_IPV6=y
11+
CONFIG_NET_UDP=y
12+
CONFIG_NET_SOCKETS=y
13+
14+
# Networking drivers
15+
CONFIG_NET_DRIVERS=y
16+
CONFIG_NET_TEST=y
17+
CONFIG_NET_LOOPBACK=y
18+
CONFIG_TEST_RANDOM_GENERATOR=y
19+
20+
# Avoid tests blocking forever if something goes wrong
21+
CONFIG_NET_CONTEXT_SNDTIMEO=y
22+
CONFIG_NET_CONTEXT_RCVTIMEO=y

tests/net/udp/src/lib.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2025 Witekio
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#![no_std]
5+
6+
use zephyr::net::UdpSocket;
7+
use zephyr::printkln;
8+
9+
#[no_mangle]
10+
extern "C" fn rust_main() {
11+
printkln!("Starting tests");
12+
13+
let rx_sa = "127.0.0.1:4242"
14+
.parse()
15+
.expect("Failed to parse RX SocketAddr");
16+
let tx_sa = "127.0.0.1:12345"
17+
.parse()
18+
.expect("Failed to parse TX SocketAddr");
19+
20+
let rx_sock = UdpSocket::bind(&rx_sa).expect("Failed to create RX UDP socket");
21+
let tx_sock = UdpSocket::bind(&tx_sa).expect("Failed to create TX UDP socket");
22+
23+
let tx_data: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
24+
let sent = tx_sock
25+
.send_to(&tx_data, &rx_sa)
26+
.expect("Failed to send data");
27+
assert_eq!(sent, tx_data.len());
28+
29+
let mut rx_data: [u8; 8] = [0; 8];
30+
let (recvd, peer) = rx_sock
31+
.recv_from(&mut rx_data)
32+
.expect("Failed to receive data");
33+
assert_eq!(recvd, tx_data.len());
34+
assert_eq!(tx_data, rx_data);
35+
assert_eq!(peer, tx_sa);
36+
37+
printkln!("All tests passed");
38+
}

tests/net/udp/testcase.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
common:
2+
filter: CONFIG_RUST_SUPPORTED
3+
platform_allow:
4+
- qemu_cortex_m0
5+
- qemu_cortex_m3
6+
- qemu_riscv32
7+
- qemu_riscv64
8+
- nrf52840dk/nrf52840
9+
tests:
10+
test.rust.udp:
11+
harness: console
12+
harness_config:
13+
type: one_line
14+
regex:
15+
- "All tests passed"

zephyr/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
//! also a [`kio::sync::Mutex`] type that works with async.
5353
//! - [`logging`]: A logging backend for Rust on Zephyr. This will log to either `printk` or
5454
//! through Zephyr's logging framework.
55+
//! - [`net`]: Networking support based on top of Zephyr's sockets API.
5556
//!
5657
//! [`Instant`]: time::Instant
5758
//! [`Duration`]: time::Duration
@@ -83,6 +84,7 @@ pub mod error;
8384
#[cfg(CONFIG_RUST_ALLOC)]
8485
pub mod kio;
8586
pub mod logging;
87+
pub mod net;
8688
pub mod object;
8789
#[cfg(CONFIG_RUST_ALLOC)]
8890
pub mod simpletls;

zephyr/src/net/ipaddr.rs

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//! Conversion between Rust and C representations of IP and Socket addresses.
2+
//!
3+
//! The intention is that the Rust networking API presents an interface using the types defined in
4+
//! [`core::net`](https://doc.rust-lang.org/stable/core/net/index.html). Conversion to and from the
5+
//! C types should only be required inside this crate.
6+
7+
use crate::error::{Error, Result};
8+
use crate::raw::{in6_addr, in_addr, sockaddr, sockaddr_in, sockaddr_in6, socklen_t};
9+
use core::mem::MaybeUninit;
10+
use core::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
11+
12+
/// Convert an IPv4 address from Rust to C representation
13+
pub(crate) fn ipv4_addr_to_c(addr: &Ipv4Addr) -> in_addr {
14+
let octets = addr.octets();
15+
16+
// SAFETY: It's not possible to easily construct this type due to the BindgenUnion field, and in
17+
// any case constructing it would always require unsafe code.
18+
//
19+
// The C type and the octets field of the Rust type both have a well-defined layout: 4 bytes in
20+
// big-endian order. So it is safe to transmute between the two types.
21+
unsafe { core::mem::transmute(octets) }
22+
}
23+
24+
/// Convert an IPv4 address from C to Rust representation
25+
pub(crate) fn ipv4_addr_from_c(addr: &in_addr) -> Ipv4Addr {
26+
// SAFETY: The s4_addr union field has no restrictions on which values are valid, so is always
27+
// safe to access.
28+
let s4_addr = unsafe { addr.__bindgen_anon_1.s4_addr.as_ref() };
29+
30+
Ipv4Addr::new(s4_addr[0], s4_addr[1], s4_addr[2], s4_addr[3])
31+
}
32+
33+
/// Convert an IPv6 address from Rust to C representation
34+
pub(crate) fn ipv6_addr_to_c(addr: &Ipv6Addr) -> in6_addr {
35+
let octets = addr.octets();
36+
37+
// SAFETY: It's not possible to easily construct this type due to the BindgenUnion field, and in
38+
// any case constructing it would always require unsafe code.
39+
//
40+
// The C type and the octets field of the Rust type both have a well-defined layout: 16 bytes in
41+
// big-endian order. So it is safe to transmute between the two types.
42+
unsafe { core::mem::transmute(octets) }
43+
}
44+
45+
/// Convert an IPv6 address from C to Rust representation
46+
pub(crate) fn ipv6_addr_from_c(addr: &in6_addr) -> Ipv6Addr {
47+
// SAFETY: The s6_addr16 union field has no restrictions on which values are valid, so is always
48+
// safe to access.
49+
let s6_addr16 = unsafe { addr.__bindgen_anon_1.s6_addr16.as_ref() };
50+
51+
Ipv6Addr::new(
52+
s6_addr16[0],
53+
s6_addr16[1],
54+
s6_addr16[2],
55+
s6_addr16[3],
56+
s6_addr16[4],
57+
s6_addr16[5],
58+
s6_addr16[6],
59+
s6_addr16[7],
60+
)
61+
}
62+
63+
/// Convert an IPv4 socket address from Rust to C representation
64+
pub(crate) fn sockaddr_v4_to_c(sa: &SocketAddrV4) -> sockaddr_in {
65+
sockaddr_in {
66+
sin_family: crate::raw::AF_INET as u16,
67+
sin_port: sa.port().to_be(),
68+
sin_addr: ipv4_addr_to_c(sa.ip()),
69+
}
70+
}
71+
72+
/// Try to convert an IPv4 socket address from C to Rust representation
73+
///
74+
/// This will return an error if the `sin_family` field of `sa` is not `AF_INET`.
75+
pub(crate) fn try_sockaddr_v4_from_c(sa: &sockaddr_in) -> Result<SocketAddrV4> {
76+
if sa.sin_family != crate::raw::AF_INET as u16 {
77+
return Err(Error(crate::raw::EINVAL));
78+
}
79+
80+
Ok(SocketAddrV4::new(
81+
ipv4_addr_from_c(&sa.sin_addr),
82+
u16::from_be(sa.sin_port),
83+
))
84+
}
85+
86+
/// Try to convert an IPv6 socket address from Rust to C representation
87+
///
88+
/// This will return an error in either of the following cases:
89+
/// - The `flowinfo` field is non-zero
90+
/// - The `scope_id` field cannot be represented as a `u8`
91+
///
92+
/// Zephyr's `struct sockaddr_in6` differs slightly from the traditional BSD sockets API in that
93+
/// it does not contain a `flowinfo` field, and the `scope_id` is stored as a `uint8_t`.
94+
pub(crate) fn try_sockaddr_v6_to_c(sa: &SocketAddrV6) -> Result<sockaddr_in6> {
95+
if sa.flowinfo() != 0 || sa.scope_id() > u8::MAX as u32 {
96+
return Err(Error(crate::raw::EINVAL));
97+
}
98+
99+
Ok(sockaddr_in6 {
100+
sin6_family: crate::raw::AF_INET6 as u16,
101+
sin6_port: sa.port().to_be(),
102+
sin6_addr: ipv6_addr_to_c(sa.ip()),
103+
sin6_scope_id: sa.scope_id() as u8,
104+
})
105+
}
106+
107+
/// Try to convert an IPv6 socker address from C to Rust representation
108+
///
109+
/// This will return an error if the `sin6_family` field of `sa` is not `AF_INET6`.
110+
pub(crate) fn try_sockaddr_v6_from_c(sa: &sockaddr_in6) -> Result<SocketAddrV6> {
111+
if sa.sin6_family != crate::raw::AF_INET6 as u16 {
112+
return Err(Error(crate::raw::EINVAL));
113+
}
114+
115+
Ok(SocketAddrV6::new(
116+
ipv6_addr_from_c(&sa.sin6_addr),
117+
u16::from_be(sa.sin6_port),
118+
0,
119+
sa.sin6_scope_id as u32,
120+
))
121+
}
122+
123+
/// Try to convert a socket address from Rust to C representation
124+
///
125+
/// This will return an error if the socket address is a `SocketAddrV6`, and this address contains
126+
/// fields which cannot be represented in the C struct (see [try_sockaddr_v6_from_c]).
127+
pub(crate) fn try_sockaddr_to_c(sa: &SocketAddr) -> Result<(sockaddr, socklen_t)> {
128+
let mut sa_out = MaybeUninit::<sockaddr>::zeroed();
129+
let dst_ptr = sa_out.as_mut_ptr() as *mut u8;
130+
131+
let socklen = match sa {
132+
SocketAddr::V4(sa4) => {
133+
let src = sockaddr_v4_to_c(sa4);
134+
let src_ptr = (&src as *const sockaddr_in) as *const u8;
135+
let len = core::mem::size_of::<sockaddr>();
136+
137+
// SAFETY: The `sockaddr` struct is sized to be able to contain either a `sockaddr_in`
138+
// or a `sockaddr_in6`, so it is safe to copy either type into it.
139+
unsafe {
140+
core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, len);
141+
}
142+
143+
len
144+
}
145+
SocketAddr::V6(sa6) => {
146+
let src = try_sockaddr_v6_to_c(sa6)?;
147+
let src_ptr = (&src as *const sockaddr_in6) as *const u8;
148+
let len = core::mem::size_of::<sockaddr_in6>();
149+
150+
// SAFETY: The `sockaddr` struct is sized to be able to contain either a `sockaddr_in`
151+
// or a `sockaddr_in6`, so it is safe to copy either type into it.
152+
unsafe {
153+
core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, len);
154+
}
155+
156+
len
157+
}
158+
};
159+
160+
// SAFETY: Both match arms initialise `sa_out` with a valid `sockaddr`
161+
let sa_out = unsafe { sa_out.assume_init() };
162+
163+
Ok((sa_out, socklen))
164+
}
165+
166+
/// Try to convert a socket address from C to Rust representation
167+
///
168+
/// This will return an error if the `sa_family` field is not either `AF_INET` or `AF_INET6`.
169+
pub(crate) fn try_sockaddr_from_c(sa: &sockaddr, socklen: socklen_t) -> Result<SocketAddr> {
170+
match sa.sa_family as u32 {
171+
crate::raw::AF_INET => {
172+
if socklen != core::mem::size_of::<sockaddr_in>() {
173+
return Err(Error(crate::raw::EINVAL));
174+
}
175+
176+
let sa4_ref: &sockaddr_in = unsafe { core::mem::transmute(sa) };
177+
let res = try_sockaddr_v4_from_c(sa4_ref)?;
178+
Ok(SocketAddr::V4(res))
179+
}
180+
crate::raw::AF_INET6 => {
181+
if socklen != core::mem::size_of::<sockaddr_in6>() {
182+
return Err(Error(crate::raw::EINVAL));
183+
}
184+
185+
let sa6_ref: &sockaddr_in6 = unsafe { core::mem::transmute(sa) };
186+
let res = try_sockaddr_v6_from_c(sa6_ref)?;
187+
Ok(SocketAddr::V6(res))
188+
}
189+
_ => Err(Error(crate::raw::EINVAL)),
190+
}
191+
}

zephyr/src/net/mod.rs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! Networking support.
2+
//!
3+
//! This module provides support for networking over UDP.
4+
//!
5+
//! Currently only a blocking networking API is provided, which builds on top of Zephyr's sockets
6+
//! API.
7+
8+
mod ipaddr;
9+
mod socket;
10+
11+
use crate::error::Result;
12+
use crate::net::socket::{Domain, Protocol, SockType, Socket};
13+
use core::net::SocketAddr;
14+
15+
/// UDP socket.
16+
///
17+
/// A UDP socket can be created by binding to a local socket address. Once bound, data can be sent
18+
/// and received using [`send_to`] and [`recv_from`].
19+
///
20+
/// [`send_to`]: UdpSocket::send_to
21+
/// [`recv_from`]: UdpSocket::recv_from
22+
pub struct UdpSocket {
23+
sock: Socket,
24+
}
25+
26+
impl UdpSocket {
27+
/// Create a UDP socket bound to the provided socket address.
28+
pub fn bind(addr: &SocketAddr) -> Result<Self> {
29+
let domain = match addr {
30+
SocketAddr::V4(_) => Domain::AfInet,
31+
SocketAddr::V6(_) => Domain::AfInet6,
32+
};
33+
34+
let mut sock = Socket::new(domain, SockType::Dgram, Protocol::IpprotoUdp)?;
35+
sock.bind(addr)?;
36+
37+
Ok(Self { sock })
38+
}
39+
40+
/// Send data to the specified socket address.
41+
pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> Result<usize> {
42+
self.sock.send_to(buf, addr)
43+
}
44+
45+
/// Receive from the socket, returning the data length and peer address it was received from.
46+
pub fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr)> {
47+
self.sock.recv_from(buf)
48+
}
49+
}

0 commit comments

Comments
 (0)