aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rwxr-xr-xci.sh16
-rw-r--r--embassy-net-examples/Cargo.toml19
-rw-r--r--embassy-net-examples/src/main.rs102
-rw-r--r--embassy-net-examples/src/tuntap.rs225
-rw-r--r--embassy-net/Cargo.toml44
-rw-r--r--embassy-net/README.md34
-rw-r--r--embassy-net/src/config/dhcp.rs59
-rw-r--r--embassy-net/src/config/mod.rs38
-rw-r--r--embassy-net/src/config/statik.rs35
-rw-r--r--embassy-net/src/device.rs105
-rw-r--r--embassy-net/src/fmt.rs118
-rw-r--r--embassy-net/src/lib.rs31
-rw-r--r--embassy-net/src/packet_pool.rs92
-rw-r--r--embassy-net/src/stack.rs259
-rw-r--r--embassy-net/src/tcp_socket.rs177
16 files changed, 1355 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 893a87368..11781dc15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
1target 1target
2Cargo.lock 2Cargo.lock
3third_party 3third_party
4/Cargo.toml \ No newline at end of file 4/Cargo.toml
diff --git a/ci.sh b/ci.sh
new file mode 100755
index 000000000..f43ea5e1a
--- /dev/null
+++ b/ci.sh
@@ -0,0 +1,16 @@
1#!/bin/bash
2
3set -euxo pipefail
4
5# build for std
6(cd embassy-net; cargo build --no-default-features --features log,medium-ethernet,tcp)
7(cd embassy-net; cargo build --no-default-features --features log,medium-ethernet,tcp,dhcpv4)
8(cd embassy-net; cargo build --no-default-features --features log,medium-ip,tcp)
9(cd embassy-net; cargo build --no-default-features --features log,medium-ethernet,medium-ip,tcp,dhcpv4)
10
11# build for embedded
12(cd embassy-net; cargo build --target thumbv7em-none-eabi --no-default-features --features log,medium-ethernet,medium-ip,tcp,dhcpv4)
13(cd embassy-net; cargo build --target thumbv7em-none-eabi --no-default-features --features defmt,smoltcp/defmt,medium-ethernet,medium-ip,tcp,dhcpv4)
14
15# build examples
16(cd embassy-net-examples; cargo build)
diff --git a/embassy-net-examples/Cargo.toml b/embassy-net-examples/Cargo.toml
new file mode 100644
index 000000000..0a63a3bbd
--- /dev/null
+++ b/embassy-net-examples/Cargo.toml
@@ -0,0 +1,19 @@
1[package]
2name = "embassy-net-examples"
3version = "0.1.0"
4authors = ["Dario Nieuwenhuis <[email protected]>"]
5edition = "2018"
6
7[dependencies]
8heapless = { version = "0.5.6", default-features = false }
9embassy = { version = "0.1.0", features=["std", "log"] }
10embassy-std = { version = "0.1.0" }
11embassy-net = { version = "0.1.0", path = "../embassy-net", features=["std", "log", "medium-ethernet", "tcp", "dhcpv4"] }
12env_logger = "0.8.2"
13log = "0.4.11"
14futures = "0.3.8"
15libc = "0.2.81"
16async-io = "1.3.1"
17smoltcp = { version = "0.7.0", default-features = false }
18clap = { version = "3.0.0-beta.2", features = ["derive"] }
19rand_core = { version = "0.6.0", features = ["std"] }
diff --git a/embassy-net-examples/src/main.rs b/embassy-net-examples/src/main.rs
new file mode 100644
index 000000000..d1c2658dd
--- /dev/null
+++ b/embassy-net-examples/src/main.rs
@@ -0,0 +1,102 @@
1#![feature(type_alias_impl_trait)]
2#![feature(min_type_alias_impl_trait)]
3#![feature(impl_trait_in_bindings)]
4#![allow(incomplete_features)]
5
6use clap::{AppSettings, Clap};
7use embassy::executor::Spawner;
8use embassy::io::AsyncWriteExt;
9use embassy::util::Forever;
10use embassy_net::*;
11use embassy_std::Executor;
12use heapless::Vec;
13use log::*;
14
15mod tuntap;
16
17use crate::tuntap::TunTapDevice;
18
19static DEVICE: Forever<TunTapDevice> = Forever::new();
20static CONFIG: Forever<DhcpConfigurator> = Forever::new();
21
22#[derive(Clap)]
23#[clap(version = "1.0")]
24#[clap(setting = AppSettings::ColoredHelp)]
25struct Opts {
26 /// TAP device name
27 #[clap(long, default_value = "tap0")]
28 tap: String,
29}
30
31#[embassy::task]
32async fn net_task() {
33 embassy_net::run().await
34}
35
36#[embassy::task]
37async fn main_task(spawner: Spawner) {
38 let opts: Opts = Opts::parse();
39
40 // Init network device
41 let device = TunTapDevice::new(&opts.tap).unwrap();
42
43 // Static IP configuration
44 let config = StaticConfigurator::new(Config {
45 address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24),
46 dns_servers: Vec::new(),
47 gateway: Some(Ipv4Address::new(192, 168, 69, 1)),
48 });
49
50 // DHCP configruation
51 let config = DhcpConfigurator::new();
52
53 // Init network stack
54 embassy_net::init(DEVICE.put(device), CONFIG.put(config));
55
56 // Launch network task
57 spawner.spawn(net_task()).unwrap();
58
59 // Then we can use it!
60 let mut rx_buffer = [0; 4096];
61 let mut tx_buffer = [0; 4096];
62 let mut socket = TcpSocket::new(&mut rx_buffer, &mut tx_buffer);
63
64 socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10)));
65
66 let remote_endpoint = (Ipv4Address::new(192, 168, 69, 74), 8000);
67 info!("connecting to {:?}...", remote_endpoint);
68 let r = socket.connect(remote_endpoint).await;
69 if let Err(e) = r {
70 warn!("connect error: {:?}", e);
71 return;
72 }
73 info!("connected!");
74 loop {
75 let r = socket.write_all(b"Hello!\n").await;
76 if let Err(e) = r {
77 warn!("write error: {:?}", e);
78 return;
79 }
80 }
81}
82
83#[no_mangle]
84fn _embassy_rand(buf: &mut [u8]) {
85 use rand_core::{OsRng, RngCore};
86 OsRng.fill_bytes(buf);
87}
88
89static EXECUTOR: Forever<Executor> = Forever::new();
90
91fn main() {
92 env_logger::builder()
93 .filter_level(log::LevelFilter::Debug)
94 .filter_module("async_io", log::LevelFilter::Info)
95 .format_timestamp_nanos()
96 .init();
97
98 let executor = EXECUTOR.put(Executor::new());
99 executor.run(|spawner| {
100 spawner.spawn(main_task(spawner)).unwrap();
101 });
102}
diff --git a/embassy-net-examples/src/tuntap.rs b/embassy-net-examples/src/tuntap.rs
new file mode 100644
index 000000000..dd453deb3
--- /dev/null
+++ b/embassy-net-examples/src/tuntap.rs
@@ -0,0 +1,225 @@
1use async_io::Async;
2use libc;
3use log::*;
4use smoltcp::wire::EthernetFrame;
5use std::io;
6use std::io::{Read, Write};
7use std::os::unix::io::{AsRawFd, RawFd};
8
9pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
10pub const SIOCGIFINDEX: libc::c_ulong = 0x8933;
11pub const ETH_P_ALL: libc::c_short = 0x0003;
12pub const TUNSETIFF: libc::c_ulong = 0x400454CA;
13pub const IFF_TUN: libc::c_int = 0x0001;
14pub const IFF_TAP: libc::c_int = 0x0002;
15pub const IFF_NO_PI: libc::c_int = 0x1000;
16
17#[repr(C)]
18#[derive(Debug)]
19struct ifreq {
20 ifr_name: [libc::c_char; libc::IF_NAMESIZE],
21 ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
22}
23
24fn ifreq_for(name: &str) -> ifreq {
25 let mut ifreq = ifreq {
26 ifr_name: [0; libc::IF_NAMESIZE],
27 ifr_data: 0,
28 };
29 for (i, byte) in name.as_bytes().iter().enumerate() {
30 ifreq.ifr_name[i] = *byte as libc::c_char
31 }
32 ifreq
33}
34
35fn ifreq_ioctl(
36 lower: libc::c_int,
37 ifreq: &mut ifreq,
38 cmd: libc::c_ulong,
39) -> io::Result<libc::c_int> {
40 unsafe {
41 let res = libc::ioctl(lower, cmd as _, ifreq as *mut ifreq);
42 if res == -1 {
43 return Err(io::Error::last_os_error());
44 }
45 }
46
47 Ok(ifreq.ifr_data)
48}
49
50#[derive(Debug)]
51pub struct TunTap {
52 fd: libc::c_int,
53 ifreq: ifreq,
54 mtu: usize,
55}
56
57impl AsRawFd for TunTap {
58 fn as_raw_fd(&self) -> RawFd {
59 self.fd
60 }
61}
62
63impl TunTap {
64 pub fn new(name: &str) -> io::Result<TunTap> {
65 unsafe {
66 let fd = libc::open(
67 "/dev/net/tun\0".as_ptr() as *const libc::c_char,
68 libc::O_RDWR | libc::O_NONBLOCK,
69 );
70 if fd == -1 {
71 return Err(io::Error::last_os_error());
72 }
73
74 let mut ifreq = ifreq_for(name);
75 ifreq.ifr_data = IFF_TAP | IFF_NO_PI;
76 ifreq_ioctl(fd, &mut ifreq, TUNSETIFF)?;
77
78 let socket = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
79 if socket == -1 {
80 return Err(io::Error::last_os_error());
81 }
82
83 let ip_mtu = ifreq_ioctl(socket, &mut ifreq, SIOCGIFMTU);
84 libc::close(socket);
85 let ip_mtu = ip_mtu? as usize;
86
87 // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
88 // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
89 let mtu = ip_mtu + EthernetFrame::<&[u8]>::header_len();
90
91 Ok(TunTap { fd, mtu, ifreq })
92 }
93 }
94}
95
96impl Drop for TunTap {
97 fn drop(&mut self) {
98 unsafe {
99 libc::close(self.fd);
100 }
101 }
102}
103
104impl io::Read for TunTap {
105 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
106 let len = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
107 if len == -1 {
108 Err(io::Error::last_os_error())
109 } else {
110 Ok(len as usize)
111 }
112 }
113}
114
115impl io::Write for TunTap {
116 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
117 let len = unsafe { libc::write(self.fd, buf.as_ptr() as *mut libc::c_void, buf.len()) };
118 if len == -1 {
119 Err(io::Error::last_os_error())
120 } else {
121 Ok(len as usize)
122 }
123 }
124
125 fn flush(&mut self) -> io::Result<()> {
126 Ok(())
127 }
128}
129
130pub struct TunTapDevice {
131 device: Async<TunTap>,
132 waker: Option<Waker>,
133}
134
135impl TunTapDevice {
136 pub fn new(name: &str) -> io::Result<TunTapDevice> {
137 Ok(Self {
138 device: Async::new(TunTap::new(name)?)?,
139 waker: None,
140 })
141 }
142}
143
144use core::task::Waker;
145use embassy_net::{DeviceCapabilities, LinkState, Packet, PacketBox, PacketBoxExt, PacketBuf};
146use std::task::Context;
147
148impl crate::Device for TunTapDevice {
149 fn is_transmit_ready(&mut self) -> bool {
150 true
151 }
152
153 fn transmit(&mut self, pkt: PacketBuf) {
154 // todo handle WouldBlock
155 match self.device.get_mut().write(&pkt) {
156 Ok(_) => {}
157 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
158 info!("transmit WouldBlock");
159 }
160 Err(e) => panic!("transmit error: {:?}", e),
161 }
162 }
163
164 fn receive(&mut self) -> Option<PacketBuf> {
165 let mut pkt = PacketBox::new(Packet::new()).unwrap();
166 loop {
167 match self.device.get_mut().read(&mut pkt[..]) {
168 Ok(n) => {
169 return Some(pkt.slice(0..n));
170 }
171 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
172 let ready = if let Some(w) = self.waker.as_ref() {
173 let mut cx = Context::from_waker(w);
174 let ready = self.device.poll_readable(&mut cx).is_ready();
175 ready
176 } else {
177 false
178 };
179 if !ready {
180 return None;
181 }
182 }
183 Err(e) => panic!("read error: {:?}", e),
184 }
185 }
186 }
187
188 fn register_waker(&mut self, w: &Waker) {
189 match self.waker {
190 // Optimization: If both the old and new Wakers wake the same task, we can simply
191 // keep the old waker, skipping the clone. (In most executor implementations,
192 // cloning a waker is somewhat expensive, comparable to cloning an Arc).
193 Some(ref w2) if (w2.will_wake(w)) => {}
194 _ => {
195 // clone the new waker and store it
196 if let Some(old_waker) = core::mem::replace(&mut self.waker, Some(w.clone())) {
197 // We had a waker registered for another task. Wake it, so the other task can
198 // reregister itself if it's still interested.
199 //
200 // If two tasks are waiting on the same thing concurrently, this will cause them
201 // to wake each other in a loop fighting over this WakerRegistration. This wastes
202 // CPU but things will still work.
203 //
204 // If the user wants to have two tasks waiting on the same thing they should use
205 // a more appropriate primitive that can store multiple wakers.
206 old_waker.wake()
207 }
208 }
209 }
210 }
211
212 fn capabilities(&mut self) -> DeviceCapabilities {
213 let mut caps = DeviceCapabilities::default();
214 caps.max_transmission_unit = self.device.get_ref().mtu;
215 caps
216 }
217
218 fn link_state(&mut self) -> LinkState {
219 LinkState::Up
220 }
221
222 fn ethernet_address(&mut self) -> [u8; 6] {
223 [0x02, 0x03, 0x04, 0x05, 0x06, 0x07]
224 }
225}
diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml
new file mode 100644
index 000000000..36f64dbd4
--- /dev/null
+++ b/embassy-net/Cargo.toml
@@ -0,0 +1,44 @@
1[package]
2name = "embassy-net"
3version = "0.1.0"
4authors = ["Dario Nieuwenhuis <[email protected]>"]
5edition = "2018"
6
7[features]
8std = []
9defmt-trace = []
10defmt-debug = []
11defmt-info = []
12defmt-warn = []
13defmt-error = []
14
15tcp = ["smoltcp/socket-tcp"]
16dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"]
17medium-ethernet = ["smoltcp/medium-ethernet"]
18medium-ip = ["smoltcp/medium-ip"]
19
20[dependencies]
21
22defmt = { version = "0.2.0", optional = true }
23log = { version = "0.4.11", optional = true }
24
25embassy = { version = "0.1.0" }
26
27managed = { version = "0.8.0", default-features = false, features = [ "map" ]}
28heapless = { version = "0.5.6", default-features = false }
29as-slice = { version = "0.1.4" }
30generic-array = { version = "0.14.4", default-features = false }
31stable_deref_trait = { version = "1.2.0", default-features = false }
32futures = { version = "0.3.5", default-features = false, features = [ "async-await" ]}
33atomic-pool = "0.2.0"
34
35[dependencies.smoltcp]
36version = "0.7.0"
37#git = "https://github.com/akiles/smoltcp"
38#rev = "00952e2c5cdf5667a1dfb6142258055f58d3851c"
39default-features = false
40features = [
41 "proto-ipv4",
42 "socket",
43 "async",
44]
diff --git a/embassy-net/README.md b/embassy-net/README.md
new file mode 100644
index 000000000..64f656709
--- /dev/null
+++ b/embassy-net/README.md
@@ -0,0 +1,34 @@
1# embassy-net
2
3embassy-net contains an async network API based on smoltcp and embassy, designed
4for embedded systems.
5
6## Running the example
7
8First, create the tap0 interface. You only need to do this once.
9
10```sh
11sudo ip tuntap add name tap0 mode tap user $USER
12sudo ip link set tap0 up
13sudo ip addr add 192.168.69.100/24 dev tap0
14sudo ip -6 addr add fe80::100/64 dev tap0
15sudo ip -6 addr add fdaa::100/64 dev tap0
16sudo ip -6 route add fe80::/64 dev tap0
17sudo ip -6 route add fdaa::/64 dev tap0
18```
19
20Then, run it
21
22```sh
23cargo run --bin embassy-net-examples
24```
25
26## License
27
28This work is licensed under either of
29
30- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
31 http://www.apache.org/licenses/LICENSE-2.0)
32- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
33
34at your option.
diff --git a/embassy-net/src/config/dhcp.rs b/embassy-net/src/config/dhcp.rs
new file mode 100644
index 000000000..f0c144321
--- /dev/null
+++ b/embassy-net/src/config/dhcp.rs
@@ -0,0 +1,59 @@
1use heapless::Vec;
2use smoltcp::socket::{Dhcpv4Event, Dhcpv4Socket, SocketHandle};
3use smoltcp::time::Instant;
4
5use super::*;
6use crate::device::LinkState;
7use crate::fmt::*;
8use crate::{Interface, SocketSet};
9
10pub struct DhcpConfigurator {
11 handle: Option<SocketHandle>,
12}
13
14impl DhcpConfigurator {
15 pub fn new() -> Self {
16 Self { handle: None }
17 }
18}
19
20impl Configurator for DhcpConfigurator {
21 fn poll(
22 &mut self,
23 iface: &mut Interface,
24 sockets: &mut SocketSet,
25 _timestamp: Instant,
26 ) -> Event {
27 if self.handle.is_none() {
28 let handle = sockets.add(Dhcpv4Socket::new());
29 self.handle = Some(handle)
30 }
31
32 let mut socket = sockets.get::<Dhcpv4Socket>(self.handle.unwrap());
33
34 let link_up = iface.device_mut().device.link_state() == LinkState::Up;
35 if !link_up {
36 socket.reset();
37 return Event::Deconfigured;
38 }
39
40 match socket.poll() {
41 None => Event::NoChange,
42 Some(Dhcpv4Event::Deconfigured) => Event::Deconfigured,
43 Some(Dhcpv4Event::Configured(config)) => {
44 let mut dns_servers = Vec::new();
45 for s in &config.dns_servers {
46 if let Some(addr) = s {
47 dns_servers.push(addr.clone()).unwrap();
48 }
49 }
50
51 Event::Configured(Config {
52 address: config.address,
53 gateway: config.router,
54 dns_servers,
55 })
56 }
57 }
58 }
59}
diff --git a/embassy-net/src/config/mod.rs b/embassy-net/src/config/mod.rs
new file mode 100644
index 000000000..16470f7e6
--- /dev/null
+++ b/embassy-net/src/config/mod.rs
@@ -0,0 +1,38 @@
1use heapless::consts::*;
2use heapless::Vec;
3use smoltcp::time::Instant;
4use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
5
6use crate::fmt::*;
7use crate::{Interface, SocketSet};
8
9mod statik;
10pub use statik::StaticConfigurator;
11
12#[cfg(feature = "dhcpv4")]
13mod dhcp;
14#[cfg(feature = "dhcpv4")]
15pub use dhcp::DhcpConfigurator;
16
17/// Return value for the `Configurator::poll` function
18#[derive(Debug, Clone)]
19pub enum Event {
20 /// No change has occured to the configuration.
21 NoChange,
22 /// Configuration has been lost (for example, DHCP lease has expired)
23 Deconfigured,
24 /// Configuration has been newly acquired, or modified.
25 Configured(Config),
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Config {
30 pub address: Ipv4Cidr,
31 pub gateway: Option<Ipv4Address>,
32 pub dns_servers: Vec<Ipv4Address, U3>,
33}
34
35pub trait Configurator {
36 fn poll(&mut self, iface: &mut Interface, sockets: &mut SocketSet, timestamp: Instant)
37 -> Event;
38}
diff --git a/embassy-net/src/config/statik.rs b/embassy-net/src/config/statik.rs
new file mode 100644
index 000000000..912143bff
--- /dev/null
+++ b/embassy-net/src/config/statik.rs
@@ -0,0 +1,35 @@
1use smoltcp::time::Instant;
2
3use super::*;
4use crate::fmt::*;
5use crate::{Interface, SocketSet};
6
7pub struct StaticConfigurator {
8 config: Config,
9 returned: bool,
10}
11
12impl StaticConfigurator {
13 pub fn new(config: Config) -> Self {
14 Self {
15 config,
16 returned: false,
17 }
18 }
19}
20
21impl Configurator for StaticConfigurator {
22 fn poll(
23 &mut self,
24 _iface: &mut Interface,
25 _sockets: &mut SocketSet,
26 _timestamp: Instant,
27 ) -> Event {
28 if self.returned {
29 Event::NoChange
30 } else {
31 self.returned = true;
32 Event::Configured(self.config.clone())
33 }
34 }
35}
diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs
new file mode 100644
index 000000000..6c06b0605
--- /dev/null
+++ b/embassy-net/src/device.rs
@@ -0,0 +1,105 @@
1use core::task::Waker;
2use smoltcp::phy::Device as SmolDevice;
3use smoltcp::phy::DeviceCapabilities;
4use smoltcp::time::Instant as SmolInstant;
5
6use crate::fmt::*;
7use crate::packet_pool::PacketBoxExt;
8use crate::Result;
9use crate::{Packet, PacketBox, PacketBuf};
10
11#[derive(PartialEq, Eq, Clone, Copy)]
12pub enum LinkState {
13 Down,
14 Up,
15}
16
17pub trait Device {
18 fn is_transmit_ready(&mut self) -> bool;
19 fn transmit(&mut self, pkt: PacketBuf);
20 fn receive(&mut self) -> Option<PacketBuf>;
21
22 fn register_waker(&mut self, waker: &Waker);
23 fn capabilities(&mut self) -> DeviceCapabilities;
24 fn link_state(&mut self) -> LinkState;
25 fn ethernet_address(&mut self) -> [u8; 6];
26}
27
28pub struct DeviceAdapter {
29 pub device: &'static mut dyn Device,
30 caps: DeviceCapabilities,
31}
32
33impl DeviceAdapter {
34 pub(crate) fn new(device: &'static mut dyn Device) -> Self {
35 Self {
36 caps: device.capabilities(),
37 device,
38 }
39 }
40}
41
42impl<'a> SmolDevice<'a> for DeviceAdapter {
43 type RxToken = RxToken;
44 type TxToken = TxToken<'a>;
45
46 fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
47 let rx_pkt = self.device.receive()?;
48 let tx_pkt = PacketBox::new(Packet::new()).unwrap(); // TODO: not sure about unwrap
49 let rx_token = RxToken { pkt: rx_pkt };
50 let tx_token = TxToken {
51 device: self.device,
52 pkt: tx_pkt,
53 };
54
55 Some((rx_token, tx_token))
56 }
57
58 /// Construct a transmit token.
59 fn transmit(&'a mut self) -> Option<Self::TxToken> {
60 if !self.device.is_transmit_ready() {
61 return None;
62 }
63
64 let tx_pkt = PacketBox::new(Packet::new())?;
65 Some(TxToken {
66 device: self.device,
67 pkt: tx_pkt,
68 })
69 }
70
71 /// Get a description of device capabilities.
72 fn capabilities(&self) -> DeviceCapabilities {
73 self.caps.clone()
74 }
75}
76
77pub struct RxToken {
78 pkt: PacketBuf,
79}
80
81impl smoltcp::phy::RxToken for RxToken {
82 fn consume<R, F>(mut self, _timestamp: SmolInstant, f: F) -> Result<R>
83 where
84 F: FnOnce(&mut [u8]) -> Result<R>,
85 {
86 f(&mut self.pkt)
87 }
88}
89
90pub struct TxToken<'a> {
91 device: &'a mut dyn Device,
92 pkt: PacketBox,
93}
94
95impl<'a> smoltcp::phy::TxToken for TxToken<'a> {
96 fn consume<R, F>(self, _timestamp: SmolInstant, len: usize, f: F) -> Result<R>
97 where
98 F: FnOnce(&mut [u8]) -> Result<R>,
99 {
100 let mut buf = self.pkt.slice(0..len);
101 let r = f(&mut buf)?;
102 self.device.transmit(buf);
103 Ok(r)
104 }
105}
diff --git a/embassy-net/src/fmt.rs b/embassy-net/src/fmt.rs
new file mode 100644
index 000000000..4da69766c
--- /dev/null
+++ b/embassy-net/src/fmt.rs
@@ -0,0 +1,118 @@
1#![macro_use]
2
3#[cfg(all(feature = "defmt", feature = "log"))]
4compile_error!("You may not enable both `defmt` and `log` features.");
5
6pub use fmt::*;
7
8#[cfg(feature = "defmt")]
9mod fmt {
10 pub use defmt::{
11 assert, assert_eq, assert_ne, debug, debug_assert, debug_assert_eq, debug_assert_ne, error,
12 info, panic, todo, trace, unreachable, unwrap, warn,
13 };
14}
15
16#[cfg(feature = "log")]
17mod fmt {
18 pub use core::{
19 assert, assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, panic, todo,
20 unreachable,
21 };
22 pub use log::{debug, error, info, trace, warn};
23}
24
25#[cfg(not(any(feature = "defmt", feature = "log")))]
26mod fmt {
27 #![macro_use]
28
29 pub use core::{
30 assert, assert_eq, assert_ne, debug_assert, debug_assert_eq, debug_assert_ne, panic, todo,
31 unreachable,
32 };
33
34 #[macro_export]
35 macro_rules! trace {
36 ($($msg:expr),+ $(,)?) => {
37 ()
38 };
39 }
40
41 #[macro_export]
42 macro_rules! debug {
43 ($($msg:expr),+ $(,)?) => {
44 ()
45 };
46 }
47
48 #[macro_export]
49 macro_rules! info {
50 ($($msg:expr),+ $(,)?) => {
51 ()
52 };
53 }
54
55 #[macro_export]
56 macro_rules! warn {
57 ($($msg:expr),+ $(,)?) => {
58 ()
59 };
60 }
61
62 #[macro_export]
63 macro_rules! error {
64 ($($msg:expr),+ $(,)?) => {
65 ()
66 };
67 }
68}
69
70#[cfg(not(feature = "defmt"))]
71#[macro_export]
72macro_rules! unwrap {
73 ($arg:expr) => {
74 match $crate::fmt::Try::into_result($arg) {
75 ::core::result::Result::Ok(t) => t,
76 ::core::result::Result::Err(e) => {
77 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
78 }
79 }
80 };
81 ($arg:expr, $($msg:expr),+ $(,)? ) => {
82 match $crate::fmt::Try::into_result($arg) {
83 ::core::result::Result::Ok(t) => t,
84 ::core::result::Result::Err(e) => {
85 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
86 }
87 }
88 }
89}
90
91#[derive(Debug, Copy, Clone, Eq, PartialEq)]
92pub struct NoneError;
93
94pub trait Try {
95 type Ok;
96 type Error;
97 fn into_result(self) -> Result<Self::Ok, Self::Error>;
98}
99
100impl<T> Try for Option<T> {
101 type Ok = T;
102 type Error = NoneError;
103
104 #[inline]
105 fn into_result(self) -> Result<T, NoneError> {
106 self.ok_or(NoneError)
107 }
108}
109
110impl<T, E> Try for Result<T, E> {
111 type Ok = T;
112 type Error = E;
113
114 #[inline]
115 fn into_result(self) -> Self {
116 self
117 }
118}
diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs
new file mode 100644
index 000000000..88dcf0aa5
--- /dev/null
+++ b/embassy-net/src/lib.rs
@@ -0,0 +1,31 @@
1#![cfg_attr(not(feature = "std"), no_std)]
2
3// This mod MUST go first, so that the others see its macros.
4pub(crate) mod fmt;
5
6mod config;
7mod device;
8mod packet_pool;
9mod stack;
10
11#[cfg(feature = "dhcpv4")]
12pub use config::DhcpConfigurator;
13pub use config::{Config, Configurator, Event as ConfigEvent, StaticConfigurator};
14
15pub use device::{Device, LinkState};
16pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf};
17pub use stack::{init, is_config_up, is_init, is_link_up, run};
18
19#[cfg(feature = "tcp")]
20mod tcp_socket;
21#[cfg(feature = "tcp")]
22pub use tcp_socket::TcpSocket;
23
24// smoltcp reexports
25pub use smoltcp::phy::{DeviceCapabilities, Medium};
26pub use smoltcp::time::Duration as SmolDuration;
27pub use smoltcp::time::Instant as SmolInstant;
28pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
29pub type Interface = smoltcp::iface::Interface<'static, device::DeviceAdapter>;
30pub type SocketSet = smoltcp::socket::SocketSet<'static>;
31pub use smoltcp::{Error, Result};
diff --git a/embassy-net/src/packet_pool.rs b/embassy-net/src/packet_pool.rs
new file mode 100644
index 000000000..2c27d4013
--- /dev/null
+++ b/embassy-net/src/packet_pool.rs
@@ -0,0 +1,92 @@
1use as_slice::{AsMutSlice, AsSlice};
2use core::ops::{Deref, DerefMut, Range};
3
4use atomic_pool::{pool, Box};
5
6pub const MTU: usize = 1514;
7pub const PACKET_POOL_SIZE: usize = 4;
8
9pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]);
10pub type PacketBox = Box<PacketPool>;
11
12pub struct Packet(pub [u8; MTU]);
13
14impl Packet {
15 pub const fn new() -> Self {
16 Self([0; MTU])
17 }
18}
19
20pub trait PacketBoxExt {
21 fn slice(self, range: Range<usize>) -> PacketBuf;
22}
23
24impl PacketBoxExt for PacketBox {
25 fn slice(self, range: Range<usize>) -> PacketBuf {
26 PacketBuf {
27 packet: self,
28 range,
29 }
30 }
31}
32
33impl AsSlice for Packet {
34 type Element = u8;
35
36 fn as_slice(&self) -> &[Self::Element] {
37 &self.deref()[..]
38 }
39}
40
41impl AsMutSlice for Packet {
42 fn as_mut_slice(&mut self) -> &mut [Self::Element] {
43 &mut self.deref_mut()[..]
44 }
45}
46
47impl Deref for Packet {
48 type Target = [u8; MTU];
49
50 fn deref(&self) -> &[u8; MTU] {
51 &self.0
52 }
53}
54
55impl DerefMut for Packet {
56 fn deref_mut(&mut self) -> &mut [u8; MTU] {
57 &mut self.0
58 }
59}
60
61pub struct PacketBuf {
62 packet: PacketBox,
63 range: Range<usize>,
64}
65
66impl AsSlice for PacketBuf {
67 type Element = u8;
68
69 fn as_slice(&self) -> &[Self::Element] {
70 &self.packet[self.range.clone()]
71 }
72}
73
74impl AsMutSlice for PacketBuf {
75 fn as_mut_slice(&mut self) -> &mut [Self::Element] {
76 &mut self.packet[self.range.clone()]
77 }
78}
79
80impl Deref for PacketBuf {
81 type Target = [u8];
82
83 fn deref(&self) -> &[u8] {
84 &self.packet[self.range.clone()]
85 }
86}
87
88impl DerefMut for PacketBuf {
89 fn deref_mut(&mut self) -> &mut [u8] {
90 &mut self.packet[self.range.clone()]
91 }
92}
diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs
new file mode 100644
index 000000000..e436beb1e
--- /dev/null
+++ b/embassy-net/src/stack.rs
@@ -0,0 +1,259 @@
1use core::cell::RefCell;
2use core::future::Future;
3use core::task::Context;
4use core::task::Poll;
5use embassy::time::{Instant, Timer};
6use embassy::util::ThreadModeMutex;
7use embassy::util::{Forever, WakerRegistration};
8use futures::pin_mut;
9use smoltcp::iface::InterfaceBuilder;
10#[cfg(feature = "medium-ethernet")]
11use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes};
12use smoltcp::phy::Device as _;
13use smoltcp::phy::Medium;
14use smoltcp::socket::SocketSetItem;
15use smoltcp::time::Instant as SmolInstant;
16#[cfg(feature = "medium-ethernet")]
17use smoltcp::wire::EthernetAddress;
18use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr};
19
20use crate::config::Configurator;
21use crate::config::Event;
22use crate::device::{Device, DeviceAdapter, LinkState};
23use crate::fmt::*;
24use crate::{Interface, SocketSet};
25
26const ADDRESSES_LEN: usize = 1;
27const NEIGHBOR_CACHE_LEN: usize = 8;
28const SOCKETS_LEN: usize = 2;
29const LOCAL_PORT_MIN: u16 = 1025;
30const LOCAL_PORT_MAX: u16 = 65535;
31
32struct StackResources {
33 addresses: [IpCidr; ADDRESSES_LEN],
34 sockets: [Option<SocketSetItem<'static>>; SOCKETS_LEN],
35
36 #[cfg(feature = "medium-ethernet")]
37 routes: [Option<(IpCidr, Route)>; 1],
38 #[cfg(feature = "medium-ethernet")]
39 neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR_CACHE_LEN],
40}
41
42static STACK_RESOURCES: Forever<StackResources> = Forever::new();
43static STACK: ThreadModeMutex<RefCell<Option<Stack>>> = ThreadModeMutex::new(RefCell::new(None));
44
45pub(crate) struct Stack {
46 iface: Interface,
47 pub sockets: SocketSet,
48 link_up: bool,
49 config_up: bool,
50 next_local_port: u16,
51 configurator: &'static mut dyn Configurator,
52 waker: WakerRegistration,
53}
54
55impl Stack {
56 pub(crate) fn with<R>(f: impl FnOnce(&mut Stack) -> R) -> R {
57 let mut stack = STACK.borrow().borrow_mut();
58 let stack = stack.as_mut().unwrap();
59 f(stack)
60 }
61
62 pub fn get_local_port(&mut self) -> u16 {
63 let res = self.next_local_port;
64 self.next_local_port = if res >= LOCAL_PORT_MAX {
65 LOCAL_PORT_MIN
66 } else {
67 res + 1
68 };
69 res
70 }
71
72 pub(crate) fn wake(&mut self) {
73 self.waker.wake()
74 }
75
76 fn poll_configurator(&mut self, timestamp: SmolInstant) {
77 let medium = self.iface.device().capabilities().medium;
78
79 match self
80 .configurator
81 .poll(&mut self.iface, &mut self.sockets, timestamp)
82 {
83 Event::NoChange => {}
84 Event::Configured(config) => {
85 debug!("Acquired IP configuration:");
86
87 debug!(" IP address: {}", config.address);
88 set_ipv4_addr(&mut self.iface, config.address);
89
90 #[cfg(feature = "medium-ethernet")]
91 if medium == Medium::Ethernet {
92 if let Some(gateway) = config.gateway {
93 debug!(" Default gateway: {}", gateway);
94 self.iface
95 .routes_mut()
96 .add_default_ipv4_route(gateway)
97 .unwrap();
98 } else {
99 debug!(" Default gateway: None");
100 self.iface.routes_mut().remove_default_ipv4_route();
101 }
102 }
103 for (i, s) in config.dns_servers.iter().enumerate() {
104 debug!(" DNS server {}: {}", i, s);
105 }
106
107 self.config_up = true;
108 }
109 Event::Deconfigured => {
110 debug!("Lost IP configuration");
111 set_ipv4_addr(&mut self.iface, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0));
112 #[cfg(feature = "medium-ethernet")]
113 if medium == Medium::Ethernet {
114 self.iface.routes_mut().remove_default_ipv4_route();
115 }
116 self.config_up = false;
117 }
118 }
119 }
120
121 fn poll(&mut self, cx: &mut Context<'_>) {
122 self.iface.device_mut().device.register_waker(cx.waker());
123 self.waker.register(cx.waker());
124
125 let timestamp = instant_to_smoltcp(Instant::now());
126 if let Err(_) = self.iface.poll(&mut self.sockets, timestamp) {
127 // If poll() returns error, it may not be done yet, so poll again later.
128 cx.waker().wake_by_ref();
129 return;
130 }
131
132 // Update link up
133 let old_link_up = self.link_up;
134 self.link_up = self.iface.device_mut().device.link_state() == LinkState::Up;
135
136 // Print when changed
137 if old_link_up != self.link_up {
138 if self.link_up {
139 info!("Link up!");
140 } else {
141 info!("Link down!");
142 }
143 }
144
145 if old_link_up || self.link_up {
146 self.poll_configurator(timestamp)
147 }
148
149 if let Some(poll_at) = self.iface.poll_at(&mut self.sockets, timestamp) {
150 let t = Timer::at(instant_from_smoltcp(poll_at));
151 pin_mut!(t);
152 if t.poll(cx).is_ready() {
153 cx.waker().wake_by_ref();
154 }
155 }
156 }
157}
158
159fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) {
160 iface.update_ip_addrs(|addrs| {
161 let dest = addrs.iter_mut().next().unwrap();
162 *dest = IpCidr::Ipv4(cidr);
163 });
164}
165
166/// Initialize embassy_net.
167/// This function must be called from thread mode.
168pub fn init(device: &'static mut dyn Device, configurator: &'static mut dyn Configurator) {
169 const NONE_SOCKET: Option<SocketSetItem<'static>> = None;
170 let res = STACK_RESOURCES.put(StackResources {
171 addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32)],
172 sockets: [NONE_SOCKET; SOCKETS_LEN],
173
174 #[cfg(feature = "medium-ethernet")]
175 routes: [None; 1],
176 #[cfg(feature = "medium-ethernet")]
177 neighbor_cache: [None; NEIGHBOR_CACHE_LEN],
178 });
179
180 let medium = device.capabilities().medium;
181
182 #[cfg(feature = "medium-ethernet")]
183 let ethernet_addr = if medium == Medium::Ethernet {
184 device.ethernet_address()
185 } else {
186 [0, 0, 0, 0, 0, 0]
187 };
188
189 let mut b = InterfaceBuilder::new(DeviceAdapter::new(device));
190 b = b.ip_addrs(&mut res.addresses[..]);
191
192 #[cfg(feature = "medium-ethernet")]
193 if medium == Medium::Ethernet {
194 b = b.ethernet_addr(EthernetAddress(ethernet_addr));
195 b = b.neighbor_cache(NeighborCache::new(&mut res.neighbor_cache[..]));
196 b = b.routes(Routes::new(&mut res.routes[..]));
197 }
198
199 let iface = b.finalize();
200
201 let sockets = SocketSet::new(&mut res.sockets[..]);
202
203 let local_port = loop {
204 let mut res = [0u8; 2];
205 rand(&mut res);
206 let port = u16::from_le_bytes(res);
207 if port >= LOCAL_PORT_MIN && port <= LOCAL_PORT_MAX {
208 break port;
209 }
210 };
211
212 let stack = Stack {
213 iface,
214 sockets,
215 link_up: false,
216 config_up: false,
217 configurator,
218 next_local_port: local_port,
219 waker: WakerRegistration::new(),
220 };
221
222 *STACK.borrow().borrow_mut() = Some(stack);
223}
224
225pub fn is_init() -> bool {
226 STACK.borrow().borrow().is_some()
227}
228
229pub fn is_link_up() -> bool {
230 STACK.borrow().borrow().as_ref().unwrap().link_up
231}
232
233pub fn is_config_up() -> bool {
234 STACK.borrow().borrow().as_ref().unwrap().config_up
235}
236
237pub async fn run() {
238 futures::future::poll_fn(|cx| {
239 Stack::with(|stack| stack.poll(cx));
240 Poll::<()>::Pending
241 })
242 .await
243}
244
245fn instant_to_smoltcp(instant: Instant) -> SmolInstant {
246 SmolInstant::from_millis(instant.as_millis() as i64)
247}
248
249fn instant_from_smoltcp(instant: SmolInstant) -> Instant {
250 Instant::from_millis(instant.total_millis() as u64)
251}
252
253extern "Rust" {
254 fn _embassy_rand(buf: &mut [u8]);
255}
256
257fn rand(buf: &mut [u8]) {
258 unsafe { _embassy_rand(buf) }
259}
diff --git a/embassy-net/src/tcp_socket.rs b/embassy-net/src/tcp_socket.rs
new file mode 100644
index 000000000..4f43bc611
--- /dev/null
+++ b/embassy-net/src/tcp_socket.rs
@@ -0,0 +1,177 @@
1use core::marker::PhantomData;
2use core::mem;
3use core::pin::Pin;
4use core::task::{Context, Poll};
5use embassy::io;
6use embassy::io::{AsyncBufRead, AsyncWrite};
7use smoltcp::socket::SocketHandle;
8use smoltcp::socket::TcpSocket as SyncTcpSocket;
9use smoltcp::socket::{TcpSocketBuffer, TcpState};
10use smoltcp::time::Duration;
11use smoltcp::wire::IpEndpoint;
12
13use super::stack::Stack;
14use crate::fmt::*;
15use crate::{Error, Result};
16
17pub struct TcpSocket<'a> {
18 handle: SocketHandle,
19 ghost: PhantomData<&'a mut [u8]>,
20}
21
22impl<'a> Unpin for TcpSocket<'a> {}
23
24impl<'a> TcpSocket<'a> {
25 pub fn new(rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
26 let handle = Stack::with(|stack| {
27 let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
28 let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
29 stack.sockets.add(SyncTcpSocket::new(
30 TcpSocketBuffer::new(rx_buffer),
31 TcpSocketBuffer::new(tx_buffer),
32 ))
33 });
34
35 Self {
36 handle,
37 ghost: PhantomData,
38 }
39 }
40
41 pub async fn connect<T>(&mut self, remote_endpoint: T) -> Result<()>
42 where
43 T: Into<IpEndpoint>,
44 {
45 let local_port = Stack::with(|stack| stack.get_local_port());
46 self.with(|s| s.connect(remote_endpoint, local_port))?;
47
48 futures::future::poll_fn(|cx| {
49 self.with(|s| match s.state() {
50 TcpState::Closed | TcpState::TimeWait => Poll::Ready(Err(Error::Unaddressable)),
51 TcpState::Listen => Poll::Ready(Err(Error::Illegal)),
52 TcpState::SynSent | TcpState::SynReceived => {
53 s.register_send_waker(cx.waker());
54 Poll::Pending
55 }
56 _ => Poll::Ready(Ok(())),
57 })
58 })
59 .await
60 }
61
62 pub fn set_timeout(&mut self, duration: Option<Duration>) {
63 self.with(|s| s.set_timeout(duration))
64 }
65
66 pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
67 self.with(|s| s.set_keep_alive(interval))
68 }
69
70 pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
71 self.with(|s| s.set_hop_limit(hop_limit))
72 }
73
74 pub fn local_endpoint(&self) -> IpEndpoint {
75 self.with(|s| s.local_endpoint())
76 }
77
78 pub fn remote_endpoint(&self) -> IpEndpoint {
79 self.with(|s| s.remote_endpoint())
80 }
81
82 pub fn state(&self) -> TcpState {
83 self.with(|s| s.state())
84 }
85
86 pub fn close(&mut self) {
87 self.with(|s| s.close())
88 }
89
90 pub fn abort(&mut self) {
91 self.with(|s| s.abort())
92 }
93
94 pub fn may_send(&self) -> bool {
95 self.with(|s| s.may_send())
96 }
97
98 pub fn may_recv(&self) -> bool {
99 self.with(|s| s.may_recv())
100 }
101
102 fn with<R>(&self, f: impl FnOnce(&mut SyncTcpSocket) -> R) -> R {
103 Stack::with(|stack| {
104 let res = {
105 let mut s = stack.sockets.get::<SyncTcpSocket>(self.handle);
106 f(&mut *s)
107 };
108 stack.wake();
109 res
110 })
111 }
112}
113
114fn to_ioerr(_err: Error) -> io::Error {
115 // todo
116 io::Error::Other
117}
118
119impl<'a> Drop for TcpSocket<'a> {
120 fn drop(&mut self) {
121 Stack::with(|stack| {
122 stack.sockets.remove(self.handle);
123 })
124 }
125}
126
127impl<'a> AsyncBufRead for TcpSocket<'a> {
128 fn poll_fill_buf<'z>(
129 self: Pin<&'z mut Self>,
130 cx: &mut Context<'_>,
131 ) -> Poll<io::Result<&'z [u8]>> {
132 self.with(|socket| match socket.peek(1 << 30) {
133 // No data ready
134 Ok(buf) if buf.len() == 0 => {
135 socket.register_recv_waker(cx.waker());
136 Poll::Pending
137 }
138 // Data ready!
139 Ok(buf) => {
140 // Safety:
141 // - User can't touch the inner TcpSocket directly at all.
142 // - The socket itself won't touch these bytes until consume() is called, which
143 // requires the user to release this borrow.
144 let buf: &'z [u8] = unsafe { core::mem::transmute(&*buf) };
145 Poll::Ready(Ok(buf))
146 }
147 // EOF
148 Err(Error::Finished) => Poll::Ready(Ok(&[][..])),
149 // Error
150 Err(e) => Poll::Ready(Err(to_ioerr(e))),
151 })
152 }
153
154 fn consume(self: Pin<&mut Self>, amt: usize) {
155 self.with(|s| s.recv(|_| (amt, ()))).unwrap()
156 }
157}
158
159impl<'a> AsyncWrite for TcpSocket<'a> {
160 fn poll_write(
161 self: Pin<&mut Self>,
162 cx: &mut Context<'_>,
163 buf: &[u8],
164 ) -> Poll<io::Result<usize>> {
165 self.with(|s| match s.send_slice(buf) {
166 // Not ready to send (no space in the tx buffer)
167 Ok(0) => {
168 s.register_send_waker(cx.waker());
169 Poll::Pending
170 }
171 // Some data sent
172 Ok(n) => Poll::Ready(Ok(n)),
173 // Error
174 Err(e) => Poll::Ready(Err(to_ioerr(e))),
175 })
176 }
177}