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}