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