Skip to content

Commit

Permalink
Merge pull request #21 from firezone/feat/v1
Browse files Browse the repository at this point in the history
First completely working checks
  • Loading branch information
jamilbk authored Jul 12, 2024
2 parents 1478db5 + cf61c2f commit faa65ae
Show file tree
Hide file tree
Showing 22 changed files with 622 additions and 170 deletions.
16 changes: 15 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,21 @@ import Config

config :probe,
ecto_repos: [Probe.Repo],
generators: [timestamp_type: :utc_datetime, binary_id: true]
generators: [timestamp_type: :utc_datetime, binary_id: true],
udp_bind_address: {127, 0, 0, 1},
port_options: [
# Needs root to run. These are enabled on Fly for prod.
# {"53 (DNS)", 53},
# {"80 (HTTP)", 80},
# {"123 (NTP)", 123},
# {"161 (SNMP)", 161},
# {"443 (HTTPS)", 443},
# {"500 (IKE)", 500},
# {"514 (Syslog)", 514},
{"1701 (L2TP)", 1701},
{"51820 (WireGuard)", 51_820},
{"60000", 60_000}
]

config :probe, Probe.Repo,
migration_timestamps: [type: :timestamptz],
Expand Down
19 changes: 19 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,25 @@ if config_env() == :prod do

config :probe, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")

# Fly requires listening on the IP address of "fly-global-services"
# See https://fly.io/docs/networking/udp-and-tcp/
{:ok, addr} = :inet.getaddr(~c"fly-global-services", :inet)

config :probe,
udp_bind_address: addr,
port_options: [
{"53 (DNS)", 53},
{"80 (HTTP)", 80},
{"123 (NTP)", 123},
{"161 (SNMP)", 161},
{"443 (HTTPS)", 443},
{"500 (IKE)", 500},
{"514 (Syslog)", 514},
{"1701 (L2TP)", 1701},
{"51820 (WireGuard)", 51_820},
{"60000", 60_000}
]

