virtual-machine 0.0.3 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/{chunk-GZ343GYI.mjs → chunk-MELUIZWO.mjs} +6 -104
- package/build/cli.js +591 -709
- package/build/index.d.ts +2 -11
- package/build/index.js +9 -107
- package/build/index.mjs +4 -4
- package/build/{riscv_vm-3CIEJ5K7.mjs → riscv_vm-JTPPWIT3.mjs} +1 -1
- package/package.json +12 -5
- package/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +0 -1
- package/Cargo.toml +0 -49
- package/build/chunk-LJUNPJTY.mjs +0 -670
- package/build/chunk-Q7TFYQ5G.mjs +0 -670
- package/build/riscv_vm-MHIWEZQY.mjs +0 -12
- package/build/riscv_vm-VNML57ES.mjs +0 -12
- package/build.sh +0 -25
- package/cli.ts +0 -268
- package/index.ts +0 -16
- package/src/bus.rs +0 -558
- package/src/clint.rs +0 -132
- package/src/console.rs +0 -83
- package/src/cpu.rs +0 -1913
- package/src/csr.rs +0 -67
- package/src/decoder.rs +0 -789
- package/src/dram.rs +0 -146
- package/src/emulator.rs +0 -603
- package/src/lib.rs +0 -270
- package/src/main.rs +0 -373
- package/src/mmu.rs +0 -331
- package/src/net.rs +0 -121
- package/src/net_tap.rs +0 -164
- package/src/net_webtransport.rs +0 -446
- package/src/net_ws.rs +0 -396
- package/src/plic.rs +0 -261
- package/src/uart.rs +0 -233
- package/src/virtio.rs +0 -1074
- package/tsconfig.json +0 -19
- package/tsup/index.ts +0 -79
- package/tsup/tsup.cli.ts +0 -8
- package/tsup/tsup.core.cjs.ts +0 -7
- package/tsup/tsup.core.esm.ts +0 -8
package/src/mmu.rs
DELETED
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
use crate::bus::Bus;
|
|
2
|
-
use crate::csr::Mode;
|
|
3
|
-
use crate::Trap;
|
|
4
|
-
|
|
5
|
-
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
6
|
-
pub enum AccessType {
|
|
7
|
-
Instruction,
|
|
8
|
-
Load,
|
|
9
|
-
Store,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const PAGE_SIZE: u64 = 4096;
|
|
13
|
-
const PTE_SIZE: u64 = 8;
|
|
14
|
-
const MAX_LEVELS: usize = 4;
|
|
15
|
-
|
|
16
|
-
const TLB_SIZE: usize = 64;
|
|
17
|
-
|
|
18
|
-
#[derive(Clone, Copy, Debug)]
|
|
19
|
-
pub struct TlbEntry {
|
|
20
|
-
pub vpn: u64,
|
|
21
|
-
pub ppn: u64,
|
|
22
|
-
pub valid: bool,
|
|
23
|
-
pub asid: u64,
|
|
24
|
-
pub global: bool, // Global mapping bit
|
|
25
|
-
pub r: bool,
|
|
26
|
-
pub w: bool,
|
|
27
|
-
pub x: bool,
|
|
28
|
-
pub u: bool,
|
|
29
|
-
pub a: bool,
|
|
30
|
-
pub d: bool,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
impl Default for TlbEntry {
|
|
34
|
-
fn default() -> Self {
|
|
35
|
-
Self {
|
|
36
|
-
vpn: 0,
|
|
37
|
-
ppn: 0,
|
|
38
|
-
valid: false,
|
|
39
|
-
asid: 0,
|
|
40
|
-
global: false,
|
|
41
|
-
r: false,
|
|
42
|
-
w: false,
|
|
43
|
-
x: false,
|
|
44
|
-
u: false,
|
|
45
|
-
a: false,
|
|
46
|
-
d: false,
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
pub struct Tlb {
|
|
52
|
-
entries: [TlbEntry; TLB_SIZE],
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
impl Tlb {
|
|
56
|
-
pub fn new() -> Self {
|
|
57
|
-
Self {
|
|
58
|
-
entries: [TlbEntry::default(); TLB_SIZE],
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
pub fn flush(&mut self) {
|
|
63
|
-
for entry in self.entries.iter_mut() {
|
|
64
|
-
entry.valid = false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
pub fn flush_asid(&mut self, asid: u64) {
|
|
69
|
-
for entry in self.entries.iter_mut() {
|
|
70
|
-
if !entry.global && entry.asid == asid {
|
|
71
|
-
entry.valid = false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
pub fn flush_page(&mut self, vpn: u64, asid: u64) {
|
|
77
|
-
let idx = (vpn as usize) % TLB_SIZE;
|
|
78
|
-
let entry = &mut self.entries[idx];
|
|
79
|
-
|
|
80
|
-
if entry.valid && entry.vpn == vpn {
|
|
81
|
-
// For page-specific flush, invalidate matching ASID mappings.
|
|
82
|
-
// Global mappings ignore ASID and are treated as matching.
|
|
83
|
-
let match_asid = entry.global || entry.asid == asid;
|
|
84
|
-
if match_asid {
|
|
85
|
-
entry.valid = false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
pub fn lookup(&self, vpn: u64, asid: u64) -> Option<&TlbEntry> {
|
|
91
|
-
let idx = (vpn as usize) % TLB_SIZE;
|
|
92
|
-
let entry = &self.entries[idx];
|
|
93
|
-
|
|
94
|
-
// Hit if: valid AND VPN matches AND (entry is global OR ASID matches).
|
|
95
|
-
if entry.valid && entry.vpn == vpn && (entry.global || entry.asid == asid) {
|
|
96
|
-
Some(entry)
|
|
97
|
-
} else {
|
|
98
|
-
None
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
pub fn insert(&mut self, entry: TlbEntry) {
|
|
103
|
-
let idx = (entry.vpn as usize) % TLB_SIZE;
|
|
104
|
-
self.entries[idx] = entry;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/// Sv39/Sv48 translation + A/D bit updates.
|
|
109
|
-
///
|
|
110
|
-
/// `addr` is a virtual address. Returns the translated physical address or a
|
|
111
|
-
/// `Trap` corresponding to the appropriate page/access fault.
|
|
112
|
-
pub fn translate(
|
|
113
|
-
bus: &mut dyn Bus,
|
|
114
|
-
tlb: &mut Tlb,
|
|
115
|
-
mode: Mode,
|
|
116
|
-
satp: u64,
|
|
117
|
-
mstatus: u64,
|
|
118
|
-
addr: u64,
|
|
119
|
-
access_type: AccessType,
|
|
120
|
-
) -> Result<u64, Trap> {
|
|
121
|
-
// No translation in Machine mode (always Bare).
|
|
122
|
-
if mode == Mode::Machine {
|
|
123
|
-
return Ok(addr);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let satp_mode = (satp >> 60) & 0xF;
|
|
127
|
-
let current_asid = (satp >> 44) & 0xFFFF;
|
|
128
|
-
|
|
129
|
-
let (levels, va_bits, vpn_full_mask): (usize, u64, u64) = match satp_mode {
|
|
130
|
-
0 => {
|
|
131
|
-
// Bare: no translation.
|
|
132
|
-
return Ok(addr);
|
|
133
|
-
}
|
|
134
|
-
8 => {
|
|
135
|
-
// Sv39
|
|
136
|
-
let levels = 3;
|
|
137
|
-
let va_bits = 39;
|
|
138
|
-
let vpn_full_mask = (1u64 << (9 * levels)) - 1;
|
|
139
|
-
(levels, va_bits, vpn_full_mask)
|
|
140
|
-
}
|
|
141
|
-
9 => {
|
|
142
|
-
// Sv48 (supported by this MMU, though not required for virt).
|
|
143
|
-
let levels = 4;
|
|
144
|
-
let va_bits = 48;
|
|
145
|
-
let vpn_full_mask = (1u64 << (9 * levels)) - 1;
|
|
146
|
-
(levels, va_bits, vpn_full_mask)
|
|
147
|
-
}
|
|
148
|
-
_ => {
|
|
149
|
-
// Unsupported mode: treat as Bare.
|
|
150
|
-
return Ok(addr);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// Check canonical form of the virtual address for the configured VA width.
|
|
155
|
-
let sign_bit = va_bits - 1;
|
|
156
|
-
let upper_mask = !((1u64 << va_bits) - 1);
|
|
157
|
-
let sign = (addr >> sign_bit) & 1;
|
|
158
|
-
let expected_upper = if sign == 1 { upper_mask } else { 0 };
|
|
159
|
-
if (addr & upper_mask) != expected_upper {
|
|
160
|
-
return Err(page_fault(access_type, addr));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let vpn_full = (addr >> 12) & vpn_full_mask;
|
|
164
|
-
|
|
165
|
-
// TLB hit path.
|
|
166
|
-
if let Some(entry) = tlb.lookup(vpn_full, current_asid) {
|
|
167
|
-
if check_permission_tlb(mode, mstatus, entry, access_type) {
|
|
168
|
-
// For now we do not lazily update A/D on TLB hits – page table
|
|
169
|
-
// entries are already marked by the walk that inserted this entry.
|
|
170
|
-
let offset = addr & 0xFFF;
|
|
171
|
-
let pa = (entry.ppn << 12) | offset;
|
|
172
|
-
return Ok(pa);
|
|
173
|
-
} else {
|
|
174
|
-
return Err(page_fault(access_type, addr));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Page table walk on TLB miss.
|
|
179
|
-
let mut vpn = [0u64; MAX_LEVELS];
|
|
180
|
-
for level in 0..levels {
|
|
181
|
-
vpn[level] = (addr >> (12 + 9 * level as u64)) & 0x1FF;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
let root_ppn = satp & ((1u64 << 44) - 1);
|
|
185
|
-
let mut a = root_ppn * PAGE_SIZE;
|
|
186
|
-
|
|
187
|
-
for i in (0..levels).rev() {
|
|
188
|
-
let pte_addr = a + vpn[i] * PTE_SIZE;
|
|
189
|
-
|
|
190
|
-
let pte = match bus.load(pte_addr, 8) {
|
|
191
|
-
Ok(val) => val,
|
|
192
|
-
Err(_) => return Err(access_fault(access_type, addr)),
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
let v = (pte >> 0) & 1;
|
|
196
|
-
let r = (pte >> 1) & 1;
|
|
197
|
-
let w = (pte >> 2) & 1;
|
|
198
|
-
let x = (pte >> 3) & 1;
|
|
199
|
-
|
|
200
|
-
// Invalid or malformed.
|
|
201
|
-
if v == 0 || (r == 0 && w == 1) {
|
|
202
|
-
return Err(page_fault(access_type, addr));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Pointer to next level if R=X=0.
|
|
206
|
-
if r == 0 && x == 0 {
|
|
207
|
-
if i == 0 {
|
|
208
|
-
return Err(page_fault(access_type, addr));
|
|
209
|
-
}
|
|
210
|
-
let ppn = (pte >> 10) & 0xFFF_FFFF_FFFF;
|
|
211
|
-
a = ppn * PAGE_SIZE;
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Leaf PTE.
|
|
216
|
-
let mut entry = TlbEntry {
|
|
217
|
-
vpn: vpn_full,
|
|
218
|
-
ppn: (pte >> 10) & 0xFFF_FFFF_FFFF,
|
|
219
|
-
valid: true,
|
|
220
|
-
asid: current_asid,
|
|
221
|
-
global: (pte >> 5) & 1 != 0, // G bit
|
|
222
|
-
r: r != 0,
|
|
223
|
-
w: w != 0,
|
|
224
|
-
x: x != 0,
|
|
225
|
-
u: (pte >> 4) & 1 != 0,
|
|
226
|
-
a: (pte >> 6) & 1 != 0,
|
|
227
|
-
d: (pte >> 7) & 1 != 0,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
if !check_permission_tlb(mode, mstatus, &entry, access_type) {
|
|
231
|
-
return Err(page_fault(access_type, addr));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Superpage alignment checks (Sv39/48 spec).
|
|
235
|
-
if i > 0 {
|
|
236
|
-
let ppn_mask = (1 << (9 * i)) - 1;
|
|
237
|
-
let ppn = (pte >> 10) & 0xFFF_FFFF_FFFF;
|
|
238
|
-
if (ppn & ppn_mask) != 0 {
|
|
239
|
-
return Err(page_fault(access_type, addr));
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// A/D bit updates: set in memory and in the cached entry.
|
|
244
|
-
let mut new_pte = pte;
|
|
245
|
-
let mut update = false;
|
|
246
|
-
|
|
247
|
-
if !entry.a {
|
|
248
|
-
new_pte |= 1 << 6;
|
|
249
|
-
entry.a = true;
|
|
250
|
-
update = true;
|
|
251
|
-
}
|
|
252
|
-
if matches!(access_type, AccessType::Store) && !entry.d {
|
|
253
|
-
new_pte |= 1 << 7;
|
|
254
|
-
entry.d = true;
|
|
255
|
-
update = true;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if update {
|
|
259
|
-
if bus.store(pte_addr, 8, new_pte).is_err() {
|
|
260
|
-
return Err(access_fault(access_type, addr));
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
let offset_in_page = addr & 0xFFF;
|
|
265
|
-
|
|
266
|
-
// Construct final PPN, filling low parts from the VA on superpages.
|
|
267
|
-
let ppn = (pte >> 10) & 0xFFF_FFFF_FFFF;
|
|
268
|
-
let vpn_mask = (1 << (9 * i)) - 1;
|
|
269
|
-
let result_ppn = (ppn & !vpn_mask) | ((addr >> 12) & vpn_mask);
|
|
270
|
-
|
|
271
|
-
entry.ppn = result_ppn;
|
|
272
|
-
tlb.insert(entry);
|
|
273
|
-
|
|
274
|
-
let pa = (result_ppn << 12) | offset_in_page;
|
|
275
|
-
return Ok(pa);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
Err(page_fault(access_type, addr))
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
fn check_permission_tlb(mode: Mode, mstatus: u64, entry: &TlbEntry, access_type: AccessType) -> bool {
|
|
282
|
-
let mxr = (mstatus >> 19) & 1;
|
|
283
|
-
let sum = (mstatus >> 18) & 1;
|
|
284
|
-
|
|
285
|
-
match mode {
|
|
286
|
-
Mode::Supervisor => {
|
|
287
|
-
if entry.u {
|
|
288
|
-
if matches!(access_type, AccessType::Instruction) {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
if sum == 0 {
|
|
292
|
-
return false;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
Mode::User => {
|
|
297
|
-
if !entry.u {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
Mode::Machine => {}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
match access_type {
|
|
305
|
-
AccessType::Instruction => entry.x,
|
|
306
|
-
AccessType::Store => entry.w,
|
|
307
|
-
AccessType::Load => {
|
|
308
|
-
if entry.r {
|
|
309
|
-
true
|
|
310
|
-
} else {
|
|
311
|
-
mxr == 1 && entry.x
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
fn page_fault(access_type: AccessType, addr: u64) -> Trap {
|
|
318
|
-
match access_type {
|
|
319
|
-
AccessType::Instruction => Trap::InstructionPageFault(addr),
|
|
320
|
-
AccessType::Load => Trap::LoadPageFault(addr),
|
|
321
|
-
AccessType::Store => Trap::StorePageFault(addr),
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
fn access_fault(access_type: AccessType, addr: u64) -> Trap {
|
|
326
|
-
match access_type {
|
|
327
|
-
AccessType::Instruction => Trap::InstructionAccessFault(addr),
|
|
328
|
-
AccessType::Load => Trap::LoadAccessFault(addr),
|
|
329
|
-
AccessType::Store => Trap::StoreAccessFault(addr),
|
|
330
|
-
}
|
|
331
|
-
}
|
package/src/net.rs
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
//! Network backend abstraction for VirtIO networking.
|
|
2
|
-
//!
|
|
3
|
-
//! This module defines the `NetworkBackend` trait that abstracts packet I/O
|
|
4
|
-
//! to support both Host (TAP) and WASM (WebSocket) environments.
|
|
5
|
-
|
|
6
|
-
/// Trait for network backends that provide packet I/O.
|
|
7
|
-
///
|
|
8
|
-
/// Implementations must be `Send` to allow the backend to be used
|
|
9
|
-
/// across thread boundaries (e.g., when the VM runs in a separate thread).
|
|
10
|
-
pub trait NetworkBackend: Send {
|
|
11
|
-
/// Initialize the backend (e.g., open TAP device or connect WebSocket).
|
|
12
|
-
fn init(&mut self) -> Result<(), String>;
|
|
13
|
-
|
|
14
|
-
/// Poll for an incoming packet. Returns None if no packet is available.
|
|
15
|
-
/// This should be non-blocking.
|
|
16
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String>;
|
|
17
|
-
|
|
18
|
-
/// Send a packet.
|
|
19
|
-
fn send(&self, buf: &[u8]) -> Result<(), String>;
|
|
20
|
-
|
|
21
|
-
/// Get the MAC address of the backend (if available).
|
|
22
|
-
/// Returns a default MAC if the backend doesn't have one.
|
|
23
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
24
|
-
// Default MAC: locally administered, unicast
|
|
25
|
-
[0x52, 0x54, 0x00, 0x12, 0x34, 0x56]
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/// A no-op network backend for testing purposes.
|
|
30
|
-
///
|
|
31
|
-
/// This backend discards all sent packets and never receives any packets.
|
|
32
|
-
pub struct DummyBackend {
|
|
33
|
-
initialized: bool,
|
|
34
|
-
mac: [u8; 6],
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
impl DummyBackend {
|
|
38
|
-
pub fn new() -> Self {
|
|
39
|
-
Self {
|
|
40
|
-
initialized: false,
|
|
41
|
-
mac: [0x52, 0x54, 0x00, 0x12, 0x34, 0x56],
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/// Create a dummy backend with a custom MAC address.
|
|
46
|
-
pub fn with_mac(mac: [u8; 6]) -> Self {
|
|
47
|
-
Self {
|
|
48
|
-
initialized: false,
|
|
49
|
-
mac,
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
impl Default for DummyBackend {
|
|
55
|
-
fn default() -> Self {
|
|
56
|
-
Self::new()
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
impl NetworkBackend for DummyBackend {
|
|
61
|
-
fn init(&mut self) -> Result<(), String> {
|
|
62
|
-
self.initialized = true;
|
|
63
|
-
log::debug!("[DummyBackend] Initialized (no-op)");
|
|
64
|
-
Ok(())
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
|
|
68
|
-
// No packets ever available
|
|
69
|
-
Ok(None)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
fn send(&self, buf: &[u8]) -> Result<(), String> {
|
|
73
|
-
// Discard packet, but log it for debugging
|
|
74
|
-
log::trace!("[DummyBackend] Discarding {} byte packet", buf.len());
|
|
75
|
-
Ok(())
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
79
|
-
self.mac
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#[cfg(test)]
|
|
84
|
-
mod tests {
|
|
85
|
-
use super::*;
|
|
86
|
-
|
|
87
|
-
#[test]
|
|
88
|
-
fn test_dummy_backend_init() {
|
|
89
|
-
let mut backend = DummyBackend::new();
|
|
90
|
-
assert!(backend.init().is_ok());
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
#[test]
|
|
94
|
-
fn test_dummy_backend_recv_returns_none() {
|
|
95
|
-
let mut backend = DummyBackend::new();
|
|
96
|
-
backend.init().unwrap();
|
|
97
|
-
assert!(backend.recv().unwrap().is_none());
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
#[test]
|
|
101
|
-
fn test_dummy_backend_send_succeeds() {
|
|
102
|
-
let backend = DummyBackend::new();
|
|
103
|
-
assert!(backend.send(&[1, 2, 3, 4]).is_ok());
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
#[test]
|
|
107
|
-
fn test_dummy_backend_mac_address() {
|
|
108
|
-
let backend = DummyBackend::new();
|
|
109
|
-
let mac = backend.mac_address();
|
|
110
|
-
// Check locally administered bit is set (second bit of first byte)
|
|
111
|
-
assert_eq!(mac[0] & 0x02, 0x02);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
#[test]
|
|
115
|
-
fn test_dummy_backend_custom_mac() {
|
|
116
|
-
let custom_mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
|
|
117
|
-
let backend = DummyBackend::with_mac(custom_mac);
|
|
118
|
-
assert_eq!(backend.mac_address(), custom_mac);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
package/src/net_tap.rs
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
//! TAP network backend for native (non-WASM) builds.
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides a network backend that uses a TAP interface
|
|
4
|
-
//! to communicate with the host network stack on Linux/macOS.
|
|
5
|
-
|
|
6
|
-
use crate::net::NetworkBackend;
|
|
7
|
-
use std::io::Write;
|
|
8
|
-
use std::os::unix::io::AsRawFd;
|
|
9
|
-
|
|
10
|
-
/// TAP network backend using the tun-tap crate.
|
|
11
|
-
pub struct TapBackend {
|
|
12
|
-
name: String,
|
|
13
|
-
iface: Option<tun_tap::Iface>,
|
|
14
|
-
mac: [u8; 6],
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
impl TapBackend {
|
|
18
|
-
/// Create a new TAP backend with the given interface name.
|
|
19
|
-
///
|
|
20
|
-
/// The interface will not be opened until `init()` is called.
|
|
21
|
-
/// Creating the TAP interface typically requires root privileges.
|
|
22
|
-
///
|
|
23
|
-
/// # Example
|
|
24
|
-
/// ```ignore
|
|
25
|
-
/// let mut tap = TapBackend::new("tap0");
|
|
26
|
-
/// tap.init()?;
|
|
27
|
-
/// ```
|
|
28
|
-
pub fn new(name: &str) -> Self {
|
|
29
|
-
Self {
|
|
30
|
-
name: name.to_string(),
|
|
31
|
-
iface: None,
|
|
32
|
-
// Default MAC - locally administered, unicast
|
|
33
|
-
mac: [0x52, 0x54, 0x00, 0x12, 0x34, 0x56],
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/// Create a TAP backend with a custom MAC address.
|
|
38
|
-
pub fn with_mac(name: &str, mac: [u8; 6]) -> Self {
|
|
39
|
-
Self {
|
|
40
|
-
name: name.to_string(),
|
|
41
|
-
iface: None,
|
|
42
|
-
mac,
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/// Set the interface to non-blocking mode.
|
|
47
|
-
fn set_nonblocking(&self) -> Result<(), String> {
|
|
48
|
-
if let Some(ref iface) = self.iface {
|
|
49
|
-
let fd = iface.as_raw_fd();
|
|
50
|
-
|
|
51
|
-
// Get current flags
|
|
52
|
-
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
|
|
53
|
-
if flags < 0 {
|
|
54
|
-
return Err(format!(
|
|
55
|
-
"Failed to get fd flags: {}",
|
|
56
|
-
std::io::Error::last_os_error()
|
|
57
|
-
));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Set non-blocking flag
|
|
61
|
-
let result = unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) };
|
|
62
|
-
if result < 0 {
|
|
63
|
-
return Err(format!(
|
|
64
|
-
"Failed to set non-blocking mode: {}",
|
|
65
|
-
std::io::Error::last_os_error()
|
|
66
|
-
));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
Ok(())
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
impl NetworkBackend for TapBackend {
|
|
74
|
-
fn init(&mut self) -> Result<(), String> {
|
|
75
|
-
// Open the TAP interface
|
|
76
|
-
let iface = tun_tap::Iface::without_packet_info(&self.name, tun_tap::Mode::Tap)
|
|
77
|
-
.map_err(|e| format!("Failed to open TAP interface '{}': {}", self.name, e))?;
|
|
78
|
-
|
|
79
|
-
self.iface = Some(iface);
|
|
80
|
-
|
|
81
|
-
// Set non-blocking mode for recv polling
|
|
82
|
-
self.set_nonblocking()?;
|
|
83
|
-
|
|
84
|
-
log::info!("[TapBackend] Opened TAP interface '{}'", self.name);
|
|
85
|
-
Ok(())
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
|
|
89
|
-
let iface = self.iface.as_mut().ok_or("TAP interface not initialized")?;
|
|
90
|
-
|
|
91
|
-
// Buffer for maximum Ethernet frame size (MTU 1500 + headers)
|
|
92
|
-
let mut buf = vec![0u8; 1514];
|
|
93
|
-
|
|
94
|
-
match iface.recv(&mut buf) {
|
|
95
|
-
Ok(n) => {
|
|
96
|
-
buf.truncate(n);
|
|
97
|
-
log::trace!("[TapBackend] Received {} byte packet", n);
|
|
98
|
-
Ok(Some(buf))
|
|
99
|
-
}
|
|
100
|
-
Err(e) => {
|
|
101
|
-
// Check if it's a would-block error (no data available)
|
|
102
|
-
if e.kind() == std::io::ErrorKind::WouldBlock {
|
|
103
|
-
Ok(None)
|
|
104
|
-
} else {
|
|
105
|
-
Err(format!("TAP recv error: {}", e))
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
fn send(&self, buf: &[u8]) -> Result<(), String> {
|
|
112
|
-
let iface = self.iface.as_ref().ok_or("TAP interface not initialized")?;
|
|
113
|
-
|
|
114
|
-
// tun_tap::Iface doesn't implement Send on the write side,
|
|
115
|
-
// so we need to use the raw fd for writing. However, for simplicity
|
|
116
|
-
// we'll just write directly. Note: this may need adjustment for
|
|
117
|
-
// thread safety in the future.
|
|
118
|
-
let mut iface_clone = unsafe {
|
|
119
|
-
// This is safe because we're only writing and the fd is valid
|
|
120
|
-
std::fs::File::from_raw_fd(std::os::unix::io::AsRawFd::as_raw_fd(iface))
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
let result = iface_clone.write_all(buf);
|
|
124
|
-
|
|
125
|
-
// Don't drop the file - it doesn't own the fd
|
|
126
|
-
std::mem::forget(iface_clone);
|
|
127
|
-
|
|
128
|
-
result.map_err(|e| format!("TAP send error: {}", e))?;
|
|
129
|
-
log::trace!("[TapBackend] Sent {} byte packet", buf.len());
|
|
130
|
-
Ok(())
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
134
|
-
self.mac
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Manual implementation to handle the Iface not being Send
|
|
139
|
-
unsafe impl Send for TapBackend {}
|
|
140
|
-
|
|
141
|
-
use std::os::unix::io::FromRawFd;
|
|
142
|
-
|
|
143
|
-
#[cfg(test)]
|
|
144
|
-
mod tests {
|
|
145
|
-
use super::*;
|
|
146
|
-
|
|
147
|
-
#[test]
|
|
148
|
-
fn test_tap_backend_creation() {
|
|
149
|
-
let tap = TapBackend::new("test0");
|
|
150
|
-
assert_eq!(tap.name, "test0");
|
|
151
|
-
assert!(tap.iface.is_none());
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
#[test]
|
|
155
|
-
fn test_tap_backend_custom_mac() {
|
|
156
|
-
let mac = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
|
|
157
|
-
let tap = TapBackend::with_mac("test0", mac);
|
|
158
|
-
assert_eq!(tap.mac_address(), mac);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Note: Actually opening a TAP interface requires root privileges,
|
|
162
|
-
// so we can't test init() in regular unit tests.
|
|
163
|
-
}
|
|
164
|
-
|