WebRTSP / ReStreamer

Media URLs ReStreamer and Cloud DVR
GNU General Public License v3.0
34 stars 6 forks source link

Some webrtc web clients can establish a connection others can't #21

Closed eslam-ahmed-khair closed 2 years ago

eslam-ahmed-khair commented 2 years ago

Hello,

Assume that Machine X is hosting a docker container that is running ReStreamerand coturn acting as the local stun/turn server (i.e. both are behind NAT). The container is exposing some ports needed for coturn to the host Machine X. For reference the ports are:

     - "3478:3478"
     - "3478:3478/udp"
     - "3479:3479"
     - "3479:3479/udp"
     - "80:80"
     - "80:80/udp"
     - "49152-49200:49152-49200" # limit the relay port range
     - "49152-49200:49152-49200/udp" # limit the relay port range

ReStreamer is configured with the following ICE servers: stun-server: "stun://192.168.1.108:3478" turn-server : "turn://admin:admin@192.168.1.108:3478" coturn launch command: turnserver -X 192.168.1.108/172.16.238.2 -a -f -r north.gov -v --min-port 49152 --max-port 49200

Now I would like other machines on the same LAN as X, say machine Y and Z, to connect as webrtc clients. Problem is Machine Y can and Machine Z can't!

Refer to Machine Z ICE gathering trace here Refer to Machine Y ICE gathering trace here

The difference I found is that Machine Y sent the following ICE candidate which is its local IP in the LAN: 0/candidate:2222700650 1 udp 2113937151 192.168.1.105 37622 typ host generation 0 ufrag mIbA network-cost 999 Yet Machine Z sent this IPv6 IP ICE candidate and didn't even send an IPv4 candidate: 0/candidate:2530088836 1 udp 2113937151 9af8f8fc-e12a-4b3c-b5b4-fb76a833e17c.local 58799 typ host generation 0 ufrag XwOb network-cost 999

What could cause such issue? any clues?

RSATom commented 2 years ago

I don't see any srflx Ice Candidate from 192.168.1.x network for ReStreamer in your logs. This can mean your STUN server is not working properly. I think the problem is it is on the same docker network as ReStreamer and provides only Ice Candidates from docker private network. It's possible coturn can be configured better for that case, but I didn't do something like this myself yet.

eslam-ahmed-khair commented 2 years ago

Would it help to use google's STUN server? also do you think that coturn should be placed directly on the Host X? but I am sure that coturn doesn't mind being behind NAT itself.

eslam-ahmed-khair commented 2 years ago

let's go back to the basic setup: ReStreamer running natively on 192.168.1.108 with no STUN/TURN server. A local client 192.168.1.106 is able to connect without issues. yet the client's IP doesn't show in the ice candidates here

ReStreamer running natively on 192.168.1.108 with just google's STUN server. A local client 192.168.1.106 is able to connect without issues. again, the client's IP doesn't show in the ice candidates here. Now you can see that more cadidates are available that uses the public IP 197.58.221.59 to the local IP 192.168.1.108

ReStreamer and coturn (STUN) running natively on 192.168.1.108. A local client 192.168.1.106 is able to connect without issues. again, the client's IP doesn't show in the ice candidates here.

Why is it that some clients are able to send their local IPs and work and when others don't send their local IPs, such as Machine Z, they don't work. Yet here all the trials mentioned above, the client never sent its IP and it still worked!

Is there a way to figure out what ice candidate did the webrtcbinof gstreamer pick up? What do you make of this?

RSATom commented 2 years ago

it's from your log for first try:

[2021-12-30 12:52:57.919] [WsServer] [trace] WsServer -> : SETUP 1 WEBRTSP/0.2
CSeq: 34
Session: 1
content-type: application/x-ice-candidate

0/a=candidate:34 1 UDP 2013266420 192.168.1.108 55775 typ host

[2021-12-30 12:52:57.919] [WsServer] [trace] WsServer -> : SETUP 1 WEBRTSP/0.2
CSeq: 35
Session: 1
content-type: application/x-ice-candidate

0/a=candidate:35 1 TCP 1015024639 192.168.1.108 9 typ host tcptype active

[2021-12-30 12:52:57.919] [WsServer] [trace] WsServer -> : SETUP 1 WEBRTSP/0.2
CSeq: 36
Session: 1
content-type: application/x-ice-candidate

0/a=candidate:36 1 TCP 1010830335 192.168.1.108 59467 typ host tcptype passive
RSATom commented 2 years ago

