Compare commits

..

No commits in common. "0bb702f312d65c83761a04b827f6fcd5e4ff7704" and "2b4695934e8bcdedd388b43ff766b0ffb9cf67d6" have entirely different histories.

12 changed files with 476 additions and 560 deletions

60
Cargo.lock generated
View File

@ -43,15 +43,6 @@ version = "0.2.109"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "lookaround" name = "lookaround"
version = "0.1.4" version = "0.1.4"
@ -59,7 +50,6 @@ dependencies = [
"mac_address", "mac_address",
"rand", "rand",
"thiserror", "thiserror",
"tokio",
] ]
[[package]] [[package]]
@ -81,28 +71,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.22.2" version = "0.22.2"
@ -116,21 +84,6 @@ dependencies = [
"memoffset", "memoffset",
] ]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "pin-project-lite"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.15" version = "0.2.15"
@ -226,19 +179,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tokio"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
dependencies = [
"autocfg",
"libc",
"mio",
"pin-project-lite",
"winapi",
]
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"

View File

@ -15,9 +15,9 @@ version = "0.1.4"
mac_address = "1.1.2" mac_address = "1.1.2"
rand = "0.8.4" rand = "0.8.4"
thiserror = "1.0.30" thiserror = "1.0.30"
tokio = { version = "1.14.0", features = ["fs", "net", "rt", "time"] }
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
lto = true lto = true
opt-level = "z" opt-level = "z"
panic = "abort"

View File

@ -3,4 +3,3 @@ Cool ideas that can be done but probably won't be.
- Command for shell substituting IPs into commands - Command for shell substituting IPs into commands
- Arbitrary TCP forwarding of (stdin? stdout? TCP?) - Arbitrary TCP forwarding of (stdin? stdout? TCP?)
- Netcat replacement "Just send a file" _including filename_ - Netcat replacement "Just send a file" _including filename_
- Public-key crypto for trusting peers on first use (Hard cause it requires mutable disk state)

View File

@ -21,3 +21,12 @@ and get their IPs, so I might have to find another dependency
for that. I think on Linux I can get it from `/sys/class/net` but for that. I think on Linux I can get it from `/sys/class/net` but
I can't remember the trick for that. I think last time I did this I can't remember the trick for that. I think last time I did this
(for that work project) I just punted to Qt. (for that work project) I just punted to Qt.
# 01FPDWYH3ZY52DFF6PNRSA63GB
Sending an invalid packet directly to the server's UDP port crashes the server.
# 01FPDY51GHC7NRFV7Z14Q1KV7N
Remove all unwraps. Since I used abort-on-panic to make the binary smaller,
it's not giving any error message for panics

View File

