Compare commits

...

3 Commits

Author SHA1 Message Date
_ 4f66c0495e add `find-nick` command 2021-12-09 16:46:55 +00:00
_ b261d7ba4a ♻️ refactor: refactor the client a lot so I can reuse its code for new subcommands 2021-12-09 16:21:14 +00:00
_ 5665f484a2 🔇 remove debugging println 2021-12-09 15:49:23 +00:00
9 changed files with 185 additions and 86 deletions

View File

@ -59,6 +59,19 @@ Run the server manually: (If you haven't installed it with systemd yet)
lookaround server --nickname my-desktop lookaround server --nickname my-desktop
``` ```
Use `find-nick` to find an IP, or ping it, or SSH into it, or pull a file from it:
```bash
lookaround find-nick laptop
ping $(lookaround find-nick laptop)
ssh user@$(lookaround find-nick laptop)
# After starting `nc -l -p 9000 < some-file` on the laptop
nc $(lookaround find-nick laptop) 9000
``
Run a client to ping all servers in the same multi-cast domain: Run a client to ping all servers in the same multi-cast domain:
```bash ```bash
@ -71,8 +84,6 @@ Use a longer timeout if some servers need longer than 500 ms to respond:
lookaround client --timeout-ms 1000 lookaround client --timeout-ms 1000
``` ```
For less common uses, see [the command-line documentation.](docs/cli.md)
## Contributing ## Contributing
Pull requests are welcome. This is a hobby project, so I may reject Pull requests are welcome. This is a hobby project, so I may reject
contributions that are too big to review. contributions that are too big to review.

View File

@ -1,3 +0,0 @@
# Command-line args

View File

@ -1,7 +1,6 @@
Cool ideas that can be done but probably won't be. Cool ideas that can be done but probably won't be.
- Exit faster if the user only wants to see known servers - Exit faster if the user only wants to see known servers
- 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) - Public-key crypto for trusting peers on first use (Hard cause it requires mutable disk state)

View File

@ -6,6 +6,8 @@ pub enum AppError {
AddrParse (#[from] std::net::AddrParseError), AddrParse (#[from] std::net::AddrParseError),
#[error (transparent)] #[error (transparent)]
CliArgs (#[from] CliArgError), CliArgs (#[from] CliArgError),
#[error ("Operation timed out")]
Elapsed (#[from] tokio::time::error::Elapsed),
#[error (transparent)] #[error (transparent)]
Io (#[from] std::io::Error), Io (#[from] std::io::Error),
#[error (transparent)] #[error (transparent)]
@ -26,6 +28,8 @@ pub enum AppError {
pub enum CliArgError { pub enum CliArgError {
#[error ("Missing value for argument `{0}`")] #[error ("Missing value for argument `{0}`")]
MissingArgumentValue (String), MissingArgumentValue (String),
#[error ("Missing required argument <{0}>")]
MissingRequiredArg (String),
#[error ("First argument should be a subcommand")] #[error ("First argument should be a subcommand")]
MissingSubcommand, MissingSubcommand,
#[error ("Unknown subcommand `{0}`")] #[error ("Unknown subcommand `{0}`")]

View File

@ -5,66 +5,29 @@ struct ServerResponse {
nickname: Option <String>, nickname: Option <String>,
} }
pub async fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), AppError> { struct ClientParams {
use rand::RngCore; common: app_common::Params,
bind_addrs: Vec <Ipv4Addr>,
timeout_ms: u64,
}
let common_params = app_common::Params::default (); pub async fn client <I: Iterator <Item=String>> (args: I) -> Result <(), AppError> {
let mut bind_addrs = vec! []; match get_mac_address() {
let mut timeout_ms = 500; Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma);
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)?,
});
},
"--timeout-ms" => {
timeout_ms = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => u64::from_str (&x)?,
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
} }
Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e),
} }
if bind_addrs.is_empty () { let params = configure_client (args)?;
bind_addrs = get_ips ()?; let socket = make_socket (&params).await?;
} let msg = Message::new_request1 ().to_vec ()?;
tokio::spawn (send_requests (Arc::clone (&socket), params.common, msg));
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, 0)).await?;
for bind_addr in bind_addrs {
if let Err (e) = socket.join_multicast_v4 (common_params.multicast_addr, bind_addr) {
println! ("Error joining multicast group with iface {}: {:?}", bind_addr, e);
}
}
let mut idem_id = [0u8; 8];
rand::thread_rng ().fill_bytes (&mut idem_id);
let msg = Message::Request1 {
idem_id,
mac: None,
}.to_vec ()?;
let socket = Arc::new (socket);
let socket2 = Arc::clone (&socket);
tokio::spawn (async move {
for _ in 0..10 {
socket2.send_to (&msg, (common_params.multicast_addr, common_params.server_port)).await?;
sleep (Duration::from_millis (100)).await;
}
Ok::<_, AppError> (())
});
let mut peers = HashMap::with_capacity (10); let mut peers = HashMap::with_capacity (10);
timeout (Duration::from_millis (timeout_ms), listen_for_responses (&*socket, &mut peers)).await.ok (); timeout (Duration::from_millis (params.timeout_ms), listen_for_responses (&*socket, &mut peers)).await.ok ();
let mut peers: Vec <_> = peers.into_iter ().collect (); let mut peers: Vec <_> = peers.into_iter ().collect ();
peers.sort_by_key (|(_, v)| v.mac); peers.sort_by_key (|(_, v)| v.mac);
@ -93,12 +56,130 @@ pub async fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), Ap
Ok (()) Ok (())
} }
pub async fn find_nick <I: Iterator <Item=String>> (mut args: I) -> Result <(), AppError>
{
let mut nick = None;
let mut timeout_ms = 500;
while let Some (arg) = args.next () {
match arg.as_str () {
"--timeout-ms" => {
timeout_ms = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => u64::from_str (&x)?,
};
},
_ => nick = Some (arg),
}
}
let needle_nick = nick.ok_or_else (|| CliArgError::MissingRequiredArg ("nickname".to_string ()))?;
let needle_nick = Some (needle_nick);
let params = ClientParams {
common: Default::default (),
bind_addrs: get_ips ()?,
timeout_ms,
};
let socket = make_socket (&params).await?;
let msg = Message::new_request1 ().to_vec ()?;
tokio::spawn (send_requests (Arc::clone (&socket), params.common, msg));
timeout (Duration::from_millis (params.timeout_ms), async move { 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),
_ => (),
}
}
if resp.nickname == needle_nick {
println! ("{}", remote_addr.ip ());
return;
}
}}).await?;
Ok (())
}
fn configure_client <I: Iterator <Item=String>> (mut args: I)
-> Result <ClientParams, AppError>
{
let mut bind_addrs = vec! [];
let mut timeout_ms = 500;
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)?,
});
},
"--timeout-ms" => {
timeout_ms = match args.next () {
None => return Err (CliArgError::MissingArgumentValue (arg).into ()),
Some (x) => u64::from_str (&x)?,
};
},
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
}
}
if bind_addrs.is_empty () {
bind_addrs = get_ips ()?;
}
Ok (ClientParams {
common: Default::default (),
bind_addrs,
timeout_ms,
})
}
async fn make_socket (params: &ClientParams) -> Result <Arc <UdpSocket>, AppError> {
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, 0)).await?;
for bind_addr in &params.bind_addrs {
if let Err (e) = socket.join_multicast_v4 (params.common.multicast_addr, *bind_addr) {
println! ("Error joining multicast group with iface {}: {:?}", bind_addr, e);
}
}
Ok (Arc::new (socket))
}
async fn send_requests (
socket: Arc <UdpSocket>,
params: app_common::Params,
msg: Vec <u8>,
)
-> Result <(), AppError>
{
for _ in 0..10 {
socket.send_to (&msg, (params.multicast_addr, params.server_port)).await?;
sleep (Duration::from_millis (100)).await;
}
Ok::<_, AppError> (())
}
async fn listen_for_responses ( async fn listen_for_responses (
socket: &UdpSocket, socket: &UdpSocket,
peers: &mut HashMap <SocketAddr, ServerResponse> peers: &mut HashMap <SocketAddr, ServerResponse>
) { ) {
let start_time = Instant::now ();
loop { loop {
let (msgs, remote_addr) = match recv_msg_from (socket).await { let (msgs, remote_addr) = match recv_msg_from (socket).await {
Err (_) => continue, Err (_) => continue,
@ -118,9 +199,6 @@ async fn listen_for_responses (
} }
} }
if peers.insert (remote_addr, resp).is_none () { peers.insert (remote_addr, resp);
let now = Instant::now ();
// println! ("Added peer at {} ms", (now - start_time).as_millis ());
}
} }
} }

View File

@ -6,7 +6,7 @@ mod ip;
pub mod message; pub mod message;
mod prelude; mod prelude;
mod server; mod server;
mod tlv; pub mod tlv;
fn main () -> Result <(), AppError> { fn main () -> Result <(), AppError> {
let rt = tokio::runtime::Builder::new_current_thread () let rt = tokio::runtime::Builder::new_current_thread ()
@ -24,19 +24,12 @@ async fn async_main () -> Result <(), AppError> {
let _exe_name = args.next (); let _exe_name = args.next ();
match get_mac_address() {
Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma);
}
Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e),
}
let subcommand: Option <String> = args.next (); let subcommand: Option <String> = args.next ();
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::client (args).await?,
Some ("find-nick") => client::find_nick (args).await?,
Some ("my-ips") => my_ips ()?, Some ("my-ips") => my_ips ()?,
Some ("server") => server::server (args).await?, 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 ()),

View File

@ -1,13 +1,4 @@
use std::{ use crate::prelude::*;
io::{
Cursor,
Write,
},
};
use crate::tlv;
use thiserror::Error;
const MAGIC_NUMBER: [u8; 4] = [0x9a, 0x4a, 0x43, 0x81]; const MAGIC_NUMBER: [u8; 4] = [0x9a, 0x4a, 0x43, 0x81];
pub const PACKET_SIZE: usize = 1024; pub const PACKET_SIZE: usize = 1024;
@ -27,13 +18,25 @@ pub enum Message {
Response2 (Response2), Response2 (Response2),
} }
impl Message {
pub fn new_request1 () -> Message {
let mut idem_id = [0u8; 8];
rand::thread_rng ().fill_bytes (&mut idem_id);
Message::Request1 {
idem_id,
mac: None,
}
}
}
#[derive (Debug, PartialEq)] #[derive (Debug, PartialEq)]
pub struct Response2 { pub struct Response2 {
pub idem_id: [u8; 8], pub idem_id: [u8; 8],
pub nickname: String, pub nickname: String,
} }
#[derive (Debug, Error)] #[derive (Debug, thiserror::Error)]
pub enum MessageError { pub enum MessageError {
#[error (transparent)] #[error (transparent)]
Io (#[from] std::io::Error), Io (#[from] std::io::Error),

View File

@ -1,6 +1,10 @@
pub use std::{ pub use std::{
collections::HashMap, collections::HashMap,
env, env,
io::{
Cursor,
Write,
},
net::{ net::{
Ipv4Addr, Ipv4Addr,
SocketAddr, SocketAddr,
@ -18,6 +22,7 @@ pub use mac_address::{
MacAddress, MacAddress,
get_mac_address, get_mac_address,
}; };
pub use rand::RngCore;
pub use tokio::{ pub use tokio::{
net::UdpSocket, net::UdpSocket,
time::{ time::{
@ -39,4 +44,5 @@ pub use crate::{
PACKET_SIZE, PACKET_SIZE,
Message, Message,
}, },
tlv,
}; };

View File

@ -10,6 +10,14 @@ struct Params {
pub async fn server <I: Iterator <Item=String>> (args: I) -> Result <(), AppError> pub async fn server <I: Iterator <Item=String>> (args: I) -> Result <(), AppError>
{ {
match get_mac_address() {
Ok(Some(ma)) => {
println!("Our MAC addr = {}", ma);
}
Ok(None) => println!("No MAC address found."),
Err(e) => println!("{:?}", e),
}
let params = configure (args)?; let params = configure (args)?;
let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, params.common.server_port)).await?; let socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, params.common.server_port)).await?;