Initial code commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
25
Cargo.lock
generated
Normal file
25
Cargo.lock
generated
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "canihazudp"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"getopts",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getopts"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||||
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "canihazudp"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
getopts = "0.2.21"
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 IIM
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
124
src/main.rs
Normal file
124
src/main.rs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
extern crate getopts;
|
||||||
|
|
||||||
|
use std::{net::{UdpSocket, SocketAddr, IpAddr}, collections::HashMap, io::{self, Write}};
|
||||||
|
use getopts::Options;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
const MESSAGE_TOKENS: u32 = 10_000;
|
||||||
|
const REPORT_AFTER: usize = 60 * 10;
|
||||||
|
|
||||||
|
fn print_usage(program: &str, opts: Options)
|
||||||
|
{
|
||||||
|
let brief = format!("Usage: {} [options] -s <source_address> -p <listen_port> -t <target_address>:<port>", program);
|
||||||
|
print!("{}", opts.usage(&brief));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_forwarding(listen_addr: IpAddr, port: u16, source_address: SocketAddr, target_addr: SocketAddr, buffer_size: usize, subscribe_keyword: Option<&str>, source_keyword: Option<&str>) {
|
||||||
|
let socket = match UdpSocket::bind(SocketAddr::new(listen_addr, port)) {
|
||||||
|
Ok(sock) => sock,
|
||||||
|
Err(e) => panic!("{}", e)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buffer = vec![0; buffer_size];
|
||||||
|
let mut sub_map: HashMap<SocketAddr, u32> = HashMap::new();
|
||||||
|
let mut report_counter = 0;
|
||||||
|
let mut report_size = 0;
|
||||||
|
let mut keyword_resend = 0;
|
||||||
|
|
||||||
|
println!("Listening on port {}, forwarding to {}...", port, target_addr);
|
||||||
|
if let Some(kw) = source_keyword {
|
||||||
|
println!("Source keyword: {}", kw);
|
||||||
|
}
|
||||||
|
if let Some(kw) = subscribe_keyword {
|
||||||
|
println!("Multicast subscribe keyword: {}", kw);
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
buffer.fill(0);
|
||||||
|
|
||||||
|
if let Some(kw) = source_keyword {
|
||||||
|
if keyword_resend <= 0 {
|
||||||
|
keyword_resend = MESSAGE_TOKENS / 2;
|
||||||
|
socket.send_to(kw.as_bytes(), source_address).unwrap();
|
||||||
|
} else {
|
||||||
|
keyword_resend -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (recv_size, src_addr) = socket.recv_from(buffer.as_mut_slice()).unwrap();
|
||||||
|
report_size += recv_size;
|
||||||
|
|
||||||
|
if src_addr.ip() == source_address.ip() {
|
||||||
|
socket.send_to(&buffer[..recv_size], target_addr).unwrap();
|
||||||
|
|
||||||
|
sub_map.retain(|addr, tokens| {
|
||||||
|
if let Err(e) = socket.send_to(&buffer, addr) {
|
||||||
|
eprintln!("Failed to send data to a subscriber! Error: {:?}", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*tokens -= 1;
|
||||||
|
|
||||||
|
return *tokens > 0u32;
|
||||||
|
});
|
||||||
|
|
||||||
|
print!(".");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
report_counter += 1;
|
||||||
|
if report_counter % 60 == 0 {
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
if report_counter >= REPORT_AFTER {
|
||||||
|
println!("Forwarded: {} bytes", report_size);
|
||||||
|
report_size = 0;
|
||||||
|
report_counter = 0;
|
||||||
|
}
|
||||||
|
} else if let Some(kw) = subscribe_keyword {
|
||||||
|
let data = String::from_utf8(buffer[..recv_size].to_vec());
|
||||||
|
match data {
|
||||||
|
Ok(s) if s == kw => {
|
||||||
|
println!("New multicast client subscribed: {}", src_addr);
|
||||||
|
|
||||||
|
sub_map.insert(src_addr, MESSAGE_TOKENS);
|
||||||
|
}
|
||||||
|
Ok(s) => println!("Received unrecognized keyword \"{}\" from unexpected source {}.", s, src_addr),
|
||||||
|
Err(_) => println!("Could not parse keyword from {}", src_addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut options = Options::new();
|
||||||
|
options.optflag("h", "help", "Show this help message.");
|
||||||
|
options.optopt("s", "source", "Source address to listen for packets from. (required, port only used for source keyword)", "X.X.X.X:PORT");
|
||||||
|
options.optopt("p", "port", "Port on which to listen for incoming packets. (required)", "PORT");
|
||||||
|
options.optopt("t", "target", "Address to which received packets will be forwarded. (required)", "X.X.X.X:PORT");
|
||||||
|
options.optopt("l", "listen_address", "Limit the address on which to listen for packets (default is any)", "X.X.X.X");
|
||||||
|
options.optopt("b", "buffer", "Specify the size of the receive buffer in bytes.", "BYTES");
|
||||||
|
options.optopt("k", "keyword", "Keyword for multicast subscription.", "KEYWORD");
|
||||||
|
options.optopt("w", "source_keyword", "Keyword to send to the source address (used to chain this program).", "KEYWORD");
|
||||||
|
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let program = args[0].clone();
|
||||||
|
|
||||||
|
let matches = options.parse(args).expect("Error parsing arguments.");
|
||||||
|
|
||||||
|
if matches.opt_present("h") {
|
||||||
|
print_usage(&program, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ! matches.opt_present("s") || ! matches.opt_present("p") || ! matches.opt_present("t") {
|
||||||
|
println!("ERROR: Missing required options. See help.");
|
||||||
|
print_usage(&program, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_addr = matches.opt_str("s").unwrap().parse().expect("Error parsing listen address.");
|
||||||
|
let target_addr = matches.opt_str("t").unwrap().parse().expect("Error parsing send address.");
|
||||||
|
let port = matches.opt_get("p").expect("Error parsing port.").expect("Error parsing port.");
|
||||||
|
let buffer_size = matches.opt_get_default("b", 1024).expect("Error parsing buffer size.");
|
||||||
|
let keyword: Option<String> = matches.opt_get("k").expect("Error parsing multicast keyword.");
|
||||||
|
let source_keyword: Option<String> = matches.opt_get("w").expect("Error parsing source keyword.");
|
||||||
|
let listen_addr = matches.opt_str("l").unwrap_or(String::from("0.0.0.0")).parse().expect("Error parsing listen address.");
|
||||||
|
|
||||||
|
start_forwarding(listen_addr, port, source_addr, target_addr, buffer_size, keyword.as_deref(), source_keyword.as_deref());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user