Skip to content
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

Clients can't signal each other on initial connection without internet access #12

Open
andrew103 opened this issue Aug 14, 2020 · 8 comments

Comments

@andrew103
Copy link
Contributor

I'm looking to have two clients connect with each other over a local network that doesn't have internet access. The default constructors in RTCConnection use Google's free STUN server for signaling which requires an internet connection. Traditionally with WebRTC, you can simply remove the entry from the configuration and WebRTC would adopt the first client as the signaling server (at least I think so from what I know).

When attempting to do something similar here with

conn = RTCConnection(rtcConfiguration=RTCConfiguration())

and

var conn = new rtcbot.RTCConnection(rtcConfiguration={iceServers: []});

the connection fails to complete as it seems there is no signaling happening.

Now I noticed that in a previous issue and in the tutorial for connecting over 4G there was mention that the parent library aiortc doesn't have its own TURN server built in and one of the suggested solutions was to install coTURN as a local TURN server. I haven't done it yet but I wanted to confirm my thinking that this would be the solution to my problem of wanting RTC communication within an isolated network.

@dkumor
Copy link
Owner

dkumor commented Aug 16, 2020

In general, WebRTC should allow connections over a local network - I will check what is going on here.

@dkumor
Copy link
Owner

dkumor commented Aug 17, 2020

