Compare commits
13 Commits
2b4695934e
...
0bb702f312
Author | SHA1 | Date |
---|---|---|
_ | 0bb702f312 | |
_ | 1b7c2ce0f4 | |
_ | 39bcea54d4 | |
_ | 2ba1dc2834 | |
_ | 33e6ae29ca | |
_ | c7681ce9f5 | |
_ | b620bcfe06 | |
_ | 5d8bb3282b | |
_ | bf9d185092 | |
_ | ee51bb7d3d | |
_ | dbedc6083e | |
_ | 50bfd422f9 | |
_ | 5d6c566317 |
|
@ -43,6 +43,15 @@ 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"
|
||||||
|
@ -50,6 +59,7 @@ dependencies = [
|
||||||
"mac_address",
|
"mac_address",
|
||||||
"rand",
|
"rand",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -71,6 +81,28 @@ 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"
|
||||||
|
@ -84,6 +116,21 @@ 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"
|
||||||
|
@ -179,6 +226,19 @@ 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"
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
1
ideas.md
1
ideas.md
|
@ -3,3 +3,4 @@ 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)
|
||||||
|
|
|
@ -21,12 +21,3 @@ 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
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
146
src/ip.rs
146
src/ip.rs
|
@ -4,59 +4,119 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::AppError;
|
#[derive (Debug, thiserror::Error)]
|
||||||
|
pub enum IpError {
|
||||||
pub fn get_ip_addr_output () -> Result <String, AppError> {
|
#[error (transparent)]
|
||||||
let output = Command::new ("ip")
|
Io (#[from] std::io::Error),
|
||||||
.arg ("addr")
|
#[error (transparent)]
|
||||||
.output ()?;
|
FromUtf8 (#[from] std::string::FromUtf8Error),
|
||||||
let output = output.stdout.as_slice ();
|
#[error ("Self-IP detection is not implemented on Mac OS")]
|
||||||
let output = String::from_utf8 (output.to_vec ())?;
|
NotImplementedOnMac,
|
||||||
Ok (output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ip_addr_output (output: &str) -> Vec <Ipv4Addr> {
|
#[cfg(target_os = "linux")]
|
||||||
// I wrote this in FP style because I was bored.
|
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
|
||||||
|
let output = linux::get_ip_addr_output ()?;
|
||||||
|
|
||||||
output.lines ()
|
Ok (linux::parse_ip_addr_output (&output))
|
||||||
.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 ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ip_config_output () -> Result <String, AppError> {
|
#[cfg(target_os = "macos")]
|
||||||
let output = Command::new ("ipconfig")
|
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
|
||||||
.output ()?;
|
Err (IpError::NotImplementedOnMac)
|
||||||
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> {
|
#[cfg(target_os = "windows")]
|
||||||
let mut addrs = vec! [];
|
pub fn get_ips () -> Result <Vec <Ipv4Addr>, IpError> {
|
||||||
|
let output = windows::get_ip_config_output ()?;
|
||||||
|
|
||||||
for line in output.lines () {
|
Ok (windows::parse_ip_config_output (&output))
|
||||||
let line = line.trim_start ();
|
}
|
||||||
|
|
||||||
// Maybe only works on English locales?
|
#[cfg(target_os = "linux")]
|
||||||
if ! line.starts_with ("IPv4 Address") {
|
pub mod linux {
|
||||||
continue;
|
use super::*;
|
||||||
}
|
|
||||||
let colon_pos = match line.find (':') {
|
|
||||||
None => continue,
|
|
||||||
Some (x) => x,
|
|
||||||
};
|
|
||||||
let line = &line [colon_pos + 2..];
|
|
||||||
|
|
||||||
let addr = match Ipv4Addr::from_str (line) {
|
pub fn get_ip_addr_output () -> Result <String, IpError> {
|
||||||
Err (_) => continue,
|
let output = Command::new ("ip")
|
||||||
Ok (x) => x,
|
.arg ("addr")
|
||||||
};
|
.output ()?;
|
||||||
|
let output = output.stdout.as_slice ();
|
||||||
addrs.push (addr);
|
let output = String::from_utf8 (output.to_vec ())?;
|
||||||
|
Ok (output)
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg (test)]
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
299
src/main.rs
299
src/main.rs
|
@ -1,81 +1,25 @@
|
||||||
use std::{
|
use prelude::*;
|
||||||
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;
|
||||||
mod message;
|
pub mod message;
|
||||||
|
mod prelude;
|
||||||
|
mod server;
|
||||||
mod tlv;
|
mod tlv;
|
||||||
|
|
||||||
use message::{
|
|
||||||
PACKET_SIZE,
|
|
||||||
Message,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive (Debug, Error)]
|
|
||||||
pub enum AppError {
|
|
||||||
#[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),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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> {
|
fn main () -> Result <(), AppError> {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread ()
|
||||||
|
.enable_io ()
|
||||||
|
.enable_time ()
|
||||||
|
.build ()?;
|
||||||
|
|
||||||
|
rt.block_on (async_main ())?;
|
||||||
|
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn async_main () -> Result <(), AppError> {
|
||||||
let mut args = env::args ();
|
let mut args = env::args ();
|
||||||
|
|
||||||
let _exe_name = args.next ();
|
let _exe_name = args.next ();
|
||||||
|
@ -92,223 +36,20 @@ fn 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 (args)?,
|
Some ("client") => client::client (args).await?,
|
||||||
Some ("my-ips") => my_ips ()?,
|
Some ("my-ips") => my_ips ()?,
|
||||||
Some ("server") => server (args)?,
|
Some ("server") => server::server (args).await?,
|
||||||
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> {
|
||||||
let output = ip::get_ip_addr_output ()?;
|
for addr in ip::get_ips ()?
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
154
src/message.rs
154
src/message.rs
|
@ -19,10 +19,10 @@ pub enum Message {
|
||||||
// 1
|
// 1
|
||||||
Request1 {
|
Request1 {
|
||||||
idem_id: [u8; 8],
|
idem_id: [u8; 8],
|
||||||
mac: Option <[u8; 6]>
|
mac: Option <Mac>
|
||||||
},
|
},
|
||||||
// 2
|
// 2
|
||||||
Response1 (Option <[u8; 6]>),
|
Response1 (Option <Mac>),
|
||||||
// 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 <(), std::io::Error>
|
pub fn write <T> (&self, w: &mut Cursor <T>) -> Result <(), MessageError>
|
||||||
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).unwrap ();
|
let len = u32::try_from (dummy_writer.position)?;
|
||||||
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,13 +102,11 @@ 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 <(), std::io::Error>
|
-> Result <(), MessageError>
|
||||||
{
|
{
|
||||||
w.write_all (¶ms.idem_id)?;
|
w.write_all (¶ms.idem_id)?;
|
||||||
let nickname = params.nickname.as_bytes ();
|
let nickname = params.nickname.as_bytes ();
|
||||||
let nickname_len = u32::try_from (nickname.len ()).unwrap ();
|
tlv::Writer::<_>::lv_bytes (w, nickname)?;
|
||||||
w.write_all (&nickname_len.to_le_bytes ())?;
|
|
||||||
w.write_all (&nickname)?;
|
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,14 +122,14 @@ impl Message {
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_vec (&self) -> Result <Vec <u8>, tlv::TlvError> {
|
pub fn to_vec (&self) -> Result <Vec <u8>, MessageError> {
|
||||||
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>, tlv::TlvError> {
|
pub fn many_to_vec (msgs: &[Self]) -> Result <Vec <u8>, MessageError> {
|
||||||
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 {
|
||||||
|
@ -140,28 +138,6 @@ 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)?;
|
||||||
|
|
||||||
|
@ -181,24 +157,12 @@ impl Message {
|
||||||
Self::Response1 (mac)
|
Self::Response1 (mac)
|
||||||
},
|
},
|
||||||
3 => {
|
3 => {
|
||||||
let mut len = [0; 4];
|
tlv::Reader::<_>::length (r)?;
|
||||||
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 mut nickname_len = [0; 4];
|
let nickname = tlv::Reader::<_>::lv_bytes_to_vec (r, 64)?;
|
||||||
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 {
|
||||||
|
@ -223,12 +187,6 @@ 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)?;
|
||||||
|
@ -248,7 +206,7 @@ mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_write_2 () {
|
fn test_write_2 () -> Result <(), MessageError> {
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
(
|
(
|
||||||
vec! [
|
vec! [
|
||||||
|
@ -296,13 +254,15 @@ mod test {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
] {
|
] {
|
||||||
let actual = Message::many_to_vec (&input).unwrap ();
|
let actual = Message::many_to_vec (&input)?;
|
||||||
assert_eq! (actual, expected, "{:?}", input);
|
assert_eq! (actual, expected, "{:?}", input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_write_1 () {
|
fn test_write_1 () -> Result <(), MessageError> {
|
||||||
for (input, expected) in [
|
for (input, expected) in [
|
||||||
(
|
(
|
||||||
Message::Request1 {
|
Message::Request1 {
|
||||||
|
@ -344,13 +304,15 @@ mod test {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
].into_iter () {
|
].into_iter () {
|
||||||
let actual = input.to_vec ().unwrap ();
|
let actual = input.to_vec ()?;
|
||||||
assert_eq! (actual, expected, "{:?}", input);
|
assert_eq! (actual, expected, "{:?}", input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_read_2 () {
|
fn test_read_2 () -> Result <(), MessageError> {
|
||||||
for input in [
|
for input in [
|
||||||
vec! [
|
vec! [
|
||||||
Message::Request1 {
|
Message::Request1 {
|
||||||
|
@ -372,83 +334,11 @@ mod test {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
].into_iter () {
|
].into_iter () {
|
||||||
let encoded = Message::many_to_vec (&input).unwrap ();
|
let encoded = Message::many_to_vec (&input)?;
|
||||||
let decoded = Message::from_slice2 (&encoded).unwrap ();
|
let decoded = Message::from_slice2 (&encoded)?;
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (expected, input) in [
|
Ok (())
|
||||||
(
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,133 @@
|
||||||
|
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 ¶ms.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?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/tlv.rs
38
src/tlv.rs
|
@ -6,8 +6,14 @@ 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")]
|
|
||||||
CallerBufferTooSmall,
|
// Violets are purple,
|
||||||
|
// 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")]
|
||||||
|
@ -54,22 +60,24 @@ impl <R: std::io::Read> Reader <R> {
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn length (r: &mut R) -> Result <u32> {
|
pub 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 (r: &mut R, buf: &mut [u8]) -> Result <u32> {
|
pub fn lv_bytes_to_vec (r: &mut R, limit: usize) -> Result <Vec <u8>> {
|
||||||
let l = Self::length (r)?;
|
let l = Self::length (r)?;
|
||||||
if usize::try_from (l)? > buf.len () {
|
let l = usize::try_from (l)?;
|
||||||
return Err (TlvError::CallerBufferTooSmall);
|
if l > limit {
|
||||||
|
return Err (TlvError::DataTooBig);
|
||||||
}
|
}
|
||||||
|
|
||||||
r.read_exact (&mut buf [0..usize::try_from (l)?])?;
|
let mut v = vec! [0u8; l];
|
||||||
|
r.read_exact (&mut v)?;
|
||||||
|
|
||||||
Ok (l)
|
Ok (v)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn u8 (r: &mut R) -> std::io::Result <u8> {
|
pub fn u8 (r: &mut R) -> std::io::Result <u8> {
|
||||||
|
@ -82,15 +90,17 @@ impl <R: std::io::Read> Reader <R> {
|
||||||
|
|
||||||
#[cfg (test)]
|
#[cfg (test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_1 () {
|
fn test_1 () -> Result <()> {
|
||||||
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).unwrap ();
|
super::Writer::lv_bytes (&mut w, b)?;
|
||||||
|
|
||||||
let v = w.into_inner ();
|
let v = w.into_inner ();
|
||||||
|
|
||||||
|
@ -102,11 +112,11 @@ mod test {
|
||||||
|
|
||||||
let mut r = Cursor::new (v);
|
let mut r = Cursor::new (v);
|
||||||
|
|
||||||
let mut buf = vec! [0; 1024];
|
let buf = Reader::lv_bytes_to_vec (&mut r, 1024)?;
|
||||||
|
|
||||||
let bytes_read = super::Reader::lv_bytes (&mut r, &mut buf).unwrap ();
|
assert_eq! (buf.len (), b.len ());
|
||||||
|
assert_eq! (b, &buf);
|
||||||
|
|
||||||
assert_eq! (usize::try_from (bytes_read).unwrap (), b.len ());
|
Ok (())
|
||||||
assert_eq! (b, &buf [0..usize::try_from (bytes_read).unwrap ()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue