1use 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 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 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 let (stop_tx, stop_rx) = mpsc::channel::<()>();
130
131 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 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 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"#;