mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-11-06 08:15:35 +00:00
fix(mobile): keep reaching the computer after the gateway restarts
Every `serve` bound a random port, so a restart left each phone holding addresses that no longer answered. Where the n0 relays are unreachable, which is common behind the Great Firewall, the phone had no other way to find the gateway until it was paired again. - `serve` listens on the same UDP port every time. It picks one on first run, keeps it in `<config dir>/mobile/port`, and moves only if the port is taken. IPv6 binding may fail, as in iroh's own defaults. - Both ends add mDNS lookup (`iroh-mdns-address-lookup`, service `_tty7._udp`). On the same network a phone finds the gateway by key when its addresses are stale, such as after a new DHCP lease or a new IPv6 prefix. The gateway advertises and the phone only listens. If multicast is refused, each side starts without it and says so, rather than failing. - iOS: `Info.ios.plist` declares the local-network use and the Bonjour service, without which iOS blocks multicast. - An ignored test (`--test mdns`) connects by key alone over mDNS, for a machine that allows multicast.
This commit is contained in:
Generated
+88
-2
@@ -101,6 +101,21 @@ dependencies = [
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "acto"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598381761ee991bf2f1455f700380e2191fb370dc9df1ee764f348b7f089d8b6"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"rustc_version",
|
||||
"smol_str 0.1.24",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.25.1"
|
||||
@@ -1894,7 +1909,7 @@ dependencies = [
|
||||
"rustc-hash 2.1.3",
|
||||
"self_cell",
|
||||
"skrifa 0.40.0",
|
||||
"smol_str",
|
||||
"smol_str 0.3.6",
|
||||
"swash",
|
||||
"sys-locale",
|
||||
"unicode-bidi",
|
||||
@@ -1936,6 +1951,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.16"
|
||||
@@ -3792,7 +3813,7 @@ source = "git+https://github.com/l0ng-ai/zed?branch=tty7#5c390b9359e39f645ed6819
|
||||
dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"smol_str",
|
||||
"smol_str 0.3.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4097,6 +4118,24 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"jni 0.22.4",
|
||||
"once_cell",
|
||||
"rand 0.10.2",
|
||||
"thiserror 2.0.20",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
@@ -4795,6 +4834,26 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iroh-mdns-address-lookup"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cad3dfcaddd5c3681bf0a42606498fc6e8891de793408dfbc46a9fbe76f370b3"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"iroh",
|
||||
"iroh-base",
|
||||
"n0-error",
|
||||
"n0-future",
|
||||
"n0-watcher",
|
||||
"swarm-discovery",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iroh-metrics"
|
||||
version = "1.0.2"
|
||||
@@ -6842,6 +6901,10 @@ name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
@@ -9498,6 +9561,12 @@ dependencies = [
|
||||
"futures-lite 2.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.3.6"
|
||||
@@ -9895,6 +9964,21 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swarm-discovery"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b4877f20f064de34b77d79237c9caa37c393590af8ef5a2f3253d59be5a338b"
|
||||
dependencies = [
|
||||
"acto",
|
||||
"hickory-proto",
|
||||
"rand 0.10.2",
|
||||
"socket2",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swash"
|
||||
version = "0.2.10"
|
||||
@@ -11109,6 +11193,7 @@ dependencies = [
|
||||
"clap",
|
||||
"getrandom 0.3.4",
|
||||
"iroh",
|
||||
"iroh-mdns-address-lookup",
|
||||
"libc",
|
||||
"qrcode",
|
||||
"serde",
|
||||
@@ -11126,6 +11211,7 @@ version = "26.9.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"iroh",
|
||||
"iroh-mdns-address-lookup",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
|
||||
@@ -24,6 +24,10 @@ clap = { version = "4", features = ["derive"] }
|
||||
# cannot — so a phone on cellular reaches a laptop behind a home router with no
|
||||
# port forwarding and no VPN, end-to-end encrypted either way.
|
||||
iroh = "1.2"
|
||||
# Lets a phone on the same network find the gateway by its key when the
|
||||
# addresses in its pairing code have gone stale — a new DHCP lease, a new IPv6
|
||||
# prefix — with no relay reachable to ask instead.
|
||||
iroh-mdns-address-lookup = "0.5"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "io-util"] }
|
||||
getrandom = "0.3"
|
||||
# Draws the pairing code as a QR code right in the terminal.
|
||||
|
||||
@@ -3,12 +3,15 @@ use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use clap::{Parser, Subcommand};
|
||||
use iroh::Endpoint;
|
||||
use iroh::endpoint::presets;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
|
||||
use iroh::endpoint::{BindOpts, presets};
|
||||
use iroh::{Endpoint, SecretKey};
|
||||
use iroh_mdns_address_lookup::MdnsAddressLookup;
|
||||
use tty7_gateway::daemon::{Daemon, hostname};
|
||||
use tty7_gateway::serve::{self, Backend as _};
|
||||
use tty7_gateway::state::{Reachable, State};
|
||||
use tty7_mobile_proto::{ALPN, PairCode};
|
||||
use tty7_mobile_proto::{ALPN, MDNS_SERVICE, PairCode};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
@@ -82,12 +85,16 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
async fn serve_forever(state: State, daemon: Arc<Daemon>) -> Result<()> {
|
||||
let endpoint = Endpoint::builder(presets::N0)
|
||||
.secret_key(state.secret_key()?)
|
||||
.alpns(vec![ALPN.to_vec()])
|
||||
.bind()
|
||||
.await
|
||||
.context("starting the iroh endpoint")?;
|
||||
let endpoint = bind(state.secret_key()?, state.port()).await?;
|
||||
if let Some(port) = endpoint
|
||||
.bound_sockets()
|
||||
.iter()
|
||||
.map(SocketAddr::port)
|
||||
.find(|&p| p != 0)
|
||||
&& state.port() != Some(port)
|
||||
{
|
||||
state.set_port(port).context("remembering the port")?;
|
||||
}
|
||||
println!("tty7-gateway: listening as {}", endpoint.id());
|
||||
|
||||
// Keep the addresses `pair` puts in a code current. The local ones are
|
||||
@@ -116,6 +123,54 @@ async fn serve_forever(state: State, daemon: Arc<Daemon>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Binds the gateway's endpoint on the port it had last time, so the
|
||||
/// addresses in phones' pairing codes still reach it, and advertises it on the
|
||||
/// local network. Each of those is given up, with a note, rather than let it
|
||||
/// keep the gateway from starting: the port may be taken, and multicast may be
|
||||
/// off.
|
||||
async fn bind(key: SecretKey, port: Option<u16>) -> Result<Endpoint> {
|
||||
let mut attempts = Vec::new();
|
||||
if let Some(port) = port {
|
||||
attempts.extend([(port, true), (port, false)]);
|
||||
}
|
||||
attempts.extend([(0, true), (0, false)]);
|
||||
|
||||
let mut failures = Vec::new();
|
||||
for (port, mdns) in attempts {
|
||||
let builder = Endpoint::builder(presets::N0)
|
||||
.secret_key(key.clone())
|
||||
.alpns(vec![ALPN.to_vec()])
|
||||
.clear_ip_transports()
|
||||
.bind_addr(SocketAddr::from((Ipv4Addr::UNSPECIFIED, port)))?
|
||||
// IPv6 is a bonus, as it is in iroh's own defaults.
|
||||
.bind_addr_with_opts(
|
||||
SocketAddr::from((Ipv6Addr::UNSPECIFIED, port)),
|
||||
BindOpts::default().set_is_required(false),
|
||||
)?;
|
||||
let builder = match mdns {
|
||||
true => builder.address_lookup(MdnsAddressLookup::builder().service_name(MDNS_SERVICE)),
|
||||
false => builder,
|
||||
};
|
||||
match builder.bind().await {
|
||||
Ok(endpoint) => {
|
||||
for failure in failures {
|
||||
eprintln!("tty7-gateway: {failure} — carrying on without it");
|
||||
}
|
||||
return Ok(endpoint);
|
||||
}
|
||||
Err(e) => {
|
||||
let what = match (port, mdns) {
|
||||
(0, true) => "local network discovery".to_string(),
|
||||
(0, false) => "the iroh endpoint".to_string(),
|
||||
(port, _) => format!("port {port}"),
|
||||
};
|
||||
failures.push(format!("{what}: {e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow::bail!("could not start: {}", failures.join("; "))
|
||||
}
|
||||
|
||||
fn pair(state: &State, ttl: u64) -> Result<()> {
|
||||
let host_id = state.secret_key()?.public().to_string();
|
||||
let reachable = state.reachable();
|
||||
|
||||
@@ -21,6 +21,7 @@ const DEVICES_FILE: &str = "devices.json";
|
||||
const PAIRING_FILE: &str = "pairing.json";
|
||||
const ADDR_FILE: &str = "addr.json";
|
||||
const LOCK_FILE: &str = "serve.lock";
|
||||
const PORT_FILE: &str = "port";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State {
|
||||
@@ -194,6 +195,22 @@ impl State {
|
||||
pairing.expires_at >= unix_now() && constant_time_eq(&pairing.secret, secret)
|
||||
}
|
||||
|
||||
/// The UDP port `serve` last listened on. A phone dials the addresses in
|
||||
/// its pairing code first, so keeping the port keeps those addresses good
|
||||
/// across restarts.
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
fs::read_to_string(self.dir.join(PORT_FILE))
|
||||
.ok()?
|
||||
.trim()
|
||||
.parse()
|
||||
.ok()
|
||||
.filter(|&port| port != 0)
|
||||
}
|
||||
|
||||
pub fn set_port(&self, port: u16) -> Result<()> {
|
||||
write_private(&self.dir.join(PORT_FILE), port.to_string().as_bytes())
|
||||
}
|
||||
|
||||
pub fn reachable(&self) -> Reachable {
|
||||
read_json(&self.dir.join(ADDR_FILE))
|
||||
.ok()
|
||||
@@ -324,6 +341,14 @@ mod tests {
|
||||
.expect("free again once the holder is gone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_port_is_kept() {
|
||||
let (_tmp, state) = state();
|
||||
assert_eq!(state.port(), None);
|
||||
state.set_port(41641).unwrap();
|
||||
assert_eq!(state.port(), Some(41641));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn devices_are_added_replaced_and_revoked() {
|
||||
let (_tmp, state) = state();
|
||||
|
||||
@@ -15,10 +15,13 @@ tty7-mobile-proto = { path = "../tty7-mobile-proto", features = ["tokio"] }
|
||||
anyhow.workspace = true
|
||||
serde = { workspace = true }
|
||||
iroh = "1.2"
|
||||
# Finds a paired gateway on the local network after its addresses change.
|
||||
iroh-mdns-address-lookup = "0.5"
|
||||
tokio = { version = "1", features = ["sync", "time", "io-util"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
||||
tty7-mobile-proto = { path = "../tty7-mobile-proto" }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -11,10 +11,11 @@ use std::time::Duration;
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use iroh::endpoint::{Connection, RecvStream, SendStream, presets};
|
||||
use iroh::{Endpoint, EndpointAddr, EndpointId, RelayUrl, SecretKey};
|
||||
use iroh_mdns_address_lookup::MdnsAddressLookup;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tty7_mobile_proto::{
|
||||
ALPN, ControlEvent, ControlRequest, Frame, GridSize, Open, OpenReply, PROTOCOL_VERSION,
|
||||
PairCode, PaneEvent, TabCreated, read_frame, write_bytes, write_msg,
|
||||
ALPN, ControlEvent, ControlRequest, Frame, GridSize, MDNS_SERVICE, Open, OpenReply,
|
||||
PROTOCOL_VERSION, PairCode, PaneEvent, TabCreated, read_frame, write_bytes, write_msg,
|
||||
};
|
||||
|
||||
/// How long to wait for a gateway to answer an [`Open`].
|
||||
@@ -52,13 +53,28 @@ impl Host {
|
||||
}
|
||||
}
|
||||
|
||||
/// Binds the phone's endpoint with iroh's public relays and address lookup.
|
||||
/// Binds the phone's endpoint with iroh's public relays and address lookup,
|
||||
/// plus mDNS: on the gateway's own network that finds it by key even when the
|
||||
/// addresses the phone knows are stale and no relay can be reached. The phone
|
||||
/// only listens; it has no reason to announce itself. Where multicast is not
|
||||
/// allowed it does without.
|
||||
pub async fn bind(secret: SecretKey) -> Result<Endpoint> {
|
||||
Endpoint::builder(presets::N0)
|
||||
.secret_key(secret)
|
||||
let mdns = MdnsAddressLookup::builder()
|
||||
.service_name(MDNS_SERVICE)
|
||||
.advertise(false);
|
||||
match Endpoint::builder(presets::N0)
|
||||
.secret_key(secret.clone())
|
||||
.address_lookup(mdns)
|
||||
.bind()
|
||||
.await
|
||||
.context("starting the iroh endpoint")
|
||||
{
|
||||
Ok(endpoint) => Ok(endpoint),
|
||||
Err(_) => Endpoint::builder(presets::N0)
|
||||
.secret_key(secret)
|
||||
.bind()
|
||||
.await
|
||||
.context("starting the iroh endpoint"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Trades a pairing code for a [`Host`] the app can store and dial later.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//! A phone finds a gateway on the local network by key alone, with none of
|
||||
//! its addresses known and no relay or DNS to ask — what happens when every
|
||||
//! address in the pairing code has gone stale.
|
||||
//!
|
||||
//! Ignored by default: it needs multicast on the machine's network, which CI
|
||||
//! runners and sandboxes may not allow. `cargo test -p tty7-mobile-client
|
||||
//! --test mdns -- --ignored`.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use iroh::endpoint::presets;
|
||||
use iroh::{Endpoint, EndpointAddr, SecretKey};
|
||||
use iroh_mdns_address_lookup::MdnsAddressLookup;
|
||||
use tty7_mobile_proto::{ALPN, MDNS_SERVICE};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[ignore = "needs multicast on the local network"]
|
||||
async fn a_gateway_is_found_by_key_on_the_local_network() {
|
||||
let gateway = Endpoint::builder(presets::Minimal)
|
||||
.secret_key(SecretKey::generate())
|
||||
.alpns(vec![ALPN.to_vec()])
|
||||
.address_lookup(MdnsAddressLookup::builder().service_name(MDNS_SERVICE))
|
||||
.bind()
|
||||
.await
|
||||
.unwrap();
|
||||
let accept = tokio::spawn({
|
||||
let gateway = gateway.clone();
|
||||
async move { gateway.accept().await.unwrap().await.unwrap() }
|
||||
});
|
||||
|
||||
let phone = tty7_mobile_client::bind(SecretKey::generate())
|
||||
.await
|
||||
.unwrap();
|
||||
let only_the_key = EndpointAddr::new(gateway.id());
|
||||
let conn = tokio::time::timeout(Duration::from_secs(20), phone.connect(only_the_key, ALPN))
|
||||
.await
|
||||
.expect("found within 20 s")
|
||||
.expect("connected");
|
||||
assert_eq!(conn.remote_id(), gateway.id());
|
||||
accept.await.unwrap();
|
||||
}
|
||||
@@ -30,6 +30,10 @@ use serde::{Deserialize, Serialize};
|
||||
/// during the handshake, before a byte of this protocol is read.
|
||||
pub const ALPN: &[u8] = b"tty7/mobile/1";
|
||||
|
||||
/// The mDNS service both ends use to find each other on a local network,
|
||||
/// whatever addresses the gateway has moved to since pairing.
|
||||
pub const MDNS_SERVICE: &str = "tty7";
|
||||
|
||||
/// Bumped when a message changes shape. Carried in [`OpenReply::Ok`] so an app
|
||||
/// can tell the user to update rather than misread a newer gateway.
|
||||
pub const PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
@@ -75,6 +75,20 @@ cargo run -p tty7-mobile-client --example probe -- tree
|
||||
cargo run -p tty7-mobile-client --example probe -- type 1 'echo hi'
|
||||
```
|
||||
|
||||
## Finding the computer again
|
||||
|
||||
A pairing code carries the gateway's addresses at the time it was made. Those
|
||||
stay good across restarts: `serve` listens on the same UDP port every time
|
||||
(`<config dir>/mobile/port`, a fresh one only if it is taken). When they go stale
|
||||
anyway — a new DHCP lease, a new IPv6 prefix — the phone looks the gateway up by
|
||||
key: through n0's relay and DNS where those are reachable, and by mDNS
|
||||
(`_tty7._udp`) on the same local network where they are not.
|
||||
|
||||
mDNS needs the OS's local-network permission on both ends. On macOS that belongs
|
||||
to whatever launched the gateway (your terminal), and it answers "Allow … to find
|
||||
devices on local networks" once. On iOS the app declares it in
|
||||
`src-tauri/Info.ios.plist`. Without it, known addresses and the relay still work.
|
||||
|
||||
## Not done yet
|
||||
|
||||
- **QR scanning.** The app takes a pasted code for now. Next step is
|
||||
|
||||
Generated
+85
@@ -2,6 +2,21 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "acto"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598381761ee991bf2f1455f700380e2191fb370dc9df1ee764f348b7f089d8b6"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"rustc_version",
|
||||
"smol_str",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
@@ -605,6 +620,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.17"
|
||||
@@ -1722,6 +1743,24 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hickory-proto"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b92608f679a6fa515dd1d15c1ff89443026e391200a2c840c7afcba482893d"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"jni 0.22.4",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"thiserror 2.0.21",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.38.0"
|
||||
@@ -2169,6 +2208,26 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iroh-mdns-address-lookup"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cad3dfcaddd5c3681bf0a42606498fc6e8891de793408dfbc46a9fbe76f370b3"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"iroh",
|
||||
"iroh-base",
|
||||
"n0-error",
|
||||
"n0-future",
|
||||
"n0-watcher",
|
||||
"swarm-discovery",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iroh-metrics"
|
||||
version = "1.0.2"
|
||||
@@ -3295,6 +3354,10 @@ name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -4429,6 +4492,12 @@ version = "1.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.5"
|
||||
@@ -4583,6 +4652,21 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swarm-discovery"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b4877f20f064de34b77d79237c9caa37c393590af8ef5a2f3253d59be5a338b"
|
||||
dependencies = [
|
||||
"acto",
|
||||
"hickory-proto",
|
||||
"rand",
|
||||
"socket2",
|
||||
"thiserror 2.0.21",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.8"
|
||||
@@ -5418,6 +5502,7 @@ version = "26.9.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"iroh",
|
||||
"iroh-mdns-address-lookup",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tty7-mobile-proto",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- The app finds a paired computer on the local network by mDNS when the
|
||||
addresses in its pairing code have changed (tty7-mobile-client::bind).
|
||||
iOS blocks multicast without both of these. -->
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>tty7 looks for your paired computer on this network, so it can reach it after its address changes.</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_tty7._udp</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user