mproxy_server/lib.rs
1//! Multicast Network Dispatcher and Proxy
2//!
3//! # MPROXY: Server
4//! Listen for incoming UDP messages and log to file.
5//!
6//! ## Quick Start
7//! In `Cargo.toml`
8//! ```toml
9//! [dependencies]
10//! mproxy-server = "0.1"
11//! ```
12//!
13//! Example `src/main.rs`
14//! ```rust,no_run
15//! use std::path::PathBuf;
16//! use std::thread::JoinHandle;
17//!
18//! use mproxy_server::listener;
19//!
20//! // bind to IPv6 multicast channel on port 9920
21//! let listen_addr: String = "[ff01::1]:9920".into();
22//!
23//! // output filepath
24//! let logpath = PathBuf::from("server_demo.log");
25//!
26//! // copy input to stdout
27//! let tee = true;
28//!
29//! // bind socket listener thread
30//! let server_thread: JoinHandle<_> = listener(listen_addr, logpath, tee);
31//! server_thread.join().unwrap();
32//! ```
33//!
34//! ## Command Line Interface
35//! Install with cargo
36//! ```bash
37//! cargo install mproxy-server
38//! ```
39//!
40//! ```text
41//! MPROXY: UDP Server
42//!
43//! Listen for incoming UDP messages and log to file or socket.
44//!
45//! USAGE:
46//! mproxy-server [FLAGS] [OPTIONS] ...
47//!
48//! OPTIONS:
49//! --path [FILE_DESCRIPTOR] Filepath, descriptor, or handle.
50//! --listen-addr [SOCKET_ADDR] Upstream UDP listening address. May be repeated
51//!
52//! FLAGS:
53//! -h, --help Prints help information
54//! -t, --tee Copy input to stdout
55//!
56//! EXAMPLE:
57//! mproxy-server --path logfile.log --listen-addr '127.0.0.1:9920' --listen-addr '[::1]:9921'
58//! ```
59//!
60//! ### See Also
61//! - [mproxy-client](https://docs.rs/mproxy-client/)
62//! - [mproxy-server](https://docs.rs/mproxy-server/)
63//! - [mproxy-forward](https://docs.rs/mproxy-forward/)
64//! - [mproxy-reverse](https://docs.rs/mproxy-reverse/)
65//!
66
67use std::fs::OpenOptions;
68use std::io::{stdout, BufWriter, Result as ioResult, Write};
69use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs, UdpSocket};
70use std::path::PathBuf;
71use std::thread::{Builder, JoinHandle};
72
73use net2::UdpBuilder;
74
75const BUFSIZE: usize = 8096;
76
77pub fn upstream_socket_interface(listen_addr: String) -> ioResult<(SocketAddr, UdpSocket)> {
78 let addr = listen_addr
79 .to_socket_addrs()
80 .unwrap()
81 .next()
82 .expect("parsing socket address");
83 let listen_socket;
84 match (addr.ip().is_multicast(), addr.ip()) {
85 (false, std::net::IpAddr::V4(_)) => {
86 listen_socket = UdpSocket::bind(addr).expect("binding server socket");
87 }
88 (false, std::net::IpAddr::V6(_)) => {
89 listen_socket = UdpSocket::bind(addr).expect("binding server socket");
90 }
91 (true, std::net::IpAddr::V4(ip)) => {
92 #[cfg(not(target_os = "windows"))]
93 {
94 //listen_socket = UdpSocket::bind(addr).expect("binding server socket");
95 listen_socket = UdpBuilder::new_v4()
96 .expect("binding ipv4 socket")
97 .reuse_address(true)
98 .unwrap()
99 .bind(addr)
100 .unwrap();
101 listen_socket
102 .join_multicast_v4(&ip, &Ipv4Addr::UNSPECIFIED)
103 .unwrap_or_else(|e| panic!("{}", e));
104 }
105 #[cfg(target_os = "windows")]
106 {
107 listen_socket = UdpSocket::bind(SocketAddr::new(
108 IpAddr::V4(Ipv4Addr::UNSPECIFIED),
109 addr.port(),
110 ))
111 .expect("binding server socket");
112
113 listen_socket
114 .join_multicast_v4(&ip, &Ipv4Addr::UNSPECIFIED)
115 .unwrap_or_else(|e| panic!("{}", e));
116 }
117 }
118 (true, std::net::IpAddr::V6(ip)) => {
119 /*
120 listen_socket = UdpSocket::bind(SocketAddr::new(
121 IpAddr::V6(Ipv6Addr::UNSPECIFIED),
122 addr.port(),
123 ))
124 .expect("binding server socket");
125 */
126 listen_socket = UdpBuilder::new_v6()
127 .expect("binding ipv6 socket")
128 .reuse_address(true)
129 .unwrap()
130 .bind(SocketAddr::new(
131 IpAddr::V6(Ipv6Addr::UNSPECIFIED),
132 addr.port(),
133 ))
134 .unwrap();
135
136 // specify "any available interface" with index 0
137 #[cfg(not(target_os = "macos"))]
138 let itf = 0; // unspecified
139 #[cfg(target_os = "macos")]
140 let itf = 12; // en0
141
142 listen_socket
143 .join_multicast_v6(&ip, itf)
144 .unwrap_or_else(|e| panic!("{}", e));
145
146 #[cfg(target_os = "windows")]
147 listen_socket
148 .connect(&addr)
149 .unwrap_or_else(|e| panic!("{}", e));
150 }
151 };
152 Ok((addr, listen_socket))
153}
154
155/// Server UDP socket listener.
156/// Binds to UDP socket address `addr`, and logs input to `logfile`.
157/// Can optionally copy input to stdout if `tee` is true.
158/// `logfile` may be a filepath, file descriptor/handle, etc.
159pub fn listener(addr: String, logfile: PathBuf, tee: bool) -> JoinHandle<()> {
160 let file = OpenOptions::new()
161 .create(true)
162 .write(true)
163 .append(true)
164 .open(&logfile);
165 let mut writer = BufWriter::new(file.unwrap());
166 let mut output_buffer = BufWriter::new(stdout());
167
168 let (addr, listen_socket) = upstream_socket_interface(addr).unwrap();
169
170 Builder::new()
171 .name(format!("{}:server", addr))
172 .spawn(move || {
173 let mut buf = [0u8; BUFSIZE]; // receive buffer
174 loop {
175 match listen_socket.recv_from(&mut buf[0..]) {
176 Ok((c, _remote_addr)) => {
177 if tee {
178 let _o = output_buffer
179 .write(&buf[0..c])
180 .expect("writing to output buffer");
181 #[cfg(debug_assertions)]
182 assert!(c == _o);
183 }
184 let _ = writer
185 .write(&buf[0..c])
186 .unwrap_or_else(|_| panic!("writing to {:?}", &logfile));
187 }
188 Err(err) => {
189 writer.flush().unwrap();
190 eprintln!("{}:server: got an error: {}", addr, err);
191 #[cfg(debug_assertions)]
192 panic!("{}:server: got an error: {}", addr, err);
193 }
194 }
195
196 writer.flush().unwrap();
197 if tee {
198 output_buffer.flush().unwrap();
199 }
200 }
201 })
202 .unwrap()
203}