From 2e4979c029714bfea212cdaf05cb32874ab1cb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Fri, 20 Sep 2024 20:07:51 +0200 Subject: [PATCH] Add support for Fly.io deployments --- guides/advanced/debugging.md | 15 ++++-- .../deploying.md => deploying/bare.md} | 14 ++---- guides/deploying/fly.md | 50 +++++++++++++++++++ lib/ex_webrtc/ice/fly_ip_filter.ex | 22 ++++++++ .../peer_connection/configuration.ex | 3 +- mix.exs | 12 +++-- mix.lock | 2 +- 7 files changed, 97 insertions(+), 21 deletions(-) rename guides/{advanced/deploying.md => deploying/bare.md} (91%) create mode 100644 guides/deploying/fly.md create mode 100644 lib/ex_webrtc/ice/fly_ip_filter.ex diff --git a/guides/advanced/debugging.md b/guides/advanced/debugging.md index b407dc4..76e932d 100644 --- a/guides/advanced/debugging.md +++ b/guides/advanced/debugging.md @@ -18,6 +18,7 @@ The visual aspects may not knock you off your feet, but the page provides a lot to learn more about what's in the WebRTC Internals, or simply explore the tool and see what you find useful. > #### Other browsers {: .info} +> > Chromium's WebRTC Internals is arguably the best tool of this kind. Firefox provides `about:webrtc` page, but it's not nearly as featureful as `chrome://webrtc-internals`. > Safari does not have an equivalent, but it allows you to enable verbose logging of WebRTC-related stuff. @@ -54,7 +55,7 @@ open up Wireshark and capture the RTP traffic directly. Unfortunately, WebRTC by you can obtain from packets captured live is highly limited. There are two solutions: - run Chromium with `--disable-webrtc-encryption` flag. In this case, the other WebRTC peer also needs to somehow bypass encryption, which (as of now) is -impossible in Elixir WebRTC. + impossible in Elixir WebRTC. - make Chromium dump received RTP packets after they were decrypted. @@ -79,6 +80,7 @@ text2pcap -D -u 5443,62132 -t %H:%M:%S.%f rtp-dump.txt rtp-dump.pcap Now, you should be able to open the `rtp-dump.pcap` file with Wireshark and inspect the packets! > #### What about Firefox? {: .info} +> > You can also do similar things in Firefox. Check out this [SO post](https://stackoverflow.com/questions/74399155/how-do-i-see-internal-webrtc-logs-in-firefox) > to learn how to turn on WebRTC logs, and this [blog post](https://blog.mozilla.org/webrtc/debugging-encrypted-rtp-is-more-fun-than-it-used-to-be/) > on how to dump RTP packets. @@ -135,14 +137,17 @@ When using Simulcast in a browser, make sure that you allocate not too little ba if the browser deems that there is too little bandwidth available, or that the CPU load is too big, it might decide to just stop sending one of the layers. ```js -mediaConstraints = {video: {width: { ideal: 1280 }, height: { ideal: 720 } }, audio: true}; +mediaConstraints = { + video: { width: { ideal: 1280 }, height: { ideal: 720 } }, + audio: true, +}; const localStream = await navigator.mediaDevices.getUserMedia(mediaConstraints); const pc = new RTCPeerConnection(); pc.addTransceivers(localStream.getVideoTracks()[0], { sendEncodings: [ - { rid: "h", maxBitrate: 1200 * 1024}, - { rid: "m", scaleResolutionDownBy: 2, maxBitrate: 600 * 1024}, + { rid: "h", maxBitrate: 1200 * 1024 }, + { rid: "m", scaleResolutionDownBy: 2, maxBitrate: 600 * 1024 }, { rid: "l", scaleResolutionDownBy: 4, maxBitrate: 300 * 1024 }, ], }); @@ -162,4 +167,4 @@ You can also configure PeerConnection to use a specific port range by doing Otherwise, the connection won't be established at all, or just in one direction. -Read more in our [Deploying tutorial](./deploying.md#allow-udp-traffic-in-your-firewall)! +Read more in our [Deploying tutorial](./bare.md#allow-udp-traffic-in-your-firewall)! diff --git a/guides/advanced/deploying.md b/guides/deploying/bare.md similarity index 91% rename from guides/advanced/deploying.md rename to guides/deploying/bare.md index f8620b5..2ae689c 100644 --- a/guides/advanced/deploying.md +++ b/guides/deploying/bare.md @@ -1,7 +1,7 @@ -# Deploying +# Deploying on bare machines Deploying WebRTC applications can be cumbersome. -Here are a few details you should keep in mind when trying to push your project into production. +Here are a few details you should keep in mind when trying to push your project into production on a bare machine. ## Allow UDP traffic in your firewall @@ -37,12 +37,6 @@ docker run -p 50000-50010/udp myapp Keep in mind that exporting a lot of ports might take a lot of time or even cause the Docker daemon to timeout. That's why we recommend using host's network. -## Choose your cloud provider wisely - -Many cloud providers do not offer good support for UDP traffic. -In such cases, deploying a WebRTC-based application might be impossible. -We recommend using bare machines that you can configure as you need. - ## Enable HTTPS in your frontend The server hosting your frontend site must have HTTPS enabled. @@ -66,7 +60,7 @@ Read more [here](https://nginx.org/en/docs/http/websocket.html). ## Configure STUN servers -If you are deploying your application behind a NAT, you have to configure a STUN +If you are deploying your application behind a NAT, you have to configure a STUN server that will allow it to discover its public IP address. In Elixir WebRTC this will be: @@ -86,6 +80,6 @@ And as a TURN server, you can always use our [Rel](https://github.com/elixir-web If your application is deployed behind a very restrictive NAT, which should be very rare (e.g. a symmetric NAT), you will need to configure a TURN server. -In most cases, TURN servers are needed on the client side as you don't have any control +In most cases, TURN servers are needed on the client side as you don't have any control over a network your clients connect from. For testing and experimental purposes, you can use our publicly available TURN called [Rel](https://github.com/elixir-webrtc/rel)! diff --git a/guides/deploying/fly.md b/guides/deploying/fly.md new file mode 100644 index 0000000..1448952 --- /dev/null +++ b/guides/deploying/fly.md @@ -0,0 +1,50 @@ +# Deploying on Fly.io + +Elixir WebRTC-based apps can be easily deployed on [Fly.io](https://fly.io)! + +There are just two things you need to do: + +- configure a STUN server both on the client and server side +- use custom Fly.io IP filter on the server side + +In theory, configuring a STUN server just on a one side should be enough but we recommend to do it on both sides. + +In JavaScript code: + +```js +pc = new RTCPeerConnection({ + iceServers: [{ urls: "stun:stun.l.google.com:19302" }], +}); +``` + +in Elixir code: + +```elixir +ip_filter = Application.get_env(:your_app, :ice_ip_filter) + +{:ok, pc} = + PeerConnection.start_link( + ice_ip_filter: ip_filter, + ice_servers: [%{urls: "stun:stun.l.google.com:19302"}] + ) +``` + +in `runtime.exs`: + +```elixir +if System.get_env("FLY_IO") do + config :your_app, ice_ip_filter: &ExWebRTC.ICE.FlyIpFilter.ip_filter/1 +end +``` + +in fly.toml: + +```toml +[env] + # add one additional env + FLY_IO = 'true' +``` + +That's it! +No special UDP port exports or dedicated IP address needed. +Just run `fly launch` and enjoy your deployment :) diff --git a/lib/ex_webrtc/ice/fly_ip_filter.ex b/lib/ex_webrtc/ice/fly_ip_filter.ex new file mode 100644 index 0000000..54f4c5f --- /dev/null +++ b/lib/ex_webrtc/ice/fly_ip_filter.ex @@ -0,0 +1,22 @@ +defmodule ExWebRTC.ICE.FlyIpFilter do + @moduledoc """ + ICE IP filter for Fly.io deployments. + + This module defines a single function, which filters out IP addresses, + which ICE Agent will use as its host candidates. + """ + + @spec ip_filter(:inet.ip_address()) :: boolean() + def ip_filter(ip_address) do + case :inet.gethostbyname(~c"fly-global-services") do + # Assume that fly-global-services has to resolve + # to a single ipv4 address. + # In other case, don't even try to connect. + {:ok, {:hostent, _, _, :inet, 4, [addr]}} -> + addr == ip_address + + _ -> + false + end + end +end diff --git a/lib/ex_webrtc/peer_connection/configuration.ex b/lib/ex_webrtc/peer_connection/configuration.ex index 5d5af28..c0d1dcf 100644 --- a/lib/ex_webrtc/peer_connection/configuration.ex +++ b/lib/ex_webrtc/peer_connection/configuration.ex @@ -5,6 +5,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do require Logger + alias ExICE.ICEAgent alias ExWebRTC.{RTPCodecParameters, SDPUtils} alias ExSDP.Attribute.{Extmap, FMTP, RTCPFeedback} @@ -148,7 +149,7 @@ defmodule ExWebRTC.PeerConnection.Configuration do controlling_process: Process.dest(), ice_servers: [ice_server()], ice_transport_policy: :relay | :all, - ice_ip_filter: (:inet.ip_address() -> boolean()), + ice_ip_filter: ICEAgent.ip_filter(), ice_port_range: Enumerable.t(non_neg_integer()), audio_codecs: [RTPCodecParameters.t()], video_codecs: [RTPCodecParameters.t()], diff --git a/mix.exs b/mix.exs index de149e8..6c26294 100644 --- a/mix.exs +++ b/mix.exs @@ -57,7 +57,8 @@ defmodule ExWebRTC.MixProject do defp deps do [ {:ex_sdp, "~> 1.0"}, - {:ex_ice, "~> 0.8.0"}, + # {:ex_ice, "~> 0.8.0"}, + {:ex_ice, github: "elixir-webrtc/ex_ice", override: true}, {:ex_dtls, "~> 0.16.0"}, {:ex_libsrtp, "~> 0.7.1"}, {:ex_rtp, "~> 0.4.0"}, @@ -80,17 +81,19 @@ defmodule ExWebRTC.MixProject do "simulcast", "modifying", "mastering_transceivers", - "deploying", "debugging" ] + deploying_guides = ["bare", "fly"] + [ main: "readme", logo: "logo.svg", extras: ["README.md"] ++ Enum.map(intro_guides, &"guides/introduction/#{&1}.md") ++ - Enum.map(advanced_guides, &"guides/advanced/#{&1}.md"), + Enum.map(advanced_guides, &"guides/advanced/#{&1}.md") ++ + Enum.map(deploying_guides, &"guides/deploying/#{&1}.md"), assets: "guides/assets", source_ref: "v#{@version}", formatters: ["html"], @@ -98,7 +101,8 @@ defmodule ExWebRTC.MixProject do nest_modules_by_prefix: [ExWebRTC], groups_for_extras: [ Introduction: Path.wildcard("guides/introduction/*.md"), - Advanced: Path.wildcard("guides/advanced/*.md") + Advanced: Path.wildcard("guides/advanced/*.md"), + Deploying: Path.wildcard("guides/deploying/*.md") ], groups_for_modules: [ MEDIA: ~r"ExWebRTC\.Media\..*", diff --git a/mix.lock b/mix.lock index b9b74f3..d9467b9 100644 --- a/mix.lock +++ b/mix.lock @@ -13,7 +13,7 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.31.2", "8b06d0a5ac69e1a54df35519c951f1f44a7b7ca9a5bb7a260cd8a174d6322ece", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "317346c14febaba9ca40fd97b5b5919f7751fb85d399cc8e7e8872049f37e0af"}, "ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"}, - "ex_ice": {:hex, :ex_ice, "0.8.0", "f9bd181e8fd2f8ac9a808587ee8a47bf667143069d75f6e4892a62156d798aa7", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.1.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "b0476f6b18986f6df48fda4cecb3be5022323572790d1bb49da10b177c936b4e"}, + "ex_ice": {:git, "https://github.com/elixir-webrtc/ex_ice.git", "4c5190687e11b28a8209f2c930895647d0b2a64c", []}, "ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"}, "ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"}, "ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"},