diff --git a/apps/fz_http/lib/fz_http/devices/device.ex b/apps/fz_http/lib/fz_http/devices/device.ex index dc1671954..6b74f2874 100644 --- a/apps/fz_http/lib/fz_http/devices/device.ex +++ b/apps/fz_http/lib/fz_http/devices/device.ex @@ -145,7 +145,7 @@ defmodule FzHttp.Devices.Device do end defp put_default_ip(changeset, field) do - cidr_string = FzHttp.Config.fetch_env!(:fz_http, :"wireguard_#{field}_network") + cidr_string = wireguard_network(field) {:ok, cidr_inet} = EctoNetwork.INET.cast(cidr_string) cidr = CIDR.parse(cidr_string) offset = Enum.random(2..(cidr.hosts - 2)) @@ -162,6 +162,16 @@ defmodule FzHttp.Devices.Device do end end + defp wireguard_network(field) do + cidr_string = FzHttp.Config.fetch_env!(:fz_http, :"wireguard_#{field}_network") + [inet, network] = String.split(cidr_string, "/") + network = String.to_integer(network) + "#{inet}/#{limit_cidr_range(field, network)}" + end + + defp limit_cidr_range(:ipv4, network), do: network + defp limit_cidr_range(:ipv6, network), do: max(network, 70) + defp ipv4_address do FzHttp.Config.fetch_env!(:fz_http, :wireguard_ipv4_address) |> EctoNetwork.INET.cast() diff --git a/apps/fz_http/test/fz_http/devices/device/query_test.exs b/apps/fz_http/test/fz_http/devices/device/query_test.exs index 512ef6cd9..23606b0ce 100644 --- a/apps/fz_http/test/fz_http/devices/device/query_test.exs +++ b/apps/fz_http/test/fz_http/devices/device/query_test.exs @@ -112,6 +112,38 @@ defmodule FzHttp.Devices.Device.QueryTest do assert Repo.one(queryable) == %Postgrex.INET{address: {64_768, 0, 0, 0, 0, 3, 3, 4}} end + test "selects available IPv6 at end of CIDR range" do + cidr = string_to_inet("fd00::/106") + gateway_ip = string_to_inet("fd00::3:3:3") + offset = 4_194_304 + + queryable = next_available_address(cidr, offset, [gateway_ip]) + + assert Repo.one(queryable) == %Postgrex.INET{address: {64_768, 0, 0, 0, 0, 0, 63, 65_535}} + end + + test "works when offset is out of IPv6 CIDR range" do + cidr = string_to_inet("fd00::/106") + gateway_ip = string_to_inet("fd00::3:3:3") + offset = 4_194_305 + + queryable = next_available_address(cidr, offset, [gateway_ip]) + + assert Repo.one(queryable) == %Postgrex.INET{address: {64_768, 0, 0, 0, 0, 0, 64, 0}} + end + + test "works when netmask allows a large number of devices" do + cidr = string_to_inet("fd00::/70") + gateway_ip = string_to_inet("fd00::3:3:3") + offset = 9_223_372_036_854_775_807 + + queryable = next_available_address(cidr, offset, [gateway_ip]) + + assert Repo.one(queryable) == %Postgrex.INET{ + address: {64_768, 0, 0, 0, 32_767, 65_535, 65_535, 65_534} + } + end + test "selects nothing when IPv6 CIDR range is exhausted" do cidr = string_to_inet("fd00::3:2:0/126") gateway_ip = string_to_inet("fd00::3:2:1") diff --git a/apps/fz_http/test/fz_http/devices_test.exs b/apps/fz_http/test/fz_http/devices_test.exs index d443b3414..0e0636a9e 100644 --- a/apps/fz_http/test/fz_http/devices_test.exs +++ b/apps/fz_http/test/fz_http/devices_test.exs @@ -82,6 +82,12 @@ defmodule FzHttp.DevicesTest do refute is_nil(device.ipv6) end + test "soft limit max network range for IPv6", %{device: device} do + FzHttp.Config.put_env(:wireguard_ipv6_network, "fd00::/20") + attrs = %{@device_attrs | ipv4: nil, ipv6: nil, user_id: device.user_id} + assert {:ok, _device} = Devices.create_device(attrs) + end + test "returns error when device IP can't be assigned due to CIDR pool exhaustion", %{ device: device } do