Skip to content

Conversation

anchalshivank
Copy link

Hi team,

I’ve added the basic struct for WebRTC support to get things started.
Kindly guide me if this is a good direction or if there’s anything I should take care of early on.

Also, what signaling server setup would you recommend for Iroh’s WebRTC integration?

Thanks!

@n0bot n0bot bot added this to iroh Aug 18, 2025
@github-project-automation github-project-automation bot moved this to 🏗 In progress in iroh Aug 18, 2025
@matheus23
Copy link
Member

matheus23 commented Aug 18, 2025

We're discussing this in the iroh discord: https://discord.com/channels/1161119546170687619/1406897550140772393

Copy link
Contributor

@flub flub left a comment

Choose a reason for hiding this comment

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

I've had a quick scroll through and noticed a bunch of printlns and commented out code still. Could you add a note on what the stated of completion of the PR is, what the first round of review should pay attention to?

@anchalshivank
Copy link
Author

anchalshivank commented Sep 9, 2025

I've had a quick scroll through and noticed a bunch of printlns and commented out code still. Could you add a note on what the stated of completion of the PR is, what the first round of review should pay attention to?

This PR is still in progress - I’ll clean up the println! statements and remove the commented-out code before finalizing. For the first round of review, I’d like you to mainly focus on whether I am correctly sending the WebRTC offer/ICE candidate from one node to another.

Current flow

Node A is spawned.

Node B connects.

Node B uses discovery to send a ping .
We do share different message types ping, pong , call me may be . I added another message type.
I also need to confirm when shall the webrtc offer be exchanged.

I refactored the message-sending logic so that send_ping now also carries WebRTC ICE candidate information.

Points where I need guidance

  1. Virtual socket: Matheus mentioned we should bind a socket. I’m not fully sure if the current approach is sufficient. I could not get intuition.

  2. Refactoring correctness: Did I refactor the send_ping (and related messages) correctly so that ICE candidates are shared properly?

  3. Transport mode setup: The PR currently focuses only on native WebRTC with a transport mode of WebrtcRelay. Could you check if I set up the transport modes correctly?

  4. Socket binding intuition: In handle::new(), IP transports generate a socket address. For WebRTC, should I also bind it to some sort of socket, or is the transport abstraction enough?

  5. WASM case: In WASM, there’s no real socket. How should I be thinking about this when extending the transport to work in WASM?

For now you can ignore webrtc actor (peer connection state ) as it needs to be changed

/// Socket addresses where the peer might be reached directly.
pub direct_addresses: BTreeSet<SocketAddr>,
/// Static Webrtc connection information for the node
pub webrtc_info: Option<WebRtcInfo>,
}
Copy link
Author

Choose a reason for hiding this comment

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

My understanding:
NodeAddr is used by the discovery system to locate nodes in the network. For discovery purposes, we only need the node_id to find a node. Once found, WebRTC connections are established through the standard offer/answer exchange process.

My concern:
Since WebRTC connections require dynamic offer/answer negotiation anyway, is it necessary to store static WebRTC information like channel_id and webrtc_info (certificate fingerprints) here?

Copy link
Author

Choose a reason for hiding this comment

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

I added this file to uniquely identify the webrtc connection on the node locally. We need to decide if this correct.

Copy link
Author

Choose a reason for hiding this comment

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

this file can be ignored for now

