diff --git a/lib/peridiod/binary/downloader.ex b/lib/peridiod/binary/downloader.ex index d1eaa17..9e6707f 100644 --- a/lib/peridiod/binary/downloader.ex +++ b/lib/peridiod/binary/downloader.ex @@ -24,6 +24,7 @@ defmodule Peridiod.Binary.Downloader do Downloader.VerifyConfig } + alias Peridiod.Cloud.NetworkMonitor alias Peridiod.LogSanitizer require Logger @@ -612,7 +613,12 @@ defmodule Peridiod.Binary.Downloader do # like this. There may be a better way to do this.. path = if query, do: "#{path}?#{query}", else: path - with {:ok, conn} <- Mint.HTTP.connect(String.to_existing_atom(scheme), host, port), + transport_opts = bound_interface_transport_opts() + + with {:ok, conn} <- + Mint.HTTP.connect(String.to_existing_atom(scheme), host, port, + transport_opts: transport_opts + ), {:ok, conn, request_ref} <- Mint.HTTP.request(conn, "GET", path, request_headers, nil) do {:ok, %Downloader{ @@ -657,4 +663,16 @@ defmodule Peridiod.Binary.Downloader do defp add_user_agent_header(headers, _), do: [{"User-Agent", "Peridiod/#{Application.spec(:peridiod)[:vsn]}"} | headers] + + @doc false + def bound_interface_transport_opts do + try do + case NetworkMonitor.get_bound_interface() do + nil -> [] + ifname -> [bind_to_device: ifname] + end + catch + :exit, _ -> [] + end + end end diff --git a/test/peridiod/binary/downloader_test.exs b/test/peridiod/binary/downloader_test.exs index 0048753..5ad2166 100644 --- a/test/peridiod/binary/downloader_test.exs +++ b/test/peridiod/binary/downloader_test.exs @@ -3,6 +3,7 @@ defmodule Peridiod.Binary.DownloaderTest do alias Peridiod.Binary.Downloader alias Peridiod.Binary.Downloader.RetryConfig alias Peridiod.Binary.Downloader.VerifyConfig + alias Peridiod.Cloud.NetworkMonitor # SHA-256 of test/fixtures/binaries/1M.bin @bin_1m_hash Base.decode16!("a073ad730e540107fbb92ee48baab97c9bc16105333a42b15a53bcc183f6f5c2", @@ -280,4 +281,35 @@ defmodule Peridiod.Binary.DownloaderTest do assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 2000 end end + + describe "interface binding" do + test "returns empty transport opts when no interface is bound" do + # NetworkMonitor starts with bound_interface: nil in the test environment + assert NetworkMonitor.get_bound_interface() == nil + assert Downloader.bound_interface_transport_opts() == [] + end + + test "returns bind_to_device transport opt for the currently bound interface" do + original = :sys.get_state(NetworkMonitor) + + on_exit(fn -> + :sys.replace_state(NetworkMonitor, fn _ -> original end) + end) + + :sys.replace_state(NetworkMonitor, fn state -> + %{state | bound_interface: {"eth0", %NetworkMonitor.InterfaceInfo{}}} + end) + + assert Downloader.bound_interface_transport_opts() == [bind_to_device: "eth0"] + end + + test "returns empty transport opts when NetworkMonitor is not running" do + pid = Process.whereis(NetworkMonitor) + ref = Process.monitor(pid) + Process.exit(pid, :kill) + assert_receive {:DOWN, ^ref, :process, ^pid, :killed} + + assert Downloader.bound_interface_transport_opts() == [] + end + end end