fips_core/node/lifecycle/
runtime.rs1use super::*;
2
3impl Node {
4 pub async fn start(&mut self) -> Result<(), NodeError> {
11 node_start_debug_log("Node::start begin");
12 if !self.state.can_start() {
13 return Err(NodeError::AlreadyStarted);
14 }
15 self.state = NodeState::Starting;
16 node_start_debug_log("Node::start state set to starting");
17
18 let packet_buffer_size = self.config.node.buffers.packet_channel;
20 let (packet_tx, packet_rx) = packet_channel(packet_buffer_size);
21 self.packet_tx = Some(packet_tx.clone());
22 self.packet_rx = Some(packet_rx);
23 node_start_debug_log("Node::start packet channel created");
24
25 node_start_debug_log("Node::start create transports begin");
27 let transport_handles = self.create_transports(&packet_tx).await;
28 node_start_debug_log(format!(
29 "Node::start create transports complete count={}",
30 transport_handles.len()
31 ));
32
33 for mut handle in transport_handles {
34 let transport_id = handle.transport_id();
35 let transport_type = handle.transport_type().name;
36 let name = handle.name().map(|s| s.to_string());
37
38 node_start_debug_log(format!(
39 "Node::start transport start begin id={} type={} name={:?}",
40 transport_id, transport_type, name
41 ));
42 match handle.start().await {
43 Ok(()) => {
44 node_start_debug_log(format!(
45 "Node::start transport start ok id={} type={}",
46 transport_id, transport_type
47 ));
48 self.udp_transport_resolution_cache.clear();
49 self.transports.insert(transport_id, handle);
50 }
51 Err(e) => {
52 node_start_debug_log(format!(
53 "Node::start transport start error id={} type={} error={}",
54 transport_id, transport_type, e
55 ));
56 if let Some(ref n) = name {
57 warn!(transport_type, name = %n, error = %e, "Transport failed to start");
58 } else {
59 warn!(transport_type, error = %e, "Transport failed to start");
60 }
61 }
62 }
63 }
64
65 if !self.transports.is_empty() {
66 info!(count = self.transports.len(), "Transports initialized");
67 }
68
69 #[cfg(unix)]
85 {
86 if self.config.node.worker_pools_enabled {
87 node_start_debug_log("Node::start worker pools begin");
88 let cpu_default = std::thread::available_parallelism()
89 .map(|n| n.get())
90 .unwrap_or(1)
91 .max(1);
92 let encrypt_worker_count: usize = std::env::var("FIPS_ENCRYPT_WORKERS")
93 .ok()
94 .and_then(|s| s.parse().ok())
95 .unwrap_or(cpu_default)
96 .max(1);
97 self.encrypt_workers = Some(crate::node::encrypt_worker::EncryptWorkerPool::spawn(
98 encrypt_worker_count,
99 ));
100 info!(
101 workers = encrypt_worker_count,
102 "Spawned FMP-encrypt worker pool"
103 );
104
105 let decrypt_worker_count: usize = std::env::var("FIPS_DECRYPT_WORKERS")
114 .ok()
115 .and_then(|s| s.parse().ok())
116 .unwrap_or(cpu_default);
117 if decrypt_worker_count == 0 {
118 info!("FIPS_DECRYPT_WORKERS=0 → in-line decrypt in rx_loop (no worker pool)");
119 } else {
120 let direct_delivery_sink = self.decrypt_direct_session_delivery_sink();
121 self.decrypt_workers = Some(
122 crate::node::decrypt_worker::DecryptWorkerPool::spawn_with_direct_delivery_sink(
123 decrypt_worker_count,
124 direct_delivery_sink,
125 ),
126 );
127 info!(
128 workers = decrypt_worker_count,
129 "Spawned FMP+FSP-decrypt worker pool"
130 );
131 }
132 node_start_debug_log("Node::start worker pools complete");
133 } else {
134 node_start_debug_log("Node::start worker pools disabled");
135 info!("FIPS worker pools disabled; using in-line crypto/send path");
136 }
137 }
138
139 if self.config.node.discovery.nostr.enabled {
140 node_start_debug_log("Node::start nostr discovery start begin");
141 match NostrDiscovery::start(&self.identity, self.config.node.discovery.nostr.clone())
142 .await
143 {
144 Ok(runtime) => {
145 node_start_debug_log("Node::start nostr discovery runtime created");
146 if let Err(err) = self.refresh_overlay_advert(&runtime).await {
147 warn!(error = %err, "Failed to publish initial Nostr overlay advert");
148 }
149 node_start_debug_log("Node::start nostr overlay advert refreshed");
150 self.nostr_discovery = Some(runtime);
151 self.nostr_discovery_started_at_ms = Some(Self::now_ms());
152 info!("Nostr overlay discovery enabled");
153 }
154 Err(err) => {
155 node_start_debug_log(format!(
156 "Node::start nostr discovery start error error={}",
157 err
158 ));
159 warn!(error = %err, "Failed to start Nostr overlay discovery");
160 }
161 }
162 }
163
164 if self.config.node.discovery.lan.enabled {
168 node_start_debug_log("Node::start lan discovery start begin");
169 let advertised_udp_port = self
170 .transports
171 .values()
172 .filter(|h| h.is_operational())
173 .filter(|h| h.transport_type().name == "udp")
174 .find_map(|h| h.local_addr().map(|addr| addr.port()))
175 .unwrap_or(0);
176 let scope = self.lan_discovery_scope();
177 match crate::discovery::lan::LanDiscovery::start(
178 &self.identity,
179 scope,
180 advertised_udp_port,
181 self.config.node.discovery.lan.clone(),
182 )
183 .await
184 {
185 Ok(runtime) => {
186 node_start_debug_log("Node::start lan discovery start ok");
187 self.lan_discovery = Some(runtime);
188 info!("LAN mDNS discovery enabled");
189 }
190 Err(err) => {
191 node_start_debug_log(format!(
192 "Node::start lan discovery start error error={}",
193 err
194 ));
195 debug!(error = %err, "LAN mDNS discovery not started");
196 }
197 }
198 }
199
200 self.start_local_instance_discovery();
201 self.poll_local_instance_discovery().await;
202
203 node_start_debug_log("Node::start initiate peer connections begin");
206 self.initiate_peer_connections().await;
207 node_start_debug_log("Node::start initiate peer connections complete");
208
209 if self.config.tun.enabled {
211 node_start_debug_log("Node::start tun init begin");
212 let address = *self.identity.address();
213 match TunDevice::create(&self.config.tun, address).await {
214 Ok(device) => {
215 let mtu = device.mtu();
216 let name = device.name().to_string();
217 let our_addr = *device.address();
218
219 info!("TUN device active:");
220 info!(" name: {}", name);
221 info!(" address: {}", device.address());
222 info!(" mtu: {}", mtu);
223
224 let effective_mtu = self.effective_ipv6_mtu();
226 let max_mss = effective_mtu.saturating_sub(40).saturating_sub(20); info!("effective MTU: {} bytes", effective_mtu);
229 debug!(" max TCP MSS: {} bytes", max_mss);
230
231 #[cfg(target_os = "macos")]
235 let (shutdown_read_fd, shutdown_write_fd) = {
236 let mut fds = [0i32; 2];
237 if unsafe { libc::pipe(fds.as_mut_ptr()) } < 0 {
238 return Err(NodeError::Tun(crate::upper::tun::TunError::Configure(
239 "failed to create shutdown pipe".into(),
240 )));
241 }
242 (fds[0], fds[1])
243 };
244
245 let (writer, tun_tx) =
249 device.create_writer(max_mss, self.path_mtu_lookup.clone())?;
250
251 let writer_handle = thread::spawn(move || {
253 writer.run();
254 });
255
256 let reader_tun_tx = tun_tx.clone();
258
259 let tun_channel_size = self.config.node.buffers.tun_channel;
261 let (outbound_tx, outbound_rx) = tokio::sync::mpsc::channel(tun_channel_size);
262
263 let transport_mtu = self.transport_mtu();
265 let path_mtu_lookup = self.path_mtu_lookup.clone();
266 #[cfg(target_os = "macos")]
267 let reader_handle = thread::spawn(move || {
268 run_tun_reader(
269 device,
270 mtu,
271 our_addr,
272 reader_tun_tx,
273 outbound_tx,
274 transport_mtu,
275 path_mtu_lookup,
276 shutdown_read_fd,
277 );
278 });
279 #[cfg(not(target_os = "macos"))]
280 let reader_handle = thread::spawn(move || {
281 run_tun_reader(
282 device,
283 mtu,
284 our_addr,
285 reader_tun_tx,
286 outbound_tx,
287 transport_mtu,
288 path_mtu_lookup,
289 );
290 });
291
292 self.tun_state = TunState::Active;
293 self.tun_name = Some(name);
294 self.tun_tx = Some(tun_tx);
295 self.tun_outbound_rx = Some(outbound_rx);
296 self.tun_reader_handle = Some(reader_handle);
297 self.tun_writer_handle = Some(writer_handle);
298 #[cfg(target_os = "macos")]
299 {
300 self.tun_shutdown_fd = Some(shutdown_write_fd);
301 }
302 }
303 Err(e) => {
304 self.tun_state = TunState::Failed;
305 warn!(error = %e, "Failed to initialize TUN, continuing without it");
306 }
307 }
308 node_start_debug_log("Node::start tun init complete");
309 }
310
311 if self.config.dns.enabled {
328 node_start_debug_log("Node::start dns init begin");
329 let addr_str = self.config.dns.bind_addr();
330 match addr_str.parse::<std::net::IpAddr>() {
331 Ok(ip) => {
332 let bind = std::net::SocketAddr::new(ip, self.config.dns.port());
333 match Self::bind_dns_socket(bind) {
334 Ok(socket) => {
335 let dns_channel_size = self.config.node.buffers.dns_channel;
336 let (identity_tx, identity_rx) =
337 tokio::sync::mpsc::channel(dns_channel_size);
338 let dns_ttl = self.config.dns.ttl();
339 let base_hosts = crate::upper::hosts::HostMap::from_peer_configs(
340 self.config.peers(),
341 );
342 let reloader = if self.config.node.system_files_enabled {
343 let hosts_path = std::path::PathBuf::from(
344 crate::upper::hosts::DEFAULT_HOSTS_PATH,
345 );
346 crate::upper::hosts::HostMapReloader::new(base_hosts, hosts_path)
347 } else {
348 crate::upper::hosts::HostMapReloader::memory_only(base_hosts)
349 };
350 let mesh_ifindex = Self::lookup_mesh_ifindex(self.config.tun.name());
358 info!(
359 bind = %bind,
360 hosts = reloader.hosts().len(),
361 mesh_ifindex = ?mesh_ifindex,
362 "DNS responder started for .fips domain (auto-reload enabled)"
363 );
364 let handle = tokio::spawn(crate::upper::dns::run_dns_responder(
365 socket,
366 identity_tx,
367 dns_ttl,
368 reloader,
369 mesh_ifindex,
370 ));
371 self.dns_identity_rx = Some(identity_rx);
372 self.dns_task = Some(handle);
373 }
374 Err(e) => {
375 warn!(bind = %bind, error = %e, "Failed to start DNS responder");
376 }
377 }
378 }
379 Err(e) => {
380 warn!(addr = %addr_str, error = %e, "Invalid dns.bind_addr; DNS responder not started");
381 }
382 }
383 node_start_debug_log("Node::start dns init complete");
384 }
385
386 self.state = NodeState::Running;
387 node_start_debug_log("Node::start running");
388 info!("Node started:");
389 info!(" state: {}", self.state);
390 info!(" transports: {}", self.transports.len());
391 info!(" connections: {}", self.peers.connection_len());
392 Ok(())
393 }
394
395 pub(super) fn bind_dns_socket(
408 addr: std::net::SocketAddr,
409 ) -> Result<tokio::net::UdpSocket, std::io::Error> {
410 use socket2::{Domain, Protocol, Socket, Type};
411 let domain = if addr.is_ipv4() {
412 Domain::IPV4
413 } else {
414 Domain::IPV6
415 };
416 let sock = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP))?;
417 if addr.is_ipv6() {
418 sock.set_only_v6(false)?;
419 #[cfg(unix)]
420 Self::set_recv_pktinfo_v6(&sock)?;
421 }
422 sock.set_nonblocking(true)?;
423 sock.bind(&addr.into())?;
424 tokio::net::UdpSocket::from_std(sock.into())
425 }
426
427 #[cfg(unix)]
433 pub(super) fn set_recv_pktinfo_v6(sock: &socket2::Socket) -> Result<(), std::io::Error> {
434 use std::os::fd::AsRawFd;
435 let enable: libc::c_int = 1;
436 let ret = unsafe {
437 libc::setsockopt(
438 sock.as_raw_fd(),
439 libc::IPPROTO_IPV6,
440 libc::IPV6_RECVPKTINFO,
441 &enable as *const _ as *const libc::c_void,
442 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
443 )
444 };
445 if ret < 0 {
446 return Err(std::io::Error::last_os_error());
447 }
448 Ok(())
449 }
450
451 pub(super) fn lookup_mesh_ifindex(name: &str) -> Option<u32> {
458 #[cfg(unix)]
459 {
460 let c_name = std::ffi::CString::new(name).ok()?;
461 let idx = unsafe { libc::if_nametoindex(c_name.as_ptr()) };
462 if idx == 0 { None } else { Some(idx) }
463 }
464 #[cfg(not(unix))]
465 {
466 let _ = name;
467 None
468 }
469 }
470
471 pub async fn stop(&mut self) -> Result<(), NodeError> {
476 if !self.state.can_stop() {
477 return Err(NodeError::NotStarted);
478 }
479 self.state = NodeState::Stopping;
480 info!(state = %self.state, "Node stopping");
481
482 if let Some(handle) = self.dns_task.take() {
484 handle.abort();
485 debug!("DNS responder stopped");
486 }
487
488 self.send_disconnect_to_all_peers(DisconnectReason::Shutdown)
490 .await;
491
492 if let Some(bootstrap) = self.nostr_discovery.take()
494 && let Err(e) = bootstrap.shutdown().await
495 {
496 warn!(error = %e, "Failed to shutdown Nostr overlay discovery");
497 }
498
499 if let Some(lan) = self.lan_discovery.take() {
503 lan.shutdown().await;
504 }
505
506 if let Some(registry) = self.local_instance_registry.take()
507 && let Err(err) = registry.remove()
508 {
509 debug!(error = %err, "failed to remove same-host FIPS instance record");
510 }
511
512 let transport_ids: Vec<_> = self.transports.keys().cloned().collect();
514 for transport_id in transport_ids {
515 if let Some(mut handle) = self.transports.remove(&transport_id) {
516 self.udp_transport_resolution_cache.clear();
517 let transport_type = handle.transport_type().name;
518 match handle.stop().await {
519 Ok(()) => {
520 info!(transport_id = %transport_id, transport_type, "Transport stopped");
521 }
522 Err(e) => {
523 warn!(
524 transport_id = %transport_id,
525 transport_type,
526 error = %e,
527 "Transport stop failed"
528 );
529 }
530 }
531 }
532 }
533
534 self.packet_tx.take();
536 self.packet_rx.take();
537
538 if let Some(name) = self.tun_name.take() {
540 info!(name = %name, "Shutting down TUN interface");
541
542 self.tun_tx.take();
544
545 if let Err(e) = shutdown_tun_interface(&name).await {
547 warn!(name = %name, error = %e, "Failed to shutdown TUN interface");
548 }
549
550 #[cfg(target_os = "macos")]
553 if let Some(fd) = self.tun_shutdown_fd.take() {
554 unsafe {
555 libc::write(fd, b"x".as_ptr() as *const libc::c_void, 1);
556 libc::close(fd);
557 }
558 }
559
560 if let Some(handle) = self.tun_reader_handle.take() {
562 let _ = handle.join();
563 }
564 if let Some(handle) = self.tun_writer_handle.take() {
565 let _ = handle.join();
566 }
567
568 self.tun_state = TunState::Disabled;
569 }
570
571 self.state = NodeState::Stopped;
572 info!(state = %self.state, "Node stopped");
573 Ok(())
574 }
575
576 pub(super) async fn send_disconnect_to_all_peers(&mut self, reason: DisconnectReason) {
581 let peer_addrs: Vec<NodeAddr> = self
583 .peers
584 .iter()
585 .filter(|(_, peer)| peer.can_send() && peer.has_session())
586 .map(|(addr, _)| *addr)
587 .collect();
588
589 if peer_addrs.is_empty() {
590 debug!(
591 total_peers = self.peers.len(),
592 "No sendable peers for disconnect notification"
593 );
594 return;
595 }
596
597 let mut sent = 0usize;
598 for node_addr in &peer_addrs {
599 if self.send_disconnect_to_peer(node_addr, reason).await {
600 sent += 1;
601 }
602 }
603
604 info!(sent, total = peer_addrs.len(), reason = %reason, "Sent disconnect notifications");
605 }
606
607 pub(super) async fn send_disconnect_to_peer(
609 &mut self,
610 node_addr: &NodeAddr,
611 reason: DisconnectReason,
612 ) -> bool {
613 let plaintext = Disconnect::new(reason).encode();
614 match self
615 .send_encrypted_link_message(node_addr, &plaintext)
616 .await
617 {
618 Ok(()) => true,
619 Err(e) => {
620 debug!(
621 peer = %self.peer_display_name(node_addr),
622 error = %e,
623 "Failed to send disconnect (transport may be down)"
624 );
625 false
626 }
627 }
628 }
629}