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 STOP_TX.lock().unwrap().replace(stop_tx);
144
145 log::info!("rnsd started");
146
147 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"#;