-
-
Notifications
You must be signed in to change notification settings - Fork 365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
some questions about support for WebRTC Direct #1166
Comments
This has already been discussed in #970 (comment) about disabling fingerprint validation, which would also be required by for this. SDP munging between
Therefore, libdatachannel has no mechanism to set a modified local description by design to make the implementation way simpler (as it doesn't need to reparse the SDP and synchronize its internal state). Not only changing the whole architecture to allow SDP munging hack is out of the question, but it would only allow you to implement "peer A" in the WebRTC Direct spec, since to implement "peer B" you would also need to modify the whole ICE ufrag and password generation and validation process, and you would need a specific ICE hook to create peer connections triggered by incoming STUN probes. I suggest that WebRTC Direct could be implemented as a specific operating mode of libdatachannel instead, which would not emit a local description nor accept a remote one, but would perform the required ufrag and password operations under the hood and offer an API to listen on incoming peer connections. |
Since we're thinking about supporting WebRTC Direct, wouldn't it be a good idea to consider specifying ice_ufrag and ice_pwd when creating the PeerConnection. And with the addition of disabling fingerprint authentication, and a stun event, it might be possible to build a somewhat more elegant implementation of WebRTC Direct. |
I made some changes, passed in ice_ufrag and ice_pwd and initiated a connection to go-libp2p-server. Looking at the log the handshake is already successful, but onOpen cannot be triggered. case Message::String:
case Message::Binary:
if (!mIsOpen.exchange(true)) {
triggerOpen();
}
mRecvQueue.push(message);
triggerAvailable(mRecvQueue.size());
break; I'm not sure if this is a problem with the go-libp2p implementation. Is this an appropriate change? |
I made some modifications to support setting up cert and key, and it's working fine so far. |
No, this is not normal. If there is no ACK message, it's either a confusion between datachannels opened in-band and negotiated datachannels or a bug in the remote implementation. |
Yes, it's confusing, I connect to libdatachannel's peer node and don't have this problem. |
is a simplified piece of client-side code for WebRTC Direct. const rtc = require('rtc');
const HANDSHAKE_TIMEOUT_MS = 10000;
async function main() {
const ufrag = 'libp2p+webrtc+v1/' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const ip = '192.168.65.5';
const port = 60916;
var answerSdp = {
"type": "answer",
"sdp": `v=0\r\no=- 0 0 IN IP4 ${ip}\r\ns=-\r\nc=IN IP4 ${ip}\r\nt=0 0\r\na=ice-lite\r\nm=application ${port} UDP/DTLS/SCTP webrtc-datachannel\r\na=mid:0\r\na=setup:active\r\na=ice-ufrag:${ufrag}\r\na=ice-pwd:${ufrag}\r\na=fingerprint:SHA-256 07:E5:6F:2A:1A:0C:2C:32:0E:C1:C3:9C:34:5A:78:4E:A5:8B:32:05:D1:57:D6:F4:E7:02:41:12:E6:01:C6:8F\r\na=sctp-port:5000\r\na=max-message-size:16384\r\na=candidate:1467250027 1 UDP 1467250027 ${ip} ${port} typ host\r\n`
};
console.log(answerSdp);
const peerConnection = new rtc.RTCPeerConnection({
iceUfrag: ufrag,
icePwd: ufrag,
port: 12345,
maxMessageSize: 16384
});
const dataChannelOpenPromise = new Promise((resolve, reject) => {
console.log("peerConnection.createDataChannel");
const handshakeDataChannel = peerConnection.createDataChannel('');
const handshakeTimeout = setTimeout(() => {
const error = `Data channel was never opened: state: ${handshakeDataChannel.readyState}`;
reject(new Error(error));
}, HANDSHAKE_TIMEOUT_MS);
handshakeDataChannel.onopen = (_) => {
console.log('Data channel opened');
clearTimeout(handshakeTimeout);
resolve(handshakeDataChannel);
};
handshakeDataChannel.onerror = (event) => {
console.error('Data channel error:', event);
clearTimeout(handshakeTimeout);
const error = `Error opening a data channel for handshaking.`;
reject(new Error(error));
};
handshakeDataChannel.onmessage = (event) => {
console.log('Data channel message:', event.data);
};
});
await peerConnection.setRemoteDescription(answerSdp);
console.log("setRemoteDescription ok");
const handshakeDataChannel = await dataChannelOpenPromise;
console.log("connect ok");
}
main(); |
Today, based on the implementation of the skipCheckFingerprint option, it is possible to implement a simple Peer B. The code for Peer B looks like this. var rtc = require('rtc');
const key_pem =
`-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3bbuT2SjSlMZH/J1
vHwmF0Blb/DBc/v7f1Za9GPUXHmhRANCAATDpmYxZozjVw6xlERNjJJGgfY3bEmj
xAKFRq3nbxbDHvMEs34u9HntMZWJ0hp3GUC+Ax7JHTv3cYqSaAg2SpR4
-----END PRIVATE KEY-----`
const cert_pem =
`-----BEGIN CERTIFICATE-----
MIIBgjCCASigAwIBAgIJAPMXEoZXOaDEMAoGCCqGSM49BAMCMEoxDzANBgNVBAMM
BmNhLmNvbTELMAkGA1UEBhMCVVMxCzAJBgNVBAcMAkNBMRAwDgYDVQQKDAdleGFt
cGxlMQswCQYDVQQIDAJDQTAeFw0yNDA1MDUxNjAzMjFaFw0yNDA4MTMxNjAzMjFa
MDExCzAJBgNVBAYTAkNOMRAwDgYDVQQKDAdiYW96LmNuMRAwDgYDVQQDDAdiYW96
Lm1lMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw6ZmMWaM41cOsZRETYySRoH2
N2xJo8QChUat528Wwx7zBLN+LvR57TGVidIadxlAvgMeyR0793GKkmgINkqUeKMQ
MA4wDAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNIADBFAiAPNldqGJHryfjPFyX3
zfHHWlO7xSDTzdyoxzroFdwy+gIhAKmZizEVvDlBiIe+3ptCArU3dbp+bzLynTcr
Ma9ayzQy
-----END CERTIFICATE-----`
const ufrag = 'libp2p+webrtc+v1/' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
const ip = '192.168.65.5';
const port = 12345;
var offerSdp = {
"type": "offer",
"sdp": `v=0\r\no=- 0 0 IN IP4 ${ip}\r\ns=-\r\nc=IN IP4 ${ip}\r\nt=0 0\r\na=ice-lite\r\nm=application ${port} UDP/DTLS/SCTP webrtc-datachannel\r\na=mid:0\r\na=setup:passive\r\na=ice-ufrag:${ufrag}\r\na=ice-pwd:${ufrag}\r\na=fingerprint:SHA-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00\r\na=sctp-port:5000\r\na=max-message-size:16384\r\na=candidate:1467250027 1 UDP 1467250027 ${ip} ${port} typ host\r\n`
};
let peer1 = new rtc.RTCPeerConnection({
enableIceUdpMux: true,
skipCheckFingerprint: true,
iceUfrag: ufrag,
icePwd: ufrag,
certPem: cert_pem,
keyPem: key_pem,
port: 60916,
maxMessageSize: 16384
});
peer1.setRemoteDescription(offerSdp);
console.readLine(); |
You shouldn't set an empty label, it is technically allowed but might cause issues with some implementations.
Great, nice work! |
var rtc = require('rtc');
const key_pem =
`-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3bbuT2SjSlMZH/J1
vHwmF0Blb/DBc/v7f1Za9GPUXHmhRANCAATDpmYxZozjVw6xlERNjJJGgfY3bEmj
xAKFRq3nbxbDHvMEs34u9HntMZWJ0hp3GUC+Ax7JHTv3cYqSaAg2SpR4
-----END PRIVATE KEY-----`
const cert_pem =
`-----BEGIN CERTIFICATE-----
MIIBgjCCASigAwIBAgIJAPMXEoZXOaDEMAoGCCqGSM49BAMCMEoxDzANBgNVBAMM
BmNhLmNvbTELMAkGA1UEBhMCVVMxCzAJBgNVBAcMAkNBMRAwDgYDVQQKDAdleGFt
cGxlMQswCQYDVQQIDAJDQTAeFw0yNDA1MDUxNjAzMjFaFw0yNDA4MTMxNjAzMjFa
MDExCzAJBgNVBAYTAkNOMRAwDgYDVQQKDAdiYW96LmNuMRAwDgYDVQQDDAdiYW96
Lm1lMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw6ZmMWaM41cOsZRETYySRoH2
N2xJo8QChUat528Wwx7zBLN+LvR57TGVidIadxlAvgMeyR0793GKkmgINkqUeKMQ
MA4wDAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNIADBFAiAPNldqGJHryfjPFyX3
zfHHWlO7xSDTzdyoxzroFdwy+gIhAKmZizEVvDlBiIe+3ptCArU3dbp+bzLynTcr
Ma9ayzQy
-----END CERTIFICATE-----`
let accepter = new rtc.RTCPeerConnection({
enableIceUdpMux: true,
stunBinding: true,
port: 60916
});
accepter.onstunbinding = function (binding) {
console.log('onstunbinding', binding);
const ufrag = binding.ufrag;
const [ip, port] = binding.address.split(':');
let peer1 = new rtc.RTCPeerConnection({
enableIceUdpMux: true,
skipCheckFingerprint: true,
iceUfrag: ufrag,
icePwd: ufrag,
certPem: cert_pem,
keyPem: key_pem,
maxMessageSize: 16384
});
peer1.setRemoteDescription({
"type": "offer",
"sdp": `v=0\r\no=- 0 0 IN IP4 ${ip}\r\ns=-\r\nc=IN IP4 ${ip}\r\nt=0 0\r\na=ice-lite\r\nm=application ${port} UDP/DTLS/SCTP webrtc-datachannel\r\na=mid:0\r\na=setup:passive\r\na=ice-ufrag:${ufrag}\r\na=ice-pwd:${ufrag}\r\na=fingerprint:SHA-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00\r\na=sctp-port:5000\r\na=max-message-size:16384\r\na=candidate:1467250027 1 UDP 1467250027 ${ip} ${port} typ host\r\n`
});
}
accepter.setRemoteDescription({
"type": "offer",
"sdp": `v=0\r\no=- 0 0 IN IP4 00.0.0\r\ns=-\r\nc=IN IP4 00.0.0\r\nt=0 0\r\na=ice-lite\r\nm=application 1 UDP/DTLS/SCTP webrtc-datachannel\r\na=mid:0\r\na=setup:passive\r\na=ice-ufrag:1\r\na=ice-pwd:1\r\na=fingerprint:SHA-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00\r\na=sctp-port:5000\r\na=max-message-size:16384\r\na=candidate:1467250027 1 UDP 1467250027 00.0.0 1 typ host\r\n`
});
console.readLine(); After adding the stunbinding event, the full version of Peer B has established a connection. There is still an issue where the second connection handshake fails if there is a short time between connections, still working on it. It's a bit of a shame that the implementation of the stunbinding event doesn't look very good. |
I found it. It was an issue with my test code, where I was testing with Peer A bound to a port, causing Peer B to treat the same address and port as the same connection. I thought there would be connection confusion here when udp is multiplexed, but then I figured out that this only happens when udp is multiplexed on both ends. So it's not a problem. |
So far, I've made the following changes:
I will submit pr for each of these features if you find them acceptable. Because I don't understand many implementation details, the implementation of reflect stun in libjuice and libdatachannel is too complicated, I'm sure you will have a better design. I don't think I'll submit it. |
Adds two new optional config keys - `iceUfrag` and `icePwd` which are passed to libjuice. Depends on: paullouisageneau/libjuice#249 Refs: paullouisageneau#1166
Adds two new optional config keys - `iceUfrag` and `icePwd` which are passed to libjuice. Depends on: paullouisageneau/libjuice#249 Refs: paullouisageneau#1166
Adds two new optional config keys - `iceUfrag` and `icePwd` which are passed to libjuice. Refs: paullouisageneau#1166
This is very exciting stuff, thanks so much for picking it up @xicilion I've just got js-libp2p successfully dialling go-libp2p over webrtc-direct from node.js using a patched version of node-datachannel (murat-dogan/node-datachannel#256) that uses libdatachannel with #1201 applied and the latest version of libjuice (so it has paullouisageneau/libjuice#243 which doesn't appear to have made it into a release yet). Haven't quite go the listener end done yet, I get a DTLS error (logs below) but this is some nice progress!
|
paullouisageneau/libjuice#243 allows setting the ICE ufrag and pwd fields instead of generating random ones every time. paullouisageneau/libdatachannel#1201 exposes config keys to allow setting the fields in libjuice from libdatachannel. This PR allows setting the fields in libdatachannel from the PeerConnection constructor. It will require the two PRs above being merged an shipped before this is ready for merging. Refs: paullouisageneau/libdatachannel#1166
I think I've figured this out, I had the wrong DTLS roles set in the inferred SDP offers/answers, seems to work now. |
yep, I was waiting for libjuice@243 to be released to implement this feature, glad you released it. |
I have a PR at libjuice, but don't think the implementation is very nice. |
Ah, no - nothing's been released yet, I'm running locally off patched versions. Hopefully @paullouisageneau can take a look soon.
Yes, I saw that - I ended up just opening a UDP port using node.js and decoding the incoming messages using the stun module to extract the ufrag, though I'd much rather not have to pull an additional dependency in so something like this would be better. |
How can libdatachannel establish a connection with Peer A again on the same port in this way? I read the code: It looks like that the listener is creating a new rtc connection on a different port. that may not work behind NAT I thought. Have you tested it in the NAT environment? @achingbrain |
Returns a vector that contains the certificate fingerprints used by the connection to the remote peer. Closes paullouisageneau#1203 Refs paullouisageneau#1166
Returns a vector that contains the certificate fingerprints used by the connection to the remote peer. Closes paullouisageneau#1203 Refs paullouisageneau#1166
Returns a vector that contains the certificate fingerprints used by the connection to the remote peer. Closes paullouisageneau#1203 Refs paullouisageneau#1166
Returns a vector that contains the certificate fingerprints used by the connection to the remote peer. Closes paullouisageneau#1203 Refs paullouisageneau#1166
Adds an optional second argument to `rtc::PeerConnection::setLocalDescription` that can contain an ICE ufrag and pwd that if passed will be used in place of the randomly generated versions. Refs: paullouisageneau#1166 Refs: paullouisageneau#1201 (comment)
Adds an optional second argument to `rtc::PeerConnection::setLocalDescription` that can contain an ICE ufrag and pwd that if passed will be used in place of the randomly generated versions. Refs: paullouisageneau#1166 Refs: paullouisageneau#1201 (comment)
Perfect, now the official version can be directly used for implementing webrtc direct. |
I did some tests and confirmed that this approach won't work. When Peer A is behind NAT, the connection will fail because NAT detects incoming packets from another port that has not been accessed from the inside before. In other words, if Peer B wants to support access from Peer A behind NAT, the only way is to listen for connections on the same port using UdpMux. So, we may have to consider the feasibility of paullouisageneau/libjuice#248. |
I figured something like that might be necessary, I haven't got as far as testing behind NATs yet - thanks for looking into it. |
Adds an optional second argument to `rtc::PeerConnection::setLocalDescription` that can contain an ICE ufrag and pwd that if passed will be used in place of the randomly generated versions. Refs: paullouisageneau#1166 Refs: paullouisageneau#1201 (comment) Co-authored-by: Paul-Louis Ageneau <paul-louis@ageneau.org>
Calls the functions added to libjuice in paullouisageneau/libjuice#248 Exports a `OnUnhandledStunRequest` function that can be passed a callback that will be invoked when an incoming STUN message is received that has no corresponding agent for the ICE ufrag. Closes paullouisageneau#1166
In the WebRTC specification, setLocalDescription accepts the full sessionDescription:
https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setLocalDescription#syntax
In general, applications do not need to set up sdp specifically, however libp2p is designed to establish connections independent of signaling by modifying ice-ufrag and ice-pwd:
https://github.com/libp2p/specs/blob/master/webrtc/webrtc-direct.md#browser-to-public-server
The setLocalDescription in libdatachannel only supports rtc::Description::Type, is there any way to implement a protocol like libp2p WebRTC Direct in libdatachannel?
The text was updated successfully, but these errors were encountered: