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    STOP_TX.lock().unwrap().replace(stop_tx);
144
145    log::info!("rnsd started");
146
147    // Block until signal
148    loop {
149        match stop_rx.recv_timeout(std::time::Duration::from_secs(1)) {
150            Ok(()) => break,
151            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => continue,
152            Err(_) => break,
153        }
154    }
155
156    log::info!("Shutting down...");
157    node.shutdown();
158    log::info!("rnsd stopped");
159}
160
161static STOP_TX: std::sync::Mutex<Option<mpsc::Sender<()>>> = std::sync::Mutex::new(None);
162
163extern "C" fn signal_handler(_sig: libc::c_int) {
164    if let Ok(guard) = STOP_TX.lock() {
165        if let Some(ref tx) = *guard {
166            let _ = tx.send(());
167        }
168    }
169}
170
171fn print_usage() {
172    println!("Usage: rns-ctl daemon [OPTIONS]");
173    println!();
174    println!("Options:");
175    println!("  --config PATH, -c PATH  Path to config directory");
176    println!("  -s                      Service mode (log to file)");
177    println!("  --exampleconfig         Print example config and exit");
178    println!("  -v                      Increase verbosity (can repeat)");
179    println!("  -q                      Decrease verbosity (can repeat)");
180    println!("  --version               Print version and exit");
181    println!("  --help, -h              Print this help");
182}
183
184const EXAMPLE_CONFIG: &str = r#"# This is an example Reticulum config file.
185# It can be used as a starting point for your own configuration.
186
187[reticulum]
188  enable_transport = false
189  share_instance = true
190  shared_instance_port = 37428
191  instance_control_port = 37429
192  panic_on_interface_error = false
193
194[logging]
195  loglevel = 4
196
197# ─── Interface examples ──────────────────────────────────────────────
198
199# TCP client: connect to a remote transport node
200#
201# [[TCP Client]]
202#   type = TCPClientInterface
203#   target_host = amsterdam.connect.reticulum.network
204#   target_port = 4965
205
206# TCP server: accept incoming connections
207#
208# [[TCP Server]]
209#   type = TCPServerInterface
210#   listen_ip = 0.0.0.0
211#   listen_port = 4965
212
213# UDP interface: broadcast on LAN
214#
215# [[UDP Interface]]
216#   type = UDPInterface
217#   listen_ip = 0.0.0.0
218#   listen_port = 4242
219#   forward_ip = 255.255.255.255
220#   forward_port = 4242
221
222# Serial interface: point-to-point serial port
223#
224# [[Serial Interface]]
225#   type = SerialInterface
226#   port = /dev/ttyUSB0
227#   speed = 115200
228#   databits = 8
229#   parity = none
230#   stopbits = 1
231
232# KISS interface: for TNC modems
233#
234# [[KISS Interface]]
235#   type = KISSInterface
236#   port = /dev/ttyUSB1
237#   speed = 115200
238#   databits = 8
239#   parity = none
240#   stopbits = 1
241#   preamble = 350
242#   txtail = 20
243#   persistence = 64
244#   slottime = 20
245#   flow_control = false
246
247# RNode LoRa interface
248#
249# [[RNode LoRa Interface]]
250#   type = RNodeInterface
251#   port = /dev/ttyACM0
252#   frequency = 867200000
253#   bandwidth = 125000
254#   txpower = 7
255#   spreadingfactor = 8
256#   codingrate = 5
257
258# Pipe interface: stdin/stdout of a subprocess
259#
260# [[Pipe Interface]]
261#   type = PipeInterface
262#   command = cat
263
264# Backbone interface: TCP mesh
265#
266# [[Backbone]]
267#   type = BackboneInterface
268#   listen_ip = 0.0.0.0
269#   listen_port = 4243
270#   peers = 10.0.0.1:4243, 10.0.0.2:4243
271"#;