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

Support for specifying STUN server: configure client (2.5/3) #1657

Merged
merged 2 commits into from
Oct 31, 2023

Conversation

jotaen4tinypilot
Copy link
Contributor

@jotaen4tinypilot jotaen4tinypilot commented Oct 12, 2023

Related #1460.

Now that the backend is able to re-write the Janus configuration to make Janus connect to a STUN server, this PR adds the client counterpart. That means when the user enabled STUN, the frontend’s WebRTC library will also be configured to use the same STUN server.

This PR complements #1647, and strictly speaking, it even should have preceded it.

Note that the PR review is only about the code itself, but it does not include an end-to-end test of the full STUN functionality on device. Charles thankfully did that separately. (To sum that up: the testing procedure turned out to be rather tricky, unfortunately… We still aren’t 100% confident that our STUN implementation reliably solves connection issues in remote access scenarios, but given the testing complexity, we decided that the status quo has yielded enough evidence for us to move forward.)

Some notes on the code:

  • Since the STUN server address comes from the backend and are hence validated, I used a rather pragmatic approach in createIceServerUrls to account for IPv6 addresses.
  • webrtc-video.js is of type “module”, so it’s a bit tricky to pass parameters to it from the parent script. I think using global variables is not super beautiful, but it gets the job done. I’m open for better ideas, but the current mechanism would also be “good enough” for me.
  • There is, unfortunately, one quirk with ESLint and Jinja templates, regarding the TINYPILOT_JANUS_STUN_PORT assignment, where we have to apply a rather ugly workaround. As far as I can see, there is not much we can do about that.

@jotaen4tinypilot jotaen4tinypilot changed the title Stun/2.5 client stun Support for specifying STUN server: configure client Oct 12, 2023
@jotaen4tinypilot
Copy link
Contributor Author

@mtlynch I don’t have a fully-fledged test setup for STUN, so I’m not sure how to ultimately verify this PR on my end. I was successfully able to trace down the ICE-Server/STUN configuration in the Janus client, but I could only see that the STUN config gets passed on correctly inside the Janus client, however I was not able to verify that it actually has the desired effect. It neither says something in the logs (not even in verbose/debug mode), nor does it reveal anything in the Network tab of the browser console.

Would it make sense to loop in Charles here, and ask him to perform an end-to-end test of the STUN feature in its entirety? (I could set up a test bundle that contains all the things, including the yet-to-be-merged UI.) As an alternative, I could try to reproduce the test setup, but I’m not sure how long that takes.

@mtlynch
Copy link
Contributor

mtlynch commented Oct 13, 2023