config :probe, Probe.Endpoint,
url: [host: host, port: 443, scheme: "https"],
http: [
Expand Down
63 changes: 59 additions & 4 deletions fly.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,70 @@ type = 'connections'
hard_limit = 1000
soft_limit = 1000

# wireguard
# We can't forward a range of ports unfortunately, so we pick a few common
# ports to run the test from.

[[services]]
internal_port = 53
protocol = "udp"
[[services.ports]]
port = 53

[[services]]
internal_port = 80
protocol = "udp"
[[services.ports]]
port = 80

[[services]]
internal_port = 123
protocol = "udp"
[[services.ports]]
port = 123

[[services]]
internal_port = 161
protocol = "udp"
[[services.ports]]
port = 161

[[services]]
internal_port = 443
protocol = "udp"
[[services.ports]]
port = 443

[[services]]
internal_port = 500
protocol = "udp"
[[services.ports]]
port = 500

[[services]]
internal_port = 514
protocol = "udp"
[[services.ports]]
port = 514

[[services]]
internal_port = 1701
protocol = "udp"
[[services.ports]]
port = 1701

[[services]]
internal_port = 51820
protocol = "udp"
[[services.ports]]
start_port = 9000
end_port = 65535
port = 51820

[[services]]
internal_port = 60000
protocol = "udp"
[[services.ports]]
port = 60000

[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpu_kind = 'performanc'
cpus = 1
2 changes: 1 addition & 1 deletion lib/probe/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ defmodule Probe.CoreComponents do
<select
id={@id}
name={@name}
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
class="mt-2 block w-full rounded-md border border-gray-300 bg-white dark:bg-gray-200 shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
multiple={@multiple}
{@rest}
>
Expand Down
138 changes: 48 additions & 90 deletions lib/probe/controllers/run_controller.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
defmodule Probe.Controllers.Run do
use Probe, :controller
alias Probe.Runs.UdpServer

@run_timeout 10_000
@run_timeout 15_000

action_fallback Probe.Controllers.Fallback

def start(conn, %{"token" => token}) do
# session_id = Map.get(params, "session_id")

# The home page is often custom made,
# so skip the default app layout.

with {:ok, %{topic: topic, port: port} = attrs} <-
with {:ok, %{topic: topic} = attrs} <-
Phoenix.Token.verify(Probe.Endpoint, "topic", token, max_age: 60) do
Probe.PubSub.broadcast("run:#{topic}", {:started, %{remote_ip: conn.remote_ip}})

Expand All @@ -31,12 +27,36 @@ defmodule Probe.Controllers.Run do

Task.start(fn ->
Process.sleep(@run_timeout)

run = Probe.Repo.reload(run)
Probe.PubSub.broadcast("run:#{topic}", {:failed, %{checks: run.checks}})
Probe.PubSub.broadcast("run:#{topic}", {:completed, run.id})
end)

send_resp(conn, 200, "#{port}")
send_resp(conn, 200, init_data(run))
else
_ ->
send_resp(conn, 401, "invalid or expired token")
end
end

def complete(conn, %{"token" => token}) do
with {:ok, %{topic: topic}} <-
Phoenix.Token.verify(Probe.Endpoint, "topic", token, max_age: 60),
{:ok, run} = Probe.Runs.fetch_run_by_topic(topic) do
Probe.PubSub.broadcast("run:#{topic}", {:completed, run.id})

send_resp(conn, 200, "")
else
_ ->
send_resp(conn, 401, "invalid or expired token")
end
end

def cancel(conn, %{"token" => token}) do
with {:ok, %{topic: topic}} <-
Phoenix.Token.verify(Probe.Endpoint, "topic", token, max_age: 60),
{:ok, run} = Probe.Runs.fetch_run_by_topic(topic) do
Probe.PubSub.broadcast("run:#{topic}", {:canceled, run.id})

send_resp(conn, 200, "")
else
_ ->
send_resp(conn, 401, "invalid or expired token")
Expand All @@ -56,95 +76,33 @@ defmodule Probe.Controllers.Run do

def show(conn, %{"token" => token}) do
with {:ok, %{topic: topic}} <-
Phoenix.Token.verify(Probe.Endpoint, "topic", token, max_age: :infinity) do
run = Probe.Runs.fetch_run_by_topic!(topic)

Phoenix.Token.verify(Probe.Endpoint, "topic", token, max_age: :infinity),
{:ok, run} = Probe.Runs.fetch_run_by_topic(topic) do
send_resp(conn, 200, ~s"""
Status: #{run.status}
Checks: #{inspect(run.checks)}
Started: #{run.started_at}
Ended: #{run.completed_at}
Port: #{run.port}
Country: #{run.remote_ip_location_country}
Latitude: #{run.remote_ip_location_lat}
Longitude: #{run.remote_ip_location_lon}
Started: #{run.inserted_at}
Ended: #{run.updated_at}
""")
else
_ ->
send_resp(conn, 401, "invalid or expired token")
end
end

# defp get_load_balancer_ip_location(%Plug.Conn{} = conn) do
# location_region =
# case Plug.Conn.get_req_header(conn, "x-geo-location-region") do
# ["" | _] -> nil
# [location_region | _] -> location_region
# [] -> nil
# end

# location_city =
# case Plug.Conn.get_req_header(conn, "x-geo-location-city") do
# ["" | _] -> nil
# [location_city | _] -> location_city
# [] -> nil
# end

# {location_lat, location_lon} =
# case Plug.Conn.get_req_header(conn, "x-geo-location-coordinates") do
# ["" | _] ->
# {nil, nil}

# ["," | _] ->
# {nil, nil}

# [coordinates | _] ->
# [lat, lon] = String.split(coordinates, ",", parts: 2)
# lat = String.to_float(lat)
# lon = String.to_float(lon)
# {lat, lon}

# [] ->
# {nil, nil}
# end

# {location_lat, location_lon} =
# Domain.Geo.maybe_put_default_coordinates(location_region, {location_lat, location_lon})

# {location_region, location_city, {location_lat, location_lon}}
# end

# def index(conn, _params) do
# bars = Foo.list_bars()
# render(conn, :index, bars: bars)
# end

# def create(conn, %{"bar" => bar_params}) do
# with {:ok, %Bar{} = bar} <- Foo.create_bar(bar_params) do
# conn
# |> put_status(:created)
# |> put_resp_header("location", ~p"/api/bars/#{bar}")
# |> render(:show, bar: bar)
# end
# end

# def show(conn, %{"id" => id}) do
# bar = Foo.get_bar!(id)
# render(conn, :show, bar: bar)
# end

# def update(conn, %{"id" => id, "bar" => bar_params}) do
# bar = Foo.get_bar!(id)

# with {:ok, %Bar{} = bar} <- Foo.update_bar(bar, bar_params) do
# render(conn, :show, bar: bar)
# end
# end

# def delete(conn, %{"id" => id}) do
# bar = Foo.get_bar!(id)

# with {:ok, %Bar{}} <- Foo.delete_bar(bar) do
# send_resp(conn, :no_content, "")
# end
# end
defp init_data(run) do
{:ok, ip} = :inet.getaddr(String.to_charlist(Probe.Endpoint.host()), :inet)

~s"""
#{run.port}
#{:inet.ntoa(ip)}
#{Base.encode64(UdpServer.generate_handshake_initiation_payload(run))}
#{Base.encode64(UdpServer.generate_handshake_response_payload(run))}
#{Base.encode64(UdpServer.generate_cookie_reply_payload(run))}
#{Base.encode64(UdpServer.generate_data_payload(run))}
"""
end
end
2 changes: 1 addition & 1 deletion lib/probe/live/component/faq.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ defmodule Probe.Live.Component.Faq do
<li>Test result for each WireGuard message type</li>
<li>Timestamp</li>
<li>IP location</li>
<li>Your ISP</li>
<li>Name of your ISP</li>
</ul>
</p>
</li>
Expand Down
39 changes: 20 additions & 19 deletions lib/probe/live/component/run.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ defmodule Probe.Live.Component.Run do
def mount(socket) do
if connected?(socket) do
topic = :crypto.strong_rand_bytes(8) |> Base.url_encode64(padding: false)

# FYI: Comoponents don't have their process -- this binds the parent LiveView PID
# to the topic, so that's where our handle_info's are. This is here for the sake of
# encapsulation and to prevent the LiveView from having to know about the PubSub topic.
:ok = Probe.PubSub.subscribe("run:#{topic}")

token =
Expand All @@ -22,7 +18,8 @@ defmodule Probe.Live.Component.Run do
assign(socket,
token: token,
topic: topic,
port: @default_port
default_port: @default_port,
port_options: Application.fetch_env!(:probe, :port_options)
)}
else
{:ok, socket}
Expand Down Expand Up @@ -157,20 +154,24 @@ defmodule Probe.Live.Component.Run do
</div>
<%= if connected?(@socket) do %>
<div class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
<span class="mb-4">Step 1: Choose the port to test:</span>
<div class="text-xl font-semibold mb-4">
<span class="mb-4 text-gray-900 dark:text-white">Step 1: Choose a port:</span>
<.form for={%{}} phx-change="port_change" phx-target={@myself}>
<div class="w-24 py-2">
<.input
phx-hook="InitFlowbite"
id="run-port"
max="65535"
min="1"
name="port"
type="number"
value={@port}
phx-debounce="250"
/>
<div class="py-2">
<div class="w-64">
<.input
phx-hook="InitFlowbite"
id="run-port"
name="port"
type="select"
options={@port_options}
value={@default_port}
phx-debounce="250"
/>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
Select a different port if you suspect WireGuard's default port is blocked.
</p>
</div>
</.form>
</div>
Expand All @@ -180,7 +181,7 @@ defmodule Probe.Live.Component.Run do
style={(@os in ["Mac OS X"] && "display: block") || "display: none"}
>
<p class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
Step 2: Copy and paste the command below into your terminal:
Step 2: Run this command:
</p>
<.code_block value={"bash <(curl -fsSL \"#{url(~p"/scripts/unix.sh")}\") #{url(~p"/runs/#{@token}")}"} />
Expand Down
Loading

0 comments on commit faa65ae

Please sign in to comment.