Skip to content

feat: expose local_socket_addrs on Transport trait for ICE candidate extraction#478

Draft
HexaField wants to merge 1 commit intoholochain:mainfrom
HexaField:feat/iroh-ice-v2
Draft

feat: expose local_socket_addrs on Transport trait for ICE candidate extraction#478
HexaField wants to merge 1 commit intoholochain:mainfrom
HexaField:feat/iroh-ice-v2

Conversation

@HexaField
Copy link
Copy Markdown

@HexaField HexaField commented Mar 11, 2026

Summary

Adds local_socket_addrs() to the Transport trait, enabling consumers to extract local and reflexive socket addresses discovered by the Iroh transport. These addresses are used as WebRTC ICE candidates, replacing external STUN server dependencies.

Changes

  • crates/api/src/transport.rs: Added local_socket_addrs() to TxBaseImp trait with default empty implementation. Surfaced through DefaultTransport and Transport.
  • crates/transport_iroh/src/endpoint.rs: Extracts TransportAddr::Ip variants from Endpoint::addr(), filters unspecified addresses (0.0.0.0, ::).
  • crates/transport_iroh/src/lib.rs: Implements trait method, delegating to endpoint.

Design Decisions

  • Uses std::net::SocketAddr (not Iroh types) to keep kitsune2_api transport-agnostic
  • All candidates classified as host type — only real STUN responses should produce srflx
  • Wildcard/unspecified addresses filtered out

Tests

16 tests pass in kitsune2_transport_iroh including new integration tests for address extraction.

Part of: coasys/ad4m#719

Summary by CodeRabbit

  • New Features

    • Added support for querying local socket addresses discovered by transports with filtering to exclude unspecified addresses and ensure valid ports.
  • Tests

    • Added integration tests to verify local socket address discovery and filtering behavior.

@cocogitto-bot
Copy link
Copy Markdown

cocogitto-bot Bot commented Mar 11, 2026

✔️ 7ba61fe - Conventional commits check succeeded.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 11, 2026

Walkthrough

This PR introduces a new transport API for discovering local socket addresses. It adds trait methods across the transport abstraction layers, implements address discovery in the Iroh transport with IPv4/IPv6 filtering, and validates the functionality through integration tests.

Changes

Cohort / File(s) Summary
Base Transport API
crates/api/src/transport.rs
Adds local_socket_addrs() method to TxImp trait with a default no-op implementation returning an empty Vec, exposes the method on Transport trait, and delegates through DefaultTransport to the underlying implementation.
Endpoint Address Support
crates/transport_iroh/src/endpoint.rs
Adds addr() method to the Endpoint trait and implements it in IrohEndpoint to retrieve the underlying endpoint address.
IrohTransport Implementation
crates/transport_iroh/src/lib.rs
Implements local_socket_addrs() for IrohTransport with logic to clone the endpoint, query its address, and filter for non-unspecified IPv4/IPv6 socket addresses. Adds import for TransportAddr from iroh.
Integration Tests
crates/transport_iroh/tests/integration.rs
Adds two test cases verifying that local_socket_addrs() returns non-empty results with valid ports and excludes unspecified addresses (0.0.0.0, ::). Tests poll for address discovery over 5 seconds with 200ms intervals.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Suggested reviewers

  • ThetaSinner
  • jost-s
  • matthme
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: exposing a local_socket_addrs method on the Transport trait for ICE candidate extraction, which aligns with the primary objective of adding this API across the transport types.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/api/src/transport.rs (1)

529-534: Consider documenting behavioral differences across transport implementations.

Based on the relevant code snippets, Tx5Transport has get_listening_addresses() which could potentially expose socket addresses, but it doesn't override local_socket_addrs() and will return an empty Vec. Similarly, MemTransport will silently return empty.

