aboutsummaryrefslogtreecommitdiff
path: root/embassy-net-tuntap
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-08-03 14:23:11 +0200
committerGitHub <[email protected]>2023-08-03 14:23:11 +0200
commit4d60c715e683aaadf25d9f066bde805c725fefb4 (patch)
treed3601429cef8850fba4a99df0e148da19fd96efc /embassy-net-tuntap
parent2c96fe917de6e0120053e80d8da5a98d0d0f35d0 (diff)
net: move tuntap from std example to separate crate. (#1737)
Diffstat (limited to 'embassy-net-tuntap')
-rw-r--r--embassy-net-tuntap/Cargo.toml19
-rw-r--r--embassy-net-tuntap/README.md17
-rw-r--r--embassy-net-tuntap/src/lib.rs225
3 files changed, 261 insertions, 0 deletions
diff --git a/embassy-net-tuntap/Cargo.toml b/embassy-net-tuntap/Cargo.toml
new file mode 100644
index 000000000..08d309680
--- /dev/null
+++ b/embassy-net-tuntap/Cargo.toml
@@ -0,0 +1,19 @@
1[package]
2name = "embassy-net-tuntap"
3version = "0.1.0"
4description = "embassy-net driver for Linux TUN/TAP interfaces."
5keywords = ["embedded", "tuntap", "embassy-net", "embedded-hal-async", "ethernet", "async"]
6categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"]
7license = "MIT OR Apache-2.0"
8edition = "2021"
9
10[dependencies]
11embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" }
12async-io = "1.6.0"
13log = "0.4.14"
14libc = "0.2.101"
15
16[package.metadata.embassy_docs]
17src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-tuntap-v$VERSION/embassy-net-tuntap/src/"
18src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-tuntap/src/"
19target = "thumbv7em-none-eabi" \ No newline at end of file
diff --git a/embassy-net-tuntap/README.md b/embassy-net-tuntap/README.md
new file mode 100644
index 000000000..c5d9e746c
--- /dev/null
+++ b/embassy-net-tuntap/README.md
@@ -0,0 +1,17 @@
1# `embassy-net` integration for Linux TUN/TAP interfaces.
2
3[`embassy-net`](https://crates.io/crates/embassy-net) integration for for Linux TUN (IP medium) and TAP (Ethernet medium) interfaces.
4
5## Interoperability
6
7This crate can run on any executor.
8
9## License
10
11This work is licensed under either of
12
13- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
14 http://www.apache.org/licenses/LICENSE-2.0)
15- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
16
17at your option.
diff --git a/embassy-net-tuntap/src/lib.rs b/embassy-net-tuntap/src/lib.rs
new file mode 100644
index 000000000..75c54c487
--- /dev/null
+++ b/embassy-net-tuntap/src/lib.rs
@@ -0,0 +1,225 @@
1use std::io;
2use std::io::{Read, Write};
3use std::os::unix::io::{AsRawFd, RawFd};
4use std::task::Context;
5
6use async_io::Async;
7use embassy_net_driver::{self, Capabilities, Driver, HardwareAddress, LinkState};
8use log::*;
9
10pub const SIOCGIFMTU: libc::c_ulong = 0x8921;
11pub const _SIOCGIFINDEX: libc::c_ulong = 0x8933;
12pub const _ETH_P_ALL: libc::c_short = 0x0003;
13pub const TUNSETIFF: libc::c_ulong = 0x400454CA;
14pub const _IFF_TUN: libc::c_int = 0x0001;
15pub const IFF_TAP: libc::c_int = 0x0002;
16pub const IFF_NO_PI: libc::c_int = 0x1000;
17
18const ETHERNET_HEADER_LEN: usize = 14;
19
20#[repr(C)]
21#[derive(Debug)]
22#[allow(non_camel_case_types)]
23struct ifreq {
24 ifr_name: [libc::c_char; libc::IF_NAMESIZE],
25 ifr_data: libc::c_int, /* ifr_ifindex or ifr_mtu */
26}
27
28fn ifreq_for(name: &str) -> ifreq {
29 let mut ifreq = ifreq {
30 ifr_name: [0; libc::IF_NAMESIZE],
31 ifr_data: 0,
32 };
33 for (i, byte) in name.as_bytes().iter().enumerate() {
34 ifreq.ifr_name[i] = *byte as libc::c_char
35 }
36 ifreq
37}
38
39fn ifreq_ioctl(lower: libc::c_int, ifreq: &mut ifreq, cmd: libc::c_ulong) -> 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 mtu: usize,
54}
55
56impl AsRawFd for TunTap {
57 fn as_raw_fd(&self) -> RawFd {
58 self.fd
59 }
60}
61
62impl TunTap {
63 pub fn new(name: &str) -> io::Result<TunTap> {
64 unsafe {
65 let fd = libc::open(
66 "/dev/net/tun\0".as_ptr() as *const libc::c_char,
67 libc::O_RDWR | libc::O_NONBLOCK,
68 );
69 if fd == -1 {
70 return Err(io::Error::last_os_error());
71 }
72
73 let mut ifreq = ifreq_for(name);
74 ifreq.ifr_data = IFF_TAP | IFF_NO_PI;
75 ifreq_ioctl(fd, &mut ifreq, TUNSETIFF)?;
76
77 let socket = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_IP);
78 if socket == -1 {
79 return Err(io::Error::last_os_error());
80 }
81
82 let ip_mtu = ifreq_ioctl(socket, &mut ifreq, SIOCGIFMTU);
83 libc::close(socket);
84 let ip_mtu = ip_mtu? as usize;
85
86 // SIOCGIFMTU returns the IP MTU (typically 1500 bytes.)
87 // smoltcp counts the entire Ethernet packet in the MTU, so add the Ethernet header size to it.
88 let mtu = ip_mtu + ETHERNET_HEADER_LEN;
89
90 Ok(TunTap { fd, mtu })
91 }
92 }
93}
94
95impl Drop for TunTap {
96 fn drop(&mut self) {
97 unsafe {
98 libc::close(self.fd);
99 }
100 }
101}
102
103impl io::Read for TunTap {
104 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
105 let len = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
106 if len == -1 {
107 Err(io::Error::last_os_error())
108 } else {
109 Ok(len as usize)
110 }
111 }
112}
113
114impl io::Write for TunTap {
115 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
116 let len = unsafe { libc::write(self.fd, buf.as_ptr() as *mut libc::c_void, buf.len()) };
117 if len == -1 {
118 Err(io::Error::last_os_error())
119 } else {
120 Ok(len as usize)
121 }
122 }
123
124 fn flush(&mut self) -> io::Result<()> {
125 Ok(())
126 }
127}
128
129pub struct TunTapDevice {
130 device: Async<TunTap>,
131}
132
133impl TunTapDevice {
134 pub fn new(name: &str) -> io::Result<TunTapDevice> {
135 Ok(Self {
136 device: Async::new(TunTap::new(name)?)?,
137 })
138 }
139}
140
141impl Driver for TunTapDevice {
142 type RxToken<'a> = RxToken where Self: 'a;
143 type TxToken<'a> = TxToken<'a> where Self: 'a;
144
145 fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
146 let mut buf = vec![0; self.device.get_ref().mtu];
147 loop {
148 match self.device.get_mut().read(&mut buf) {
149 Ok(n) => {
150 buf.truncate(n);
151 return Some((
152 RxToken { buffer: buf },
153 TxToken {
154 device: &mut self.device,
155 },
156 ));
157 }
158 Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
159 if !self.device.poll_readable(cx).is_ready() {
160 return None;
161 }
162 }
163 Err(e) => panic!("read error: {:?}", e),
164 }
165 }
166 }
167
168 fn transmit(&mut self, _cx: &mut Context) -> Option<Self::TxToken<'_>> {
169 Some(TxToken {
170 device: &mut self.device,
171 })
172 }
173
174 fn capabilities(&self) -> Capabilities {
175 let mut caps = Capabilities::default();
176 caps.max_transmission_unit = self.device.get_ref().mtu;
177 caps
178 }
179
180 fn link_state(&mut self, _cx: &mut Context) -> LinkState {
181 LinkState::Up
182 }
183
184 fn hardware_address(&self) -> HardwareAddress {
185 HardwareAddress::Ethernet([0x02, 0x03, 0x04, 0x05, 0x06, 0x07])
186 }
187}
188
189#[doc(hidden)]
190pub struct RxToken {
191 buffer: Vec<u8>,
192}
193
194impl embassy_net_driver::RxToken for RxToken {
195 fn consume<R, F>(mut self, f: F) -> R
196 where
197 F: FnOnce(&mut [u8]) -> R,
198 {
199 f(&mut self.buffer)
200 }
201}
202
203#[doc(hidden)]
204pub struct TxToken<'a> {
205 device: &'a mut Async<TunTap>,
206}
207
208impl<'a> embassy_net_driver::TxToken for TxToken<'a> {
209 fn consume<R, F>(self, len: usize, f: F) -> R
210 where
211 F: FnOnce(&mut [u8]) -> R,
212 {
213 let mut buffer = vec![0; len];
214 let result = f(&mut buffer);
215
216 // todo handle WouldBlock with async
217 match self.device.get_mut().write(&buffer) {
218 Ok(_) => {}
219 Err(e) if e.kind() == io::ErrorKind::WouldBlock => info!("transmit WouldBlock"),
220 Err(e) => panic!("transmit error: {:?}", e),
221 }
222
223 result
224 }
225}