@ -1,61 +0,0 @@
use crate::prelude::*;
#[derive (Debug, thiserror::Error)]
pub enum AppError {
#[error (transparent)]
AddrParse (#[from] std::net::AddrParseError),
#[error (transparent)]
CliArgs (#[from] CliArgError),
#[error (transparent)]
Io (#[from] std::io::Error),
#[error (transparent)]
Ip (#[from] crate::ip::IpError),
#[error (transparent)]
Join (#[from] tokio::task::JoinError),
#[error (transparent)]
MacAddr (#[from] mac_address::MacAddressError),
#[error (transparent)]
Message (#[from] crate::message::MessageError),
#[error (transparent)]
Tlv (#[from] crate::tlv::TlvError),
}
#[derive (Debug, thiserror::Error)]
pub enum CliArgError {
#[error ("Missing value for argument `{0}`")]
MissingArgumentValue (String),
#[error ("First argument should be a subcommand")]
MissingSubcommand,
#[error ("Unknown subcommand `{0}`")]
UnknownSubcommand (String),
#[error ("Unrecognized argument `{0}`")]
UnrecognizedArgument (String),
}
pub async fn recv_msg_from (socket: &UdpSocket) -> Result <(Vec <Message>, SocketAddr), AppError>
{
let mut buf = vec! [0u8; PACKET_SIZE];
let (bytes_recved, remote_addr) = socket.recv_from (&mut buf).await?;
buf.truncate (bytes_recved);
let msgs = Message::from_slice2 (&buf)?;
Ok ((msgs, remote_addr))
}
#[derive (Clone)]
pub struct Params {
// Servers bind on this port, clients must send to the port
pub server_port: u16,
// Clients and servers will all join the same multicast addr
pub multicast_addr: Ipv4Addr,
}
impl Default for Params {
fn default () -> Self {
Self {
server_port: 9040,
multicast_addr: Ipv4Addr::new (225, 100, 99, 98),
}
}
}

View File

@ -1,99 +0,0 @@
use crate::prelude::*;
struct ServerResponse {
mac: Option <[u8; 6]>,
nickname: Option <String>,
}
pub async fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), AppError> {
use rand::RngCore;
let common_params = app_common::Params::default ();
let mut bind_addr = "0.0.0.0".to_string ();
while let Some (arg) = args.next () {
match arg.as_str () {
"--bind-addr" => {
bind_addr = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => x
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
}
}
let socket = UdpSocket::bind (&format! ("{}:0", bind_addr)).await?;
socket.join_multicast_v4 (common_params.multicast_addr, Ipv4Addr::from_str (&bind_addr)?)?;
let mut idem_id = [0u8; 8];
rand::thread_rng ().fill_bytes (&mut idem_id);
let msg = Message::Request1 {
idem_id,
mac: None,
}.to_vec ()?;
for _ in 0..10 {
socket.send_to (&msg, (common_params.multicast_addr, common_params.server_port)).await?;
sleep (Duration::from_millis (100)).await;
}
let mut peers = HashMap::with_capacity (10);
timeout (Duration::from_secs (2), listen_for_responses (&socket, &mut peers)).await.ok ();
let mut peers: Vec <_> = peers.into_iter ().collect ();
peers.sort_by_key (|(_, v)| v.mac);
println! ("Found {} peers:", peers.len ());
for (ip, resp) in peers.into_iter () {
let mac = match resp.mac {
None => {
println! ("<Unknown> = {}", ip);
continue;
},
Some (x) => x,
};
let nickname = match resp.nickname {
None => {
println! ("{} = {}", MacAddress::new (mac), ip.ip ());
continue;
},
Some (x) => x,
};
println! ("{} = {} `{}`", MacAddress::new (mac), ip.ip (), nickname);
}
Ok (())
}
async fn listen_for_responses (
socket: &UdpSocket,
peers: &mut HashMap <SocketAddr, ServerResponse>
) {
loop {
let (msgs, remote_addr) = match recv_msg_from (socket).await {
Err (_) => continue,
Ok (x) => x,
};
let mut resp = ServerResponse {
mac: None,
nickname: None,
};
for msg in msgs.into_iter () {
match msg {
Message::Response1 (x) => resp.mac = x,
Message::Response2 (x) => resp.nickname = Some (x.nickname),
_ => (),
}
}
peers.insert (remote_addr, resp);
}
}

144
src/ip.rs
View File

@ -4,119 +4,59 @@ use std::{
str::FromStr, str::FromStr,
}; };
#[derive (Debug, thiserror::Error)] use crate::AppError;
pub enum IpError {
#[error (transparent)] pub fn get_ip_addr_output () -> Result <String, AppError> {
Io (#[from] std::io::Error), let output = Command::new ("ip")
#[error (transparent)] .arg ("addr")
FromUtf8 (#[from] std::string::FromUtf8Error), .output ()?;
#[error ("Self-IP detection is not implemented on Mac OS")] let output = output.stdout.as_slice ();
NotImplementedOnMac, let output = String::from_utf8 (output.to_vec ())?;
Ok (output)
} }
#[cfg(target_os = "linux")] pub fn parse_ip_addr_output (output: &str) -> Vec <Ipv4Addr> {
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> { // I wrote this in FP style because I was bored.
let output = linux::get_ip_addr_output ()?;
Ok (linux::parse_ip_addr_output (&output)) output.lines ()
.map (|l| l.trim_start ())
.filter_map (|l| l.strip_prefix ("inet "))
.filter_map (|l| l.find ('/').map (|x| &l [0..x]))
.filter_map (|l| Ipv4Addr::from_str (l).ok ())
.collect ()
} }
#[cfg(target_os = "macos")] pub fn get_ip_config_output () -> Result <String, AppError> {
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> { let output = Command::new ("ipconfig")
Err (IpError::NotImplementedOnMac) .output ()?;
let output = output.stdout.as_slice ();
let output = String::from_utf8 (output.to_vec ())?;
Ok (output)
} }
#[cfg(target_os = "windows")] pub fn parse_ip_config_output (output: &str) -> Vec <Ipv4Addr> {
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> { let mut addrs = vec! [];
let output = windows::get_ip_config_output ()?;
Ok (windows::parse_ip_config_output (&output)) for line in output.lines () {
} let line = line.trim_start ();
#[cfg(target_os = "linux")] // Maybe only works on English locales?
pub mod linux { if ! line.starts_with ("IPv4 Address") {
use super::*; continue;
pub fn get_ip_addr_output () -> Result <String, IpError> {
let output = Command::new ("ip")
.arg ("addr")
.output ()?;
let output = output.stdout.as_slice ();
let output = String::from_utf8 (output.to_vec ())?;
Ok (output)
}
pub fn parse_ip_addr_output (output: &str) -> Vec <Ipv4Addr> {
// I wrote this in FP style because I was bored.
output.lines ()
.map (|l| l.trim_start ())
.filter_map (|l| l.strip_prefix ("inet "))
.filter_map (|l| l.find ('/').map (|x| &l [0..x]))
.filter_map (|l| Ipv4Addr::from_str (l).ok ())
.filter (|a| ! a.is_loopback ())
.collect ()
}
}
#[cfg(target_os = "windows")]
pub mod windows {
use super::*;
pub fn get_ip_config_output () -> Result <String, IpError> {
let output = Command::new ("ipconfig")
.output ()?;
let output = output.stdout.as_slice ();
let output = String::from_utf8 (output.to_vec ())?;
Ok (output)
}
pub fn parse_ip_config_output (output: &str) -> Vec <Ipv4Addr> {
let mut addrs = vec! [];
for line in output.lines () {
let line = line.trim_start ();
// Maybe only works on English locales?
if ! line.starts_with ("IPv4 Address") {
continue;
}
let colon_pos = match line.find (':') {
None => continue,
Some (x) => x,
};
let line = &line [colon_pos + 2..];
let addr = match Ipv4Addr::from_str (line) {
Err (_) => continue,
Ok (x) => x,
};
addrs.push (addr);
} }
let colon_pos = match line.find (':') {
None => continue,
Some (x) => x,
};
let line = &line [colon_pos + 2..];
addrs let addr = match Ipv4Addr::from_str (line) {
Err (_) => continue,
Ok (x) => x,
};
addrs.push (addr);
} }
#[cfg (test)] addrs
mod test {
use super::*;
#[test]
fn test () {
for (input, expected) in [
(
r"
IPv4 Address . . .. . . . : 192.168.1.1
",
vec! [
Ipv4Addr::new (192, 168, 1, 1),
]
),
] {
let actual = parse_ip_config_output (input);
assert_eq! (actual, expected);
}
}
}
} }

View File

@ -1,25 +1,81 @@
use prelude::*; use std::{
collections::HashMap,
env,
net::{
Ipv4Addr,
SocketAddr,
SocketAddrV4,
UdpSocket,
},
str::FromStr,
time::{Duration, Instant},
};
use mac_address::{
MacAddress,
get_mac_address,
};
use thiserror::Error;
pub mod app_common;
mod client;
mod ip; mod ip;
pub mod message; mod message;
mod prelude;
mod server;
mod tlv; mod tlv;
fn main () -> Result <(), AppError> { use message::{
let rt = tokio::runtime::Builder::new_current_thread () PACKET_SIZE,
.enable_io () Message,
.enable_time () };
.build ()?;
rt.block_on (async_main ())?; #[derive (Debug, Error)]
pub enum AppError {
Ok (()) #[error (transparent)]
AddrParse (#[from] std::net::AddrParseError),
#[error (transparent)]
CliArgs (#[from] CliArgError),
#[error (transparent)]
FromUtf8 (#[from] std::string::FromUtf8Error),
#[error (transparent)]
Io (#[from] std::io::Error),
#[error (transparent)]
MacAddr (#[from] mac_address::MacAddressError),
#[error (transparent)]
Message (#[from] message::MessageError),
#[error (transparent)]
Tlv (#[from] tlv::TlvError),
#[error (transparent)]
Utf8 (#[from] std::str::Utf8Error),
} }
async fn async_main () -> Result <(), AppError> { #[derive (Debug, Error)]
pub enum CliArgError {
#[error ("Missing value for argument `{0}`")]
MissingArgumentValue (String),
#[error ("First argument should be a subcommand")]
MissingSubcommand,
#[error ("Unknown subcommand `{0}`")]
UnknownSubcommand (String),
#[error ("Unrecognized argument `{0}`")]
UnrecognizedArgument (String),
}
struct CommonParams {
// Servers bind on this port, clients must send to the port
server_port: u16,
// Clients and servers will all join the same multicast addr
multicast_addr: Ipv4Addr,
}
impl Default for CommonParams {
fn default () -> Self {
Self {
server_port: 9040,
multicast_addr: Ipv4Addr::new (225, 100, 99, 98),
}
}
}
fn main () -> Result <(), AppError> {
let mut args = env::args (); let mut args = env::args ();
let _exe_name = args.next (); let _exe_name = args.next ();
@ -36,20 +92,223 @@ async fn async_main () -> Result <(), AppError> {
match subcommand.as_ref ().map (|x| &x[..]) { match subcommand.as_ref ().map (|x| &x[..]) {
None => return Err (CliArgError::MissingSubcommand.into ()), None => return Err (CliArgError::MissingSubcommand.into ()),
Some ("client") => client::client (args).await?, Some ("client") => client (args)?,
Some ("my-ips") => my_ips ()?, Some ("my-ips") => my_ips ()?,
Some ("server") => server::server (args).await?, Some ("server") => server (args)?,
Some (x) => return Err (CliArgError::UnknownSubcommand (x.to_string ()).into ()), Some (x) => return Err (CliArgError::UnknownSubcommand (x.to_string ()).into ()),
} }
Ok (()) Ok (())
} }
#[cfg(target_os = "linux")]
fn my_ips () -> Result <(), AppError> { fn my_ips () -> Result <(), AppError> {
for addr in ip::get_ips ()? let output = ip::get_ip_addr_output ()?;
for addr in ip::parse_ip_addr_output (&output)
.iter ()
.filter (|a| ! a.is_loopback ())
{ {
println! ("{:?}", addr); println! ("{:?}", addr);
} }
Ok (()) Ok (())
} }
#[cfg(target_os = "macos")]
fn my_ips () -> Result <(), AppError> {
println! ("my-ips subcommand not implemented for macos");
Ok (())
}
#[cfg(target_os = "windows")]
fn my_ips () -> Result <(), AppError> {
let output = ip::get_ip_config_output ()?;
for addr in ip::parse_ip_config_output (&output) {
println! ("{:?}", addr);
}
Ok (())
}
struct ServerResponse {
mac: Option <[u8; 6]>,
nickname: Option <String>,
}
fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), AppError> {
use rand::RngCore;
let mut common_params = CommonParams::default ();
let mut bind_addr = "0.0.0.0".to_string ();
while let Some (arg) = args.next () {
match arg.as_str () {
"--bind-addr" => {
bind_addr = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => x
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
}
}
let socket = UdpSocket::bind (&format! ("{}:0", bind_addr))?;
socket.join_multicast_v4 (&common_params.multicast_addr, &Ipv4Addr::from_str (&bind_addr)?)?;
socket.set_read_timeout (Some (Duration::from_millis (1_000)))?;
let mut idem_id = [0u8; 8];
rand::thread_rng ().fill_bytes (&mut idem_id);
let msg = Message::Request1 {
idem_id,
mac: None,
}.to_vec ()?;
for _ in 0..10 {
socket.send_to (&msg, (common_params.multicast_addr, common_params.server_port))?;
std::thread::sleep (Duration::from_millis (100));
}
let start_time = Instant::now ();
let mut peers = HashMap::with_capacity (10);
while Instant::now () < start_time + Duration::from_secs (2) {
let (msgs, remote_addr) = match recv_msg_from (&socket) {
Err (_) => continue,
Ok (x) => x,
};
let mut resp = ServerResponse {
mac: None,
nickname: None,
};
for msg in msgs.into_iter () {
match msg {
Message::Response1 (x) => resp.mac = x,
Message::Response2 (x) => resp.nickname = Some (x.nickname),
_ => (),
}
}
peers.insert (remote_addr, resp);
}
let mut peers: Vec <_> = peers.into_iter ().collect ();
peers.sort_by_key (|(k, v)| v.mac);
println! ("Found {} peers:", peers.len ());
for (ip, resp) in peers.into_iter () {
let mac = match resp.mac {
None => {
println! ("<Unknown> = {}", ip);
continue;
},
Some (x) => x,
};
let nickname = match resp.nickname {
None => {
println! ("{} = {}", MacAddress::new (mac), ip.ip ());
continue;
},
Some (x) => x,
};
println! ("{} = {} `{}`", MacAddress::new (mac), ip.ip (), nickname);
}
Ok (())
}
fn server <I: Iterator <Item=String>> (mut args: I) -> Result <(), AppError>
{
let mut common_params = CommonParams::default ();
let mut bind_addr = "0.0.0.0".to_string ();
let mut nickname = String::new ();
while let Some (arg) = args.next () {
match arg.as_str () {
"--bind-addr" => {
bind_addr = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => x
};
},
"--nickname" => {
nickname = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => x
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
}
}
let our_mac = get_mac_address ()?.map (|x| x.bytes ());
if our_mac.is_none () {
println! ("Warning: Can't find our own MAC address. We won't be able to respond to MAC-specific lookaround requests");
}
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::from_str (&bind_addr)?, common_params.server_port)).unwrap ();
socket.join_multicast_v4 (&common_params.multicast_addr, &([0u8, 0, 0, 0].into ())).unwrap ();
let mut recent_idem_ids = Vec::with_capacity (32);
loop {
println! ("Waiting for messages...");
let (req_msgs, remote_addr) = recv_msg_from (&socket)?;
let req = match req_msgs.into_iter ().next () {
Some (x) => x,
_ => {
println! ("Don't know how to handle this message, ignoring");
continue;
},
};
let resp = match req {
Message::Request1 {
mac: None,
idem_id,
} => {
if recent_idem_ids.contains (&idem_id) {
println! ("Ignoring request we already processed");
None
}
else {
recent_idem_ids.insert (0, idem_id);
recent_idem_ids.truncate (30);
Some (vec! [
Message::Response1 (our_mac),
Message::Response2 (message::Response2 {
idem_id,
nickname: nickname.clone (),
}),
])
}
},
_ => continue,
};
if let Some (resp) = resp {
socket.send_to (&Message::many_to_vec (&resp)?, remote_addr).unwrap ();
}
}
}
fn recv_msg_from (socket: &UdpSocket) -> Result <(Vec <Message>, SocketAddr), AppError>
{
let mut buf = vec! [0u8; PACKET_SIZE];
let (bytes_recved, remote_addr) = socket.recv_from (&mut buf)?;
buf.truncate (bytes_recved);
let msgs = Message::from_slice2 (&buf)?;
Ok ((msgs, remote_addr))
}

View File

@ -19,10 +19,10 @@ pub enum Message {
// 1 // 1
Request1 { Request1 {
idem_id: [u8; 8], idem_id: [u8; 8],
mac: Option <Mac> mac: Option <[u8; 6]>
}, },
// 2 // 2
Response1 (Option <Mac>), Response1 (Option <[u8; 6]>),
// 3 // 3
Response2 (Response2), Response2 (Response2),
} }
@ -66,7 +66,7 @@ impl Write for DummyWriter {
} }
impl Message { impl Message {
pub fn write <T> (&self, w: &mut Cursor <T>) -> Result <(), MessageError> pub fn write <T> (&self, w: &mut Cursor <T>) -> Result <(), std::io::Error>
where Cursor <T>: Write where Cursor <T>: Write
{ {
match self { match self {
@ -92,7 +92,7 @@ impl Message {
Self::write_response_2 (&mut dummy_writer, x)?; Self::write_response_2 (&mut dummy_writer, x)?;
// Write length and real params to real output // Write length and real params to real output
let len = u32::try_from (dummy_writer.position)?; let len = u32::try_from (dummy_writer.position).unwrap ();
w.write_all (&len.to_le_bytes ())?; w.write_all (&len.to_le_bytes ())?;
Self::write_response_2 (w, x)?; Self::write_response_2 (w, x)?;
}, },
@ -102,11 +102,13 @@ impl Message {
} }
fn write_response_2 <W: Write> (w: &mut W, params: &Response2) fn write_response_2 <W: Write> (w: &mut W, params: &Response2)
-> Result <(), MessageError> -> Result <(), std::io::Error>
{ {
w.write_all (&params.idem_id)?; w.write_all (&params.idem_id)?;
let nickname = params.nickname.as_bytes (); let nickname = params.nickname.as_bytes ();
tlv::Writer::<_>::lv_bytes (w, nickname)?; let nickname_len = u32::try_from (nickname.len ()).unwrap ();
w.write_all (&nickname_len.to_le_bytes ())?;
w.write_all (&nickname)?;
Ok (()) Ok (())
} }
@ -122,14 +124,14 @@ impl Message {
Ok (()) Ok (())
} }
pub fn to_vec (&self) -> Result <Vec <u8>, MessageError> { pub fn to_vec (&self) -> Result <Vec <u8>, tlv::TlvError> {
let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE)); let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE));
cursor.write_all (&MAGIC_NUMBER)?; cursor.write_all (&MAGIC_NUMBER)?;
self.write (&mut cursor)?; self.write (&mut cursor)?;
Ok (cursor.into_inner ()) Ok (cursor.into_inner ())
} }
pub fn many_to_vec (msgs: &[Self]) -> Result <Vec <u8>, MessageError> { pub fn many_to_vec (msgs: &[Self]) -> Result <Vec <u8>, tlv::TlvError> {
let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE)); let mut cursor = Cursor::new (Vec::with_capacity (PACKET_SIZE));
cursor.write_all (&MAGIC_NUMBER)?; cursor.write_all (&MAGIC_NUMBER)?;
for msg in msgs { for msg in msgs {
@ -138,6 +140,28 @@ impl Message {
Ok (cursor.into_inner ()) Ok (cursor.into_inner ())
} }
fn read1 <R: std::io::Read> (r: &mut R) -> Result <Self, MessageError> {
let t = tlv::Reader::u8 (r)?;
Ok (match t {
1 => {
let mut idem_id = [0u8; 8];
r.read_exact (&mut idem_id)?;
let mac = Self::read_mac_opt (r)?;
Self::Request1 {
idem_id,
mac,
}
},
2 => {
let mac = Self::read_mac_opt (r)?;
Self::Response1 (mac)
},
_ => return Err (MessageError::UnknownType),
})
}
fn read2 <R: std::io::Read> (r: &mut R) -> Result <Self, MessageError> { fn read2 <R: std::io::Read> (r: &mut R) -> Result <Self, MessageError> {
let t = tlv::Reader::u8 (r)?; let t = tlv::Reader::u8 (r)?;
@ -157,12 +181,24 @@ impl Message {
Self::Response1 (mac) Self::Response1 (mac)
}, },
3 => { 3 => {
tlv::Reader::<_>::length (r)?; let mut len = [0; 4];
r.read_exact (&mut len)?;
let _len = len;
let mut idem_id = [0; 8]; let mut idem_id = [0; 8];
r.read_exact (&mut idem_id)?; r.read_exact (&mut idem_id)?;
let nickname = tlv::Reader::<_>::lv_bytes_to_vec (r, 64)?; let mut nickname_len = [0; 4];
r.read_exact (&mut nickname_len)?;
let nickname_len = u32::from_le_bytes (nickname_len);
let nickname_len = usize::try_from (nickname_len)?;
if nickname_len > 64 {
return Err (MessageError::LengthPrefixTooLong ((64, nickname_len)));
}
let mut nickname = vec! [0u8; nickname_len];
r.read_exact (&mut nickname)?;
let nickname = String::from_utf8 (nickname)?; let nickname = String::from_utf8 (nickname)?;
Self::Response2 (Response2 { Self::Response2 (Response2 {
@ -187,6 +223,12 @@ impl Message {
}) })
} }
pub fn from_slice1 (buf: &[u8]) -> Result <Self, MessageError> {
let mut cursor = Cursor::new (buf);
tlv::Reader::expect (&mut cursor, &MAGIC_NUMBER)?;
Self::read1 (&mut cursor)
}
pub fn from_slice2 (buf: &[u8]) -> Result <Vec <Self>, MessageError> { pub fn from_slice2 (buf: &[u8]) -> Result <Vec <Self>, MessageError> {
let mut cursor = Cursor::new (buf); let mut cursor = Cursor::new (buf);
tlv::Reader::expect (&mut cursor, &MAGIC_NUMBER)?; tlv::Reader::expect (&mut cursor, &MAGIC_NUMBER)?;
@ -206,7 +248,7 @@ mod test {
use super::*; use super::*;
#[test] #[test]
fn test_write_2 () -> Result <(), MessageError> { fn test_write_2 () {
for (input, expected) in [ for (input, expected) in [
( (
vec! [ vec! [
@ -254,15 +296,13 @@ mod test {
], ],
), ),
] { ] {
let actual = Message::many_to_vec (&input)?; let actual = Message::many_to_vec (&input).unwrap ();
assert_eq! (actual, expected, "{:?}", input); assert_eq! (actual, expected, "{:?}", input);
} }
Ok (())
} }
#[test] #[test]
fn test_write_1 () -> Result <(), MessageError> { fn test_write_1 () {
for (input, expected) in [ for (input, expected) in [
( (
Message::Request1 { Message::Request1 {
@ -304,15 +344,13 @@ mod test {
], ],
), ),
].into_iter () { ].into_iter () {
let actual = input.to_vec ()?; let actual = input.to_vec ().unwrap ();
assert_eq! (actual, expected, "{:?}", input); assert_eq! (actual, expected, "{:?}", input);
} }
Ok (())
} }
#[test] #[test]
fn test_read_2 () -> Result <(), MessageError> { fn test_read_2 () {
for input in [ for input in [
vec! [ vec! [
Message::Request1 { Message::Request1 {
@ -334,11 +372,83 @@ mod test {
}), }),
], ],
].into_iter () { ].into_iter () {
let encoded = Message::many_to_vec (&input)?; let encoded = Message::many_to_vec (&input).unwrap ();
let decoded = Message::from_slice2 (&encoded)?; let decoded = Message::from_slice2 (&encoded).unwrap ();
assert_eq! (input, decoded);
}
}
#[test]
fn test_read_1 () {
for input in [
Message::Request1 {
idem_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
mac: None,
},
Message::Response1 (Some ([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
Message::Response1 (None),
].into_iter () {
let encoded = input.to_vec ().unwrap ();
let decoded = Message::from_slice1 (&encoded).unwrap ();
assert_eq! (input, decoded); assert_eq! (input, decoded);
} }
Ok (()) for (expected, input) in [
(
Message::Request1 {
idem_id: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
mac: None,
},
vec! [
154, 74, 67, 129,
// Request tag
1,
// Idem ID
1, 2, 3, 4, 5, 6, 7, 8,
// MAC is None
0,
],
),
(
Message::Response1 (Some ([0x11, 0x22, 0x33, 0x44, 0x55, 0x66])),
vec! [
// Magic number for LookAround packets
154, 74, 67, 129,
// Response tag
2,
// MAC is Some
1,
// MAC
17, 34, 51, 68, 85, 102,
],
),
(
Message::Response1 (None),
vec! [
// Magic number for LookAround packets
154, 74, 67, 129,
// Response tag
2,
// MAC is None
0,
],
),
(
Message::Response1 (None),
vec! [
// Magic number for LookAround packets
154, 74, 67, 129,
// Response tag
2,
// MAC is None
0,
// New tag that older versions will just ignore
255,
],
),
].into_iter () {
let actual = Message::from_slice1 (&input).unwrap ();
assert_eq! (actual, expected, "{:?}", actual);
}
} }
} }

View File

@ -1,38 +0,0 @@
pub use std::{
collections::HashMap,
env,
net::{
Ipv4Addr,
SocketAddr,
SocketAddrV4,
},
str::FromStr,
time::Duration,
};
pub use mac_address::{
MacAddress,
get_mac_address,
};
pub use tokio::{
net::UdpSocket,
time::{
sleep,
timeout,
},
};
pub use crate::{
app_common::{
self,
AppError,
CliArgError,
recv_msg_from,
},
ip::get_ips,
message::{
self,
PACKET_SIZE,
Message,
},
};

View File

@ -1,133 +0,0 @@
use crate::prelude::*;
#[derive (Clone)]
struct Params {
common: app_common::Params,
bind_addrs: Vec <Ipv4Addr>,
nickname: String,
our_mac: Option <[u8; 6]>,
}
pub async fn server <I: Iterator <Item=String>> (args: I) -> Result <(), AppError>
{
let params = configure (args)?;
// This was too hard to do in a functional style
let mut tasks = vec! [];
for bind_addr in &params.bind_addrs {
let socket = match UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, params.common.server_port)).await {
Err (e) => {
println! ("Error binding socket: {:?}", e);
continue;
},
Ok (x) => x,
};
socket.join_multicast_v4 (params.common.multicast_addr, *bind_addr)?;
dbg! (bind_addr);
tasks.push (tokio::spawn (serve_interface (params.clone (), socket)));
}
for task in tasks {
task.await??;
}
Ok (())
}
fn configure <I: Iterator <Item=String>> (mut args: I) -> Result <Params, AppError>
{
let common = app_common::Params::default ();
let mut bind_addrs = vec![];
let mut nickname = String::new ();
while let Some (arg) = args.next () {
match arg.as_str () {
"--bind-addr" => {
bind_addrs.push (match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => Ipv4Addr::from_str (&x)?,
});
},
"--nickname" => {
nickname = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => x
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
}
}
let our_mac = get_mac_address ()?.map (|x| x.bytes ());
if our_mac.is_none () {
println! ("Warning: Can't find our own MAC address. We won't be able to respond to MAC-specific lookaround requests");
}
if bind_addrs.is_empty () {
println! ("No bind addresses found, auto-detecting all local IPs");
bind_addrs = get_ips ()?;
}
Ok (Params {
common,
bind_addrs,
nickname,
our_mac,
})
}
async fn serve_interface (params: Params, socket: UdpSocket)
-> Result <(), AppError>
{
let mut recent_idem_ids = Vec::with_capacity (32);
loop {
println! ("Listening...");
let (req_msgs, remote_addr) = match recv_msg_from (&socket).await {
Ok (x) => x,
Err (e) => {
println! ("Error while receiving message: {:?}", e);
continue;
},
};
let req = match req_msgs.into_iter ().next () {
Some (x) => x,
_ => {
println! ("Don't know how to handle this message, ignoring");
continue;
},
};
let resp = match req {
Message::Request1 {
mac: None,
idem_id,
} => {
if recent_idem_ids.contains (&idem_id) {
None
}
else {
recent_idem_ids.insert (0, idem_id);
recent_idem_ids.truncate (30);
Some (vec! [
Message::Response1 (params.our_mac),
Message::Response2 (message::Response2 {
idem_id,
nickname: params.nickname.clone (),
}),
])
}
},
_ => continue,
};
if let Some (resp) = resp {
socket.send_to (&Message::many_to_vec (&resp)?, remote_addr).await?;
}
}
}

View File

@ -6,14 +6,8 @@ type Result <T> = std::result::Result <T, TlvError>;
pub enum TlvError { pub enum TlvError {
#[error ("Buffer too big")] #[error ("Buffer too big")]
BufferTooBig, BufferTooBig,
#[error ("Caller-provided buffer too small")]
// Violets are purple, CallerBufferTooSmall,
// To live is to suffer,
// The data is too big,
// For the gosh-darn buffer.
#[error ("Data too big")]
DataTooBig,
#[error (transparent)] #[error (transparent)]
Io (#[from] std::io::Error), Io (#[from] std::io::Error),
#[error ("Actual bytes didn't match expected bytes")] #[error ("Actual bytes didn't match expected bytes")]
@ -60,24 +54,22 @@ impl <R: std::io::Read> Reader <R> {
Ok (()) Ok (())
} }
pub fn length (r: &mut R) -> Result <u32> { fn length (r: &mut R) -> Result <u32> {
let mut buf = [0; 4]; let mut buf = [0; 4];
r.read_exact (&mut buf)?; r.read_exact (&mut buf)?;
Ok (u32::from_le_bytes (buf)) Ok (u32::from_le_bytes (buf))
} }
pub fn lv_bytes_to_vec (r: &mut R, limit: usize) -> Result <Vec <u8>> { pub fn lv_bytes (r: &mut R, buf: &mut [u8]) -> Result <u32> {
let l = Self::length (r)?; let l = Self::length (r)?;
let l = usize::try_from (l)?; if usize::try_from (l)? > buf.len () {
if l > limit { return Err (TlvError::CallerBufferTooSmall);
return Err (TlvError::DataTooBig);
} }
let mut v = vec! [0u8; l]; r.read_exact (&mut buf [0..usize::try_from (l)?])?;
r.read_exact (&mut v)?;
Ok (v) Ok (l)
} }
pub fn u8 (r: &mut R) -> std::io::Result <u8> { pub fn u8 (r: &mut R) -> std::io::Result <u8> {
@ -90,17 +82,15 @@ impl <R: std::io::Read> Reader <R> {
#[cfg (test)] #[cfg (test)]
mod test { mod test {
use super::*;
#[test] #[test]
fn test_1 () -> Result <()> { fn test_1 () {
use std::io::Cursor; use std::io::Cursor;
let b = "hi there".as_bytes (); let b = "hi there".as_bytes ();
let mut w = Cursor::new (Vec::default ()); let mut w = Cursor::new (Vec::default ());
super::Writer::lv_bytes (&mut w, b)?; super::Writer::lv_bytes (&mut w, b).unwrap ();
let v = w.into_inner (); let v = w.into_inner ();
@ -112,11 +102,11 @@ mod test {
let mut r = Cursor::new (v); let mut r = Cursor::new (v);
let buf = Reader::lv_bytes_to_vec (&mut r, 1024)?; let mut buf = vec! [0; 1024];
assert_eq! (buf.len (), b.len ()); let bytes_read = super::Reader::lv_bytes (&mut r, &mut buf).unwrap ();
assert_eq! (b, &buf);
Ok (()) assert_eq! (usize::try_from (bytes_read).unwrap (), b.len ());
assert_eq! (b, &buf [0..usize::try_from (bytes_read).unwrap ()]);
} }
} }