Would it make sense to loop in Charles here, and ask him to perform an end-to-end test of the STUN feature in its entirety? (I could set up a test bundle that contains all the things, including the #1647.)

Yeah, that sounds like the best path here.

@jotaen4tinypilot jotaen4tinypilot changed the title Support for specifying STUN server: configure client Support for specifying STUN server: configure client (2.5/3) Oct 26, 2023
@jotaen4tinypilot jotaen4tinypilot marked this pull request as ready for review October 26, 2023 20:14
Copy link
Contributor Author

Automated comment from CodeApprove ➜

@jdeanwallace please review this Pull Request

Copy link
Contributor

@jdeanwallace jdeanwallace left a comment

Choose a reason for hiding this comment

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

Automated comment from CodeApprove ➜

⏳ Approval Pending (3 unresolved comments)
Approval will be granted automatically when all comments are resolved


In: app/static/js/webrtc-video.js:

> Line 208
  const server = stunServer.includes(":") ? `[${stunServer}]` : stunServer;

Is the ipv6 stuff necessary? From what I can see, ipv6 is disabled by default:
https://github.com/meetecho/janus-gateway/blob/v1.0.1/conf/janus.jcfg.sample.in#L214


In: app/templates/index.html:

> Line 92
    {% if janus_stun_server and janus_stun_port %}

Could we immediately initialize an ICE Servers variable? It doesn't seem like we need the separate server and port variables. Something like:

window.JANUS_ICE_SERVERS = [{urls: `stun:${stunSterver}:${stunPort}`}];

In: app/templates/index.html:

> Line 102
    </script>

An alternative to the eslint issue can be something like this:

<script id="janus-stun-server" type="application/json">{{ janus_stun_server|tojson|safe }}</script>
<script id="janus-stun-port" type="application/json">{{ janus_stun_port|tojson|safe }}</script>
<script>
  window.TINYPILOT_JANUS_STUN_SERVER = JSON.parse(document.getElementById("janus-stun-server").textContent);
  window.TINYPILOT_JANUS_STUN_PORT = JSON.parse(document.getElementById("janus-stun-port").textContent);
</script>

👀 @jotaen4tinypilot it's your turn please take a look

Copy link
Contributor Author

@jotaen4tinypilot jotaen4tinypilot left a comment

Choose a reason for hiding this comment

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

Automated comment from CodeApprove ➜

In: app/static/js/webrtc-video.js:

> Line 208
  const server = stunServer.includes(":") ? `[${stunServer}]` : stunServer;

As far as I understand, Janus provides out-of-the-box support for IPv6, and the media.ipv6 option is for explicitly specifying one IP mode, instead of having Janus selecting that on its own, e.g. when gathering DNS candidates. To me, the config file documentation isn’t super clear here, unfortunately. The CLI docs seem a bit clearer on the semantics here (the --ipv6-candidates CLI flag is the equivalent of the media.ipv6 file preference).

For example, even without media.ipv6 or --ipv6-candidates, Janus still accepts and processes an IPv6 STUN address:

Screenshot 2023-10-30 at 21.37.19.png

A few notes on that screenshot:

  • 2607:f8b0:4023:1401::7f is stun.l.google.com
  • Janus doesn’t bother to format the IPv6 address correctly in the logs (by wrapping the server part in square brackets). The Janus frontend library would yield an error if we passed 2607:f8b0:4023:1401::7f:19302 instead of [2607:f8b0:4023:1401::7f]:19302.
  • IPv6 support is disabled would be IPv6 support is enabled if media.ipv6 or --ipv6-candidates was set. The STUN server connection seems to work regardless, either way.

In regards to IPv4/IPv6 in general, my thinking was to consider and provide IPv6 support for the STUN feature from the start, seeing that we don’t need much extra effort as far as Janus is concerned. E.g., the backend implementation also supports IPv6 all the way.


In: app/templates/index.html:

> Line 92
    {% if janus_stun_server and janus_stun_port %}

My thinking was that the assembly of that ICE-server value was logic that’s closely related to Janus and our WebRTC initialization procedure in webrtc-video.js, so despite the higher LOC cost, I’d think it makes for slightly better encapsulation. (Think: setting the global variables is like specifying method arguments on the call-side, and the implementation details are hidden inside the module of the method body.)


In: app/templates/index.html:

> Line 102
    </script>

That’s an interesting approach as well – although, I feel that it probably also wouldn’t spare us a comment to explain why we take a detour through JSON-land, instead of assigning the values directly. I don’t feel strongly about either approach, so unless you do, I’d just keep the current code.

Copy link
Contributor

@jdeanwallace jdeanwallace left a comment

Choose a reason for hiding this comment

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

Automated comment from CodeApprove ➜

Approved: I have approved this change on CodeApprove and all of my comments have been resolved.

@jdeanwallace jdeanwallace dismissed their stale review October 30, 2023 21:55

Review approved on CodeApprove

@jotaen4tinypilot jotaen4tinypilot merged commit a8428c6 into master Oct 31, 2023
12 checks passed
@jotaen4tinypilot jotaen4tinypilot deleted the stun/2.5-client-stun branch October 31, 2023 13:48
jotaen4tinypilot added a commit that referenced this pull request Oct 31, 2023
Resolves #1460. Stacked on
#1646, blocked on
#1657 and
tiny-pilot/tinypilotkvm.com#1053.

This PR adds the UI for specifying a STUN server, and eventually makes
the feature available to the end-user.

→ [Latest bundle off this
branch](https://output.circle-artifacts.com/output/job/f2379490-a459-43d5-b1fb-ce009c971fcd/artifacts/0/bundler/dist/tinypilot-community-20231005T1841Z-1.9.1-27+3ff1062.tgz),
for testing the entire PR stack.

## Demo


https://github.com/tiny-pilot/tinypilot/assets/83721279/8108dcf2-1fe4-42d1-8c2a-4066cc669ad0

## Notes

- In contrast to the mockups in the ticket (e.g. [the latest
one](#1460 (comment))),
the proposed implementation here has a separate field for the host part
and the port part of the STUN address. I realized that this simplifies a
lot of things, because we don’t need to parse and split a unified
address string (e.g. `stun.example.org:3478`), or serialize it again
correctly. That would be especially tricky, since Janus supports IPv6,
where the serialized format would look like e.g.
`[12:c1:1832::c1:2]:3478`, so we can’t just naively split and join at
the `:` character.
- I separated the STUN input part into its own component, otherwise the
`<video-settings-dialog>` would have become pretty large.
- I debated whether to split off the entire “Advanced Settings” section,
or just the STUN input. I eventually settled with the latter, because I
felt it was conceptually more fitting, and I thought it would be the
more granular approach, e.g. should we add more controls to the advanced
settings in the future. I’m not married to it, though – I also think the
entire `<video-settings-dialog>` component is borderline big anyway, so
should we ever expand it further, it would probably be worth to consider
a refactoring.
- I had to [refactor (reverse) the CSS logic for showing/hiding the
`.setting-...`
classes](https://github.com/tiny-pilot/tinypilot/blob/e305ca104b54e485ee4e32876f1f8d181103b810/app/templates/custom-elements/video-settings-dialog.html#L30-L38).
With the current implementation, it only allowed for `display: flex`,
but with the new code, we also need `display: block` (on
`.advanced-settings`).
- Note that the `#stun-validation-error` is still part of
`<video-settings-dialog>`, since validation errors are supposed to be
“dialog-wide” errors, displayed at the bottom of the dialog, near the
call-to-action button. If there were more inline / validation errors in
the future, we’d also add them there, at top level.
- One note about terminology: in the Janus config, the two address
components are called [`server` and
`port`](https://github.com/tiny-pilot/tinypilot/blob/0a03d2caf54aa083ef66c1606e28321f41aca781/debian-pkg/usr/share/tinypilot/templates/janus.jcfg.j2#L24-L25).
I took over that terminology in the entire code, but in the UI I
labelled the `server` fragment “Host”. To me, that’s technically more
accurate, and since we already say “STUN Server” next to the dropdown
button already, I thought “Server” might be confusing. I’m not sure
there is a “right” answer here. We could otherwise also call it
“Address”, but I feel that’s even broader.
- The UI implementation has a few edge-cases with slightly
unexpected/quirky behavior. We’d need to pay with more code and
complexity to sort them out, though, and I felt that wouldn’t be worth
it. Examples:
- If you select “Custom” in the dropdown and then manually fill in the
Google server details (`stun.l.google.com` + `19302`), then it would
display the “Google” option the next time you open the dialog, and not
the custom input fields. That’s because both ways look the same
internally, and we’d need to maintain extra state to distinguish whether
this was a custom input or one of the predefined selections.
- If you select “Custom” and enter an invalid or incomplete address, and
then choose “MJPEG” as streaming mode again, and then hit the “Apply”
button, it still displays the STUN validation error. The reason is that
the entire code around processing video settings is designed to always
send and apply **all** values, regardless of what mode you eventually
chose.

<a data-ca-tag
href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1647"><img
src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review
on CodeApprove" /></a>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants