Compare commits
No commits in common. "4f66c0495e383cdccda4aabe4fc138bbd8ced69f" and "814fee2bd50ac73b052ccca4bbf8acbff576c32b" have entirely different histories.
4f66c0495e
...
814fee2bd5
15
README.md
15
README.md
|
@ -59,19 +59,6 @@ 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
|
||||||
|
@ -84,6 +71,8 @@ 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.
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Command-line args
|
||||||
|
|
||||||
|
|
1
ideas.md
1
ideas.md
|
@ -1,6 +1,7 @@
|
||||||
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)
|
||||||
|
|
|
@ -6,8 +6,6 @@ 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)]
|
||||||
|
@ -28,8 +26,6 @@ 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}`")]
|
||||||
|
|
196
src/client.rs
196
src/client.rs
|
@ -5,29 +5,66 @@ struct ServerResponse {
|
||||||
nickname: Option <String>,
|
nickname: Option <String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ClientParams {
|
pub async fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), AppError> {
|
||||||
common: app_common::Params,
|
use rand::RngCore;
|
||||||
bind_addrs: Vec <Ipv4Addr>,
|
|
||||||
timeout_ms: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn client <I: Iterator <Item=String>> (args: I) -> Result <(), AppError> {
|
let common_params = app_common::Params::default ();
|
||||||
match get_mac_address() {
|
let mut bind_addrs = vec! [];
|
||||||
Ok(Some(ma)) => {
|
let mut timeout_ms = 500;
|
||||||
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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = configure_client (args)?;
|
if bind_addrs.is_empty () {
|
||||||
let socket = make_socket (¶ms).await?;
|
bind_addrs = get_ips ()?;
|
||||||
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 (params.timeout_ms), listen_for_responses (&*socket, &mut peers)).await.ok ();
|
timeout (Duration::from_millis (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);
|
||||||
|
@ -56,130 +93,12 @@ pub async fn client <I: Iterator <Item=String>> (args: I) -> Result <(), AppErro
|
||||||
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 (¶ms).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 ¶ms.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,
|
||||||
|
@ -199,6 +118,9 @@ async fn listen_for_responses (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peers.insert (remote_addr, resp);
|
if peers.insert (remote_addr, resp).is_none () {
|
||||||
|
let now = Instant::now ();
|
||||||
|
// println! ("Added peer at {} ms", (now - start_time).as_millis ());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -6,7 +6,7 @@ mod ip;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
mod prelude;
|
mod prelude;
|
||||||
mod server;
|
mod server;
|
||||||
pub mod tlv;
|
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,12 +24,19 @@ 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 ()),
|
||||||
|
|
|
@ -1,4 +1,13 @@
|
||||||
use crate::prelude::*;
|
use std::{
|
||||||
|
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;
|
||||||
|
@ -18,25 +27,13 @@ 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, thiserror::Error)]
|
#[derive (Debug, Error)]
|
||||||
pub enum MessageError {
|
pub enum MessageError {
|
||||||
#[error (transparent)]
|
#[error (transparent)]
|
||||||
Io (#[from] std::io::Error),
|
Io (#[from] std::io::Error),
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
pub use std::{
|
pub use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
env,
|
env,
|
||||||
io::{
|
|
||||||
Cursor,
|
|
||||||
Write,
|
|
||||||
},
|
|
||||||
net::{
|
net::{
|
||||||
Ipv4Addr,
|
Ipv4Addr,
|
||||||
SocketAddr,
|
SocketAddr,
|
||||||
|
@ -22,7 +18,6 @@ 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::{
|
||||||
|
@ -44,5 +39,4 @@ pub use crate::{
|
||||||
PACKET_SIZE,
|
PACKET_SIZE,
|
||||||
Message,
|
Message,
|
||||||
},
|
},
|
||||||
tlv,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,14 +10,6 @@ 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?;
|
||||||
|
|
Loading…
Reference in New Issue