1use alloc::{collections::BTreeMap, string::String};
2
3use smoltcp::{
4 iface::{Config, Interface, SocketHandle, SocketSet},
5 socket::{dhcpv4, dns, icmp, tcp, udp},
6 time::Instant,
7 wire::{EthernetAddress, IpAddress, IpCidr, IpEndpoint, Ipv4Address, Ipv6Address},
8};
9use strate_net::IpcMessage;
10
11use crate::{
12 device::Strat9NetDevice,
13 ip::{link_local_from_mac, IpConfig, Ipv6Config},
14 ipc::reply_read,
15};
16
17pub(crate) const PING_TIMEOUT_NS: u64 = 5_000_000_000;
18
19pub(crate) struct PendingPing {
20 pub(crate) seq: u16,
21 pub(crate) token: u64,
22 pub(crate) send_ts_ns: u64,
23 pub(crate) is_v6: bool,
24}
25
26#[derive(Clone, Copy)]
27pub(crate) struct TcpListenerState {
28 pub(crate) socket: SocketHandle,
29 pub(crate) port: u16,
30 pub(crate) auto_relisten: bool,
31}
32
33#[derive(Copy, Clone)]
34pub(crate) struct TcpConnState {
35 pub(crate) socket: SocketHandle,
36 pub(crate) local_port: u16,
37 pub(crate) remote: IpEndpoint,
38}
39
40#[derive(Copy, Clone)]
41pub(crate) struct UdpBoundState {
42 pub(crate) socket: SocketHandle,
43 pub(crate) local_port: u16,
44 pub(crate) last_peer: Option<IpEndpoint>,
45}
46
47#[derive(Copy, Clone)]
48pub(crate) struct UdpConnState {
49 pub(crate) socket: SocketHandle,
50 pub(crate) local_port: u16,
51 pub(crate) remote: IpEndpoint,
52}
53
54pub(crate) struct OpenedFile {
55 pub(crate) path: String,
56 pub(crate) cached_content: Option<alloc::vec::Vec<u8>>,
57 pub(crate) read_spill: Option<alloc::vec::Vec<u8>>,
58}
59
60impl OpenedFile {
61 pub(crate) fn new(path: &str) -> Self {
62 Self {
63 path: String::from(path),
64 cached_content: None,
65 read_spill: None,
66 }
67 }
68}
69
70pub(crate) struct NetworkStrate {
71 pub(crate) device: Strat9NetDevice,
72 pub(crate) interface: Interface,
73 pub(crate) sockets: SocketSet<'static>,
74 pub(crate) dhcp_handle: SocketHandle,
75 pub(crate) dns_handle: SocketHandle,
76 pub(crate) icmp_handle: SocketHandle,
77 pub(crate) ip_config: Option<IpConfig>,
78 pub(crate) ipv6_config: Option<Ipv6Config>,
79 pub(crate) dns_servers: [Option<IpAddress>; 3],
80 pub(crate) dns_from_dhcp: bool,
81 pub(crate) link_local_addr: Ipv6Address,
82 pub(crate) open_handles: BTreeMap<u64, OpenedFile>,
83 pub(crate) tcp_listeners: BTreeMap<u64, TcpListenerState>,
84 pub(crate) tcp_connections: BTreeMap<u64, TcpConnState>,
85 pub(crate) udp_bound: BTreeMap<u64, UdpBoundState>,
86 pub(crate) udp_connections: BTreeMap<u64, UdpConnState>,
87 pub(crate) lingering_sockets: alloc::vec::Vec<SocketHandle>,
88 pub(crate) next_fid: u64,
89 pub(crate) ping_ident: u16,
90 pub(crate) next_ping_token: u64,
91 pub(crate) pending_pings: alloc::vec::Vec<PendingPing>,
92 pub(crate) ping_replies: alloc::vec::Vec<(u16, u64)>,
93 pub(crate) dhcp_enabled: bool,
94}
95
96impl NetworkStrate {
97 pub(crate) fn new(mac: [u8; 6]) -> Self {
98 let mut device = Strat9NetDevice;
99 let config = Config::new(EthernetAddress(mac).into());
100 let mut interface = Interface::new(config, &mut device, Instant::from_micros(0));
101 let mut sockets = SocketSet::new(alloc::vec![]);
102
103 let link_local = link_local_from_mac(mac);
104 interface.update_ip_addrs(|addrs| {
105 let _ = addrs.push(IpCidr::new(IpAddress::Ipv6(link_local), 64));
106 });
107
108 let dhcp_socket = dhcpv4::Socket::new();
109 let dhcp_handle = sockets.add(dhcp_socket);
110
111 let dns_socket = dns::Socket::new(&[], alloc::vec![]);
112 let dns_handle = sockets.add(dns_socket);
113
114 let icmp_rx_buf = icmp::PacketBuffer::new(
115 alloc::vec![icmp::PacketMetadata::EMPTY; 16],
116 alloc::vec![0u8; 4096],
117 );
118 let icmp_tx_buf = icmp::PacketBuffer::new(
119 alloc::vec![icmp::PacketMetadata::EMPTY; 16],
120 alloc::vec![0u8; 4096],
121 );
122 let icmp_socket = icmp::Socket::new(icmp_rx_buf, icmp_tx_buf);
123 let icmp_handle = sockets.add(icmp_socket);
124
125 Self {
126 device,
127 interface,
128 sockets,
129 dhcp_handle,
130 dns_handle,
131 icmp_handle,
132 ip_config: None,
133 ipv6_config: None,
134 dns_servers: [None; 3],
135 dns_from_dhcp: false,
136 link_local_addr: link_local,
137 open_handles: BTreeMap::new(),
138 tcp_listeners: BTreeMap::new(),
139 tcp_connections: BTreeMap::new(),
140 udp_bound: BTreeMap::new(),
141 udp_connections: BTreeMap::new(),
142 lingering_sockets: alloc::vec::Vec::new(),
143 next_fid: 1,
144 ping_ident: 0x9001,
145 next_ping_token: 1,
146 pending_pings: alloc::vec::Vec::new(),
147 ping_replies: alloc::vec::Vec::new(),
148 dhcp_enabled: true,
149 }
150 }
151
152 pub(crate) fn clear_ipv4_runtime_config(&mut self) {
153 self.ip_config = None;
154 self.interface.update_ip_addrs(|addrs| {
155 let mut kept = [IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0); 8];
156 let mut n = 0usize;
157 for addr in addrs.iter() {
158 if matches!(addr, IpCidr::Ipv6(_)) && n < kept.len() {
159 kept[n] = *addr;
160 n += 1;
161 }
162 }
163 addrs.clear();
164 for addr in kept.into_iter().take(n) {
165 let _ = addrs.push(addr);
166 }
167 });
168 let _ = self.interface.routes_mut().remove_default_ipv4_route();
169 self.refresh_dns_servers();
170 }
171
172 pub(crate) fn drain_spilled_read(
173 &mut self,
174 sender: u64,
175 file_id: u64,
176 requested: usize,
177 ) -> Option<IpcMessage> {
178 let max_inline = requested.min(IpcMessage::READ_INLINE_CAPACITY);
179 let mut spill = self.open_handles.get_mut(&file_id)?.read_spill.take()?;
180 if spill.len() > max_inline {
181 let rest = spill.split_off(max_inline);
182 if let Some(handle) = self.open_handles.get_mut(&file_id) {
183 handle.read_spill = Some(rest);
184 }
185 }
186 Some(reply_read(sender, &spill))
187 }
188
189 pub(crate) fn reply_read_spilling(
190 &mut self,
191 sender: u64,
192 file_id: u64,
193 requested: usize,
194 mut data: alloc::vec::Vec<u8>,
195 ) -> IpcMessage {
196 let max_inline = requested.min(IpcMessage::READ_INLINE_CAPACITY);
197 if data.len() > max_inline {
198 let rest = data.split_off(max_inline);
199 if let Some(handle) = self.open_handles.get_mut(&file_id) {
200 handle.read_spill = Some(rest);
201 }
202 }
203 reply_read(sender, &data)
204 }
205
206 pub(crate) fn reset_dhcp_socket(&mut self) {
207 self.sockets
208 .get_mut::<dhcpv4::Socket>(self.dhcp_handle)
209 .reset();
210 }
211
212 pub(crate) fn enable_dhcp(&mut self) {
213 self.dhcp_enabled = true;
214 self.dns_servers = [None; 3];
215 self.dns_from_dhcp = false;
216 self.clear_ipv4_runtime_config();
217 self.reset_dhcp_socket();
218 }
219
220 pub(crate) fn alloc_ping_token(&mut self) -> u64 {
221 let token = self.next_ping_token;
222 self.next_ping_token = self.next_ping_token.wrapping_add(1);
223 token
224 }
225
226 pub(crate) fn udp_port_in_use(&self, port: u16) -> bool {
227 self.udp_bound
228 .values()
229 .any(|state| state.local_port == port)
230 || self
231 .udp_connections
232 .values()
233 .any(|state| state.local_port == port)
234 }
235
236 pub(crate) fn alloc_udp_ephemeral_port(&self) -> Option<u16> {
237 const BASE: u16 = 49_152;
238 const COUNT: usize = 16_384;
239 let start = (self.next_fid as usize) % COUNT;
240 for step in 0..COUNT {
241 let port = BASE + ((start + step) % COUNT) as u16;
242 if !self.udp_port_in_use(port) {
243 return Some(port);
244 }
245 }
246 None
247 }
248
249 pub(crate) fn create_udp_socket(
250 &mut self,
251 local_port: u16,
252 ) -> core::result::Result<SocketHandle, i32> {
253 let rx_buf = udp::PacketBuffer::new(
254 alloc::vec![udp::PacketMetadata::EMPTY; 32],
255 alloc::vec![0u8; 65536],
256 );
257 let tx_buf = udp::PacketBuffer::new(
258 alloc::vec![udp::PacketMetadata::EMPTY; 32],
259 alloc::vec![0u8; 65536],
260 );
261 let mut socket = udp::Socket::new(rx_buf, tx_buf);
262 if socket.bind(local_port).is_err() {
263 return Err(-98);
264 }
265 Ok(self.sockets.add(socket))
266 }
267
268 pub(crate) fn tcp_state_name(state: tcp::State) -> &'static str {
269 match state {
270 tcp::State::Closed => "CLOSED",
271 tcp::State::Listen => "LISTEN",
272 tcp::State::SynSent => "SYN-SENT",
273 tcp::State::SynReceived => "SYN-RECEIVED",
274 tcp::State::Established => "ESTABLISHED",
275 tcp::State::FinWait1 => "FIN-WAIT-1",
276 tcp::State::FinWait2 => "FIN-WAIT-2",
277 tcp::State::CloseWait => "CLOSE-WAIT",
278 tcp::State::Closing => "CLOSING",
279 tcp::State::LastAck => "LAST-ACK",
280 tcp::State::TimeWait => "TIME-WAIT",
281 }
282 }
283
284 pub(crate) fn alloc_fid(&mut self) -> u64 {
285 let id = self.next_fid;
286 self.next_fid += 1;
287 id
288 }
289}