Skip to content

Commit 4e532f0

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 ff8933e commit 4e532f0

File tree

10 files changed

+411
-0
lines changed

10 files changed

+411
-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::udp::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

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub mod error;
8383
#[cfg(CONFIG_RUST_ALLOC)]
8484
pub mod kio;
8585
pub mod logging;
86+
pub mod net;
8687
pub mod object;
8788
#[cfg(CONFIG_RUST_ALLOC)]
8889
pub mod simpletls;

zephyr/src/net/ipaddr.rs

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//!
2+
//! docs todo...
3+
//!
4+
5+
use crate::error::{Error, Result};
6+
use crate::raw::{in6_addr, in_addr, sockaddr, sockaddr_in, sockaddr_in6, socklen_t};
7+
use core::mem::MaybeUninit;
8+
use core::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
9+
10+
/// docs todo...
11+
pub(crate) fn ipv4_addr_to_c(addr: &Ipv4Addr) -> in_addr {
12+
let octets = addr.octets();
13+
14+
// It's not possible to easily construct this type due to the BindgenUnion field, and in any
15+
// case constructing it would always require unsafe code. Since the layout of the C type is
16+
// well defined and can be interpreted as a 4-byte array, we can just transmute the octets.
17+
unsafe { core::mem::transmute(octets) }
18+
}
19+
20+
/// docs todo...
21+
pub(crate) fn ipv4_addr_from_c(addr: &in_addr) -> Ipv4Addr {
22+
let s4_addr = unsafe { addr.__bindgen_anon_1.s4_addr.as_ref() };
23+
24+
Ipv4Addr::new(s4_addr[0], s4_addr[1], s4_addr[2], s4_addr[3])
25+
}
26+
27+
/// docs todo...
28+
pub(crate) fn ipv6_addr_to_c(addr: &Ipv6Addr) -> in6_addr {
29+
let octets = addr.octets();
30+
31+
// It's not possible to easily construct this type due to the BindgenUnion field, and in any
32+
// case constructing it would always require unsafe code. Since the layout of the C type is
33+
// well defined and can be interpreted as a 16-byte array, we can just transmute the octets.
34+
unsafe { core::mem::transmute(octets) }
35+
}
36+
37+
/// docs todo...
38+
pub(crate) fn ipv6_addr_from_c(addr: &in6_addr) -> Ipv6Addr {
39+
let s6_addr16 = unsafe { addr.__bindgen_anon_1.s6_addr16.as_ref() };
40+
41+
Ipv6Addr::new(
42+
s6_addr16[0],
43+
s6_addr16[1],
44+
s6_addr16[2],
45+
s6_addr16[3],
46+
s6_addr16[4],
47+
s6_addr16[5],
48+
s6_addr16[6],
49+
s6_addr16[7],
50+
)
51+
}
52+
53+
/// docs todo...
54+
pub(crate) fn sockaddr_v4_to_c(sa: &SocketAddrV4) -> sockaddr_in {
55+
sockaddr_in {
56+
sin_family: crate::raw::AF_INET as u16,
57+
sin_port: sa.port().to_be(),
58+
sin_addr: ipv4_addr_to_c(sa.ip()),
59+
}
60+
}
61+
62+
/// docs todo...
63+
pub(crate) fn try_sockaddr_v4_from_c(sa: &sockaddr_in) -> Result<SocketAddrV4> {
64+
if sa.sin_family != crate::raw::AF_INET as u16 {
65+
return Err(Error(crate::raw::EINVAL));
66+
}
67+
68+
Ok(SocketAddrV4::new(
69+
ipv4_addr_from_c(&sa.sin_addr),
70+
u16::from_be(sa.sin_port),
71+
))
72+
}
73+
74+
/// docs todo...
75+
pub(crate) fn sockaddr_v6_to_c(sa: &SocketAddrV6) -> sockaddr_in6 {
76+
sockaddr_in6 {
77+
sin6_family: crate::raw::AF_INET6 as u16,
78+
sin6_port: sa.port().to_be(),
79+
sin6_addr: ipv6_addr_to_c(sa.ip()),
80+
// TODO: It seems to be possible that Rust scope_id is > 255. Is panic the correct behaviour?
81+
sin6_scope_id: sa.scope_id() as u8,
82+
}
83+
}
84+
85+
/// docs todo...
86+
pub(crate) fn try_sockaddr_v6_from_c(sa: &sockaddr_in6) -> Result<SocketAddrV6> {
87+
if sa.sin6_family != crate::raw::AF_INET6 as u16 {
88+
return Err(Error(crate::raw::EINVAL));
89+
}
90+
91+
Ok(SocketAddrV6::new(
92+
ipv6_addr_from_c(&sa.sin6_addr),
93+
u16::from_be(sa.sin6_port),
94+
0,
95+
sa.sin6_scope_id as u32,
96+
))
97+
}
98+
99+
/// docs todo...
100+
pub(crate) fn sockaddr_to_c(sa: &SocketAddr) -> (sockaddr, socklen_t) {
101+
// TODO: this way of getting a `struct sockaddr` is pretty horrible, see if there is a better approach
102+
let mut sa_out = MaybeUninit::<sockaddr>::zeroed();
103+
let dst_ptr = sa_out.as_mut_ptr() as *mut u8;
104+
105+
let socklen = match sa {
106+
SocketAddr::V4(sa4) => {
107+
let src = sockaddr_v4_to_c(sa4);
108+
let src_ptr = (&src as *const sockaddr_in) as *const u8;
109+
let len = core::mem::size_of::<sockaddr>();
110+
111+
unsafe {
112+
core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, len);
113+
}
114+
115+
len
116+
}
117+
SocketAddr::V6(sa6) => {
118+
let src = sockaddr_v6_to_c(sa6);
119+
let src_ptr = (&src as *const sockaddr_in6) as *const u8;
120+
let len = core::mem::size_of::<sockaddr_in6>();
121+
122+
unsafe {
123+
core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, len);
124+
}
125+
126+
len
127+
}
128+
};
129+
130+
(unsafe { sa_out.assume_init() }, socklen)
131+
}
132+
133+
/// docs todo...
134+
pub(crate) fn try_sockaddr_from_c(sa: &sockaddr, socklen: socklen_t) -> Result<SocketAddr> {
135+
match sa.sa_family as u32 {
136+
crate::raw::AF_INET => {
137+
if socklen != core::mem::size_of::<sockaddr_in>() {
138+
return Err(Error(crate::raw::EINVAL));
139+
}
140+
141+
let sa4_ref: &sockaddr_in = unsafe { core::mem::transmute(sa) };
142+
let res = try_sockaddr_v4_from_c(sa4_ref)?;
143+
Ok(SocketAddr::V4(res))
144+
}
145+
crate::raw::AF_INET6 => {
146+
if socklen != core::mem::size_of::<sockaddr_in6>() {
147+
return Err(Error(crate::raw::EINVAL));
148+
}
149+
150+
let sa6_ref: &sockaddr_in6 = unsafe { core::mem::transmute(sa) };
151+
let res = try_sockaddr_v6_from_c(sa6_ref)?;
152+
Ok(SocketAddr::V6(res))
153+
}
154+
_ => Err(Error(crate::raw::EINVAL)),
155+
}
156+
}

