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