fips_core/node/lifecycle/
runtime.rs1use super::*;
2
3impl Node {
4 pub async fn start(&mut self) -> Result<(), NodeError> {
11 if !self.state.can_start() {
12 return Err(NodeError::AlreadyStarted);
13 }
14 self.state = NodeState::Starting;
15
16 let packet_buffer_size = self.config.node.buffers.packet_channel;
18 let (mut packet_tx, packet_rx) = packet_channel(packet_buffer_size);
19 self.dataplane_fast_ingress_rx = Some(
20 self.dataplane
21 .attach_established_fast_ingress(&mut packet_tx),
22 );
23 self.packet_tx = Some(packet_tx.clone());
24 self.packet_rx = Some(packet_rx);
25
26 let transport_handles = self.create_transports(&packet_tx).await;
28
29 for mut handle in transport_handles {
30 let transport_id = handle.transport_id();
31 let transport_type = handle.transport_type().name;
32 let name = handle.name().map(|s| s.to_string());
33
34 match handle.start().await {
35 Ok(()) => {
36 self.udp_transport_resolution_cache.clear();
37 self.transports.insert(transport_id, handle);
38 }
39 Err(e) => {
40 if let Some(ref n) = name {
41 warn!(transport_type, name = %n, error = %e, "Transport failed to start");
42 } else {
43 warn!(transport_type, error = %e, "Transport failed to start");
44 }
45 }
46 }
47 }
48
49 if !self.transports.is_empty() {
50 info!(count = self.transports.len(), "Transports initialized");
51 }
52
53 if self.config.node.discovery.nostr.enabled {
54 match NostrDiscovery::start(&self.identity, self.config.node.discovery.nostr.clone())
55 .await
56 {
57 Ok(runtime) => {
58 if let Err(err) = self.refresh_overlay_advert(&runtime).await {
59 warn!(error = %err, "Failed to publish initial Nostr overlay advert");
60 }
61 self.nostr_discovery = Some(runtime);
62 self.nostr_discovery_started_at_ms = Some(Self::now_ms());
63 info!("Nostr overlay discovery enabled");
64 }
65 Err(err) => {
66 warn!(error = %err, "Failed to start Nostr overlay discovery");
67 }
68 }
69 }
70
71 if self.config.node.discovery.lan.enabled {
75 let advertised_udp_port = self
76 .transports
77 .values()
78 .filter(|h| h.is_operational())
79 .filter(|h| h.transport_type().name == "udp")
80 .find_map(|h| h.local_addr().map(|addr| addr.port()))
81 .unwrap_or(0);
82 let scope = self.lan_discovery_scope();
83 match crate::discovery::lan::LanDiscovery::start(
84 &self.identity,
85 scope,
86 advertised_udp_port,
87 self.config.node.discovery.lan.clone(),
88 )
89 .await
90 {
91 Ok(runtime) => {
92 self.lan_discovery = Some(runtime);
93 info!("LAN mDNS discovery enabled");
94 }
95 Err(err) => {
96 debug!(error = %err, "LAN mDNS discovery not started");
97 }
98 }
99 }
100
101 self.start_local_instance_discovery();
102 self.poll_local_instance_discovery().await;
103
104 self.initiate_peer_connections().await;
107
108 if self.config.tun.enabled {
110 let address = *self.identity.address();
111 let mut tun_config = self.config.tun.clone();
112 if tun_config.mtu.is_none() {
113 tun_config.mtu = Some(self.runtime_tun_mtu());
114 }
115 match TunDevice::create(&tun_config, address).await {
116 Ok(device) => {
117 let mtu = device.mtu();
118 let name = device.name().to_string();
119 let our_addr = *device.address();
120
121 info!("TUN device active:");
122 info!(" name: {}", name);
123 info!(" address: {}", device.address());
124 info!(" mtu: {}", mtu);
125
126 let effective_mtu = self.effective_ipv6_mtu();
128 let max_mss = effective_mtu.saturating_sub(40).saturating_sub(20); info!("effective MTU: {} bytes", effective_mtu);
131 debug!(" max TCP MSS: {} bytes", max_mss);
132
133 #[cfg(target_os = "macos")]
137 let (shutdown_read_fd, shutdown_write_fd) = {
138 let mut fds = [0i32; 2];
139 if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 {
140 return Err(NodeError::Tun(crate::upper::tun::TunError::Configure(
141 "failed to create shutdown pipe".into(),
142 )));
143 }
144 (fds[0], fds[1])
145 };
146
147 let (writer, tun_tx) =
151 device.create_writer(max_mss, self.path_mtu_lookup.clone())?;
152
153 let writer_handle = thread::spawn(move || {
155 writer.run();
156 });
157
158 let reader_tun_tx = tun_tx.clone();
160
161 let tun_channel_size = self.config.node.buffers.tun_channel;
163 let (outbound_tx, outbound_rx) =
164 crate::upper::tun::tun_outbound_channel(tun_channel_size);
165
166 let transport_mtu = self.transport_mtu();
168 let path_mtu_lookup = self.path_mtu_lookup.clone();
169 let reader_runtime = TunReaderRuntime {
170 device,
171 mtu,
172 our_addr,
173 tun_tx: reader_tun_tx,
174 outbound_tx,
175 transport_mtu,
176 path_mtu_lookup,
177 };
178 #[cfg(target_os = "macos")]
179 let reader_handle = thread::spawn(move || {
180 run_tun_reader(reader_runtime, shutdown_read_fd);
181 });
182 #[cfg(not(target_os = "macos"))]
183 let reader_handle = thread::spawn(move || {
184 run_tun_reader(reader_runtime);
185 });
186
187 self.tun_state = TunState::Active;
188 self.tun_name = Some(name);
189 self.tun_tx = Some(tun_tx);
190 self.tun_outbound_rx = Some(outbound_rx);
191 self.tun_reader_handle = Some(reader_handle);
192 self.tun_writer_handle = Some(writer_handle);
193 #[cfg(target_os = "macos")]
194 {
195 self.tun_shutdown_fd = Some(shutdown_write_fd);
196 }
197 }
198 Err(e) => {
199 self.tun_state = TunState::Failed;
200 warn!(error = %e, "Failed to initialize TUN, continuing without it");
201 }
202 }
203 }
204
205 if self.config.dns.enabled {
222 let addr_str = self.config.dns.bind_addr();
223 match addr_str.parse::<std::net::IpAddr>() {
224 Ok(ip) => {
225 let bind = std::net::SocketAddr::new(ip, self.config.dns.port());
226 match Self::bind_dns_socket(bind) {
227 Ok(socket) => {
228 let dns_channel_size = self.config.node.buffers.dns_channel;
229 let (identity_tx, identity_rx) =
230 tokio::sync::mpsc::channel(dns_channel_size);
231 let dns_ttl = self.config.dns.ttl();
232 let base_hosts = crate::upper::hosts::HostMap::from_peer_configs(
233 self.config.peers(),
234 );
235 let reloader = if self.config.node.system_files_enabled {
236 let hosts_path = std::path::PathBuf::from(
237 crate::upper::hosts::DEFAULT_HOSTS_PATH,
238 );
239 crate::upper::hosts::HostMapReloader::new(base_hosts, hosts_path)
240 } else {
241 crate::upper::hosts::HostMapReloader::memory_only(base_hosts)
242 };
243 let mesh_ifindex = Self::lookup_mesh_ifindex(self.config.tun.name());
251 info!(
252 bind = %bind,
253 hosts = reloader.hosts().len(),
254 mesh_ifindex = ?mesh_ifindex,
255 "DNS responder started for .fips domain (auto-reload enabled)"
256 );
257 let handle = tokio::spawn(crate::upper::dns::run_dns_responder(
258 socket,
259 identity_tx,
260 dns_ttl,
261 reloader,
262 mesh_ifindex,
263 ));
264 self.dns_identity_rx = Some(identity_rx);
265 self.dns_task = Some(handle);
266 }
267 Err(e) => {
268 warn!(bind = %bind, error = %e, "Failed to start DNS responder");
269 }
270 }
271 }
272 Err(e) => {
273 warn!(addr = %addr_str, error = %e, "Invalid dns.bind_addr; DNS responder not started");
274 }
275 }
276 }
277
278 self.state = NodeState::Running;
279 info!("Node started:");
280 info!(" state: {}", self.state);
281 info!(" transports: {}", self.transports.len());
282 info!(" connections: {}", self.peers.connection_len());
283 Ok(())
284 }
285
286 pub(super) fn bind_dns_socket(
299 addr: std::net::SocketAddr,
300 ) -> Result<tokio::net::UdpSocket, std::io::Error> {
301 use socket2::{Domain, Protocol, Socket, Type};
302 let domain = if addr.is_ipv4() {
303 Domain::IPV4
304 } else {
305 Domain::IPV6
306 };
307 let sock = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
308 if addr.is_ipv6() {
309 sock.set_only_v6(false)?;
310 #[cfg(unix)]
311 Self::set_recv_pktinfo_v6(&sock)?;
312 }
313 sock.set_nonblocking(true)?;
314 sock.bind(&addr.into())?;
315 tokio::net::UdpSocket::from_std(sock.into())
316 }
317
318 #[cfg(unix)]
324 pub(super) fn set_recv_pktinfo_v6(sock: &socket2::Socket) -> Result<(), std::io::Error> {
325 use std::os::fd::AsRawFd;
326 let enable: libc::c_int = 1;
327 let ret = unsafe {
328 libc::setsockopt(
329 sock.as_raw_fd(),
330 libc::IPPROTO_IPV6,
331 libc::IPV6_RECVPKTINFO,
332 &enable as *const _ as *const libc::c_void,
333 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
334 )
335 };
336 if ret < 0 {
337 return Err(std::io::Error::last_os_error());
338 }
339 Ok(())
340 }
341
342 pub(super) fn lookup_mesh_ifindex(name: &str) -> Option<u32> {
349 #[cfg(unix)]
350 {
351 let c_name = std::ffi::CString::new(name).ok()?;
352 let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
353 if idx == 0 { None } else { Some(idx) }
354 }
355 #[cfg(not(unix))]
356 {
357 let _ = name;
358 None
359 }
360 }
361
362 pub async fn stop(&mut self) -> Result<(), NodeError> {
367 if !self.state.can_stop() {
368 return Err(NodeError::NotStarted);
369 }
370 self.state = NodeState::Stopping;
371 info!(state = %self.state, "Node stopping");
372
373 if let Some(handle) = self.dns_task.take() {
375 handle.abort();
376 debug!("DNS responder stopped");
377 }
378
379 self.send_disconnect_to_all_peers(DisconnectReason::Shutdown)
381 .await;
382
383 if let Some(bootstrap) = self.nostr_discovery.take()
385 && let Err(e) = bootstrap.shutdown().await
386 {
387 warn!(error = %e, "Failed to shutdown Nostr overlay discovery");
388 }
389
390 if let Some(lan) = self.lan_discovery.take() {
394 lan.shutdown().await;
395 }
396
397 if let Some(registry) = self.local_instance_registry.take()
398 && let Err(err) = registry.remove()
399 {
400 debug!(error = %err, "failed to remove same-host FIPS instance record");
401 }
402
403 let transport_ids: Vec<_> = self.transports.keys().cloned().collect();
405 for transport_id in transport_ids {
406 if let Some(mut handle) = self.transports.remove(&transport_id) {
407 self.udp_transport_resolution_cache.clear();
408 let transport_type = handle.transport_type().name;
409 match handle.stop().await {
410 Ok(()) => {
411 info!(transport_id = %transport_id, transport_type, "Transport stopped");
412 }
413 Err(e) => {
414 warn!(
415 transport_id = %transport_id,
416 transport_type,
417 error = %e,
418 "Transport stop failed"
419 );
420 }
421 }
422 }
423 }
424
425 self.packet_tx.take();
427 self.packet_rx.take();
428
429 if let Some(name) = self.tun_name.take() {
431 info!(name = %name, "Shutting down TUN interface");
432
433 self.tun_tx.take();
435
436 if let Err(e) = shutdown_tun_interface(&name).await {
438 warn!(name = %name, error = %e, "Failed to shutdown TUN interface");
439 }
440
441 #[cfg(target_os = "macos")]
444 if let Some(fd) = self.tun_shutdown_fd.take() {
445 unsafe {
446 libc::write(fd, b"x".as_ptr() as *const libc::c_void, 1);
447 libc::close(fd);
448 }
449 }
450
451 if let Some(handle) = self.tun_reader_handle.take() {
453 let _ = handle.join();
454 }
455 if let Some(handle) = self.tun_writer_handle.take() {
456 let _ = handle.join();
457 }
458
459 self.tun_state = TunState::Disabled;
460 }
461
462 self.state = NodeState::Stopped;
463 info!(state = %self.state, "Node stopped");
464 Ok(())
465 }
466
467 pub(super) async fn send_disconnect_to_all_peers(&mut self, reason: DisconnectReason) {
472 let peer_addrs: Vec<NodeAddr> = self
474 .peers
475 .iter()
476 .filter(|(_, peer)| peer.can_send() && peer.has_session())
477 .map(|(addr, _)| *addr)
478 .collect();
479
480 if peer_addrs.is_empty() {
481 debug!(
482 total_peers = self.peers.len(),
483 "No sendable peers for disconnect notification"
484 );
485 return;
486 }
487
488 let mut sent = 0usize;
489 for node_addr in &peer_addrs {
490 if self.send_disconnect_to_peer(node_addr, reason).await {
491 sent += 1;
492 }
493 }
494
495 info!(sent, total = peer_addrs.len(), reason = %reason, "Sent disconnect notifications");
496 }
497
498 pub(super) async fn send_disconnect_to_peer(
500 &mut self,
501 node_addr: &NodeAddr,
502 reason: DisconnectReason,
503 ) -> bool {
504 let plaintext = Disconnect::new(reason).encode();
505 match self
506 .send_dataplane_fmp_link_plaintext(node_addr, &plaintext, false)
507 .await
508 {
509 Ok(()) => true,
510 Err(e) => {
511 debug!(
512 peer = %self.peer_display_name(node_addr),
513 error = %e,
514 "Failed to send disconnect (transport may be down)"
515 );
516 false
517 }
518 }
519 }
520}