zephyr/src/net/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//!
2+
//! docs todo...
3+
//!
4+
5+
mod ipaddr;
6+
mod socket;
7+
pub mod udp;

zephyr/src/net/socket.rs

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//!
2+
//! docs todo...
3+
//!
4+
5+
use crate::error::{to_result_errno, Result};
6+
use crate::net::ipaddr::{sockaddr_to_c, try_sockaddr_from_c};
7+
use crate::raw::{
8+
sockaddr, socklen_t, zsock_bind, zsock_close, zsock_recvfrom, zsock_sendto, zsock_socket,
9+
};
10+
use core::ffi::{c_int, c_void};
11+
use core::mem::MaybeUninit;
12+
use core::net::SocketAddr;
13+
14+
#[derive(Debug, Copy, Clone)]
15+
pub(crate) enum Domain {
16+
AfInet = crate::raw::AF_INET as isize,
17+
AfInet6 = crate::raw::AF_INET6 as isize,
18+
}
19+
20+
#[derive(Debug, Copy, Clone)]
21+
pub(crate) enum SockType {
22+
Dgram = crate::raw::net_sock_type_SOCK_DGRAM as isize,
23+
}
24+
25+
#[derive(Debug, Copy, Clone)]
26+
pub(crate) enum Protocol {
27+
IpprotoUdp = crate::raw::net_ip_protocol_IPPROTO_UDP as isize,
28+
}
29+
30+
/// Socket type implementing minimal safe wrappers around Zephyr's C socket API
31+
pub(crate) struct Socket {
32+
fd: c_int,
33+
}
34+
35+
impl Socket {
36+
/// Create a socket
37+
///
38+
/// This is a minimal wrapper around zsock_socket
39+
pub(crate) fn new(domain: Domain, sock_type: SockType, protocol: Protocol) -> Result<Socket> {
40+
let res = unsafe { zsock_socket(domain as c_int, sock_type as c_int, protocol as c_int) };
41+
42+
let fd = to_result_errno(res)?;
43+
44+
Ok(Socket { fd })
45+
}
46+
47+
/// Bind a socket
48+
///
49+
/// This is a minimal wrapper around zsock_bind
50+
pub(crate) fn bind(&mut self, addr: &SocketAddr) -> Result<()> {
51+
let (sockaddr, socklen) = sockaddr_to_c(addr);
52+
53+
let res = unsafe { zsock_bind(self.fd, &sockaddr as *const sockaddr, socklen) };
54+
55+
let _ = to_result_errno(res)?;
56+
Ok(())
57+
}
58+
59+
/// Receive from the socket, returning the data length and peer address it was received from
60+
///
61+
/// This is a minimal wrapper around zsock_recvfrom
62+
pub(crate) fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr)> {
63+
let mut sa = MaybeUninit::<sockaddr>::uninit();
64+
let mut socklen: socklen_t = core::mem::size_of::<sockaddr>();
65+
66+
let res = unsafe {
67+
zsock_recvfrom(
68+
self.fd,
69+
buf.as_mut_ptr() as *mut c_void,
70+
buf.len(),
71+
0,
72+
sa.as_mut_ptr(),
73+
&mut socklen as *mut socklen_t,
74+
)
75+
};
76+
77+
let recvd = to_result_errno(res)?;
78+
let sa = unsafe { sa.assume_init() };
79+
let peer_sa = try_sockaddr_from_c(&sa, socklen)?;
80+
Ok((recvd as usize, peer_sa))
81+
}
82+
83+
/// Send data to the specified socket address.
84+
///
85+
/// This is a minimal wrapper around zsock_sendto
86+
pub fn send_to(&self, buf: &[u8], addr: &SocketAddr) -> Result<usize> {
87+
let (sa, socklen) = sockaddr_to_c(addr);
88+
89+
let res = unsafe {
90+
zsock_sendto(
91+
self.fd,
92+
buf.as_ptr() as *const c_void,
93+
buf.len(),
94+
0,
95+
&sa as *const sockaddr,
96+
socklen,
97+
)
98+
};
99+
100+
let sent = to_result_errno(res)?;
101+
Ok(sent as usize)
102+
}
103+
}
104+
105+
impl Drop for Socket {
106+
fn drop(&mut self) {
107+
unsafe {
108+
let _ = zsock_close(self.fd);
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)