@@ -224,7 +224,7 @@ impl From<NodeAddr> for NodeData {
/// `UserData` implements [`FromStr`] and [`TryFrom<String>`], so you can
/// convert `&str` and `String` into `UserData` easily.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct UserData(String);
pub struct UserData(pub String);
Copy link
Author

Choose a reason for hiding this comment

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

Needs to revert it back. It was a quick fix for compilation erros. #3250

@@ -587,6 +620,7 @@ impl NodeState {
// direct address paths to contact but no RelayUrl, we still need to send a DISCO
// ping to the direct address paths so that the other node will learn about us and
// accepts the connection.
println!("----------- 625");
Copy link
Author

Choose a reason for hiding this comment

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

Needs to revert it back. #3250

@@ -115,6 +117,7 @@ pub(super) struct NodeState {
///
/// The fallback/bootstrap path, if non-zero (non-zero for well-behaved clients).
relay_url: Option<(RelayUrl, PathState)>,
webrtc_channel: Option<(ChannelId, PathState)>,
Copy link
Author

Choose a reason for hiding this comment

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

I think this is incorrect. I need more intuition on what node state , what to put it here

Copy link
Author

Choose a reason for hiding this comment

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

We can ignore this file for now

@@ -55,6 +56,7 @@ pub(super) struct NodeMap {
pub(super) struct NodeMapInner {
by_node_key: HashMap<NodeId, usize>,
by_ip_port: HashMap<IpPort, usize>,
by_webrtc_port: HashMap<WebRtcPort, usize>,
Copy link
Author

Choose a reason for hiding this comment

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

I also need guidance on this part. If this is correct or needs changes

Copy link
Author

Choose a reason for hiding this comment

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

We can ignore this file for now

}
}
}
disco::Message::WebRtcIceCandidate(ice) => {

Copy link
Author

Choose a reason for hiding this comment

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

When we use this file, we can see the offer being received at this point.

Comment on lines 1660 to 2291
// the packet if grease_quic_bit is set to false.
endpoint_config.grease_quic_bit(false);

let sender = transports.create_sender(msock.clone());
let local_addrs_watch = transports.local_addrs_watch();
let network_change_sender = transports.create_network_change_sender();

let endpoint = quinn::Endpoint::new_with_abstract_socket(
endpoint_config,
Some(server_config),
Box::new(MagicUdpSocket {
socket: msock.clone(),
transports,
}),
#[cfg(not(wasm_browser))]
Arc::new(quinn::TokioRuntime),
#[cfg(wasm_browser)]
Arc::new(crate::web_runtime::WebRuntime),
)
.context(CreateQuinnEndpointSnafu)?;

let network_monitor = netmon::Monitor::new()
.await
.context(CreateNetmonMonitorSnafu)?;

let qad_endpoint = endpoint.clone();

#[cfg(any(test, feature = "test-utils"))]
let client_config = if insecure_skip_relay_cert_verify {
iroh_relay::client::make_dangerous_client_config()
} else {
default_quic_client_config()
};
#[cfg(not(any(test, feature = "test-utils")))]
let client_config = default_quic_client_config();

let net_report_config = net_report::Options::default();
#[cfg(not(wasm_browser))]
let net_report_config = net_report_config.quic_config(Some(QuicConfig {
ep: qad_endpoint,
client_config,
ipv4: true,
ipv6,
}));

#[cfg(any(test, feature = "test-utils"))]
let net_report_config =
net_report_config.insecure_skip_relay_cert_verify(insecure_skip_relay_cert_verify);

let net_reporter = net_report::Client::new(
#[cfg(not(wasm_browser))]
dns_resolver,
#[cfg(not(wasm_browser))]
Some(ip_mapped_addrs),
relay_map.clone(),
net_report_config,
metrics.net_report.clone(),
);

let (direct_addr_done_tx, direct_addr_done_rx) = mpsc::channel(8);
let direct_addr_update_state = DirectAddrUpdateState::new(
msock.clone(),
#[cfg(not(wasm_browser))]
port_mapper,
Arc::new(AsyncMutex::new(net_reporter)),
relay_map,
direct_addr_done_tx,
);

let netmon_watcher = network_monitor.interface_state();
let actor = Actor {
msg_receiver: actor_receiver,
msock: msock.clone(),
periodic_re_stun_timer: new_re_stun_timer(false),
network_monitor,
netmon_watcher,
direct_addr_update_state,
network_change_sender,
direct_addr_done_rx,
pending_call_me_maybes: Default::default(),
disco_receiver,
};

let actor_token = CancellationToken::new();
let token = actor_token.clone();
let actor_task = task::spawn(
actor
.run(token, local_addrs_watch, sender)
.instrument(info_span!("actor")),
);

let actor_task = Arc::new(Mutex::new(Some(AbortOnDropHandle::new(actor_task))));

Ok(Handle {
msock,
actor_task,
endpoint,
actor_token,
})
}
Copy link
Author

Choose a reason for hiding this comment

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

We can ignore this part for now

Comment on lines 2510 to 2723
let network_change_sender = transports.create_network_change_sender();

let endpoint = quinn::Endpoint::new_with_abstract_socket(
endpoint_config,
Some(server_config),
Box::new(MagicUdpSocket {
socket: msock.clone(),
transports,
}),
#[cfg(not(wasm_browser))]
Arc::new(quinn::TokioRuntime),
#[cfg(wasm_browser)]
Arc::new(crate::web_runtime::WebRuntime),
)
.context(CreateQuinnEndpointSnafu)?;

let network_monitor = netmon::Monitor::new()
.await
.context(CreateNetmonMonitorSnafu)?;

let qad_endpoint = endpoint.clone();

#[cfg(any(test, feature = "test-utils"))]
let client_config = if insecure_skip_relay_cert_verify {
iroh_relay::client::make_dangerous_client_config()
} else {
default_quic_client_config()
};
#[cfg(not(any(test, feature = "test-utils")))]
let client_config = default_quic_client_config();

let net_report_config = net_report::Options::default();
#[cfg(not(wasm_browser))]
let net_report_config = net_report_config.quic_config(Some(QuicConfig {
ep: qad_endpoint,
client_config,
ipv4: true,
ipv6,
}));

#[cfg(any(test, feature = "test-utils"))]
let net_report_config =
net_report_config.insecure_skip_relay_cert_verify(insecure_skip_relay_cert_verify);

let net_reporter = net_report::Client::new(
#[cfg(not(wasm_browser))]
dns_resolver,
#[cfg(not(wasm_browser))]
Some(ip_mapped_addrs),
relay_map.clone(),
net_report_config,
metrics.net_report.clone(),
);

let (direct_addr_done_tx, direct_addr_done_rx) = mpsc::channel(8);
let direct_addr_update_state = DirectAddrUpdateState::new(
msock.clone(),
#[cfg(not(wasm_browser))]
port_mapper,
Arc::new(AsyncMutex::new(net_reporter)),
relay_map,
direct_addr_done_tx,
);

let netmon_watcher = network_monitor.interface_state();
let actor = Actor {
msg_receiver: actor_receiver,
msock: msock.clone(),
periodic_re_stun_timer: new_re_stun_timer(false),
network_monitor,
netmon_watcher,
direct_addr_update_state,
network_change_sender,
direct_addr_done_rx,
pending_call_me_maybes: Default::default(),
disco_receiver,
};

let actor_token = CancellationToken::new();
let token = actor_token.clone();
let actor_task = task::spawn(
actor
.run(token, local_addrs_watch, sender)
.instrument(info_span!("actor")),
);

let actor_task = Arc::new(Mutex::new(Some(AbortOnDropHandle::new(actor_task))));

Ok(Handle {
msock,
actor_task,
endpoint,
actor_token,
})
}

/// The underlying [`quinn::Endpoint`]
pub fn endpoint(&self) -> &quinn::Endpoint {
&self.endpoint
}
Copy link
Author

Choose a reason for hiding this comment

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

Also ignore this part

@@ -57,8 +56,11 @@ pub enum MessageType {
Ping = 0x01,
Pong = 0x02,
CallMeMaybe = 0x03,
WebRtcIceCandidate = 0x06
Copy link
Author

Choose a reason for hiding this comment

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

I think this will also need some refactoring

iroh/Cargo.toml Outdated
Copy link
Author

Choose a reason for hiding this comment

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

Since I was running the complete stack locally, I had to modify the Cargo.toml. I think we should consider adding Cargo.toml to .gitignore, unless you suggest a better approach.

@anchalshivank anchalshivank requested a review from flub September 10, 2025 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🏗 In progress
Development

Successfully merging this pull request may close these issues.

3 participants