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