4 Commits
Author SHA1 Message Date
Bendik Aagaard LynghaugandClaude Fable 5.1 0f2b0504d5 release v0.1.7
ci / quality (push) Failing after 5s
release / build (aarch64-unknown-linux-gnu) (push) Successful in 2m30s
release / build (x86_64-unknown-linux-gnu) (push) Successful in 2m16s
release / publish (push) Successful in 46s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 13:45:20 +02:00
Bendik Aagaard LynghaugandClaude Fable 5.1 08fcba749a iroh 1.3, and a socket that survives a service restart
iroh 1.0.2 -> 1.3.0 (relay reconnects that keep answering, rate-limit
notices from relays, the n0 DNS resolver with configurable fallbacks,
relay connection metrics), iroh-mdns-address-lookup 0.4 -> 0.6,
n0-future 0.1 -> 0.3. No code changes were needed; the suite passes.

varde.service now keeps /run/varde across restarts
(RuntimeDirectoryPreserve=yes): stopping the service removed the
directory and with it the socket unit's file, leaving varde.socket
"active" on an unlinked path after any service restart. The install
notes say to restart the socket, which cycles both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 13:43:43 +02:00
Bendik Aagaard LynghaugandClaude Opus 5.5 6ca859c691 release v0.1.6
ci / quality (push) Failing after 5s
release / build (aarch64-unknown-linux-gnu) (push) Successful in 2m25s
release / build (x86_64-unknown-linux-gnu) (push) Successful in 2m0s
release / publish (push) Successful in 48s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 13:30:44 +02:00
Bendik Aagaard LynghaugandClaude Opus 5.5 c2b9167e1e Pushes name the peer by ticket, and an allowance can confirm its push
PeerAllowPush and Push take a blob ticket the peer issued as well as a
NodeId; the ticket's addresses are remembered, so the push dials
directly. A peer allowed to push may observe too: the sender confirms a
push by observing it, and without that every allowance-only push
reported failure though it had landed. The allowance test now fails
instead of skipping on anything but an unreachable peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 13:29:03 +02:00
10 changed files with 98 additions and 94 deletions
Generated
+33 -75
View File
@@ -780,7 +780,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090"
dependencies = [
"data-encoding",
"syn 2.0.118",
"syn 1.0.109",
]
[[package]]
@@ -845,34 +845,13 @@ dependencies = [
"syn 2.0.118",
]
[[package]]
name = "derive_more"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
dependencies = [
"derive_more-impl 1.0.0",
]
[[package]]
name = "derive_more"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
dependencies = [
"derive_more-impl 2.1.1",
]
[[package]]
name = "derive_more-impl"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.118",
"unicode-xid",
"derive_more-impl",
]
[[package]]
@@ -1818,7 +1797,7 @@ dependencies = [
"cfg_aliases",
"ctutils",
"data-encoding",
"derive_more 2.1.1",
"derive_more",
"ed25519-dalek",
"futures-util",
"getrandom 0.4.3",
@@ -1830,7 +1809,7 @@ dependencies = [
"iroh-metrics",
"iroh-relay",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"n0-watcher",
"netwatch",
"noq",
@@ -1866,7 +1845,7 @@ dependencies = [
"curve25519-dalek",
"data-encoding",
"data-encoding-macro",
"derive_more 2.1.1",
"derive_more",
"ed25519-dalek",
"getrandom 0.4.3",
"n0-error",
@@ -1889,7 +1868,7 @@ dependencies = [
"chrono",
"constant_time_eq",
"data-encoding",
"derive_more 2.1.1",
"derive_more",
"genawaiter",
"getrandom 0.4.3",
"hex",
@@ -1901,7 +1880,7 @@ dependencies = [
"iroh-util",
"irpc",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"nested_enum_utils",
"noq",
"postcard",
@@ -1925,11 +1904,11 @@ checksum = "516e4eedc38e33ab69a6bd325520332dc3d67b25454e2d590ebb84a25240dd9a"
dependencies = [
"arc-swap",
"cfg_aliases",
"derive_more 2.1.1",
"derive_more",
"hickory-resolver",
"iroh-base",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"ndk-context",
"portable-atomic",
"rand 0.10.2",
@@ -1956,17 +1935,17 @@ dependencies = [
[[package]]
name = "iroh-mdns-address-lookup"
version = "0.4.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c1f941695731bc3999d8c13d7e94d50e229427da81ac957eec3818264f17559"
checksum = "7afb03c296022fcb724a7bb76dbb8dcca6aac4f4eb65df161e084b7276c93d8c"
dependencies = [
"data-encoding",
"derive_more 2.1.1",
"derive_more",
"futures-util",
"iroh",
"iroh-base",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"n0-watcher",
"swarm-discovery",
"tokio",
@@ -2011,7 +1990,7 @@ dependencies = [
"bytes",
"cfg_aliases",
"data-encoding",
"derive_more 2.1.1",
"derive_more",
"getrandom 0.4.3",
"hickory-resolver",
"http",
@@ -2023,7 +2002,7 @@ dependencies = [
"iroh-metrics",
"lru",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"noq",
"noq-proto",
"num_enum",
@@ -2054,7 +2033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da53233419ca36bf521ed45683b7748366f9b233032891eefc2d70567a84ac54"
dependencies = [
"data-encoding",
"derive_more 2.1.1",
"derive_more",
"iroh-base",
"n0-error",
"postcard",
@@ -2067,10 +2046,10 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20e41eb982f15230c55f0a70a74a514360e1f565b07861924fd0e8db172b3d00"
dependencies = [
"derive_more 2.1.1",
"derive_more",
"iroh",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"tokio",
"tracing",
]
@@ -2085,7 +2064,7 @@ dependencies = [
"futures-util",
"irpc-derive",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"noq",
"postcard",
"rcgen",
@@ -2363,27 +2342,6 @@ dependencies = [
"syn 2.0.118",
]
[[package]]
name = "n0-future"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bb0e5d99e681ab3c938842b96fcb41bf8a7bb4bfdb11ccbd653a7e83e06c794"
dependencies = [
"cfg_aliases",
"derive_more 1.0.0",
"futures-buffered",
"futures-lite",
"futures-util",
"js-sys",
"pin-project",
"send_wrapper",
"tokio",
"tokio-util",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-time",
]
[[package]]
name = "n0-future"
version = "0.3.2"
@@ -2391,7 +2349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2ab99dfb861450e68853d34ae665243a88b8c493d01ba957321a1e9b2312bbe"
dependencies = [
"cfg_aliases",
"derive_more 2.1.1",
"derive_more",
"futures-buffered",
"futures-lite",
"futures-util",
@@ -2411,9 +2369,9 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc618745ad0b7414b149d0517ad8b5573b2fb4d4e2717add3d2446ce1fdd826"
dependencies = [
"derive_more 2.1.1",
"derive_more",
"n0-error",
"n0-future 0.3.2",
"n0-future",
]
[[package]]
@@ -2519,12 +2477,12 @@ dependencies = [
"atomic-waker",
"bytes",
"cfg_aliases",
"derive_more 2.1.1",
"derive_more",
"ipnet",
"js-sys",
"libc",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"n0-watcher",
"netdev",
"netlink-packet-core",
@@ -2565,7 +2523,7 @@ checksum = "4bf95190af1bd4a00a10e8255ca0c8ddd9e9a9f5e79151d7a7eb6d56aff5dc89"
dependencies = [
"bytes",
"cfg_aliases",
"derive_more 2.1.1",
"derive_more",
"noq-proto",
"noq-udp",
"pin-project-lite",
@@ -2587,7 +2545,7 @@ checksum = "baa7b5ccd819a9c68a0d955e67a881032d09b1a17219b1f90b0997a0888e1a15"
dependencies = [
"aes-gcm",
"bytes",
"derive_more 2.1.1",
"derive_more",
"enum-assoc",
"fastbloom",
"getrandom 0.4.3",
@@ -2990,13 +2948,13 @@ checksum = "eb3713e4977408279158444a18c1a01ac9bf2e7eaf1fbfd1a19ac9cd18d90721"
dependencies = [
"base64",
"bytes",
"derive_more 2.1.1",
"derive_more",
"hyper-util",
"igd-next",
"iroh-metrics",
"libc",
"n0-error",
"n0-future 0.3.2",
"n0-future",
"netwatch",
"num_enum",
"rand 0.10.2",
@@ -4005,7 +3963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.3",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.61.2",
@@ -4491,7 +4449,7 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "varde-ctl"
version = "0.1.5"
version = "0.1.7"
dependencies = [
"anyhow",
"clap",
@@ -4504,7 +4462,7 @@ dependencies = [
[[package]]
name = "varde-daemon"
version = "0.1.5"
version = "0.1.7"
dependencies = [
"anyhow",
"base64",
@@ -4512,7 +4470,7 @@ dependencies = [
"iroh-blobs",
"iroh-mdns-address-lookup",
"irpc",
"n0-future 0.1.3",
"n0-future",
"postcard",
"proptest",
"reflink-copy",
@@ -4530,7 +4488,7 @@ dependencies = [
[[package]]
name = "varde-proto"
version = "0.1.5"
version = "0.1.7"
dependencies = [
"serde",
"serde_json",
+1 -1
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = ["varde-proto", "varde-daemon", "varde-ctl"]
[workspace.package]
version = "0.1.5"
version = "0.1.7"
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://project.uhhm.no/uhhm/varde"
+2 -2
View File
@@ -5,6 +5,6 @@ post_install() {
}
post_upgrade() {
echo ":: Restart a running daemon to pick up the new version:"
echo " systemctl --user try-restart varde.service (and/or: systemctl try-restart varde.service)"
echo ":: Restart a running daemon to pick up the new version - the socket, not the service:"
echo " systemctl try-restart varde.socket; systemctl --user try-restart varde.socket"
}
+4
View File
@@ -16,6 +16,10 @@ Restart=on-failure
DynamicUser=yes
StateDirectory=varde
RuntimeDirectory=varde
# The socket unit's file lives in this directory: removing it on every
# service stop (the default) left varde.socket listening on an unlinked
# path after any restart - "active" with nothing to connect to.
RuntimeDirectoryPreserve=yes
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
+2 -2
View File
@@ -19,10 +19,10 @@ iroh-blobs = { workspace = true }
# Request::Put / Read carry content inline, base64.
base64 = "0.22"
# mDNS/local-network address lookup left iroh core in 1.0.
iroh-mdns-address-lookup = "0.4"
iroh-mdns-address-lookup = "0.6"
# Interop with iroh-blobs streams and channels; both are already in the
# dependency tree via iroh-blobs.
n0-future = "0.1"
n0-future = "0.3"
irpc = "0.17"
# Feature-gated D-Bus client for NetworkManager metered status.
zbus = { version = "5", optional = true, default-features = false, features = ["tokio"] }
+22 -6
View File
@@ -335,6 +335,26 @@ impl Daemon {
}
}
/// The peer a request names: a NodeId, or a blob ticket from it (whose
/// addresses are remembered, so dialing it needs no discovery). An app
/// that knows peers by the tickets they hand out - redoal does - can
/// allow and push without unpacking them.
fn peer_of(&self, peer: &str) -> Result<iroh::EndpointId, OpError> {
if let Ok(id) = peer.parse::<iroh::EndpointId>() {
return Ok(id);
}
match crate::transfer::parse_ticket(peer) {
Ok((_, _, addr)) => {
let id = addr.id;
self.transfer.remember(addr);
Ok(id)
}
Err(_) => Err(OpError::InvalidArgument(format!(
"{peer:?} is neither a node id nor a ticket"
))),
}
}
/// Trusted peers seen on the LAN recently.
fn present_trusted_peers(&self) -> Vec<PeerInfo> {
let peers = self.lan_peers.lock().expect("lan peers lock");
@@ -574,9 +594,7 @@ impl Daemon {
Ok(ResponseData::Done {})
}
Request::PeerAllowPush { node_id, ttl_secs } => {
let parsed: iroh::EndpointId = node_id
.parse()
.map_err(|e| OpError::InvalidArgument(format!("invalid node id: {e}")))?;
let parsed = self.peer_of(&node_id)?;
let ttl = ttl_secs.min(varde_proto::MAX_PUSH_TTL_SECS);
self.meta
.allow_push(&parsed.to_string(), crate::meta::unix_now() + ttl)?;
@@ -588,9 +606,7 @@ impl Daemon {
}),
Request::Push { hash, node_id } => {
let parsed = parse_hash(&hash)?;
let peer: iroh::EndpointId = node_id
.parse()
.map_err(|e| OpError::InvalidArgument(format!("invalid node id: {e}")))?;
let peer = self.peer_of(&node_id)?;
// Consent, sending side: we only push to peers we trust or
// hold a push allowance for. (The receiver independently
// requires the same of us.)
+4 -3
View File
@@ -200,11 +200,12 @@ pub fn provider_events(gate: Gate) -> EventSender {
ProviderMessage::ObserveRequestReceived(m) => {
// Presence is information too: gate it like data.
// Bitfields are metadata, not payload, so trusted
// peers may observe even while serving is off or the
// connection is metered (push confirmation needs it).
// peers - and peers allowed to push, whose push is
// confirmed by observing it - may observe even while
// serving is off or the connection is metered.
let trusted = conns
.get(&m.inner.connection_id)
.map(|i| i.trusted)
.map(|i| i.trusted || i.may_push)
.unwrap_or(false);
let allowed = trusted
|| allow(&gate, &conns, m.inner.connection_id, [m.inner.request.hash]);
+9
View File
@@ -298,6 +298,15 @@ impl Transfer {
Ok(ticket.to_string())
}
/// Teach the endpoint where a peer can be reached (the addresses a
/// ticket carries), so a push dials it directly instead of waiting on
/// discovery.
pub fn remember(&self, addr: EndpointAddr) {
if !addr.addrs.is_empty() {
self.known_addrs.add_endpoint_info(addr);
}
}
/// Queue a background fetch of `content` from `providers`. Emits
/// progress events and [`Event::PinComplete`] when fully verified.
pub fn spawn_fetch(&self, content: HashAndFormat, providers: Vec<EndpointAddr>) {
+16 -4
View File
@@ -437,7 +437,6 @@ fn push_allowance_is_enough_and_is_not_trust() {
let mut client_a = support::Client::connect(&a.socket);
let mut client_b = support::Client::connect(&b.socket);
let id_a = node_id(&mut client_a);
let id_b = node_id(&mut client_b);
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("voice.bin");
@@ -446,7 +445,16 @@ fn push_allowance_is_enough_and_is_not_trust() {
let hash = add_file(&mut client_a, &src);
pin(&mut client_a, &hash, false);
assert!(client_a.request(&Request::PeerAllowPush { node_id: id_b.clone(), ttl_secs: 600 }).ok);
// A knows B by a ticket B handed out (as redoal peers do), not by
// its bare node id.
let b_src = dir.path().join("b-own.bin");
std::fs::write(&b_src, vec![1u8; 1_000]).unwrap();
let b_hash = add_file(&mut client_b, &b_src);
let b_ticket = match client_b.request(&Request::TicketExport { hash: b_hash }).data {
Some(ResponseData::Ticket { ticket }) => ticket,
other => panic!("unexpected ticket reply: {other:?}"),
};
assert!(client_a.request(&Request::PeerAllowPush { node_id: b_ticket.clone(), ttl_secs: 600 }).ok);
assert!(client_b.request(&Request::PeerAllowPush { node_id: id_a, ttl_secs: 600 }).ok);
match client_a.request(&Request::PeerList {}).data {
Some(ResponseData::Peers { peers }) => assert!(peers.is_empty(), "an allowance is not trust: {peers:?}"),
@@ -456,14 +464,18 @@ fn push_allowance_is_enough_and_is_not_trust() {
// The peer's address arrives by mdns; retry until it does.
let deadline = Instant::now() + Duration::from_secs(30);
let reply = loop {
let reply = client_a.request(&Request::Push { hash: hash.clone(), node_id: id_b.clone() });
let reply = client_a.request(&Request::Push { hash: hash.clone(), node_id: b_ticket.clone() });
if reply.ok || Instant::now() > deadline {
break reply;
}
std::thread::sleep(Duration::from_millis(500));
};
if !reply.ok {
eprintln!("SKIP: push never reached the peer (multicast unavailable?): {:?}", reply.error);
let message = reply.error.map(|e| e.message).unwrap_or_default();
// Only an unreachable peer is the environment's fault; any other
// refusal is a real failure.
assert!(message.contains("connect"), "push failed: {message}");
eprintln!("SKIP: push never reached the peer (multicast unavailable?): {message}");
return;
}
assert!(wait_complete(&b.socket, &hash, Duration::from_secs(60)), "pushed content not complete on receiver");
+5 -1
View File
@@ -127,7 +127,8 @@ pub enum Request {
/// phones at the same place) and let it lapse. Asking again extends
/// it; [`Request::PeerUntrust`] ends it early. Added in varde 0.1.5.
PeerAllowPush {
/// The peer's iroh NodeId (z-base-32).
/// The peer's iroh NodeId (z-base-32), or a blob ticket it issued
/// (varde 0.1.6+), whose addresses are then used to reach it.
node_id: String,
/// How long the allowance lasts, in seconds.
ttl_secs: u64,
@@ -142,6 +143,9 @@ pub enum Request {
/// policy), so the content survives gc and shows up in `List`.
Push {
/// Hash (hex) of the fully-present blob or HashSeq root to push.
///
/// The receiver below may be a NodeId or, from varde 0.1.6, a blob
/// ticket it issued.
hash: String,
/// The receiving peer's iroh NodeId (z-base-32).
node_id: String,