Skip to main content

rns_ctl/cmd/
daemon.rs

1//! RNS daemon node.
2//!
3//! Starts an RNS node from config, optionally with RPC server for external tools.
4
5use std::fs;
6use std::path::Path;
7use std::sync::mpsc;
8
9use crate::args::Args;
10use rns_net::storage;
11use rns_net::{Callbacks, InterfaceId, RnsNode};
12
13struct DaemonCallbacks;
14
15impl Callbacks for DaemonCallbacks {
16    fn on_announce(&mut self, announced: rns_net::AnnouncedIdentity) {
17        log::info!(
18            "Announce received for {} (hops: {})",
19            hex(&announced.dest_hash.0),
20            announced.hops,
21        );
22    }
23
24    fn on_path_updated(&mut self, dest_hash: rns_net::DestHash, hops: u8) {
25        log::debug!("Path updated for {} (hops: {})", hex(&dest_hash.0), hops);
26    }
27
28    fn on_local_delivery(
29        &mut self,
30        dest_hash: rns_net::DestHash,
31        _raw: Vec<u8>,
32        _hash: rns_net::PacketHash,
33    ) {
34        log::debug!("Local delivery for {}", hex(&dest_hash.0));
35    }
36
37    fn on_interface_up(&mut self, id: InterfaceId) {
38        log::info!("Interface {} up", id.0);
39    }
40
41    fn on_interface_down(&mut self, id: InterfaceId) {
42        log::info!("Interface {} down", id.0);
43    }
44}
45
46fn hex(bytes: &[u8]) -> String {
47    bytes.iter().map(|b| format!("{:02x}", b)).collect()
48}
49
50pub fn run(args: Args) {
51    if args.has("version") {
52        println!("rns-ctl {}", env!("FULL_VERSION"));
53        return;
54    }
55
56    if args.has("help") {
57        print_usage();
58        return;
59    }
60
61    if args.has("exampleconfig") {
62        print!("{}", EXAMPLE_CONFIG);
63        return;
64    }
65
66    let service_mode = args.has("s");
67    let config_path = args.config_path().map(|s| s.to_string());
68
69    // Set up logging
70    let log_level = match args.verbosity {
71        0 => log::LevelFilter::Info,
72        1 => log::LevelFilter::Debug,
73        _ => log::LevelFilter::Trace,
74    };
75    let log_level = if args.quiet > 0 {
76        match args.quiet {
77            1 => log::LevelFilter::Warn,
78            _ => log::LevelFilter::Error,
79        }
80    } else {
81        log_level
82    };
83
84    if service_mode {
85        // Service mode: log to file in config dir
86        let config_dir =
87            storage::resolve_config_dir(config_path.as_ref().map(|s| Path::new(s.as_str())));
88        let logfile_path = config_dir.join("logfile");
89        match fs::OpenOptions::new()
90            .create(true)
91            .append(true)
92            .open(&logfile_path)
93        {
94            Ok(file) => {
95                env_logger::Builder::new()
96                    .filter_level(log_level)
97                    .format_timestamp_secs()
98                    .target(env_logger::Target::Pipe(Box::new(file)))
99                    .init();
100            }
101            Err(e) => {
102                eprintln!("Could not open logfile {}: {}", logfile_path.display(), e);
103                std::process::exit(1);
104            }
105        }
106    } else {
107        env_logger::Builder::new()
108            .filter_level(log_level)
109            .format_timestamp_secs()
110            .init();
111    }
112
113    log::info!("Starting rnsd {}", env!("FULL_VERSION"));
114
115    let node = RnsNode::from_config(
116        config_path.as_ref().map(|s| Path::new(s.as_str())),
117        Box::new(DaemonCallbacks),
118    );
119
120    let node = match node {
121        Ok(n) => n,
122        Err(e) => {
123            log::error!("Failed to start: {}", e);
124            std::process::exit(1);
125        }
126    };
127
128    // Set up signal handling
129    let (stop_tx, stop_rx) = mpsc::channel::<()>();
130
131    // Handle SIGTERM/SIGINT
132    unsafe {
133        libc::signal(
134            libc::SIGINT,
135            signal_handler as *const () as libc::sighandler_t,
136        );
137        libc::signal(
138            libc::SIGTERM,
139            signal_handler as *const () as libc::sighandler_t,
140        );
141    }
142    // Store the channel sender in a static so the signal handler can use it
143    match STOP_TX.lock() {
144        Ok(mut guard) => {
145            guard.replace(stop_tx);
146        }
147        Err(poisoned) => {
148            log::error!("recovering from poisoned daemon stop-channel lock");
149            poisoned.into_inner().replace(stop_tx);
150        }
151    }
152
153    log::info!("rnsd started");
154
155    // Block until signal
156    loop {
157        match stop_rx.recv_timeout(std::time::Duration::from_secs(1)) {
158            Ok(()) => break,
159            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => continue,
160            Err(_) => break,
161        }
162    }
163
164    log::info!("Shutting down...");
165    node.shutdown();
166    log::info!("rnsd stopped");
167}
168
169static STOP_TX: std::sync::Mutex<Option<mpsc::Sender<()>>> = std::sync::Mutex::new(None);
170
171extern "C" fn signal_handler(_sig: libc::c_int) {
172    if let Ok(guard) = STOP_TX.lock() {
173        if let Some(ref tx) = *guard {
174            let _ = tx.send(());
175        }
176    }
177}
178
179fn print_usage() {
180    println!("Usage: rns-ctl daemon [OPTIONS]");
181    println!();
182    println!("Options:");
183    println!("  --config PATH, -c PATH  Path to config directory");
184    println!("  -s                      Service mode (log to file)");
185    println!("  --exampleconfig         Print example config and exit");
186    println!("  -v                      Increase verbosity (can repeat)");
187    println!("  -q                      Decrease verbosity (can repeat)");
188    println!("  --version               Print version and exit");
189    println!("  --help, -h              Print this help");
190}
191
192const EXAMPLE_CONFIG: &str = r#"# This is an example Reticulum config file.
193# It can be used as a starting point for your own configuration.
194
195[reticulum]
196  enable_transport = false
197  share_instance = true
198  shared_instance_port = 37428
199  instance_control_port = 37429
200  panic_on_interface_error = false
201
202[logging]
203  loglevel = 4
204
205# ─── Interface examples ──────────────────────────────────────────────
206#
207# Ingress-control settings can be added to any interface. They are enabled
208# by default on Auto, Backbone, TCP client/server, UDP and I2P interfaces,
209# and disabled by default on local/serial/KISS/RNode/Pipe-style interfaces.
210#
211#   ingress_control = Yes
212#   ic_max_held_announces = 256
213#   ic_burst_hold = 60
214#   ic_burst_freq_new = 3.5
215#   ic_burst_freq = 12
216#   ic_new_time = 7200
217#   ic_burst_penalty = 300
218#   ic_held_release_interval = 30
219
220# TCP client: connect to a remote transport node
221#
222# [[TCP Client]]
223#   type = TCPClientInterface
224#   target_host = amsterdam.connect.reticulum.network
225#   target_port = 4965
226
227# TCP server: accept incoming connections
228#
229# [[TCP Server]]
230#   type = TCPServerInterface
231#   listen_ip = 0.0.0.0
232#   listen_port = 4965
233
234# UDP interface: broadcast on LAN
235#
236# [[UDP Interface]]
237#   type = UDPInterface
238#   listen_ip = 0.0.0.0
239#   listen_port = 4242
240#   forward_ip = 255.255.255.255
241#   forward_port = 4242
242
243# Serial interface: point-to-point serial port
244#
245# [[Serial Interface]]
246#   type = SerialInterface
247#   port = /dev/ttyUSB0
248#   speed = 115200
249#   databits = 8
250#   parity = none
251#   stopbits = 1
252
253# KISS interface: for TNC modems
254#
255# [[KISS Interface]]
256#   type = KISSInterface
257#   port = /dev/ttyUSB1
258#   speed = 115200
259#   databits = 8
260#   parity = none
261#   stopbits = 1
262#   preamble = 350
263#   txtail = 20
264#   persistence = 64
265#   slottime = 20
266#   flow_control = false
267
268# RNode LoRa interface
269#
270# [[RNode LoRa Interface]]
271#   type = RNodeInterface
272#   port = /dev/ttyACM0
273#   frequency = 867200000
274#   bandwidth = 125000
275#   txpower = 7
276#   spreadingfactor = 8
277#   codingrate = 5
278
279# Pipe interface: stdin/stdout of a subprocess
280#
281# [[Pipe Interface]]
282#   type = PipeInterface
283#   command = cat
284
285# Backbone interface: TCP mesh
286#
287# [[Backbone]]
288#   type = BackboneInterface
289#   listen_ip = 0.0.0.0
290#   listen_port = 4243
291#   peers = 10.0.0.1:4243, 10.0.0.2:4243
292"#;