Compare commits
22 Commits
Author | SHA1 | Date |
---|---|---|
_ | 2cc1ed4a92 | |
Reactor Scram | bd73d832fa | |
Reactor Scram | fd43fbbb1e | |
Reactor Scram | 97d7d000dc | |
Reactor Scram | bda991bc67 | |
Reactor Scram | ed816a0d6f | |
_ | b05a87cc9c | |
_ | 354a74aeaa | |
_ | d9fd0fd29d | |
_ | f121e3fd55 | |
_ | ab42de5823 | |
_ | ed58df2e6b | |
_ | 73434756b6 | |
_ | 8aae200ebf | |
_ | a0a64cd79c | |
_ | 0815691af2 | |
_ | 4f66c0495e | |
_ | b261d7ba4a | |
_ | 5665f484a2 | |
_ | 814fee2bd5 | |
_ | e7496a7c0e | |
_ | 18e38f0611 |
|
@ -15,7 +15,10 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
|
|
|
@ -4,9 +4,9 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
|
@ -26,6 +26,32 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "configparser"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06821ea598337a8412cf47c5b71c3bc694a7f0aed188ac28b836fab164a2c202"
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74be3be809c18e089de43bdc504652bb2bc473fca8756131f8689db8cf079ba9"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
|
@ -39,9 +65,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.109"
|
||||
version = "0.2.135"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01"
|
||||
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
@ -54,9 +80,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lookaround"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"configparser",
|
||||
"directories",
|
||||
"mac_address",
|
||||
"nix 0.25.0",
|
||||
"rand",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
@ -68,7 +97,7 @@ version = "1.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89544d9544366f6cda81244514a80809b137b5a179947b73bfa9f2797480de69"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.22.2",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -116,6 +145,20 @@ dependencies = [
|
|||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
|
@ -131,6 +174,12 @@ version = "0.2.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.15"
|
||||
|
@ -195,6 +244,25 @@ dependencies = [
|
|||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.82"
|
||||
|
@ -272,3 +340,69 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
|
|
@ -9,10 +9,13 @@ license = "AGPL-3.0"
|
|||
name = "lookaround"
|
||||
readme = "README.md"
|
||||
repository = "https://six-five-six-four.com/git/reactor/lookaround"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
|
||||
[dependencies]
|
||||
configparser = "3.0.0"
|
||||
directories = "5.0.0"
|
||||
mac_address = "1.1.2"
|
||||
nix = "0.25.0"
|
||||
rand = "0.8.4"
|
||||
thiserror = "1.0.30"
|
||||
tokio = { version = "1.14.0", features = ["fs", "net", "rt", "time"] }
|
||||
|
|
81
README.md
81
README.md
|
@ -4,6 +4,10 @@
|
|||
_Has this ever happened to you?_
|
||||
|
||||
```text
|
||||
$ ping $(lookaround find-nick laptop)
|
||||
PING 192.168.1.101 (192.168.1.101) 56(84) bytes of data.
|
||||
...
|
||||
|
||||
$ lookaround client
|
||||
|
||||
Found 3 peers:
|
||||
|
@ -15,29 +19,48 @@ Found 3 peers:
|
|||
LookAround is a Rust program for looking up your computers' MAC and IP addresses
|
||||
within a LAN. There's no central server, so it's not a look-up, it's a look-around.
|
||||
|
||||
The client uses IP multicast to find servers within the
|
||||
same multicast domain, similar to Avahi and Bonjour.
|
||||
## Installing
|
||||
|
||||
Systems self-identify by MAC address and nicknames. Public keys with
|
||||
TOFU semantics are intended before v1.0.0.
|
||||
|
||||
## Installation
|
||||
|
||||
Use the Cargo package manager from [Rust](https://rustup.rs/) to install LookAround.
|
||||
Make sure Cargo is installed from [RustUp.](https://rustup.rs/)
|
||||
|
||||
```bash
|
||||
# Install LookAround with Cargo
|
||||
cargo install lookaround
|
||||
|
||||
# Find your config directory
|
||||
# Prints something like `Using config dir "/home/user/.config/lookaround"`
|
||||
lookaround config
|
||||
```
|
||||
|
||||
To run the server as a normal user all the time,
|
||||
put this systemd unit in `~/.config/systemd/user/lookaround.service`:
|
||||
Create the files `client.ini` and/or `server.ini` in that directory
|
||||
(e.g. /home/user/.config/lookaround/server.ini)
|
||||
|
||||
```ini
|
||||
# Clients can store MAC-nickname pairs in client.ini, like a hosts file.
|
||||
# This is useful if your servers are short-lived and you want the clients
|
||||
# to be the source of truth for nicknames.
|
||||
[nicknames]
|
||||
11-11-11-11-11-11 = laptop
|
||||
22-22-22-22-22-22 = desktop
|
||||
```
|
||||
|
||||
```ini
|
||||
# Long-lived servers can have their nickname configured in server.ini
|
||||
[server]
|
||||
nickname = my-computer
|
||||
```
|
||||
|
||||
## Auto-Start (Linux)
|
||||
|
||||
Put this systemd unit in `~/.config/systemd/user/lookaround.service`:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=LookAround
|
||||
|
||||
[Service]
|
||||
ExecStart=/home/user/.cargo/bin/lookaround server --nickname my-desktop
|
||||
ExecStart=/home/user/.cargo/bin/lookaround server
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
@ -52,23 +75,43 @@ systemctl --user status lookaround
|
|||
systemctl --user enable lookaround
|
||||
```
|
||||
|
||||
## Auto-Start (Windows)
|
||||
|
||||
(untested)
|
||||
|
||||
- Create a shortcut to the LookAround exe
|
||||
- Change the shortcut's target to end in `lookaround.exe server` so it will run the server
|
||||
- Cut-paste the shortcut into the Startup folder in `C:\ProgramData\somewhere`
|
||||
|
||||
## Usage
|
||||
Run the server manually: (If you haven't installed it with systemd yet)
|
||||
Run the server manually: (To test before installing)
|
||||
|
||||
```bash
|
||||
lookaround server --nickname my-desktop
|
||||
lookaround server --nickname my-computer
|
||||
```
|
||||
|
||||
Run a client to ping all servers in the same multi-cast domain:
|
||||
On a client computer:
|
||||
|
||||
```bash
|
||||
# Use the `find-nick` subcommnad to find an IP...
|
||||
lookaround find-nick laptop
|
||||
# Prints `192.168.1.101`
|
||||
|
||||
# Or ping it...
|
||||
ping $(lookaround find-nick laptop)
|
||||
|
||||
# Or SSH to it...
|
||||
ssh user@$(lookaround find-nick laptop)
|
||||
|
||||
# Or pull a file from it
|
||||
# (after starting `nc -l -p 9000 < some-file` on the laptop)
|
||||
nc $(lookaround find-nick laptop) 9000
|
||||
|
||||
# Use the `client` subcommand to find all servers in the same multicast domain
|
||||
lookaround client
|
||||
```
|
||||
|
||||
Check which IP addresses LookAround will auto-detect:
|
||||
|
||||
```bash
|
||||
lookaround my-ips
|
||||
# Use a longer timeout if servers need more than 500 ms to respond
|
||||
lookaround client --timeout-ms 1000
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
|
4
ideas.md
4
ideas.md
|
@ -1,6 +1,6 @@
|
|||
Cool ideas that can be done but probably won't be.
|
||||
|
||||
- Command for shell substituting IPs into commands
|
||||
- Arbitrary TCP forwarding of (stdin? stdout? TCP?)
|
||||
- Advertise TCP services in server response
|
||||
- Arbitrary TCP forwarding of (stdin? stdout? TCP?) with interface cutover
|
||||
- Netcat replacement "Just send a file" _including filename_
|
||||
- Public-key crypto for trusting peers on first use (Hard cause it requires mutable disk state)
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const LOOKAROUND_VERSION: &str = env! ("CARGO_PKG_VERSION");
|
||||
|
||||
pub fn find_project_dirs () -> Option <ProjectDirs> {
|
||||
ProjectDirs::from ("", "ReactorScram", "LookAround")
|
||||
}
|
||||
|
||||
#[derive (Debug, thiserror::Error)]
|
||||
pub enum AppError {
|
||||
#[error (transparent)]
|
||||
AddrParse (#[from] std::net::AddrParseError),
|
||||
#[error (transparent)]
|
||||
CliArgs (#[from] CliArgError),
|
||||
#[error ("Operation timed out")]
|
||||
Elapsed (#[from] tokio::time::error::Elapsed),
|
||||
#[error (transparent)]
|
||||
Io (#[from] std::io::Error),
|
||||
#[error (transparent)]
|
||||
|
@ -17,6 +25,8 @@ pub enum AppError {
|
|||
#[error (transparent)]
|
||||
Message (#[from] crate::message::MessageError),
|
||||
#[error (transparent)]
|
||||
ParseInt (#[from] std::num::ParseIntError),
|
||||
#[error (transparent)]
|
||||
Tlv (#[from] crate::tlv::TlvError),
|
||||
}
|
||||
|
||||
|
@ -24,6 +34,8 @@ pub enum AppError {
|
|||
pub enum CliArgError {
|
||||
#[error ("Missing value for argument `{0}`")]
|
||||
MissingArgumentValue (String),
|
||||
#[error ("Missing required argument <{0}>")]
|
||||
MissingRequiredArg (String),
|
||||
#[error ("First argument should be a subcommand")]
|
||||
MissingSubcommand,
|
||||
#[error ("Unknown subcommand `{0}`")]
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
type Mac = [u8; 6];
|
||||
|
||||
pub fn debug () {
|
||||
for input in [
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 1],
|
||||
] {
|
||||
assert_eq! (unmix (mix (input)), input);
|
||||
}
|
||||
|
||||
println! ("Passed");
|
||||
}
|
||||
|
||||
// NOT intended for any cryptography or security. This is TRIVIALLY reversible.
|
||||
// It's just to make it easier for humans to tell apart MACs where only a couple
|
||||
// numbers differ.
|
||||
|
||||
fn mix (i: Mac) -> Mac {
|
||||
[
|
||||
i [0] ^ i [5],
|
||||
i [1] ^ i [4],
|
||||
i [2] ^ i [3],
|
||||
i [3],
|
||||
i [4],
|
||||
i [5],
|
||||
]
|
||||
}
|
||||
|
||||
fn unmix (i: Mac) -> Mac {
|
||||
[
|
||||
i [0] ^ i [5],
|
||||
i [1] ^ i [4],
|
||||
i [2] ^ i [3],
|
||||
i [3],
|
||||
i [4],
|
||||
i [5],
|
||||
]
|
||||
}
|
275
src/client.rs
275
src/client.rs
|
@ -5,52 +5,34 @@ struct ServerResponse {
|
|||
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_addrs = vec! [];
|
||||
|
||||
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)?
|
||||
});
|
||||
},
|
||||
_ => return Err (CliArgError::UnrecognizedArgument (arg).into ()),
|
||||
struct ConfigFile {
|
||||
nicknames: HashMap <String, String>,
|
||||
}
|
||||
|
||||
struct ClientParams {
|
||||
common: app_common::Params,
|
||||
bind_addrs: Vec <Ipv4Addr>,
|
||||
nicknames: HashMap <String, String>,
|
||||
timeout_ms: u64,
|
||||
}
|
||||
|
||||
pub async fn client <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),
|
||||
}
|
||||
|
||||
if bind_addrs.is_empty () {
|
||||
bind_addrs = get_ips ()?;
|
||||
}
|
||||
|
||||
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 ()?;
|
||||
|
||||
for _ in 0..10 {
|
||||
socket.send_to (&msg, (common_params.multicast_addr, common_params.server_port)).await?;
|
||||
sleep (Duration::from_millis (100)).await;
|
||||
}
|
||||
let params = configure_client (args)?;
|
||||
let socket = make_socket (¶ms.common, params.bind_addrs).await?;
|
||||
let msg = Message::new_request1 ().to_vec ()?;
|
||||
tokio::spawn (send_requests (Arc::clone (&socket), params.common, msg));
|
||||
|
||||
let mut peers = HashMap::with_capacity (10);
|
||||
|
||||
timeout (Duration::from_secs (2), listen_for_responses (&socket, &mut peers)).await.ok ();
|
||||
timeout (Duration::from_millis (params.timeout_ms), listen_for_responses (&*socket, params.nicknames, &mut peers)).await.ok ();
|
||||
|
||||
let mut peers: Vec <_> = peers.into_iter ().collect ();
|
||||
peers.sort_by_key (|(_, v)| v.mac);
|
||||
|
@ -79,8 +61,162 @@ pub async fn client <I : Iterator <Item=String>> (mut args: I) -> Result <(), Ap
|
|||
Ok (())
|
||||
}
|
||||
|
||||
pub async fn find_nick <I: Iterator <Item=String>> (mut args: I) -> Result <(), AppError>
|
||||
{
|
||||
let mut nick = None;
|
||||
let mut timeout_ms = 500;
|
||||
let ConfigFile {
|
||||
nicknames,
|
||||
} = load_config_file ();
|
||||
|
||||
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 common_params = Default::default ();
|
||||
|
||||
let socket = make_socket (&common_params, get_ips ()?).await?;
|
||||
let msg = Message::new_request1 ().to_vec ()?;
|
||||
tokio::spawn (send_requests (Arc::clone (&socket), common_params, msg));
|
||||
|
||||
timeout (Duration::from_millis (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),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
resp.nickname = get_peer_nickname (&nicknames, resp.mac, resp.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;
|
||||
|
||||
let ConfigFile {
|
||||
nicknames,
|
||||
} = load_config_file ();
|
||||
|
||||
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,
|
||||
nicknames,
|
||||
timeout_ms,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_config_file () -> ConfigFile {
|
||||
let mut nicknames: HashMap <String, String> = Default::default ();
|
||||
|
||||
if let Some (proj_dirs) = find_project_dirs () {
|
||||
let mut ini = Ini::new_cs ();
|
||||
let path = proj_dirs.config_local_dir ().join ("client.ini");
|
||||
if ini.load (&path).is_ok () {
|
||||
let map_ref = ini.get_map_ref ();
|
||||
if let Some (x) = map_ref.get ("nicknames") {
|
||||
for (k, v) in x {
|
||||
if let Some (v) = v {
|
||||
let k = k.replace ('-', ":");
|
||||
nicknames.insert (k, v.to_string ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigFile {
|
||||
nicknames,
|
||||
}
|
||||
}
|
||||
|
||||
async fn make_socket (
|
||||
common_params: &app_common::Params,
|
||||
bind_addrs: Vec <Ipv4Addr>,
|
||||
) -> Result <Arc <UdpSocket>, AppError> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
socket: &UdpSocket,
|
||||
socket: &UdpSocket,
|
||||
nicknames: HashMap <String, String>,
|
||||
peers: &mut HashMap <SocketAddr, ServerResponse>
|
||||
) {
|
||||
loop {
|
||||
|
@ -102,6 +238,63 @@ async fn listen_for_responses (
|
|||
}
|
||||
}
|
||||
|
||||
resp.nickname = get_peer_nickname (&nicknames, resp.mac, resp.nickname);
|
||||
|
||||
peers.insert (remote_addr, resp);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_peer_nickname (
|
||||
nicknames: &HashMap <String, String>,
|
||||
mac: Option <[u8; 6]>,
|
||||
peer_nickname: Option <String>
|
||||
) -> Option <String>
|
||||
{
|
||||
match peer_nickname.as_deref () {
|
||||
None => (),
|
||||
Some ("") => (),
|
||||
_ => return peer_nickname,
|
||||
}
|
||||
|
||||
if let Some (mac) = &mac {
|
||||
return nicknames.get (&format! ("{}", MacAddress::new (*mac))).cloned ()
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg (test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_nicknames () {
|
||||
let mut nicks = HashMap::new ();
|
||||
|
||||
for (k, v) in [
|
||||
("01:01:01:01:01:01", "phoenix")
|
||||
] {
|
||||
nicks.insert (k.to_string (), v.to_string ());
|
||||
}
|
||||
|
||||
for (num, (mac, peer_nickname), expected) in [
|
||||
// Somehow the server returns no MAC nor nick. In this case we are helpless
|
||||
( 1, (None, None), None),
|
||||
// If the server tells us its MAC, we can look up our nickname for it
|
||||
( 2, (Some ([1, 1, 1, 1, 1, 1]), None), Some ("phoenix")),
|
||||
// Unless it's not in our nick list.
|
||||
( 3, (Some ([1, 1, 1, 1, 1, 2]), None), None),
|
||||
// If the server tells us its nickname, that always takes priority
|
||||
( 4, (None, Some ("snowflake")), Some ("snowflake")),
|
||||
( 5, (Some ([1, 1, 1, 1, 1, 1]), Some ("snowflake")), Some ("snowflake")),
|
||||
( 6, (Some ([1, 1, 1, 1, 1, 2]), Some ("snowflake")), Some ("snowflake")),
|
||||
// But blank nicknames are treated like None
|
||||
( 7, (None, Some ("")), None),
|
||||
( 8, (Some ([1, 1, 1, 1, 1, 1]), Some ("")), Some ("phoenix")),
|
||||
( 9, (Some ([1, 1, 1, 1, 1, 2]), Some ("")), None),
|
||||
] {
|
||||
let actual = get_peer_nickname (&nicks, mac, peer_nickname.map (str::to_string));
|
||||
assert_eq! (actual.as_ref ().map (String::as_str), expected, "{}", num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -1,12 +1,13 @@
|
|||
use prelude::*;
|
||||
|
||||
pub mod app_common;
|
||||
mod avalanche;
|
||||
mod client;
|
||||
mod ip;
|
||||
pub mod message;
|
||||
mod prelude;
|
||||
mod server;
|
||||
mod tlv;
|
||||
pub mod tlv;
|
||||
|
||||
fn main () -> Result <(), AppError> {
|
||||
let rt = tokio::runtime::Builder::new_current_thread ()
|
||||
|
@ -24,19 +25,15 @@ async fn async_main () -> Result <(), AppError> {
|
|||
|
||||
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 ();
|
||||
|
||||
match subcommand.as_ref ().map (|x| &x[..]) {
|
||||
None => return Err (CliArgError::MissingSubcommand.into ()),
|
||||
Some ("--version") => println! ("lookaround v{}", LOOKAROUND_VERSION),
|
||||
Some ("client") => client::client (args).await?,
|
||||
Some ("config") => config (),
|
||||
Some ("debug-avalanche") => avalanche::debug (),
|
||||
Some ("find-nick") => client::find_nick (args).await?,
|
||||
Some ("my-ips") => my_ips ()?,
|
||||
Some ("server") => server::server (args).await?,
|
||||
Some (x) => return Err (CliArgError::UnknownSubcommand (x.to_string ()).into ()),
|
||||
|
@ -45,6 +42,15 @@ async fn async_main () -> Result <(), AppError> {
|
|||
Ok (())
|
||||
}
|
||||
|
||||
fn config () {
|
||||
if let Some (proj_dirs) = ProjectDirs::from ("", "ReactorScram", "LookAround") {
|
||||
println! ("Using config dir {:?}", proj_dirs.config_local_dir ());
|
||||
}
|
||||
else {
|
||||
println! ("Can't detect config dir.");
|
||||
}
|
||||
}
|
||||
|
||||
fn my_ips () -> Result <(), AppError> {
|
||||
for addr in ip::get_ips ()?
|
||||
{
|
||||
|
|
|
@ -1,13 +1,4 @@
|
|||
use std::{
|
||||
io::{
|
||||
Cursor,
|
||||
Write,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::tlv;
|
||||
|
||||
use thiserror::Error;
|
||||
use crate::prelude::*;
|
||||
|
||||
const MAGIC_NUMBER: [u8; 4] = [0x9a, 0x4a, 0x43, 0x81];
|
||||
pub const PACKET_SIZE: usize = 1024;
|
||||
|
@ -27,13 +18,25 @@ pub enum Message {
|
|||
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)]
|
||||
pub struct Response2 {
|
||||
pub idem_id: [u8; 8],
|
||||
pub nickname: String,
|
||||
}
|
||||
|
||||
#[derive (Debug, Error)]
|
||||
#[derive (Debug, thiserror::Error)]
|
||||
pub enum MessageError {
|
||||
#[error (transparent)]
|
||||
Io (#[from] std::io::Error),
|
||||
|
|
|
@ -1,19 +1,30 @@
|
|||
pub use std::{
|
||||
collections::HashMap,
|
||||
env,
|
||||
io::{
|
||||
Cursor,
|
||||
Write,
|
||||
},
|
||||
net::{
|
||||
Ipv4Addr,
|
||||
SocketAddr,
|
||||
SocketAddrV4,
|
||||
},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
sync::Arc,
|
||||
time::{
|
||||
Duration,
|
||||
Instant,
|
||||
},
|
||||
};
|
||||
|
||||
pub use configparser::ini::Ini;
|
||||
pub use directories::ProjectDirs;
|
||||
pub use mac_address::{
|
||||
MacAddress,
|
||||
get_mac_address,
|
||||
};
|
||||
pub use rand::RngCore;
|
||||
pub use tokio::{
|
||||
net::UdpSocket,
|
||||
time::{
|
||||
|
@ -25,8 +36,10 @@ pub use tokio::{
|
|||
pub use crate::{
|
||||
app_common::{
|
||||
self,
|
||||
LOOKAROUND_VERSION,
|
||||
AppError,
|
||||
CliArgError,
|
||||
find_project_dirs,
|
||||
recv_msg_from,
|
||||
},
|
||||
ip::get_ips,
|
||||
|
@ -35,4 +48,5 @@ pub use crate::{
|
|||
PACKET_SIZE,
|
||||
Message,
|
||||
},
|
||||
tlv,
|
||||
};
|
||||
|
|
|
@ -10,6 +10,14 @@ struct Params {
|
|||
|
||||
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 socket = UdpSocket::bind (SocketAddrV4::new (Ipv4Addr::UNSPECIFIED, params.common.server_port)).await?;
|
||||
|
@ -31,6 +39,23 @@ fn configure <I: Iterator <Item=String>> (mut args: I) -> Result <Params, AppErr
|
|||
let mut bind_addrs = vec![];
|
||||
let mut nickname = String::new ();
|
||||
|
||||
if let Some (proj_dirs) = find_project_dirs () {
|
||||
let mut ini = Ini::new_cs ();
|
||||
let path = proj_dirs.config_local_dir ().join ("server.ini");
|
||||
if ini.load (&path).is_ok () {
|
||||
if let Some (x) = ini.get ("server", "nickname") {
|
||||
nickname = x;
|
||||
eprintln! ("Loaded nickname {:?}", nickname);
|
||||
}
|
||||
}
|
||||
else {
|
||||
eprintln! ("Can't load ini from {:?}, didn't load default configs", path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
eprintln! ("Can't find config dir, didn't load default configs");
|
||||
}
|
||||
|
||||
while let Some (arg) = args.next () {
|
||||
match arg.as_str () {
|
||||
"--bind-addr" => {
|
||||
|
|
Loading…
Reference in New Issue