Alright, so there are two tiny changes needed to the posted code:

  1. An empty RTCConfiguration actually has default STUN servers added to it (https://github.com/aiortc/aiortc/blob/main/src/aiortc/rtcicetransport.py#L177). You have to explicitly specify an empty list to not have any STUN servers used:
conn = RTCConnection(rtcConfiguration=RTCConfiguration([]))
  1. In javascript, unfortunately, you can't set the second argument of the constructor by key as is done in Python. You have to explicitly specify both arguments.
var conn = new rtcbot.RTCConnection(true,{iceServers: []});

I was able to reproduce your problem, and the above changes fixed it for me - please let me know if they work for you too!

@andrew103
Copy link
Contributor Author

andrew103 commented Aug 17, 2020

This didn't seem to solve my issue, but it did make some progress(ish?). Now it doesn't access the Google STUN server when connected to the internet which is good, but still fails to establish local connections.

Did you have a local STUN/TURN server running? I can share more code as well if necessary.

@dkumor
Copy link
Owner

dkumor commented Aug 17, 2020

Here is the code I used, based on the offloading example in tutorials:

Working code
# run this first on computer 1

from aiohttp import web

routes = web.RouteTableDef()

from rtcbot import RTCConnection
from aiortc import RTCConfiguration

conn = RTCConnection(rtcConfiguration=RTCConfiguration([]))


@conn.subscribe
def onMessage(msg):
    print("GOT MESSAGE", msg)


@routes.post("/connect")
async def connect(request):
    clientOffer = await request.json()
    serverResponse = await conn.getLocalDescription(clientOffer)
    print(serverResponse)
    return web.json_response(serverResponse)


async def cleanup(app):
    await conn.close()


app = web.Application()
app.add_routes(routes)
app.on_shutdown.append(cleanup)
web.run_app(app)

and

# Run this second on computer 2

import asyncio
import aiohttp
import cv2
import json
from rtcbot import RTCConnection
from aiortc import RTCConfiguration

conn = RTCConnection(rtcConfiguration=RTCConfiguration([]))


@conn.subscribe
def onMessage(msg):
    print("GOT MESSAGE", msg)


async def connect():
    localDescription = await conn.getLocalDescription()
    print(localDescription)
    async with aiohttp.ClientSession() as session:
        async with session.post(
            "http://192.168.1.112:8080/connect", data=json.dumps(localDescription)
        ) as resp:
            response = await resp.json()
            await conn.setRemoteDescription(response)
            conn.put_nowait("hi")


asyncio.ensure_future(connect())
try:
    asyncio.get_event_loop().run_forever()
finally:
    conn.close()

However, I did notice that an example with chrome on the other end DOES fail without any ICE servers, even if there is an internet connection if the browser is run on a different machine (I tested on the same machine yesterday):

Failing example
from aiohttp import web

routes = web.RouteTableDef()

from rtcbot import RTCConnection, getRTCBotJS
import logging
from aiortc import RTCConfiguration

logging.basicConfig(level=logging.DEBUG)

# For this example, we use just one global connection
conn = RTCConnection(rtcConfiguration=RTCConfiguration([]))


@conn.subscribe
def onMessage(msg):  # Called when each message is sent
    print("Got message:", msg)


# Serve the RTCBot javascript library at /rtcbot.js
@routes.get("/rtcbot.js")
async def rtcbotjs(request):
    return web.Response(content_type="application/javascript", text=getRTCBotJS())


# This sets up the connection
@routes.post("/connect")
async def connect(request):
    clientOffer = await request.json()
    print("OFFER\n\n", clientOffer)
    serverResponse = await conn.getLocalDescription(clientOffer)
    print("RESPONSE\n\n", serverResponse)
    return web.json_response(serverResponse)


@routes.get("/")
async def index(request):
    return web.Response(
        content_type="text/html",
        text="""
    <html>
        <head>
            <title>RTCBot: Data Channel</title>
            <script src="/rtcbot.js"></script>
        </head>
        <body style="text-align: center;padding-top: 30px;">
            <h1>Click the Button</h1>
            <button type="button" id="mybutton">Click me!</button>
            <p>
            Open the browser's developer tools to see console messages (CTRL+SHIFT+C)
            </p>
            <script>
                //var conn = new rtcbot.RTCConnection();
                
                var conn = new rtcbot.RTCConnection(true,{iceServers: []});

                async function connect() {
                    console.log("START")
                    let offer = await conn.getLocalDescription();
                    console.log("OFFER")

                    // POST the information to /connect
                    let response = await fetch("/connect", {
                        method: "POST",
                        cache: "no-cache",
                        body: JSON.stringify(offer)
                    });

                    await conn.setRemoteDescription(await response.json());

                    console.log("Ready!");
                }
                connect();


                var mybutton = document.querySelector("#mybutton");
                mybutton.onclick = function() {
                    conn.put_nowait("Button Clicked!");
                };
            </script>
        </body>
    </html>
    """,
    )


async def cleanup(app):
    await conn.close()


app = web.Application()
app.add_routes(routes)
app.on_shutdown.append(cleanup)
web.run_app(app)

It therefore looks like the issue showing up right now must have something to do with how aiortc parses the offers from .local addresses from browsers, or something of the sort - I think this is an upstream issue - I will try to find the source and open an issue in aiortc.

@andrew103
Copy link
Contributor Author

So I managed to get things working by setting up a local TURN server with coturn. I still encourage trying to get the issue where no STUN/TURN servers are specified resolved upstream.

For anyone that comes across this through Google search (as there's not much in terms of resources for coturn out there), here's my /etc/turnserver.conf setup:

listening-port=3478
tls-listening-port=5349
listening-ip=<device_ip_on_connected_network>
relay-ip=<device_ip_on_connected_network>
external-ip=<device_ip_on_connected_network>
realm=rpi0.io    # This us usually the domain of the server if connected to the internet. If not, then basically anything
server-name=rpi0.io    # same here

user=rpi0_turn:rpi0_turn    # format is <username>:<password>
lt-cred-mech

The TURN server is accessed by my scripts with:

conn = RTCConnection(rtcConfiguration=RTCConfiguration([RTCIceServer("turn:<device_ip_on_connected_network>:3478", "rpi0_turn", "rpi0_turn")]))

in Python, and

var conn = new rtcbot.RTCConnection(true,{iceServers: [
    {
        "url":"turn:<device_ip_on_connected_network>:3478",
        "username":"rpi0_turn",
        "credential":"rpi0_turn"
    }
]});

in JavaScript.

Thanks @dkumor for your help!

@dkumor
Copy link
Owner

dkumor commented Aug 18, 2020

@andrew103 If you have a moment, it would be really appreciated if you'd open a PR adding this content to https://github.com/dkumor/rtcbot/blob/master/examples/mobile/README.md - it could replace the current content about pion, since coturn is much more commonly used.

I would do it myself, but I think you should get credit for the info in git history.

I will try to get this issue fixed, since it is quite absurd that an internet connection is required for LAN connections!

@andrew103
Copy link
Contributor Author

Done. Happy to help 👍

@dkumor
Copy link
Owner

dkumor commented Sep 23, 2020

I'd like to leave this issue open for the time being if that's OK with you - the underlying problem still exists!

@dkumor dkumor reopened this Sep 23, 2020
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

No branches or pull requests

2 participants