STUN server should run outside NAT, or at least it should know about it's external IP in case of 1:1 NAT (for example on AWS EC2)

RSATom commented 2 years ago

Google STUN (or any other STUN running on public internet) is not the best option if you want run WebRTC over local network, since it will give your provider's IP.

eslam-ahmed-khair commented 2 years ago

it's from your log for first try:

[2021-12-30 12:52:57.919] [WsServer] [trace] WsServer -> : SETUP 1 WEBRTSP/0.2
CSeq: 34
Session: 1
content-type: application/x-ice-candidate

0/a=candidate:34 1 UDP 2013266420 192.168.1.108 55775 typ host

[2021-12-30 12:52:57.919] [WsServer] [trace] WsServer -> : SETUP 1 WEBRTSP/0.2
CSeq: 35
Session: 1
content-type: application/x-ice-candidate

0/a=candidate:35 1 TCP 1015024639 192.168.1.108 9 typ host tcptype active

[2021-12-30 12:52:57.919] [WsServer] [trace] WsServer -> : SETUP 1 WEBRTSP/0.2
CSeq: 36
Session: 1
content-type: application/x-ice-candidate

0/a=candidate:36 1 TCP 1010830335 192.168.1.108 59467 typ host tcptype passive

That's the IP of the of ReStreamer, not the web client

STUN server should run outside NAT, or at least it should know about it's external IP in case of 1:1 NAT (for example on AWS EC2)

Except that I am using docker as my NAT, I really don't need to go out to the router for this. In this case, the Local IP is the external IP. Hence coturn is run as ./turnserver -X 192.168.1.108/172.16.238.2 -a -f -r north.gov -v

RSATom commented 2 years ago

STUN server should run outside NAT, or at least it should know about it's external IP in case of 1:1 NAT (for example on AWS EC2)

Sorry, it was about TURN. STUN server should be able get incoming connection from IP not behind NAT.

RSATom commented 2 years ago

@eslam-ahmed-khair browser give its own IP here:

[2021-12-30 12:53:00.368] [WsServer] [trace] -> WsServer: SETUP 1 WEBRTSP/0.2
CSeq: 1
Session: 1
Content-Type: application/x-ice-candidate

0/candidate:2530088836 1 udp 2113937151 bd189776-4476-45da-b1bc-a7acfed2174b.local 56353 typ host generation 0 ufrag Ap0U network-cost 999

It's so called mDns Ice Candidates. And ReStreamer is able (at some conditions) to resolve it to real IP.

eslam-ahmed-khair commented 2 years ago

That's good to know. Can we know what it was resolved to?

Also, Is there a way to figure out which ice candidate was selected by the protocol?

RSATom commented 2 years ago

That's good to know. Can we know what it was resolved to?

You can add some logging here: https://github.com/WebRTSP/RtStreaming/blob/master/GstRtStreaming/Helpers.cpp#L138 (but it will be called only if IsMDNSResolveRequired returns true)

Also, Is there a way to figure out which ice candidate was selected by the protocol?

It's possible but requires some changes to code. It can be done with https://gstreamer.freedesktop.org/documentation/webrtc/index.html?gi-language=c#webrtcbin::get-stats

eslam-ahmed-khair commented 2 years ago

I can't help but wonder why it works with same LAN clients and others don't?

RSATom commented 2 years ago

Only full list of Ice Candidates from both sides can explain this (+ info about used IPs and networks).... All I can say right now - something is still different, and I would recommend you find what exactly.

eslam-ahmed-khair commented 2 years ago

It's possible but requires some changes to code. It can be done with https://gstreamer.freedesktop.org/documentation/webrtc/index.html?gi-language=c#webrtcbin::get-stats

So I invoked the webrtcbin action get-stats when the connection state is GST_WEBRTC_PEER_CONNECTION_STATE_NEW. here's the code snippet

GstPromise* p = gst_promise_new_with_change_func(promise_changed, nullptr, nullptr);
g_signal_emit_by_name(rtcbin, "get-stats", nullptr, p);

and here are the functions printing the struct, since I am using Gstreamer 1.16.2, I can't use gst_structure_serialize or gst_structure_to_string

gboolean print_struct(GQuark field, const GValue* value, gpointer pfx) {
  if (GST_VALUE_HOLDS_STRUCTURE(value)) {
    const GstStructure* nested_struct = gst_value_get_structure(value);
    g_print("printing nested struct: %s\n", gst_structure_get_name(nested_struct));
    gst_structure_foreach(nested_struct, print_struct, (gpointer) "    ");
  } else {
    gchar* str = gst_value_serialize(value);
    g_print("%s  %s: %s\n", (gchar*)pfx, g_quark_to_string(field), str);
    g_free(str);
  }
  return TRUE;
}

void promise_changed(GstPromise* promise, gpointer user_data) {
  if (gst_promise_wait(promise) == GST_PROMISE_RESULT_REPLIED) {
    const GstStructure* reply = gst_promise_get_reply(promise);
    g_print("printing struct: %s\n", gst_structure_get_name(reply));
    gst_structure_foreach(reply, print_struct, (gpointer) "    ");
  }
  gst_promise_unref(promise);
}

Yet the results are pointless at this point, as I was hoping to find the exact ice candidate pair used (unless I am really not printing all the stats properly).

printing struct: application/x-webrtc-stats
printing nested struct: peer-connection
      data-channels-opened: 0
      data-channels-closed: 0
      data-channels-requested: 0
      data-channels-accepted: 0
      type: GST_WEBRTC_STATS_PEER_CONNECTION
      timestamp: 11098421.231000001
      id: peer-connection-stats
printing nested struct: codec
      type: GST_WEBRTC_STATS_CODEC
      timestamp: 11098421.231000001
      id: codec-stats-sink_0
      payload-type: 96
      clock-rate: 90000
      ssrc: 3404116983
printing nested struct: transport
      type: GST_WEBRTC_STATS_TRANSPORT
      timestamp: 11098421.231000001
      id: transport-stats_webrtcdtlstransport0
printing nested struct: transport
      type: GST_WEBRTC_STATS_TRANSPORT
      timestamp: 11098421.231000001
      id: ice-candidate-pair_webrtcnicetransport0
RSATom commented 2 years ago

I didn't use get-stats from webrtcbin myself yet, but I've used getStats from WebRTC.org library. And there it was possible to get selected Ice Candidates pair... So again, I would recommend you to try newer GStreamer version. Maybe stats was extended there already....

RSATom commented 2 years ago

Other possible solution can be enable logs for libnice...

eslam-ahmed-khair commented 2 years ago

I didn't use get-stats from webrtcbin myself yet, but I've used getStats from WebRTC.org library. And there it was possible to get selected Ice Candidates pair... So again, I would recommend you to try newer GStreamer version. Maybe stats was extended there already....

Okay, can you elaborate on which Ubuntu version and Gstreamer version I should be using?

RSATom commented 2 years ago

At least 20.10

eslam-ahmed-khair commented 2 years ago

Right, so I went back to the basics and started from the first post in this issue.

The fact that Machine Y is working yet Machine Z is not! if we inspect their ICE candidates, you'll find that Machine Y used its local IP address directly while Machine Z send its mDNS instead (.local)

Reading about mDNS has lead to stumble upon this helpful link. One funny thing mentioned is that you can disable mDNS from chrome browser and let it send its local IP naked, exposing user privacy bla bla bla, using this flag chrome://flags/#enable-webrtc-hide-local-ips-with-mdns.

So I disabled it on machine Z, and it worked! Chrome sent the local IP of Machine Z this time.

Reading about how mDNS and how it works now makes sense, because ReStreaming will never be able to resolve the mDNS address while residing in the container. There's even a daemon avahi that might be needed in the container to be able to resolve such addresses which wasn't running in the first place.

That's good to know. Can we know what it was resolved to?

You can add some logging here: https://github.com/WebRTSP/RtStreaming/blob/master/GstRtStreaming/Helpers.cpp#L138 (but it will be called only if IsMDNSResolveRequired returns true)

Putting some extra logging here with an else branch will tell use if the resolution was successful or not. When ReStreamer is running natively, the resolution succeeds, yet when running in container it fails. This is a different problem that I need to address next.

RSATom commented 2 years ago

So you just need get STUN working to override this issue....

eslam-ahmed-khair commented 2 years ago

So you just need get STUN working to override this issue....

I don't think it has something to do with the STUN server because I was using coturn's. but I'll try Google's STUN server just in case.

RSATom commented 2 years ago

@eslam-ahmed-khair I've added selected ICE Candidate pair logging. Please check if you still need it.

eslam-ahmed-khair commented 2 years ago

@eslam-ahmed-khair I've added selected ICE Candidate pair logging. Please check if you still need it.

Thank you! Yes, I'll need it. It's a really nice thing to have. it lets you know what happens under the curtain.