This is acceptable given the PR's design intent (feature only for Iroh), but consider adding a note in the documentation that this method currently only returns meaningful results for the Iroh transport implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/api/src/transport.rs` around lines 529 - 534, Add documentation to the
trait method local_socket_addrs explaining that not all Transport
implementations return socket addresses; specifically note that Tx5Transport
(see get_listening_addresses) and MemTransport currently return an empty Vec and
that meaningful results are only provided by the Iroh transport implementation.
Update the doc comment on fn local_socket_addrs(&self) to call out this
behavioral difference and the reason (feature currently implemented only for
Iroh), so callers know to expect empty results for other transports like
Tx5Transport and MemTransport.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/transport_iroh/tests/integration.rs`:
- Around line 1254-1278: The test local_socket_addrs_excludes_unspecified can
pass with empty results; after the retry loop that sets addrs via
transport.local_socket_addrs(), add an assertion that addrs is non-empty (e.g.,
assert!(!addrs.is_empty(), "...")) before iterating to filter unspecified
addresses so the test fails if discovery did not produce any addresses;
reference the IrohTransportTestHarness and transport.local_socket_addrs() in the
change and mirror the behavior used in local_socket_addrs_returns_addresses if
helpful.

---

Nitpick comments:
In `@crates/api/src/transport.rs`:
- Around line 529-534: Add documentation to the trait method local_socket_addrs
explaining that not all Transport implementations return socket addresses;
specifically note that Tx5Transport (see get_listening_addresses) and
MemTransport currently return an empty Vec and that meaningful results are only
provided by the Iroh transport implementation. Update the doc comment on fn
local_socket_addrs(&self) to call out this behavioral difference and the reason
(feature currently implemented only for Iroh), so callers know to expect empty
results for other transports like Tx5Transport and MemTransport.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b75408c-0e1c-460f-95f2-052a3a52118c

📥 Commits

Reviewing files that changed from the base of the PR and between 1142845 and 7ba61fe.

📒 Files selected for processing (4)
  • crates/api/src/transport.rs
  • crates/transport_iroh/src/endpoint.rs
  • crates/transport_iroh/src/lib.rs
  • crates/transport_iroh/tests/integration.rs

Comment on lines +1254 to +1278
#[tokio::test]
async fn local_socket_addrs_excludes_unspecified() {
let harness = IrohTransportTestHarness::new().await;
let handler = Arc::new(MockTxHandler::default());
let transport = harness.build_transport(handler.clone()).await;

// Retry for up to 5 seconds to allow address discovery.
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
let mut addrs;
loop {
addrs = transport.local_socket_addrs().await.unwrap();
if !addrs.is_empty() || std::time::Instant::now() >= deadline {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
}

// Verify no unspecified addresses (0.0.0.0 or ::) are returned.
for addr in &addrs {
assert!(
!addr.ip().is_unspecified(),
"Unspecified address should be filtered out: {addr}"
);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test may pass trivially with empty results.

The local_socket_addrs_excludes_unspecified test loops until addresses are found or timeout, but doesn't assert that addresses were actually discovered. If address discovery fails, the for loop body never executes and the test passes without validating anything.

Consider adding a non-empty assertion or combining this with local_socket_addrs_returns_addresses:

Proposed fix
     // Verify no unspecified addresses (0.0.0.0 or ::) are returned.
+    assert!(
+        !addrs.is_empty(),
+        "Expected at least one address to verify unspecified filtering"
+    );
     for addr in &addrs {
         assert!(
             !addr.ip().is_unspecified(),
             "Unspecified address should be filtered out: {addr}"
         );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[tokio::test]
async fn local_socket_addrs_excludes_unspecified() {
let harness = IrohTransportTestHarness::new().await;
let handler = Arc::new(MockTxHandler::default());
let transport = harness.build_transport(handler.clone()).await;
// Retry for up to 5 seconds to allow address discovery.
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
let mut addrs;
loop {
addrs = transport.local_socket_addrs().await.unwrap();
if !addrs.is_empty() || std::time::Instant::now() >= deadline {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
}
// Verify no unspecified addresses (0.0.0.0 or ::) are returned.
for addr in &addrs {
assert!(
!addr.ip().is_unspecified(),
"Unspecified address should be filtered out: {addr}"
);
}
}
#[tokio::test]
async fn local_socket_addrs_excludes_unspecified() {
let harness = IrohTransportTestHarness::new().await;
let handler = Arc::new(MockTxHandler::default());
let transport = harness.build_transport(handler.clone()).await;
// Retry for up to 5 seconds to allow address discovery.
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
let mut addrs;
loop {
addrs = transport.local_socket_addrs().await.unwrap();
if !addrs.is_empty() || std::time::Instant::now() >= deadline {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
}
// Verify no unspecified addresses (0.0.0.0 or ::) are returned.
assert!(
!addrs.is_empty(),
"Expected at least one address to verify unspecified filtering"
);
for addr in &addrs {
assert!(
!addr.ip().is_unspecified(),
"Unspecified address should be filtered out: {addr}"
);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/transport_iroh/tests/integration.rs` around lines 1254 - 1278, The
test local_socket_addrs_excludes_unspecified can pass with empty results; after
the retry loop that sets addrs via transport.local_socket_addrs(), add an
assertion that addrs is non-empty (e.g., assert!(!addrs.is_empty(), "..."))
before iterating to filter unspecified addresses so the test fails if discovery
did not produce any addresses; reference the IrohTransportTestHarness and
transport.local_socket_addrs() in the change and mirror the behavior used in
local_socket_addrs_returns_addresses if helpful.

@HexaField HexaField marked this pull request as draft March 11, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant